From 55f6c2c53b6d91bb5ac2a1eae55aac8c367c7168 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Fri, 6 Jul 2012 22:18:08 +0200 Subject: [PATCH] [#1542] Simulate the H2 MERGE syntax in other dialects supporting the SQL standard MERGE statement - GitHub Issue #18 --- .../test/_/testcases/InsertUpdateTests.java | 43 +++++- .../main/java/org/jooq/FactoryOperations.java | 10 +- jOOQ/src/main/java/org/jooq/MergeKeyStep.java | 9 +- .../main/java/org/jooq/MergeValuesStep.java | 13 +- .../main/java/org/jooq/impl/MergeImpl.java | 135 +++++++++++++++++- 5 files changed, 194 insertions(+), 16 deletions(-) diff --git a/jOOQ-test/src/org/jooq/test/_/testcases/InsertUpdateTests.java b/jOOQ-test/src/org/jooq/test/_/testcases/InsertUpdateTests.java index 75c8438ebc..ffe2692d89 100644 --- a/jOOQ-test/src/org/jooq/test/_/testcases/InsertUpdateTests.java +++ b/jOOQ-test/src/org/jooq/test/_/testcases/InsertUpdateTests.java @@ -795,7 +795,15 @@ extends BaseTest authors4 = create().selectFrom(TAuthor()).orderBy(TAuthor_ID()).fetch(); @@ -858,6 +875,28 @@ extends BaseTest authors5 = create().selectFrom(TAuthor()).orderBy(TAuthor_ID()).fetch(); + assertEquals(4, authors5.size()); + assertEquals(3, (int) authors5.get(2).getValue(TAuthor_ID())); + assertEquals("Eder", authors5.get(2).getValue(TAuthor_LAST_NAME())); + assertEquals("John", authors5.get(2).getValue(TAuthor_FIRST_NAME())); + assertEquals(4, (int) authors5.get(3).getValue(TAuthor_ID())); + assertEquals("Eder", authors5.get(3).getValue(TAuthor_LAST_NAME())); + assertEquals("John", authors5.get(3).getValue(TAuthor_FIRST_NAME())); } @Test diff --git a/jOOQ/src/main/java/org/jooq/FactoryOperations.java b/jOOQ/src/main/java/org/jooq/FactoryOperations.java index 72c07d86e3..2b57ab2a0e 100644 --- a/jOOQ/src/main/java/org/jooq/FactoryOperations.java +++ b/jOOQ/src/main/java/org/jooq/FactoryOperations.java @@ -630,9 +630,15 @@ public interface FactoryOperations extends Configuration { * www.h2database.com/html/grammar.html#merge * + * + * DB2, HSQLDB, Oracle, SQL Server, Sybase SQL Anywhere + * These databases can simulate the H2-specific MERGE statement using a + * standard SQL MERGE statement, without restrictions + * See {@link #mergeInto(Table)} for the standard MERGE statement + * * */ - @Support(H2) + @Support({ DB2, H2, HSQLDB, ORACLE, SQLSERVER, SYBASE }) MergeKeyStep mergeInto(Table table, Field... fields); /** @@ -640,7 +646,7 @@ public interface FactoryOperations extends Configuration { * * @see #mergeInto(Table, Field...) */ - @Support(H2) + @Support({ DB2, H2, HSQLDB, ORACLE, SQLSERVER, SYBASE }) MergeKeyStep mergeInto(Table table, Collection> fields); /** diff --git a/jOOQ/src/main/java/org/jooq/MergeKeyStep.java b/jOOQ/src/main/java/org/jooq/MergeKeyStep.java index 998410c25f..5ea015da54 100644 --- a/jOOQ/src/main/java/org/jooq/MergeKeyStep.java +++ b/jOOQ/src/main/java/org/jooq/MergeKeyStep.java @@ -35,7 +35,12 @@ */ package org.jooq; +import static org.jooq.SQLDialect.DB2; import static org.jooq.SQLDialect.H2; +import static org.jooq.SQLDialect.HSQLDB; +import static org.jooq.SQLDialect.ORACLE; +import static org.jooq.SQLDialect.SQLSERVER; +import static org.jooq.SQLDialect.SYBASE; import java.util.Collection; @@ -61,7 +66,7 @@ public interface MergeKeyStep extends MergeValuesStep { * Use this optional clause in order to override using the underlying * PRIMARY KEY. */ - @Support(H2) + @Support({ DB2, H2, HSQLDB, ORACLE, SQLSERVER, SYBASE }) MergeValuesStep key(Field... keys); /** @@ -70,6 +75,6 @@ public interface MergeKeyStep extends MergeValuesStep { * Use this optional clause in order to override using the underlying * PRIMARY KEY. */ - @Support(H2) + @Support({ DB2, H2, HSQLDB, ORACLE, SQLSERVER, SYBASE }) MergeValuesStep key(Collection> keys); } diff --git a/jOOQ/src/main/java/org/jooq/MergeValuesStep.java b/jOOQ/src/main/java/org/jooq/MergeValuesStep.java index f255f9cf54..6f87dfbcf7 100644 --- a/jOOQ/src/main/java/org/jooq/MergeValuesStep.java +++ b/jOOQ/src/main/java/org/jooq/MergeValuesStep.java @@ -35,7 +35,12 @@ */ package org.jooq; +import static org.jooq.SQLDialect.DB2; import static org.jooq.SQLDialect.H2; +import static org.jooq.SQLDialect.HSQLDB; +import static org.jooq.SQLDialect.ORACLE; +import static org.jooq.SQLDialect.SQLSERVER; +import static org.jooq.SQLDialect.SYBASE; import java.util.Collection; @@ -58,19 +63,19 @@ public interface MergeValuesStep { /** * Specify a VALUES clause */ - @Support(H2) + @Support({ DB2, H2, HSQLDB, ORACLE, SQLSERVER, SYBASE }) Merge values(Object... values); /** * Specify a VALUES clause */ - @Support(H2) + @Support({ DB2, H2, HSQLDB, ORACLE, SQLSERVER, SYBASE }) Merge values(Field... values); /** * Specify a VALUES clause */ - @Support(H2) + @Support({ DB2, H2, HSQLDB, ORACLE, SQLSERVER, SYBASE }) Merge values(Collection values); /** @@ -83,6 +88,6 @@ public interface MergeValuesStep { * {@link FactoryOperations#mergeInto(Table, Field...)} or * {@link FactoryOperations#mergeInto(Table, Collection)} */ - @Support(H2) + @Support({ DB2, H2, HSQLDB, ORACLE, SQLSERVER, SYBASE }) Merge select(Select select); } diff --git a/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java b/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java index 5201e30e99..a1cac772c4 100644 --- a/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java @@ -48,8 +48,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.jooq.Attachable; import org.jooq.BindContext; @@ -70,6 +73,8 @@ import org.jooq.RenderContext; import org.jooq.Select; import org.jooq.Table; import org.jooq.TableLike; +import org.jooq.UniqueKey; +import org.jooq.UpdatableTable; import org.jooq.exception.SQLDialectNotSupportedException; import org.jooq.tools.StringUtils; @@ -395,10 +400,127 @@ implements // QueryPart API // ------------------------------------------------------------------------- + /** + * Return a standard MERGE statement simulating the H2-specific syntax + */ + private final QueryPart getStandardMerge(Configuration config) { + switch (config.getDialect()) { + case DB2: + case HSQLDB: + case ORACLE: + case SQLSERVER: + case SYBASE: { + + // The SRC for the USING() clause: + // ------------------------------ + Table src; + if (h2Select != null) { + FieldList v = new FieldList(); + + for (int i = 0; i < h2Select.getFields().size(); i++) { + v.add(h2Select.getField(i).as("s" + (i + 1))); + } + + // [#579] TODO: Currently, this syntax may require aliasing + // on the call-site + src = create().select(v).from(h2Select).asTable("src"); + } + else { + FieldList v = new FieldList(); + + for (int i = 0; i < getH2Values().size(); i++) { + v.add(getH2Values().get(i).as("s" + (i + 1))); + } + + src = create().select(v).asTable("src"); + } + + // The condition for the ON clause: + // -------------------------------- + Set> onFields = new HashSet>(); + Condition condition = null; + if (getH2Keys().isEmpty()) { + if (table instanceof UpdatableTable) { + UniqueKey key = ((UpdatableTable) table).getMainKey(); + onFields.addAll(key.getFields()); + + for (int i = 0; i < key.getFields().size(); i++) { + + @SuppressWarnings({ "unchecked", "rawtypes" }) + Condition rhs = key.getFields().get(i).equal((Field) src.getField(i)); + + if (condition == null) { + condition = rhs; + } + else { + condition = condition.and(rhs); + } + } + } + + // This should probably execute an INSERT statement + else { + throw new IllegalStateException("Cannot omit KEY() clause on a non-Updatable Table"); + } + } + else { + for (int i = 0; i < getH2Keys().size(); i++) { + int matchIndex = getH2Fields().indexOf(getH2Keys().get(i)); + if (matchIndex == -1) { + throw new IllegalStateException("Fields in KEY() clause must be part of the fields specified in MERGE INTO table (...)"); + } + + onFields.addAll(getH2Keys()); + + @SuppressWarnings({ "unchecked", "rawtypes" }) + Condition rhs = getH2Keys().get(i).equal((Field) src.getField(matchIndex)); + + if (condition == null) { + condition = rhs; + } + else { + condition = condition.and(rhs); + } + } + } + + // INSERT and UPDATE clauses + // ------------------------- + Map, Field> update = new LinkedHashMap, Field>(); + Map, Field> insert = new LinkedHashMap, Field>(); + + for (int i = 0; i < src.getFields().size(); i++) { + + // Oracle does not allow to update fields from the ON clause + if (!onFields.contains(getH2Fields().get(i))) { + update.put(getH2Fields().get(i), src.getField(i)); + } + + insert.put(getH2Fields().get(i), src.getField(i)); + } + + return create().mergeInto(table) + .using(src) + .on(condition) + .whenMatchedThenUpdate() + .set(update) + .whenNotMatchedThenInsert() + .set(insert); + } + default: + throw new SQLDialectNotSupportedException("The H2-specific MERGE syntax is not supported in dialect : " + config.getDialect()); + } + } + @Override public final void toSQL(RenderContext context) { if (h2Style) { - toSQLH2(context); + if (context.getDialect() == H2) { + toSQLH2(context); + } + else { + context.sql(getStandardMerge(context)); + } } else { toSQLStandard(context); @@ -406,10 +528,6 @@ implements } 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) @@ -529,7 +647,12 @@ implements @Override public final void bind(BindContext context) { if (h2Style) { - bindH2(context); + if (context.getDialect() == H2) { + bindH2(context); + } + else { + context.bind(getStandardMerge(context)); + } } else { bindStandard(context);