[#1542] Simulate the H2 MERGE syntax in other dialects supporting the
SQL standard MERGE statement - GitHub Issue #18
This commit is contained in:
parent
dfdbc9536c
commit
55f6c2c53b
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
/**
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user