From 6823c070e2783bf44598515745187df3f1332604 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Mon, 5 Aug 2013 17:04:42 +0200 Subject: [PATCH] [#2665] Implement SPI for RenderContext and BindContext listening to allow for custom SQL transformation * Added integration test for query manipulation --- .../test/_/testcases/VisitListenerTests.java | 176 ++++++++++++++++++ .../src/org/jooq/test/jOOQAbstractTest.java | 6 + .../java/org/jooq/impl/AbstractContext.java | 22 ++- 3 files changed, 196 insertions(+), 8 deletions(-) create mode 100644 jOOQ-test/src/org/jooq/test/_/testcases/VisitListenerTests.java diff --git a/jOOQ-test/src/org/jooq/test/_/testcases/VisitListenerTests.java b/jOOQ-test/src/org/jooq/test/_/testcases/VisitListenerTests.java new file mode 100644 index 0000000000..cc45c8cd51 --- /dev/null +++ b/jOOQ-test/src/org/jooq/test/_/testcases/VisitListenerTests.java @@ -0,0 +1,176 @@ +/** + * Copyright (c) 2009-2013, Lukas Eder, lukas.eder@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq.test._.testcases; + +import static java.lang.Boolean.TRUE; +import static java.util.Arrays.asList; +import static org.jooq.Clause.SELECT_WHERE; +import static org.jooq.impl.DSL.inline; +import static org.junit.Assert.assertEquals; + +import java.sql.Date; + +import org.jooq.Clause; +import org.jooq.Condition; +import org.jooq.Record1; +import org.jooq.Record2; +import org.jooq.Record3; +import org.jooq.Record6; +import org.jooq.Result; +import org.jooq.TableRecord; +import org.jooq.UpdatableRecord; +import org.jooq.VisitContext; +import org.jooq.impl.DefaultVisitListener; +import org.jooq.test.BaseTest; +import org.jooq.test.jOOQAbstractTest; + +import org.junit.Test; + +public class VisitListenerTests< + A extends UpdatableRecord & Record6, + AP, + B extends UpdatableRecord, + S extends UpdatableRecord & Record1, + B2S extends UpdatableRecord & Record3, + BS extends UpdatableRecord, + L extends TableRecord & Record2, + X extends TableRecord, + DATE extends UpdatableRecord, + BOOL extends UpdatableRecord, + D extends UpdatableRecord, + T extends UpdatableRecord, + U extends TableRecord, + UU extends UpdatableRecord, + I extends TableRecord, + IPK extends UpdatableRecord, + T725 extends UpdatableRecord, + T639 extends UpdatableRecord, + T785 extends TableRecord> +extends BaseTest { + + public VisitListenerTests(jOOQAbstractTest delegate) { + super(delegate); + } + + @Test + public void testVisitListener() throws Exception { + + // No join with author table + Result result1 = + create(new OnlyAuthorIDEq1VisitListener()) + .select(TBook_ID()) + .from(TBook()) + .orderBy(TBook_ID()) + .fetch(); + + assertEquals(2, result1.size()); + assertEquals(asList(1, 2), result1.getValues(TBook_ID())); + + // Additional predicates + Result result2 = + create(new OnlyAuthorIDEq1VisitListener()) + .select(TBook_ID()) + .from(TBook()) + .where(TBook_ID().in(BOOK_IDS)) + .orderBy(TBook_ID()) + .fetch(); + + assertEquals(2, result2.size()); + assertEquals(asList(1, 2), result2.getValues(TBook_ID())); + + // Join with author table + Result result3 = + create(new OnlyAuthorIDEq1VisitListener()) + .select(TBook_ID()) + .from(TBook().join(TAuthor()) + .on(TBook_AUTHOR_ID().eq(TAuthor_ID()))) + .orderBy(TBook_ID()) + .fetch(); + + assertEquals(2, result3.size()); + assertEquals(asList(1, 2), result3.getValues(TBook_ID())); + } + + private class OnlyAuthorIDEq1VisitListener extends DefaultVisitListener { + + @Override + public void clauseEnd(VisitContext context) { + if (context.renderContext() == null) + return; + + if (context.clause() == SELECT_WHERE) { + if (TRUE.equals(context.data("book present"))) { + context.renderContext() + .sql(" ") + .keyword(TRUE.equals(context.data("where present")) ? "and" : "where") + .sql(" "); + + if (TRUE.equals(context.data("author present"))) { + context.renderContext().visit(TAuthor_ID().eq(inline(1))); + } + else { + context.renderContext().visit(TBook_AUTHOR_ID().eq(inline(1))); + } + } + } + } + + @Override + public void visitEnd(VisitContext context) { + if (context.visiting() == TBook()) { + + // TODO: Check if we're in a subquery, too (i.e. with several SELECT_FROMs) + if (asList(context.clauses()).contains(Clause.SELECT_FROM)) { + context.data("book present", true); + } + } + + if (context.visiting() == TAuthor()) { + + // TODO: Check if we're in a subquery, too (i.e. with several SELECT_FROMs) + if (asList(context.clauses()).contains(Clause.SELECT_FROM)) { + context.data("author present", true); + } + } + + if (context.visiting() instanceof Condition) { + if (asList(context.clauses()).contains(Clause.SELECT_WHERE)) { + context.data("where present", true); + } + } + } + } +} \ No newline at end of file diff --git a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java index 38bd94c4c7..01cee83a88 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java @@ -149,6 +149,7 @@ import org.jooq.test._.testcases.StatementTests; import org.jooq.test._.testcases.ThreadSafetyTests; import org.jooq.test._.testcases.TruncateTests; import org.jooq.test._.testcases.ValuesConstructorTests; +import org.jooq.test._.testcases.VisitListenerTests; import org.jooq.tools.JooqLogger; import org.jooq.tools.StopWatch; import org.jooq.tools.StringUtils; @@ -1523,6 +1524,11 @@ public abstract class jOOQAbstractTest< new RecordListenerTests(this).testRecordListenerBatchStore(); } + @Test + public void testVisitListener() throws Exception { + new VisitListenerTests(this).testVisitListener(); + } + @Test public void testResultSetType() throws Exception { new ResultSetTests(this).testResultSetType(); diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java index df0ef2e9c0..21dd145e09 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java @@ -35,6 +35,8 @@ */ package org.jooq.impl; +import static org.jooq.Clause.DUMMY; + import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; @@ -169,10 +171,12 @@ abstract class AbstractContext> implements Context { @Override public final C start(Clause clause) { - visitClauses.addLast(clause); + if (clause != null && clause != DUMMY) { + visitClauses.addLast(clause); - for (VisitListener listener : visitListeners) { - listener.clauseStart(visitContext); + for (VisitListener listener : visitListeners) { + listener.clauseStart(visitContext); + } } return (C) this; @@ -180,12 +184,14 @@ abstract class AbstractContext> implements Context { @Override public final C end(Clause clause) { - for (VisitListener listener : visitListeners) { - listener.clauseEnd(visitContext); - } + if (clause != null && clause != DUMMY) { + for (VisitListener listener : visitListeners) { + listener.clauseEnd(visitContext); + } - if (visitClauses.removeLast() != clause) - throw new IllegalStateException("Mismatch between visited clauses!"); + if (visitClauses.removeLast() != clause) + throw new IllegalStateException("Mismatch between visited clauses!"); + } return (C) this; }