diff --git a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java index 42ac25a3a7..0af118481c 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java @@ -219,13 +219,13 @@ public abstract class jOOQAbstractTest< T639 extends UpdatableRecord, T785 extends TableRecord> { - private static final List BOOK_IDS_SHORT = Arrays.asList((short) 1, (short) 2, (short) 3, (short) 4); - private static final List BOOK_IDS = Arrays.asList(1, 2, 3, 4); - private static final List BOOK_TITLES = Arrays.asList("1984", "Animal Farm", "O Alquimista", "Brida"); - private static final List BOOK_FIRST_NAMES = Arrays.asList("George", "George", "Paulo", "Paulo"); - private static final List BOOK_LAST_NAMES = Arrays.asList("Orwell", "Orwell", "Coelho", "Coelho"); - private static final List AUTHOR_FIRST_NAMES = Arrays.asList("George", "Paulo"); - private static final List AUTHOR_LAST_NAMES = Arrays.asList("Orwell", "Coelho"); + protected static final List BOOK_IDS_SHORT = Arrays.asList((short) 1, (short) 2, (short) 3, (short) 4); + protected static final List BOOK_IDS = Arrays.asList(1, 2, 3, 4); + protected static final List BOOK_TITLES = Arrays.asList("1984", "Animal Farm", "O Alquimista", "Brida"); + protected static final List BOOK_FIRST_NAMES = Arrays.asList("George", "George", "Paulo", "Paulo"); + protected static final List BOOK_LAST_NAMES = Arrays.asList("Orwell", "Orwell", "Coelho", "Coelho"); + protected static final List AUTHOR_FIRST_NAMES = Arrays.asList("George", "Paulo"); + protected static final List AUTHOR_LAST_NAMES = Arrays.asList("Orwell", "Coelho"); private static final String JDBC_SCHEMA = "jdbc.Schema"; private static final String JDBC_PASSWORD = "jdbc.Password"; @@ -4315,16 +4315,18 @@ public abstract class jOOQAbstractTest< q.execute(); assertEquals(Arrays.asList("John", "Alfred", "Dan"), - create().selectFrom(TAuthor()) - .orderBy(TAuthor_ID()) - .fetch(TAuthor_FIRST_NAME())); + create().selectFrom(TAuthor()) + .orderBy(TAuthor_ID()) + .fetch(TAuthor_FIRST_NAME())); q.execute(); assertEquals(Arrays.asList("John", "Alfred", "James"), - create().selectFrom(TAuthor()) - .orderBy(TAuthor_ID()) - .fetch(TAuthor_FIRST_NAME())); + create().selectFrom(TAuthor()) + .orderBy(TAuthor_ID()) + .fetch(TAuthor_FIRST_NAME())); + // TODO: Add more sophisticated MERGE statement tests + // Especially for SQL Server and Sybase, some bugs could be expected } @Test diff --git a/jOOQ-test/src/org/jooq/test/jOOQOracleTest.java b/jOOQ-test/src/org/jooq/test/jOOQOracleTest.java index 94f2228a1f..50189e4d54 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQOracleTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQOracleTest.java @@ -38,6 +38,7 @@ package org.jooq.test; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; +import static org.jooq.impl.Factory.falseCondition; import static org.jooq.impl.Factory.one; import static org.jooq.impl.Factory.substring; import static org.jooq.impl.Factory.trueCondition; @@ -742,7 +743,7 @@ public class jOOQOracleTest extends jOOQAbstractTest< } @Test - public void testMemberProcedures() throws Exception { + public void testOracleMemberProcedures() throws Exception { UAuthorTypeRecord author1; UAuthorTypeRecord author2; @@ -801,16 +802,93 @@ public class jOOQOracleTest extends jOOQAbstractTest< } @Test - public void testCursorINOUT() throws Exception { + public void testOracleCursorINOUT() throws Exception { assertEquals(4, (int) create().select(f691cursorIn(f691cursorOut())).fetchOne(0, Integer.class)); } @Test - public void testTypedSequences() throws Exception { + public void testOracleTypedSequences() throws Exception { assertEquals(Byte.valueOf("1"), ora().nextval(Sequences.S_961_BYTE)); assertEquals(Short.valueOf("1"), ora().nextval(Sequences.S_961_SHORT)); assertEquals(Integer.valueOf("1"), ora().nextval(Sequences.S_961_INT)); assertEquals(Long.valueOf("1"), ora().nextval(Sequences.S_961_LONG)); assertEquals(BigInteger.valueOf(1), ora().nextval(Sequences.S_961_BIG_INTEGER)); } + + @Test + public void testOracleMergeStatementExtensions() throws Exception { + reset = false; + TAuthorRecord author; + + // Test updating with a positive condition + // --------------------------------------- + assertEquals(1, + ora().mergeInto(T_AUTHOR) + .usingDual() + .on(T_AUTHOR.ID.equal(1)) + .whenMatchedThenUpdate() + .set(T_AUTHOR.LAST_NAME, "Frisch") + .where(T_AUTHOR.ID.equal(1)) + .execute()); + + author = create().fetchOne(T_AUTHOR, T_AUTHOR.ID.equal(1)); + assertEquals(2, create().selectCount().from(T_AUTHOR).fetchOne(0)); + assertEquals(1, (int) author.getId()); + assertEquals(AUTHOR_FIRST_NAMES.get(0), author.getFirstName()); + assertEquals("Frisch", author.getLastName()); + + // Test updating with a negative condition + // --------------------------------------- + assertEquals(0, + ora().mergeInto(T_AUTHOR) + .usingDual() + .on(T_AUTHOR.ID.equal(1)) + .whenMatchedThenUpdate() + .set(T_AUTHOR.LAST_NAME, "Frisch") + .where(T_AUTHOR.ID.equal(3)) + .execute()); + + author = create().fetchOne(T_AUTHOR, T_AUTHOR.ID.equal(1)); + assertEquals(2, create().selectCount().from(T_AUTHOR).fetchOne(0)); + assertEquals(1, (int) author.getId()); + assertEquals(AUTHOR_FIRST_NAMES.get(0), author.getFirstName()); + assertEquals("Frisch", author.getLastName()); + + // Test deleting + // ------------- + // ON DELETE CASCADE doesn't work with MERGE...? + ora().delete(T_BOOK).execute(); + + assertEquals(1, + ora().mergeInto(T_AUTHOR) + .usingDual() + .on(trueCondition()) + .whenMatchedThenUpdate() + .set(T_AUTHOR.LAST_NAME, "Frisch") + .where(T_AUTHOR.ID.equal(2)) + .deleteWhere(T_AUTHOR.ID.equal(2)) + .execute()); + + author = create().fetchOne(T_AUTHOR, T_AUTHOR.ID.equal(1)); + assertEquals(1, create().selectCount().from(T_AUTHOR).fetchOne(0)); + assertEquals(1, (int) author.getId()); + assertEquals(AUTHOR_FIRST_NAMES.get(0), author.getFirstName()); + assertEquals("Frisch", author.getLastName()); + + // Test inserting + // -------------- + assertEquals(0, + ora().mergeInto(T_AUTHOR) + .usingDual() + .on(trueCondition()) + .whenNotMatchedThenInsert( + T_AUTHOR.ID, + T_AUTHOR.FIRST_NAME, + T_AUTHOR.LAST_NAME) + .values(3, "Yvette", "Z'Graggen") + .where(falseCondition()) + .execute()); + + // No tests on results + } } diff --git a/jOOQ-test/src/org/jooq/test/oracle/create.sql b/jOOQ-test/src/org/jooq/test/oracle/create.sql index 1806fca997..903cd7f280 100644 --- a/jOOQ-test/src/org/jooq/test/oracle/create.sql +++ b/jOOQ-test/src/org/jooq/test/oracle/create.sql @@ -384,10 +384,10 @@ CREATE TABLE t_book ( content_pdf BLOB, CONSTRAINT pk_t_book PRIMARY KEY (ID), - CONSTRAINT fk_t_book_author_id FOREIGN KEY (AUTHOR_ID) REFERENCES T_AUTHOR(ID), - CONSTRAINT fk_t_book_co_author_id FOREIGN KEY (CO_AUTHOR_ID) REFERENCES T_AUTHOR(ID), - CONSTRAINT fk_t_book_details_id FOREIGN KEY (DETAILS_ID) REFERENCES T_BOOK_DETAILS(ID), - CONSTRAINT fk_t_book_language_id FOREIGN KEY (LANGUAGE_ID) REFERENCES T_LANGUAGE(ID) + CONSTRAINT fk_t_book_author_id FOREIGN KEY (AUTHOR_ID) REFERENCES T_AUTHOR(ID) ON DELETE CASCADE, + CONSTRAINT fk_t_book_co_author_id FOREIGN KEY (CO_AUTHOR_ID) REFERENCES T_AUTHOR(ID) ON DELETE CASCADE, + CONSTRAINT fk_t_book_details_id FOREIGN KEY (DETAILS_ID) REFERENCES T_BOOK_DETAILS(ID) ON DELETE CASCADE, + CONSTRAINT fk_t_book_language_id FOREIGN KEY (LANGUAGE_ID) REFERENCES T_LANGUAGE(ID) ON DELETE CASCADE ) / COMMENT ON TABLE t_book IS 'An entity holding books'/ diff --git a/jOOQ/src/main/java/org/jooq/MergeMatchedDeleteStep.java b/jOOQ/src/main/java/org/jooq/MergeMatchedDeleteStep.java new file mode 100644 index 0000000000..a0695d4f3f --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/MergeMatchedDeleteStep.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2009-2011, 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; + +/** + * This type is used for the {@link Merge}'s DSL API. + *

+ * Example:

+ * Factory create = new Factory();
+ *
+ * create.mergeInto(table)
+ *       .using(select)
+ *       .on(condition)
+ *       .whenMatchedThenUpdate()
+ *       .set(field1, value1)
+ *       .set(field2, value2)
+ *       .whenNotMatchedThenInsert(field1, field2)
+ *       .values(value1, value2)
+ *       .execute();
+ * 
+ * + * @author Lukas Eder + */ +public interface MergeMatchedDeleteStep extends MergeNotMatchedStep { + + /** + * Add an additional DELETE WHERE clause to the preceding + * WHEN MATCHED THEN UPDATE clause. + *

+ * Note: This syntax is only available for the + * {@link SQLDialect#ORACLE} database! + * + * @see http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9016.htm + * for a full definition of the Oracle MERGE statement + */ + MergeNotMatchedStep deleteWhere(Condition condition); +} diff --git a/jOOQ/src/main/java/org/jooq/MergeMatchedSetMoreStep.java b/jOOQ/src/main/java/org/jooq/MergeMatchedSetMoreStep.java index 52d3ba6161..361c73151e 100644 --- a/jOOQ/src/main/java/org/jooq/MergeMatchedSetMoreStep.java +++ b/jOOQ/src/main/java/org/jooq/MergeMatchedSetMoreStep.java @@ -54,6 +54,9 @@ package org.jooq; * * @author Lukas Eder */ -public interface MergeMatchedSetMoreStep extends MergeMatchedSetStep, MergeNotMatchedStep { +public interface MergeMatchedSetMoreStep + extends + MergeMatchedSetStep, + MergeMatchedWhereStep { } diff --git a/jOOQ/src/main/java/org/jooq/MergeMatchedStep.java b/jOOQ/src/main/java/org/jooq/MergeMatchedStep.java index 4aa8b4e6de..9441625d97 100644 --- a/jOOQ/src/main/java/org/jooq/MergeMatchedStep.java +++ b/jOOQ/src/main/java/org/jooq/MergeMatchedStep.java @@ -54,7 +54,7 @@ package org.jooq; * * @author Lukas Eder */ -public interface MergeMatchedStep { +public interface MergeMatchedStep extends MergeNotMatchedStep { /** * Add the WHEN MATCHED THEN UPDATE clause to the diff --git a/jOOQ/src/main/java/org/jooq/MergeMatchedWhereStep.java b/jOOQ/src/main/java/org/jooq/MergeMatchedWhereStep.java new file mode 100644 index 0000000000..9034918c22 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/MergeMatchedWhereStep.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2009-2011, 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; + +/** + * This type is used for the {@link Merge}'s DSL API. + *

+ * Example:

+ * Factory create = new Factory();
+ *
+ * create.mergeInto(table)
+ *       .using(select)
+ *       .on(condition)
+ *       .whenMatchedThenUpdate()
+ *       .set(field1, value1)
+ *       .set(field2, value2)
+ *       .whenNotMatchedThenInsert(field1, field2)
+ *       .values(value1, value2)
+ *       .execute();
+ * 
+ * + * @author Lukas Eder + */ +public interface MergeMatchedWhereStep extends MergeNotMatchedStep { + + /** + * Add an additional WHERE clause to the preceding + * WHEN MATCHED THEN UPDATE clause. + *

+ * Note: This syntax is only available for the + * {@link SQLDialect#ORACLE} database! + * + * @see http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9016.htm + * for a full definition of the Oracle MERGE statement + */ + MergeMatchedDeleteStep where(Condition condition); +} diff --git a/jOOQ/src/main/java/org/jooq/MergeNotMatchedStep.java b/jOOQ/src/main/java/org/jooq/MergeNotMatchedStep.java index f917a96235..83d3250dd6 100644 --- a/jOOQ/src/main/java/org/jooq/MergeNotMatchedStep.java +++ b/jOOQ/src/main/java/org/jooq/MergeNotMatchedStep.java @@ -56,7 +56,7 @@ import java.util.Collection; * * @author Lukas Eder */ -public interface MergeNotMatchedStep { +public interface MergeNotMatchedStep extends MergeFinalStep { /** * Add the WHEN NOT MATCHED THEN INSERT clause to the diff --git a/jOOQ/src/main/java/org/jooq/MergeNotMatchedValuesStep.java b/jOOQ/src/main/java/org/jooq/MergeNotMatchedValuesStep.java index 7d1cfb3938..3bfde76b6b 100644 --- a/jOOQ/src/main/java/org/jooq/MergeNotMatchedValuesStep.java +++ b/jOOQ/src/main/java/org/jooq/MergeNotMatchedValuesStep.java @@ -62,17 +62,17 @@ public interface MergeNotMatchedValuesStep { * Set VALUES for INSERT in the MERGE * statement's WHEN NOT MATCHED THEN INSERT clause. */ - MergeFinalStep values(Object... values); + MergeNotMatchedWhereStep values(Object... values); /** * Set VALUES for INSERT in the MERGE * statement's WHEN NOT MATCHED THEN INSERT clause. */ - MergeFinalStep values(Field... values); + MergeNotMatchedWhereStep values(Field... values); /** * Set VALUES for INSERT in the MERGE * statement's WHEN NOT MATCHED THEN INSERT clause. */ - MergeFinalStep values(Collection values); + MergeNotMatchedWhereStep values(Collection values); } diff --git a/jOOQ/src/main/java/org/jooq/MergeNotMatchedWhereStep.java b/jOOQ/src/main/java/org/jooq/MergeNotMatchedWhereStep.java new file mode 100644 index 0000000000..5035215818 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/MergeNotMatchedWhereStep.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2009-2011, 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; + +/** + * This type is used for the {@link Merge}'s DSL API. + *

+ * Example:

+ * Factory create = new Factory();
+ *
+ * create.mergeInto(table)
+ *       .using(select)
+ *       .on(condition)
+ *       .whenMatchedThenUpdate()
+ *       .set(field1, value1)
+ *       .set(field2, value2)
+ *       .whenNotMatchedThenInsert(field1, field2)
+ *       .values(value1, value2)
+ *       .execute();
+ * 
+ * + * @author Lukas Eder + */ +public interface MergeNotMatchedWhereStep extends MergeFinalStep { + + /** + * Add an additional WHERE clause to the preceding + * WHEN NOT MATCHED THEN INSERT clause. + *

+ * Note: This syntax is only available for the + * {@link SQLDialect#ORACLE} database! + * + * @see http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9016.htm + * for a full definition of the Oracle MERGE statement + */ + MergeFinalStep where(Condition condition); +} diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractBindContext.java b/jOOQ/src/main/java/org/jooq/impl/AbstractBindContext.java index 8d245fe184..9d2889a2e1 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractBindContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractBindContext.java @@ -73,8 +73,10 @@ abstract class AbstractBindContext extends AbstractContext implemen @Override public final BindContext bind(Collection parts) { - for (QueryPart part : parts) { - bind(part); + if (parts != null) { + for (QueryPart part : parts) { + bind(part); + } } return this; @@ -82,34 +84,39 @@ abstract class AbstractBindContext extends AbstractContext implemen @Override public final BindContext bind(QueryPart[] parts) { - bind(Arrays.asList(parts)); + if (parts != null) { + bind(Arrays.asList(parts)); + } + return this; } @Override public final BindContext bind(QueryPart part) { - QueryPartInternal internal = part.internalAPI(QueryPartInternal.class); + if (part != null) { + QueryPartInternal internal = part.internalAPI(QueryPartInternal.class); - // If this is supposed to be a declaration section and the part isn't - // able to declare anything, then disable declaration temporarily + // If this is supposed to be a declaration section and the part isn't + // able to declare anything, then disable declaration temporarily - // We're declaring fields, but "part" does not declare fields - if (declareFields() && !internal.declaresFields()) { - declareFields(false); - bindInternal(internal); - declareFields(true); - } + // We're declaring fields, but "part" does not declare fields + if (declareFields() && !internal.declaresFields()) { + declareFields(false); + bindInternal(internal); + declareFields(true); + } - // We're declaring tables, but "part" does not declare tables - else if (declareTables() && !internal.declaresTables()) { - declareTables(false); - bindInternal(internal); - declareTables(true); - } + // We're declaring tables, but "part" does not declare tables + else if (declareTables() && !internal.declaresTables()) { + declareTables(false); + bindInternal(internal); + declareTables(true); + } - // We're not declaring, or "part" can declare - else { - bindInternal(internal); + // We're not declaring, or "part" can declare + else { + bindInternal(internal); + } } return this; diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java b/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java index 935bcfaa49..afd3606766 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java @@ -151,28 +151,30 @@ class DefaultRenderContext extends AbstractContext implements Ren @Override public final RenderContext sql(QueryPart part) { - QueryPartInternal internal = part.internalAPI(QueryPartInternal.class); + if (part != null) { + QueryPartInternal internal = part.internalAPI(QueryPartInternal.class); - // If this is supposed to be a declaration section and the part isn't - // able to declare anything, then disable declaration temporarily + // If this is supposed to be a declaration section and the part isn't + // able to declare anything, then disable declaration temporarily - // We're declaring fields, but "part" does not declare fields - if (declareFields() && !internal.declaresFields()) { - declareFields(false); - internal.toSQL(this); - declareFields(true); - } + // We're declaring fields, but "part" does not declare fields + if (declareFields() && !internal.declaresFields()) { + declareFields(false); + internal.toSQL(this); + declareFields(true); + } - // We're declaring tables, but "part" does not declare tables - else if (declareTables() && !internal.declaresTables()) { - declareTables(false); - internal.toSQL(this); - declareTables(true); - } + // We're declaring tables, but "part" does not declare tables + else if (declareTables() && !internal.declaresTables()) { + declareTables(false); + internal.toSQL(this); + declareTables(true); + } - // We're not declaring, or "part" can declare - else { - internal.toSQL(this); + // We're not declaring, or "part" can declare + else { + internal.toSQL(this); + } } return this; diff --git a/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java b/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java index c4367bcc5c..4588f5556e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java @@ -52,9 +52,10 @@ import org.jooq.BindContext; import org.jooq.Condition; import org.jooq.Configuration; import org.jooq.Field; -import org.jooq.MergeFinalStep; +import org.jooq.MergeMatchedDeleteStep; import org.jooq.MergeMatchedSetMoreStep; import org.jooq.MergeNotMatchedValuesStep; +import org.jooq.MergeNotMatchedWhereStep; import org.jooq.MergeOnConditionStep; import org.jooq.MergeOnStep; import org.jooq.MergeUsingStep; @@ -77,27 +78,35 @@ implements MergeOnStep, MergeOnConditionStep, MergeMatchedSetMoreStep, + MergeMatchedDeleteStep, MergeNotMatchedValuesStep, - MergeFinalStep { + MergeNotMatchedWhereStep { /** * Generated UID */ - private static final long serialVersionUID = -8835479296876774391L; + private static final long serialVersionUID = -8835479296876774391L; private final Table table; private final ConditionProviderImpl on; - private final FieldMapForUpdate updateMap; - private final FieldMapForInsert insertMap; private TableLike using; + // [#998] Oracle extensions to the MERGE statement + private Condition matchedWhere; + private Condition matchedDeleteWhere; + private Condition notMatchedWhere; + + // Flags to keep track of DSL object creation state + private boolean matchedClause; + private FieldMapForUpdate matchedUpdate; + private boolean notMatchedClause; + private FieldMapForInsert notMatchedInsert; + MergeImpl(Configuration configuration, Table table) { super(configuration); this.table = table; this.on = new ConditionProviderImpl(); - this.updateMap = new FieldMapForUpdate(); - this.insertMap = new FieldMapForInsert(); } // ------------------------------------------------------------------------- @@ -196,6 +205,10 @@ implements @Override public final MergeImpl whenMatchedThenUpdate() { + matchedClause = true; + matchedUpdate = new FieldMapForUpdate(); + + notMatchedClause = false; return this; } @@ -206,13 +219,13 @@ implements @Override public final MergeImpl set(Field field, Field value) { - updateMap.put(field, nullSafe(value)); + matchedUpdate.put(field, nullSafe(value)); return this; } @Override public final MergeImpl set(Map, ?> map) { - updateMap.set(map); + matchedUpdate.set(map); return this; } @@ -223,7 +236,11 @@ implements @Override public final MergeImpl whenNotMatchedThenInsert(Collection> fields) { - insertMap.putFields(fields); + notMatchedClause = true; + notMatchedInsert = new FieldMapForInsert(); + notMatchedInsert.putFields(fields); + + matchedClause = false; return this; } @@ -239,7 +256,28 @@ implements @Override public final MergeImpl values(Collection values) { - insertMap.putValues(vals(values.toArray())); + notMatchedInsert.putValues(vals(values.toArray())); + return this; + } + + @Override + public final MergeImpl where(Condition condition) { + if (matchedClause) { + matchedWhere = condition; + } + else if (notMatchedClause) { + notMatchedWhere = condition; + } + else { + throw new IllegalStateException("Cannot call where() on the current state of the MERGE statement"); + } + + return this; + } + + @Override + public final MergeImpl deleteWhere(Condition condition) { + matchedDeleteWhere = condition; return this; } @@ -288,11 +326,37 @@ implements } context.sql(" on ") - .sql(Util.wrapInParentheses(context.render(on))) - .sql(" when matched then update set ") - .sql(updateMap) - .sql(" when not matched then insert ") - .sql(insertMap); + .sql(Util.wrapInParentheses(context.render(on))); + + // [#999] WHEN MATCHED clause is optional + if (matchedUpdate != null) { + context.sql(" when matched then update set ") + .sql(matchedUpdate); + } + + // [#998] Oracle MERGE extension: WHEN MATCHED THEN UPDATE .. WHERE + if (matchedWhere != null) { + context.sql(" where ") + .sql(matchedWhere); + } + + // [#998] Oracle MERGE extension: WHEN MATCHED THEN UPDATE .. DELETE WHERE + if (matchedDeleteWhere != null) { + context.sql(" delete where ") + .sql(matchedDeleteWhere); + } + + // [#999] WHEN NOT MATCHED clause is optional + if (notMatchedInsert != null) { + context.sql(" when not matched then insert ") + .sql(notMatchedInsert); + } + + // [#998] Oracle MERGE extension: WHEN NOT MATCHED THEN INSERT .. WHERE + if (notMatchedWhere != null) { + context.sql(" where ") + .sql(notMatchedWhere); + } switch (context.getDialect()) { case SQLSERVER: @@ -308,12 +372,21 @@ implements .bind(using) .declareTables(false) .bind(on) - .bind(updateMap) - .bind(insertMap); + .bind(matchedUpdate) + .bind(matchedWhere) + .bind(matchedDeleteWhere) + .bind(notMatchedInsert) + .bind(notMatchedWhere); } @Override public final List getAttachables() { - return getAttachables(table, using, on, updateMap, insertMap); + return getAttachables( + table, using, on, + matchedUpdate, + matchedWhere, + matchedDeleteWhere, + notMatchedInsert, + notMatchedWhere); } }