diff --git a/jOOQ-test/src/org/jooq/test/_/testcases/InsertUpdateTests.java b/jOOQ-test/src/org/jooq/test/_/testcases/InsertUpdateTests.java index 3653a867bf..75c8438ebc 100644 --- a/jOOQ-test/src/org/jooq/test/_/testcases/InsertUpdateTests.java +++ b/jOOQ-test/src/org/jooq/test/_/testcases/InsertUpdateTests.java @@ -792,6 +792,74 @@ extends BaseTest authors1 = create().selectFrom(TAuthor()).orderBy(TAuthor_ID()).fetch(); + assertEquals(3, authors1.size()); + assertEquals(3, (int) authors1.get(2).getValue(TAuthor_ID())); + assertEquals("Hesse", authors1.get(2).getValue(TAuthor_LAST_NAME())); + assertNull(authors1.get(2).getValue(TAuthor_FIRST_NAME())); + + // H2 MERGE test leading to a single UPDATE + assertEquals(1, + create().mergeInto(TAuthor(), TAuthor_ID(), TAuthor_FIRST_NAME()) + .values(3, "Hermann") + .execute()); + + Result authors2 = create().selectFrom(TAuthor()).orderBy(TAuthor_ID()).fetch(); + assertEquals(3, authors2.size()); + assertEquals(3, (int) authors2.get(2).getValue(TAuthor_ID())); + assertEquals("Hesse", authors2.get(2).getValue(TAuthor_LAST_NAME())); + assertEquals("Hermann", authors2.get(2).getValue(TAuthor_FIRST_NAME())); + + // H2 MERGE test specifying a custom KEY clause + assertEquals(1, + create().mergeInto(TAuthor(), TAuthor_FIRST_NAME(), TAuthor_LAST_NAME()) + .key(TAuthor_LAST_NAME()) + .values("Lukas", "Hesse") + .execute()); + + Result authors3 = create().selectFrom(TAuthor()).orderBy(TAuthor_ID()).fetch(); + assertEquals(3, authors3.size()); + assertEquals(3, (int) authors3.get(2).getValue(TAuthor_ID())); + assertEquals("Hesse", authors3.get(2).getValue(TAuthor_LAST_NAME())); + assertEquals("Lukas", authors3.get(2).getValue(TAuthor_FIRST_NAME())); + + // H2 MERGE test specifying a subselect + assertEquals(2, + create().mergeInto(TAuthor(), TAuthor_ID(), TAuthor_LAST_NAME()) + .key(TAuthor_ID()) + .select(create().select(val(3), val("Eder")).unionAll( + create().select(val(4), val("Eder")))) + .execute()); + + Result authors4 = create().selectFrom(TAuthor()).orderBy(TAuthor_ID()).fetch(); + assertEquals(4, authors4.size()); + assertEquals(3, (int) authors4.get(2).getValue(TAuthor_ID())); + assertEquals("Eder", authors4.get(2).getValue(TAuthor_LAST_NAME())); + assertEquals("Lukas", authors4.get(2).getValue(TAuthor_FIRST_NAME())); + assertEquals(4, (int) authors4.get(3).getValue(TAuthor_ID())); + assertEquals("Eder", authors4.get(3).getValue(TAuthor_LAST_NAME())); + assertNull(authors4.get(3).getValue(TAuthor_FIRST_NAME())); + } + @Test public void testUpdateSelect() throws Exception { switch (getDialect()) { diff --git a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java index 9a0888d471..3d51c93f95 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java @@ -1110,6 +1110,11 @@ public abstract class jOOQAbstractTest< new InsertUpdateTests(this).testMerge(); } + @Test + public void testH2Merge() throws Exception { + new InsertUpdateTests(this).testH2Merge(); + } + @Test public void testBlobAndClob() throws Exception { new DataTypeTests(this).testBlobAndClob(); diff --git a/jOOQ/src/main/java/org/jooq/FactoryOperations.java b/jOOQ/src/main/java/org/jooq/FactoryOperations.java index 0f47d45eea..72c07d86e3 100644 --- a/jOOQ/src/main/java/org/jooq/FactoryOperations.java +++ b/jOOQ/src/main/java/org/jooq/FactoryOperations.java @@ -549,7 +549,7 @@ public interface FactoryOperations extends Configuration { UpdateSetStep update(Table table); /** - * Create a new DSL merge statement. + * Create a new DSL SQL standard MERGE statement. *

* This statement is available from DSL syntax only. It is known to be * supported in some way by any of these dialects: @@ -610,10 +610,39 @@ public interface FactoryOperations extends Configuration { * .values(value1, value2) * .execute(); * + *

+ * Note: Using this method, you can also create an H2-specific MERGE + * statement without field specification. See also + * {@link #mergeInto(Table, Field...)} */ @Support({ DB2, HSQLDB, ORACLE, SQLSERVER, SYBASE }) MergeUsingStep mergeInto(Table table); + /** + * Create a new DSL merge statement (H2-specific syntax) + *

+ * This statement is available from DSL syntax only. It is known to be + * supported in some way by any of these dialects: + * + * + * + * + * + * + *
H2H2 natively supports this special syntaxwww.h2database.com/html/grammar.html#merge
+ */ + @Support(H2) + MergeKeyStep mergeInto(Table table, Field... fields); + + /** + * Create a new DSL merge statement (H2-specific syntax) + * + * @see #mergeInto(Table, Field...) + */ + @Support(H2) + MergeKeyStep mergeInto(Table table, Collection> fields); + /** * Create a new {@link DeleteQuery} * diff --git a/jOOQ/src/main/java/org/jooq/MergeKeyStep.java b/jOOQ/src/main/java/org/jooq/MergeKeyStep.java new file mode 100644 index 0000000000..998410c25f --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/MergeKeyStep.java @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2009-2012, 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; + +import static org.jooq.SQLDialect.H2; + +import java.util.Collection; + +/** + * This type is used for the H2-specific variant of the {@link Merge}'s DSL API. + *

+ * Example:

+ * Factory create = new Factory();
+ *
+ * create.mergeInto(table, field1, field2)
+ *       .key(id)
+ *       .values(value1, value2)
+ *       .execute();
+ * 
+ * + * @author Lukas Eder + */ +public interface MergeKeyStep extends MergeValuesStep { + + /** + * Specify an optional KEY clause. + *

+ * Use this optional clause in order to override using the underlying + * PRIMARY KEY. + */ + @Support(H2) + MergeValuesStep key(Field... keys); + + /** + * Specify an optional KEY clause. + *

+ * Use this optional clause in order to override using the underlying + * PRIMARY KEY. + */ + @Support(H2) + MergeValuesStep key(Collection> keys); +} diff --git a/jOOQ/src/main/java/org/jooq/MergeUsingStep.java b/jOOQ/src/main/java/org/jooq/MergeUsingStep.java index fb5cd12aa2..a1060a5766 100644 --- a/jOOQ/src/main/java/org/jooq/MergeUsingStep.java +++ b/jOOQ/src/main/java/org/jooq/MergeUsingStep.java @@ -46,7 +46,7 @@ import static org.jooq.SQLDialect.SYBASE; *

* Example:

  * Factory create = new Factory();
- *
+ * 
  * create.mergeInto(table)
  *       .using(select)
  *       .on(condition)
@@ -57,19 +57,21 @@ import static org.jooq.SQLDialect.SYBASE;
  *       .values(value1, value2)
  *       .execute();
  * 
- * + * * @author Lukas Eder */ -public interface MergeUsingStep { +public interface MergeUsingStep extends MergeKeyStep { /** - * Add the USING clause to the MERGE statement + * Add the USING clause to the SQL standard MERGE + * statement */ @Support({ DB2, HSQLDB, ORACLE, SQLSERVER, SYBASE }) MergeOnStep using(TableLike table); /** - * Add a dummy USING clause to the MERGE statement + * Add a dummy USING clause to the SQL standard + * MERGE statement *

* This results in USING(SELECT 1 FROM DUAL) for most RDBMS, or * in USING(SELECT 1) AS [dummy_table(dummy_field)] in SQL diff --git a/jOOQ/src/main/java/org/jooq/MergeValuesStep.java b/jOOQ/src/main/java/org/jooq/MergeValuesStep.java new file mode 100644 index 0000000000..f255f9cf54 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/MergeValuesStep.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2009-2012, 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; + +import static org.jooq.SQLDialect.H2; + +import java.util.Collection; + +/** + * This type is used for the H2-specific variant of the {@link Merge}'s DSL API. + *

+ * Example:

+ * Factory create = new Factory();
+ *
+ * create.mergeInto(table, field1, field2)
+ *       .key(id)
+ *       .values(value1, value2)
+ *       .execute();
+ * 
+ * + * @author Lukas Eder + */ +public interface MergeValuesStep { + + /** + * Specify a VALUES clause + */ + @Support(H2) + Merge values(Object... values); + + /** + * Specify a VALUES clause + */ + @Support(H2) + Merge values(Field... values); + + /** + * Specify a VALUES clause + */ + @Support(H2) + Merge values(Collection values); + + /** + * Use a SELECT statement as the source of values for the + * MERGE statement + *

+ * This variant of the MERGE .. SELECT statement expects a + * select returning exactly as many fields as specified previously in the + * INTO clause: + * {@link FactoryOperations#mergeInto(Table, Field...)} or + * {@link FactoryOperations#mergeInto(Table, Collection)} + */ + @Support(H2) + Merge select(Select select); +} diff --git a/jOOQ/src/main/java/org/jooq/impl/Factory.java b/jOOQ/src/main/java/org/jooq/impl/Factory.java index 7e3efb3d99..0b0d693133 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Factory.java +++ b/jOOQ/src/main/java/org/jooq/impl/Factory.java @@ -104,6 +104,7 @@ import org.jooq.InsertQuery; import org.jooq.InsertSetStep; import org.jooq.InsertValuesStep; import org.jooq.LoaderOptionsStep; +import org.jooq.MergeKeyStep; import org.jooq.MergeUsingStep; import org.jooq.Name; import org.jooq.OrderedAggregateFunction; @@ -1844,6 +1845,22 @@ public class Factory implements FactoryOperations { return new MergeImpl(this, table); } + /** + * {@inheritDoc} + */ + @Override + public MergeKeyStep mergeInto(Table table, Field... fields) { + return mergeInto(table, Arrays.asList(fields)); + } + + /** + * {@inheritDoc} + */ + @Override + public MergeKeyStep mergeInto(Table table, Collection> fields) { + return new MergeImpl(this, table, fields); + } + /** * {@inheritDoc} */ diff --git a/jOOQ/src/main/java/org/jooq/impl/FactoryProxy.java b/jOOQ/src/main/java/org/jooq/impl/FactoryProxy.java index 1428d6c657..cbb0fc1696 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FactoryProxy.java +++ b/jOOQ/src/main/java/org/jooq/impl/FactoryProxy.java @@ -60,6 +60,7 @@ import org.jooq.InsertQuery; import org.jooq.InsertSetStep; import org.jooq.InsertValuesStep; import org.jooq.LoaderOptionsStep; +import org.jooq.MergeKeyStep; import org.jooq.MergeUsingStep; import org.jooq.Query; import org.jooq.QueryPart; @@ -326,6 +327,16 @@ public final class FactoryProxy implements FactoryOperations { return getDelegate().mergeInto(table); } + @Override + public final MergeKeyStep mergeInto(Table table, Field... fields) { + return getDelegate().mergeInto(table, fields); + } + + @Override + public final MergeKeyStep mergeInto(Table table, Collection> fields) { + return getDelegate().mergeInto(table, fields); + } + @Override public final DeleteQuery deleteQuery(Table table) { return getDelegate().deleteQuery(table); diff --git a/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java b/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java index baad69d472..5201e30e99 100644 --- a/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java @@ -35,6 +35,7 @@ */ package org.jooq.impl; +import static org.jooq.SQLDialect.H2; import static org.jooq.impl.Factory.condition; import static org.jooq.impl.Factory.exists; import static org.jooq.impl.Factory.notExists; @@ -63,14 +64,18 @@ import org.jooq.MergeOnConditionStep; import org.jooq.MergeOnStep; import org.jooq.MergeUsingStep; import org.jooq.Operator; +import org.jooq.QueryPart; import org.jooq.Record; import org.jooq.RenderContext; import org.jooq.Select; import org.jooq.Table; import org.jooq.TableLike; +import org.jooq.exception.SQLDialectNotSupportedException; import org.jooq.tools.StringUtils; /** + * The SQL standard MERGE statement + * * @author Lukas Eder */ class MergeImpl extends AbstractQuery @@ -105,15 +110,110 @@ implements private boolean notMatchedClause; private FieldMapForInsert notMatchedInsert; + // Objects for the H2-specific syntax + private boolean h2Style; + private FieldList h2Fields; + private FieldList h2Keys; + private FieldList h2Values; + private Select h2Select; + MergeImpl(Configuration configuration, Table table) { + this(configuration, table, null); + } + + MergeImpl(Configuration configuration, Table table, Collection> fields) { super(configuration); this.table = table; this.on = new ConditionProviderImpl(); + + if (fields != null) { + h2Style = true; + h2Fields = new FieldList(fields); + } } // ------------------------------------------------------------------------- - // QueryPart API + // H2-specific MERGE API + // ------------------------------------------------------------------------- + + FieldList getH2Fields() { + if (h2Fields == null) { + h2Fields = new FieldList(); + h2Fields.addAll(table.getFields()); + } + + return h2Fields; + } + + FieldList getH2Keys() { + if (h2Keys == null) { + h2Keys = new FieldList(); + } + + return h2Keys; + } + + FieldList getH2Values() { + if (h2Values == null) { + h2Values = new FieldList(); + } + + return h2Values; + } + + @Override + public final MergeImpl select(Select select) { + h2Style = true; + h2Select = select; + return this; + } + + @Override + public final MergeImpl key(Field... k) { + return key(Arrays.asList(k)); + } + + @Override + public final MergeImpl key(Collection> keys) { + h2Style = true; + getH2Keys().addAll(keys); + return this; + } + + // ------------------------------------------------------------------------- + // Shared MERGE API + // ------------------------------------------------------------------------- + + @Override + public final MergeImpl values(Object... values) { + + // [#1541] The VALUES() clause is also supported in the H2-specific + // syntax, in case of which, the USING() was not added + if (using == null) { + h2Style = true; + getH2Values().addAll(vals(convert(getH2Fields(), values))); + } + else { + List> fields = new ArrayList>(notMatchedInsert.keySet()); + notMatchedInsert.putValues(vals(convert(fields, values))); + } + + return this; + } + + @Override + public final MergeImpl values(Field... values) { + return values((Object[]) values); + } + + @Override + public final MergeImpl values(Collection values) { + return values(values.toArray()); + } + + // ------------------------------------------------------------------------- + // Merge API // ------------------------------------------------------------------------- @Override @@ -270,23 +370,6 @@ implements return this; } - @Override - public final MergeImpl values(Object... values) { - List> fields = new ArrayList>(notMatchedInsert.keySet()); - notMatchedInsert.putValues(vals(convert(fields, values))); - return this; - } - - @Override - public final MergeImpl values(Field... values) { - return values((Object[]) values); - } - - @Override - public final MergeImpl values(Collection values) { - return values(values.toArray()); - } - @Override public final MergeImpl where(Condition condition) { if (matchedClause) { @@ -314,6 +397,46 @@ implements @Override public final void toSQL(RenderContext context) { + if (h2Style) { + toSQLH2(context); + } + else { + toSQLStandard(context); + } + } + + private final void toSQLH2(RenderContext context) { + if (context.getDialect() != H2) { + throw new SQLDialectNotSupportedException("The H2-specific MERGE syntax is only supported in H2"); + } + + context.keyword("merge into ") + .declareTables(true) + .sql(table) + .formatSeparator(); + + context.sql("("); + Util.toSQLNames(context, getH2Fields()); + context.sql(")"); + + if (!getH2Keys().isEmpty()) { + context.keyword(" key ("); + Util.toSQLNames(context, getH2Keys()); + context.sql(")"); + } + + if (h2Select != null) { + context.sql(" ") + .sql(h2Select); + } + else { + context.keyword(" values (") + .sql(getH2Values()) + .sql(")"); + } + } + + private final void toSQLStandard(RenderContext context) { context.keyword("merge into ") .declareTables(true) .sql(table) @@ -405,6 +528,25 @@ implements @Override public final void bind(BindContext context) { + if (h2Style) { + bindH2(context); + } + else { + bindStandard(context); + } + } + + private final void bindH2(BindContext context) { + context.declareTables(true) + .bind(table) + .declareTables(false) + .bind((QueryPart) getH2Fields()) + .bind((QueryPart) getH2Keys()) + .bind(h2Select) + .bind((QueryPart) getH2Values()); + } + + private final void bindStandard(BindContext context) { context.declareTables(true) .bind(table) .bind(using) @@ -425,6 +567,10 @@ implements matchedWhere, matchedDeleteWhere, notMatchedInsert, - notMatchedWhere); + notMatchedWhere, + h2Fields, + h2Keys, + h2Select, + h2Values); } }