[#2665] Added support for the MERGE clause

This commit is contained in:
Lukas Eder 2013-08-15 17:55:14 +02:00
parent 5bf3422c9b
commit 55922a354b
5 changed files with 270 additions and 21 deletions

View File

@ -132,8 +132,7 @@ public enum Clause {
* A field alias declaration.
* <p>
* This clause surrounds a field alias declaration, for instance within the
* {@link #SELECT_SELECT} clause,
* wrapping another {@link #FIELD}.
* {@link #SELECT_SELECT} clause, wrapping another {@link #FIELD}.
* <p>
* Referenced field aliases emit {@link #FIELD_REFERENCE} clauses.
*/
@ -174,7 +173,6 @@ public enum Clause {
*/
CONDITION_IS_NOT_NULL,
// TODO: Should operators be distinguished?
// - LIKE predicate
// - Subselect predicates
@ -412,7 +410,6 @@ public enum Clause {
SELECT_HAVING,
SELECT_ORDER_BY,
// -------------------------------------------------------------------------
// Clauses that are used in an INSERT statement
// -------------------------------------------------------------------------
@ -520,9 +517,9 @@ public enum Clause {
* <p>
* This clause surrounds
* <ul>
* <li>a column</li>
* <li>a {@link #FIELD} receiving the assignment</li>
* <li>an assigment operator</li>
* <li>a value being assigned</li>
* <li>a {@link #FIELD} being assigned</li>
* </ul>
*/
UPDATE_SET_ASSIGNMENT,
@ -584,11 +581,132 @@ public enum Clause {
// Clauses that are used in an MERGE statement
// -------------------------------------------------------------------------
/**
* A complete <code>MERGE</code> statement.
*/
MERGE,
/**
* A <code>MERGE INTO</code> clause within an {@link #MERGE} statement.
* <p>
* This clause surrounds
* <ul>
* <li>the <code>MERGE INTO</code> keywords</li>
* <li>the table that is being merged</li>
* </ul>
*/
MERGE_MERGE_INTO,
MERGE_WHEN_MATCHED_THEN_UPDATE_SET,
MERGE_WHEN_MATCHED_THEN_UPDATE_SET_ASSIGNMENT,
/**
* A <code>USING</code> clause within a {@link #MERGE} statement.
* <p>
* This clause surrounds
* <ul>
* <li>the <code>USING</code> keyword</li>
* <li>a {@link #TABLE}</li>
* </ul>
*/
MERGE_USING,
/**
* An <code>ON</code> clause within a {@link #MERGE} statement.
* <p>
* This clause surrounds
* <ul>
* <li>the <code>ON</code> keyword</li>
* <li>a {@link #CONDITION}</li>
* </ul>
*/
MERGE_ON,
/**
* A <code>WHEN MATCHED THEN UPDATE</code> clause within a {@link #MERGE}
* statement.
* <p>
* This clause surrounds
* <ul>
* <li>the <code>WHEN MATCHED THEN UPDATE</code> keywords</li>
* <li>a {@link #MERGE_SET} clause</li>
* <li>a {@link #MERGE_WHERE} clause</li>
* <li>a {@link #MERGE_DELETE_WHERE} clause</li>
* </ul>
*/
MERGE_WHEN_MATCHED_THEN_UPDATE,
/**
* A <code>SET</code> clause within a
* {@link #MERGE_WHEN_MATCHED_THEN_UPDATE} clause within an {@link #MERGE}
* statement.
* <p>
* This clause surrounds
* <ul>
* <li>the <code>SET</code> keyword</li>
* <li>several {@link #MERGE_SET_ASSIGNMENT} clauses</li>
* </ul>
*/
MERGE_SET,
/**
* An assigment within a {@link #MERGE_SET} clause within an {@link #MERGE}
* statement.
* <p>
* This clause surrounds
* <ul>
* <li>a {@link #FIELD} receiving the assignment</li>
* <li>an assigment operator</li>
* <li>a {@link #FIELD} being assigned</li>
* </ul>
*/
MERGE_SET_ASSIGNMENT,
/**
* A <code>WHERE</code> clause within a
* {@link #MERGE_WHEN_MATCHED_THEN_UPDATE} clause within a
* {@link #MERGE} statement.
* <p>
* This clause surrounds
* <ul>
* <li>the <code>WHERE</code> keyword</li>
* <li>a {@link #CONDITION}</li>
* </ul>
*/
MERGE_WHERE,
/**
* A <code>DELETE_WHERE</code> clause within a
* {@link #MERGE_WHEN_MATCHED_THEN_UPDATE} clause within a {@link #MERGE}
* statement.
* <p>
* This clause surrounds
* <ul>
* <li>the <code>DELETE WHERE</code> keyword</li>
* <li>a {@link #CONDITION}</li>
* </ul>
*/
MERGE_DELETE_WHERE,
/**
* A <code>WHEN NOT MATCHED THEN INSERT</code> clause within a
* {@link #MERGE} statement.
* <p>
* This clause surrounds
* <ul>
* <li>the <code>WHEN NOT MATCHED THEN INSERT</code> keywords</li>
* <li>several {@link #FIELD} clauses</li>
* </ul>
*/
MERGE_WHEN_NOT_MATCHED_THEN_INSERT,
/**
* A <code>VALUES</code> clause within a {@link #MERGE} statement.
* <p>
* This clause surrounds
* <ul>
* <li>the <code>VALUES</code> keyword</li>
* <li>several {@link #FIELD_ROW} clauses</li>
* </ul>
*/
MERGE_VALUES,
// -------------------------------------------------------------------------
// Clauses that are used in an TRUNCATE statement
@ -609,4 +727,4 @@ public enum Clause {
* </ul>
*/
TRUNCATE_TRUNCATE,
}
}

View File

@ -36,12 +36,22 @@
package org.jooq.impl;
import static org.jooq.Clause.MERGE;
import static org.jooq.Clause.MERGE_WHEN_MATCHED_THEN_UPDATE_SET_ASSIGNMENT;
import static org.jooq.Clause.MERGE_DELETE_WHERE;
import static org.jooq.Clause.MERGE_MERGE_INTO;
import static org.jooq.Clause.MERGE_ON;
import static org.jooq.Clause.MERGE_SET;
import static org.jooq.Clause.MERGE_SET_ASSIGNMENT;
import static org.jooq.Clause.MERGE_USING;
import static org.jooq.Clause.MERGE_VALUES;
import static org.jooq.Clause.MERGE_WHEN_MATCHED_THEN_UPDATE;
import static org.jooq.Clause.MERGE_WHEN_NOT_MATCHED_THEN_INSERT;
import static org.jooq.Clause.MERGE_WHERE;
import static org.jooq.SQLDialect.H2;
import static org.jooq.impl.DSL.condition;
import static org.jooq.impl.DSL.exists;
import static org.jooq.impl.DSL.notExists;
import static org.jooq.impl.DSL.nullSafe;
import static org.jooq.impl.Utils.DATA_WRAP_DERIVED_TABLES_IN_PARENTHESES;
import java.util.ArrayList;
import java.util.Arrays;
@ -678,7 +688,7 @@ implements
@Override
public final MergeImpl whenMatchedThenUpdate() {
matchedClause = true;
matchedUpdate = new FieldMapForUpdate(MERGE_WHEN_MATCHED_THEN_UPDATE_SET_ASSIGNMENT);
matchedUpdate = new FieldMapForUpdate(MERGE_SET_ASSIGNMENT);
notMatchedClause = false;
return this;
@ -1075,15 +1085,22 @@ implements
}
private final void toSQLStandard(RenderContext context) {
context.keyword("merge into").sql(" ")
context.start(MERGE_MERGE_INTO)
.keyword("merge into").sql(" ")
.declareTables(true)
.visit(table)
.declareTables(false)
.end(MERGE_MERGE_INTO)
.formatSeparator()
.start(MERGE_USING)
.declareTables(true)
.keyword("using").sql(" ")
.formatIndentStart()
.formatNewLine()
.sql(Utils.wrapInParentheses(context.render(using)))
.formatIndentEnd()
.formatNewLine();
context.data(DATA_WRAP_DERIVED_TABLES_IN_PARENTHESES, true);
context.visit(using);
context.data(DATA_WRAP_DERIVED_TABLES_IN_PARENTHESES, null);
context.formatIndentEnd()
.declareTables(false);
switch (context.configuration().dialect().family()) {
@ -1117,9 +1134,15 @@ implements
}
}
context.formatSeparator()
.keyword("on").sql(" ")
.sql(Utils.wrapInParentheses(context.render(on)));
context.end(MERGE_USING)
.formatSeparator()
.start(MERGE_ON)
.keyword("on").sql(" (")
.visit(on)
.sql(")")
.end(MERGE_ON)
.start(MERGE_WHEN_MATCHED_THEN_UPDATE)
.start(MERGE_SET);
// [#999] WHEN MATCHED clause is optional
if (matchedUpdate != null) {
@ -1128,6 +1151,9 @@ implements
.visit(matchedUpdate);
}
context.end(MERGE_SET)
.start(MERGE_WHERE);
// [#998] Oracle MERGE extension: WHEN MATCHED THEN UPDATE .. WHERE
if (matchedWhere != null) {
context.formatSeparator()
@ -1135,6 +1161,9 @@ implements
.visit(matchedWhere);
}
context.end(MERGE_WHERE)
.start(MERGE_DELETE_WHERE);
// [#998] Oracle MERGE extension: WHEN MATCHED THEN UPDATE .. DELETE WHERE
if (matchedDeleteWhere != null) {
context.formatSeparator()
@ -1142,15 +1171,24 @@ implements
.visit(matchedDeleteWhere);
}
context.end(MERGE_DELETE_WHERE)
.end(MERGE_WHEN_MATCHED_THEN_UPDATE)
.start(MERGE_WHEN_NOT_MATCHED_THEN_INSERT);
// [#999] WHEN NOT MATCHED clause is optional
if (notMatchedInsert != null) {
context.formatSeparator()
.keyword("when not matched then insert").sql(" ");
notMatchedInsert.toSQLReferenceKeys(context);
context.formatSeparator().keyword("values")
.sql(" ").visit(notMatchedInsert);
context.formatSeparator()
.start(MERGE_VALUES)
.keyword("values").sql(" ")
.visit(notMatchedInsert)
.end(MERGE_VALUES);
}
context.start(MERGE_WHERE);
// [#998] Oracle MERGE extension: WHEN NOT MATCHED THEN INSERT .. WHERE
if (notMatchedWhere != null) {
context.formatSeparator()
@ -1158,6 +1196,9 @@ implements
.visit(notMatchedWhere);
}
context.end(MERGE_WHERE)
.end(MERGE_WHEN_NOT_MATCHED_THEN_INSERT);
switch (context.configuration().dialect().family()) {
case SQLSERVER:
context.sql(";");

View File

@ -35,6 +35,7 @@
*/
package org.jooq.impl;
import static java.lang.Boolean.TRUE;
import static java.util.Arrays.asList;
import static org.jooq.Clause.SELECT;
import static org.jooq.Clause.SELECT_CONNECT_BY;
@ -65,6 +66,7 @@ import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DSL.one;
import static org.jooq.impl.DSL.rowNumber;
import static org.jooq.impl.Utils.DATA_ROW_VALUE_EXPRESSION_PREDICATE_SUBQUERY;
import static org.jooq.impl.Utils.DATA_WRAP_DERIVED_TABLES_IN_PARENTHESES;
import java.util.Arrays;
import java.util.Collection;
@ -197,6 +199,11 @@ class SelectQueryImpl<R extends Record> extends AbstractSelect<R> implements Sel
@Override
public final void toSQL(RenderContext context) {
Boolean wrapDerivedTables = (Boolean) context.data(DATA_WRAP_DERIVED_TABLES_IN_PARENTHESES);
if (TRUE.equals(wrapDerivedTables)) {
context.sql("(")
.data(DATA_WRAP_DERIVED_TABLES_IN_PARENTHESES, null);
}
// If a limit applies
if (getLimit().isApplicable()) {
@ -340,6 +347,11 @@ class SelectQueryImpl<R extends Record> extends AbstractSelect<R> implements Sel
context.formatSeparator()
.sql(option);
}
if (TRUE.equals(wrapDerivedTables)) {
context.sql(")")
.data(DATA_WRAP_DERIVED_TABLES_IN_PARENTHESES, true);
}
}
/**

View File

@ -194,6 +194,17 @@ final class Utils {
*/
static final String DATA_OMIT_CLAUSE_EVENT_EMISSION = "org.jooq.configuration.omit-clause-event-emission";
/**
* [#2665] Wrap derived tables in parentheses.
* <p>
* Before allowing for hooking into the SQL transformation SPI, new
* {@link RenderContext} instances could be created to "try" to render a
* given SQL subclause before inserting it into the real SQL string. This
* practice should no longer be pursued, as such "sub-renderers" will emit /
* divert {@link Clause} events.
*/
static final String DATA_WRAP_DERIVED_TABLES_IN_PARENTHESES = "org.jooq.configuration.wrap-derived-tables-in-parentheses";
// ------------------------------------------------------------------------
// Other constants
// ------------------------------------------------------------------------

View File

@ -68,6 +68,17 @@ import static org.jooq.Clause.INSERT_ON_DUPLICATE_KEY_UPDATE_ASSIGNMENT;
import static org.jooq.Clause.INSERT_RETURNING;
import static org.jooq.Clause.INSERT_SELECT;
import static org.jooq.Clause.INSERT_VALUES;
import static org.jooq.Clause.MERGE;
import static org.jooq.Clause.MERGE_DELETE_WHERE;
import static org.jooq.Clause.MERGE_MERGE_INTO;
import static org.jooq.Clause.MERGE_ON;
import static org.jooq.Clause.MERGE_SET;
import static org.jooq.Clause.MERGE_SET_ASSIGNMENT;
import static org.jooq.Clause.MERGE_USING;
import static org.jooq.Clause.MERGE_VALUES;
import static org.jooq.Clause.MERGE_WHEN_MATCHED_THEN_UPDATE;
import static org.jooq.Clause.MERGE_WHEN_NOT_MATCHED_THEN_INSERT;
import static org.jooq.Clause.MERGE_WHERE;
import static org.jooq.Clause.SELECT;
import static org.jooq.Clause.SELECT_CONNECT_BY;
import static org.jooq.Clause.SELECT_FROM;
@ -104,6 +115,8 @@ import static org.jooq.test.data.Table1.FIELD_DATE1;
import static org.jooq.test.data.Table1.FIELD_ID1;
import static org.jooq.test.data.Table1.FIELD_NAME1;
import static org.jooq.test.data.Table1.TABLE1;
import static org.jooq.test.data.Table2.FIELD_ID2;
import static org.jooq.test.data.Table2.TABLE2;
import java.sql.SQLException;
import java.util.ArrayList;
@ -465,6 +478,60 @@ public class VisitContextTest extends AbstractTest {
.select(select(val(1), val("value"), val(null))));
}
@Test
public void test_MERGE_simple() {
assertEvents(asList(
asList(MERGE),
asList(MERGE, MERGE_MERGE_INTO),
asList(MERGE, MERGE_MERGE_INTO, TABLE),
asList(MERGE, MERGE_MERGE_INTO, TABLE, TABLE_REFERENCE),
asList(MERGE, MERGE_USING),
asList(MERGE, MERGE_USING, TABLE),
asList(MERGE, MERGE_USING, TABLE, TABLE_REFERENCE),
asList(MERGE, MERGE_ON),
asList(MERGE, MERGE_ON, CONDITION),
asList(MERGE, MERGE_ON, CONDITION, CONDITION_COMPARISON),
asList(MERGE, MERGE_ON, CONDITION, CONDITION_COMPARISON, FIELD),
asList(MERGE, MERGE_ON, CONDITION, CONDITION_COMPARISON, FIELD, FIELD_REFERENCE),
asList(MERGE, MERGE_ON, CONDITION, CONDITION_COMPARISON, FIELD),
asList(MERGE, MERGE_ON, CONDITION, CONDITION_COMPARISON, FIELD, FIELD_REFERENCE),
asList(MERGE, MERGE_WHEN_MATCHED_THEN_UPDATE),
asList(MERGE, MERGE_WHEN_MATCHED_THEN_UPDATE, MERGE_SET),
asList(MERGE, MERGE_WHEN_MATCHED_THEN_UPDATE, MERGE_SET, MERGE_SET_ASSIGNMENT),
asList(MERGE, MERGE_WHEN_MATCHED_THEN_UPDATE, MERGE_SET, MERGE_SET_ASSIGNMENT, FIELD),
asList(MERGE, MERGE_WHEN_MATCHED_THEN_UPDATE, MERGE_SET, MERGE_SET_ASSIGNMENT, FIELD, FIELD_REFERENCE),
asList(MERGE, MERGE_WHEN_MATCHED_THEN_UPDATE, MERGE_SET, MERGE_SET_ASSIGNMENT, FIELD),
asList(MERGE, MERGE_WHEN_MATCHED_THEN_UPDATE, MERGE_SET, MERGE_SET_ASSIGNMENT, FIELD, FIELD_VALUE),
asList(MERGE, MERGE_WHEN_MATCHED_THEN_UPDATE, MERGE_SET, MERGE_SET_ASSIGNMENT),
asList(MERGE, MERGE_WHEN_MATCHED_THEN_UPDATE, MERGE_SET, MERGE_SET_ASSIGNMENT, FIELD),
asList(MERGE, MERGE_WHEN_MATCHED_THEN_UPDATE, MERGE_SET, MERGE_SET_ASSIGNMENT, FIELD, FIELD_REFERENCE),
asList(MERGE, MERGE_WHEN_MATCHED_THEN_UPDATE, MERGE_SET, MERGE_SET_ASSIGNMENT, FIELD),
asList(MERGE, MERGE_WHEN_MATCHED_THEN_UPDATE, MERGE_SET, MERGE_SET_ASSIGNMENT, FIELD, FIELD_REFERENCE),
asList(MERGE, MERGE_WHEN_MATCHED_THEN_UPDATE, MERGE_WHERE),
asList(MERGE, MERGE_WHEN_MATCHED_THEN_UPDATE, MERGE_DELETE_WHERE),
asList(MERGE, MERGE_WHEN_NOT_MATCHED_THEN_INSERT),
asList(MERGE, MERGE_WHEN_NOT_MATCHED_THEN_INSERT, FIELD),
asList(MERGE, MERGE_WHEN_NOT_MATCHED_THEN_INSERT, FIELD, FIELD_REFERENCE),
asList(MERGE, MERGE_WHEN_NOT_MATCHED_THEN_INSERT, FIELD),
asList(MERGE, MERGE_WHEN_NOT_MATCHED_THEN_INSERT, FIELD, FIELD_REFERENCE),
asList(MERGE, MERGE_WHEN_NOT_MATCHED_THEN_INSERT, MERGE_VALUES),
asList(MERGE, MERGE_WHEN_NOT_MATCHED_THEN_INSERT, MERGE_VALUES, FIELD_ROW),
asList(MERGE, MERGE_WHEN_NOT_MATCHED_THEN_INSERT, MERGE_VALUES, FIELD_ROW, FIELD),
asList(MERGE, MERGE_WHEN_NOT_MATCHED_THEN_INSERT, MERGE_VALUES, FIELD_ROW, FIELD, FIELD_VALUE),
asList(MERGE, MERGE_WHEN_NOT_MATCHED_THEN_INSERT, MERGE_VALUES, FIELD_ROW, FIELD),
asList(MERGE, MERGE_WHEN_NOT_MATCHED_THEN_INSERT, MERGE_VALUES, FIELD_ROW, FIELD, FIELD_VALUE),
asList(MERGE, MERGE_WHEN_NOT_MATCHED_THEN_INSERT, MERGE_WHERE)
),
ctx.mergeInto(TABLE1)
.using(TABLE2)
.on(FIELD_ID1.eq(FIELD_ID2))
.whenMatchedThenUpdate()
.set(FIELD_NAME1, "a")
.set(FIELD_DATE1, FIELD_DATE1)
.whenNotMatchedThenInsert(FIELD_ID1, FIELD_NAME1)
.values(1, "a"));
}
@Test
public void test_TRUNCATE_simple() {
assertEvents(asList(