[#1541] Add support for the H2 MERGE syntax - GitHub Issue #18

This commit is contained in:
Lukas Eder 2012-07-06 21:02:11 +02:00
parent 633d1a43a9
commit dfdbc9536c
9 changed files with 466 additions and 25 deletions

View File

@ -792,6 +792,74 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658,
// Especially for SQL Server and Sybase, some bugs could be expected
}
@Test
public void testH2Merge() throws Exception {
switch (getDialect()) {
case H2:
break;
default:
log.info("SKIPPING", "H2-specific MERGE syntax test");
return;
}
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")
.execute());
Result<A> 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<A> 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<A> 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<A> 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()) {

View File

@ -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();

View File

@ -549,7 +549,7 @@ public interface FactoryOperations extends Configuration {
<R extends Record> UpdateSetStep<R> update(Table<R> table);
/**
* Create a new DSL merge statement.
* Create a new DSL SQL standard MERGE statement.
* <p>
* 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();
* </pre></code>
* <p>
* 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 })
<R extends Record> MergeUsingStep<R> mergeInto(Table<R> table);
/**
* Create a new DSL merge statement (H2-specific syntax)
* <p>
* This statement is available from DSL syntax only. It is known to be
* supported in some way by any of these dialects:
* <table border="1">
* <tr>
* <td>H2</td>
* <td>H2 natively supports this special syntax</td>
* <td><a href= "www.h2database.com/html/grammar.html#merge"
* >www.h2database.com/html/grammar.html#merge</a></td>
* </tr>
* </table>
*/
@Support(H2)
<R extends Record> MergeKeyStep<R> mergeInto(Table<R> table, Field<?>... fields);
/**
* Create a new DSL merge statement (H2-specific syntax)
*
* @see #mergeInto(Table, Field...)
*/
@Support(H2)
<R extends Record> MergeKeyStep<R> mergeInto(Table<R> table, Collection<? extends Field<?>> fields);
/**
* Create a new {@link DeleteQuery}
*

View File

@ -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.
* <p>
* Example: <code><pre>
* Factory create = new Factory();
*
* create.mergeInto(table, field1, field2)
* .key(id)
* .values(value1, value2)
* .execute();
* </pre></code>
*
* @author Lukas Eder
*/
public interface MergeKeyStep<R extends Record> extends MergeValuesStep<R> {
/**
* Specify an optional <code>KEY</code> clause.
* <p>
* Use this optional clause in order to override using the underlying
* <code>PRIMARY KEY</code>.
*/
@Support(H2)
MergeValuesStep<R> key(Field<?>... keys);
/**
* Specify an optional <code>KEY</code> clause.
* <p>
* Use this optional clause in order to override using the underlying
* <code>PRIMARY KEY</code>.
*/
@Support(H2)
MergeValuesStep<R> key(Collection<? extends Field<?>> keys);
}

View File

@ -46,7 +46,7 @@ import static org.jooq.SQLDialect.SYBASE;
* <p>
* Example: <code><pre>
* 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();
* </pre></code>
*
*
* @author Lukas Eder
*/
public interface MergeUsingStep<R extends Record> {
public interface MergeUsingStep<R extends Record> extends MergeKeyStep<R> {
/**
* Add the <code>USING</code> clause to the <code>MERGE</code> statement
* Add the <code>USING</code> clause to the SQL standard <code>MERGE</code>
* statement
*/
@Support({ DB2, HSQLDB, ORACLE, SQLSERVER, SYBASE })
MergeOnStep<R> using(TableLike<?> table);
/**
* Add a dummy <code>USING</code> clause to the <code>MERGE</code> statement
* Add a dummy <code>USING</code> clause to the SQL standard
* <code>MERGE</code> statement
* <p>
* This results in <code>USING(SELECT 1 FROM DUAL)</code> for most RDBMS, or
* in <code>USING(SELECT 1) AS [dummy_table(dummy_field)]</code> in SQL

View File

@ -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.
* <p>
* Example: <code><pre>
* Factory create = new Factory();
*
* create.mergeInto(table, field1, field2)
* .key(id)
* .values(value1, value2)
* .execute();
* </pre></code>
*
* @author Lukas Eder
*/
public interface MergeValuesStep<R extends Record> {
/**
* Specify a <code>VALUES</code> clause
*/
@Support(H2)
Merge<R> values(Object... values);
/**
* Specify a <code>VALUES</code> clause
*/
@Support(H2)
Merge<R> values(Field<?>... values);
/**
* Specify a <code>VALUES</code> clause
*/
@Support(H2)
Merge<R> values(Collection<?> values);
/**
* Use a <code>SELECT</code> statement as the source of values for the
* <code>MERGE</code> statement
* <p>
* This variant of the <code>MERGE .. SELECT</code> statement expects a
* select returning exactly as many fields as specified previously in the
* <code>INTO</code> clause:
* {@link FactoryOperations#mergeInto(Table, Field...)} or
* {@link FactoryOperations#mergeInto(Table, Collection)}
*/
@Support(H2)
Merge<R> select(Select<?> select);
}

View File

@ -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<R>(this, table);
}
/**
* {@inheritDoc}
*/
@Override
public <R extends Record> MergeKeyStep<R> mergeInto(Table<R> table, Field<?>... fields) {
return mergeInto(table, Arrays.asList(fields));
}
/**
* {@inheritDoc}
*/
@Override
public <R extends Record> MergeKeyStep<R> mergeInto(Table<R> table, Collection<? extends Field<?>> fields) {
return new MergeImpl<R>(this, table, fields);
}
/**
* {@inheritDoc}
*/

View File

@ -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 <R extends Record> MergeKeyStep<R> mergeInto(Table<R> table, Field<?>... fields) {
return getDelegate().mergeInto(table, fields);
}
@Override
public final <R extends Record> MergeKeyStep<R> mergeInto(Table<R> table, Collection<? extends Field<?>> fields) {
return getDelegate().mergeInto(table, fields);
}
@Override
public final <R extends Record> DeleteQuery<R> deleteQuery(Table<R> table) {
return getDelegate().deleteQuery(table);

View File

@ -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<R extends Record> 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<R> table) {
this(configuration, table, null);
}
MergeImpl(Configuration configuration, Table<R> table, Collection<? extends Field<?>> 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<R> select(Select<?> select) {
h2Style = true;
h2Select = select;
return this;
}
@Override
public final MergeImpl<R> key(Field<?>... k) {
return key(Arrays.asList(k));
}
@Override
public final MergeImpl<R> key(Collection<? extends Field<?>> keys) {
h2Style = true;
getH2Keys().addAll(keys);
return this;
}
// -------------------------------------------------------------------------
// Shared MERGE API
// -------------------------------------------------------------------------
@Override
public final MergeImpl<R> 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<Field<?>> fields = new ArrayList<Field<?>>(notMatchedInsert.keySet());
notMatchedInsert.putValues(vals(convert(fields, values)));
}
return this;
}
@Override
public final MergeImpl<R> values(Field<?>... values) {
return values((Object[]) values);
}
@Override
public final MergeImpl<R> values(Collection<?> values) {
return values(values.toArray());
}
// -------------------------------------------------------------------------
// Merge API
// -------------------------------------------------------------------------
@Override
@ -270,23 +370,6 @@ implements
return this;
}
@Override
public final MergeImpl<R> values(Object... values) {
List<Field<?>> fields = new ArrayList<Field<?>>(notMatchedInsert.keySet());
notMatchedInsert.putValues(vals(convert(fields, values)));
return this;
}
@Override
public final MergeImpl<R> values(Field<?>... values) {
return values((Object[]) values);
}
@Override
public final MergeImpl<R> values(Collection<?> values) {
return values(values.toArray());
}
@Override
public final MergeImpl<R> 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);
}
}