[#1542] Simulate the H2 MERGE syntax in other dialects supporting the

SQL standard MERGE statement - GitHub Issue #18
This commit is contained in:
Lukas Eder 2012-07-06 22:18:08 +02:00
parent dfdbc9536c
commit 55f6c2c53b
5 changed files with 194 additions and 16 deletions

View File

@ -795,7 +795,15 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658,
@Test
public void testH2Merge() throws Exception {
switch (getDialect()) {
// Native support by H2
case H2:
// Simulation through SQL MERGE
case DB2:
case HSQLDB:
case ORACLE:
case SQLSERVER:
case SYBASE:
break;
default:
@ -806,6 +814,7 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658,
jOOQAbstractTest.reset = false;
// H2 MERGE test leading to a single INSERT .. VALUES
// -------------------------------------------------------------
assertEquals(1,
create().mergeInto(TAuthor(), TAuthor_ID(), TAuthor_LAST_NAME())
.values(3, "Hesse")
@ -818,6 +827,7 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658,
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")
@ -830,6 +840,7 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658,
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())
@ -843,11 +854,17 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658,
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"))))
// inline() strings here. It seems that DB2 will lack page size
// in the system temporary table space, otherwise
// [#579] TODO: Aliasing shouldn't be necessary
.select(create().select(val(3).as("a"), inline("Eder").as("b")).unionAll(
create().select(val(4).as("a"), inline("Eder").as("b"))))
.execute());
Result<A> authors4 = create().selectFrom(TAuthor()).orderBy(TAuthor_ID()).fetch();
@ -858,6 +875,28 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658,
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()));
// H2 MERGE test specifying a subselect
// -------------------------------------------------------------
assertEquals(2,
create().mergeInto(TAuthor(), TAuthor_ID(), TAuthor_FIRST_NAME())
// inline() strings here. It seems that DB2 will lack page size
// in the system temporary table space, otherwise
// [#579] TODO: Aliasing shouldn't be necessary
.select(create().select(val(3).as("a"), inline("John").as("b")).unionAll(
create().select(val(4).as("a"), inline("John").as("b"))))
.execute());
Result<A> 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

View File

@ -630,9 +630,15 @@ public interface FactoryOperations extends Configuration {
* <td><a href= "www.h2database.com/html/grammar.html#merge"
* >www.h2database.com/html/grammar.html#merge</a></td>
* </tr>
* <tr>
* <td>DB2, HSQLDB, Oracle, SQL Server, Sybase SQL Anywhere</td>
* <td>These databases can simulate the H2-specific MERGE statement using a
* standard SQL MERGE statement, without restrictions</td>
* <td>See {@link #mergeInto(Table)} for the standard MERGE statement</td>
* </tr>
* </table>
*/
@Support(H2)
@Support({ DB2, H2, HSQLDB, ORACLE, SQLSERVER, SYBASE })
<R extends Record> MergeKeyStep<R> mergeInto(Table<R> 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 })
<R extends Record> MergeKeyStep<R> mergeInto(Table<R> table, Collection<? extends Field<?>> fields);
/**

View File

@ -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<R extends Record> extends MergeValuesStep<R> {
* Use this optional clause in order to override using the underlying
* <code>PRIMARY KEY</code>.
*/
@Support(H2)
@Support({ DB2, H2, HSQLDB, ORACLE, SQLSERVER, SYBASE })
MergeValuesStep<R> key(Field<?>... keys);
/**
@ -70,6 +75,6 @@ public interface MergeKeyStep<R extends Record> extends MergeValuesStep<R> {
* Use this optional clause in order to override using the underlying
* <code>PRIMARY KEY</code>.
*/
@Support(H2)
@Support({ DB2, H2, HSQLDB, ORACLE, SQLSERVER, SYBASE })
MergeValuesStep<R> key(Collection<? extends Field<?>> keys);
}

View File

@ -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<R extends Record> {
/**
* Specify a <code>VALUES</code> clause
*/
@Support(H2)
@Support({ DB2, H2, HSQLDB, ORACLE, SQLSERVER, SYBASE })
Merge<R> values(Object... values);
/**
* Specify a <code>VALUES</code> clause
*/
@Support(H2)
@Support({ DB2, H2, HSQLDB, ORACLE, SQLSERVER, SYBASE })
Merge<R> values(Field<?>... values);
/**
* Specify a <code>VALUES</code> clause
*/
@Support(H2)
@Support({ DB2, H2, HSQLDB, ORACLE, SQLSERVER, SYBASE })
Merge<R> values(Collection<?> values);
/**
@ -83,6 +88,6 @@ public interface MergeValuesStep<R extends Record> {
* {@link FactoryOperations#mergeInto(Table, Field...)} or
* {@link FactoryOperations#mergeInto(Table, Collection)}
*/
@Support(H2)
@Support({ DB2, H2, HSQLDB, ORACLE, SQLSERVER, SYBASE })
Merge<R> select(Select<?> select);
}

View File

@ -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<Field<?>> onFields = new HashSet<Field<?>>();
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<?>, Field<?>> update = new LinkedHashMap<Field<?>, Field<?>>();
Map<Field<?>, Field<?>> insert = new LinkedHashMap<Field<?>, 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);