From 2ffd6f424609d3bd53d232c7d5d825df2b3bc16d Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Wed, 23 Mar 2022 16:20:08 +0100 Subject: [PATCH] [jOOQ/jOOQ#10523] Emulate UPDATE .. SET row = (SELECT ...) for dialects with no native support --- jOOQ/src/main/java/org/jooq/UpdateQuery.java | 46 +++---- .../java/org/jooq/UpdateSetFirstStep.java | 46 +++---- .../java/org/jooq/impl/FieldMapForUpdate.java | 128 ++++++++++++++---- .../java/org/jooq/impl/InsertQueryImpl.java | 37 ++++- .../main/java/org/jooq/impl/MergeImpl.java | 3 +- jOOQ/src/main/java/org/jooq/impl/Tools.java | 6 + .../java/org/jooq/impl/UpdateQueryImpl.java | 22 ++- 7 files changed, 195 insertions(+), 93 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/UpdateQuery.java b/jOOQ/src/main/java/org/jooq/UpdateQuery.java index 7e3899dceb..284b32b2cc 100644 --- a/jOOQ/src/main/java/org/jooq/UpdateQuery.java +++ b/jOOQ/src/main/java/org/jooq/UpdateQuery.java @@ -220,139 +220,139 @@ public interface UpdateQuery extends StoreQuery, ConditionP /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(RowN row, Select select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row1 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row2 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row3 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row4 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row5 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row6 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row7 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row8 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row9 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row10 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row11 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row12 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row13 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row14 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row15 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row16 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row17 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row18 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row19 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row20 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row21 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support void addValues(Row22 row, Select> select); diff --git a/jOOQ/src/main/java/org/jooq/UpdateSetFirstStep.java b/jOOQ/src/main/java/org/jooq/UpdateSetFirstStep.java index ceecae359a..72e0406d97 100644 --- a/jOOQ/src/main/java/org/jooq/UpdateSetFirstStep.java +++ b/jOOQ/src/main/java/org/jooq/UpdateSetFirstStep.java @@ -337,161 +337,161 @@ public interface UpdateSetFirstStep extends UpdateSetStep { * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(RowN row, Select select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row1 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row2 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row3 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row4 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row5 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row6 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row7 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row8 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row9 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row10 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row11 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row12 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row13 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row14 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row15 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row16 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row17 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row18 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row19 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row20 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row21 row, Select> select); /** * Specify a multi-column set clause for the UPDATE statement. */ @NotNull @CheckReturnValue - @Support({ H2, HSQLDB, POSTGRES, YUGABYTEDB }) + @Support UpdateFromStep set(Row22 row, Select> select); } diff --git a/jOOQ/src/main/java/org/jooq/impl/FieldMapForUpdate.java b/jOOQ/src/main/java/org/jooq/impl/FieldMapForUpdate.java index 7552cdedae..2e47185643 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FieldMapForUpdate.java +++ b/jOOQ/src/main/java/org/jooq/impl/FieldMapForUpdate.java @@ -37,22 +37,33 @@ */ package org.jooq.impl; -import static org.jooq.Clause.UPDATE_SET_ASSIGNMENT; // ... // ... // ... // ... // ... +// ... +import static org.jooq.SQLDialect.CUBRID; +// ... +import static org.jooq.SQLDialect.DERBY; +// ... +import static org.jooq.SQLDialect.FIREBIRD; import static org.jooq.SQLDialect.H2; // ... import static org.jooq.SQLDialect.HSQLDB; +import static org.jooq.SQLDialect.IGNITE; // ... // ... +import static org.jooq.SQLDialect.MARIADB; +// ... +import static org.jooq.SQLDialect.MYSQL; +// ... import static org.jooq.SQLDialect.POSTGRES; // ... // ... // ... // ... +// ... import static org.jooq.SQLDialect.SQLITE; // ... // ... @@ -61,10 +72,13 @@ import static org.jooq.SQLDialect.SQLITE; import static org.jooq.SQLDialect.YUGABYTEDB; import static org.jooq.conf.WriteIfReadonly.IGNORE; import static org.jooq.conf.WriteIfReadonly.THROW; +import static org.jooq.impl.DSL.field; import static org.jooq.impl.DSL.name; +import static org.jooq.impl.DSL.row; import static org.jooq.impl.DSL.select; import static org.jooq.impl.DSL.table; import static org.jooq.impl.DSL.when; +import static org.jooq.impl.FieldMapsForInsert.keysAndComputedOnClient; import static org.jooq.impl.Keywords.K_ROW; import static org.jooq.impl.Tools.anyMatch; import static org.jooq.impl.Tools.collect; @@ -101,22 +115,25 @@ import org.jooq.impl.QOM.UNotYetImplemented; */ final class FieldMapForUpdate extends AbstractQueryPartMap implements UNotYetImplemented { - static final Set CASTS_NEEDED = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB); - static final Set NO_SUPPORT_QUALIFY = SQLDialect.supportedBy(POSTGRES, SQLITE, YUGABYTEDB); + static final Set CASTS_NEEDED = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB); + static final Set NO_SUPPORT_QUALIFY = SQLDialect.supportedBy(POSTGRES, SQLITE, YUGABYTEDB); + static final Set EMULATE_RVE_SET_QUERY = SQLDialect.supportedBy(CUBRID, DERBY, FIREBIRD, IGNITE, MARIADB, MYSQL, SQLITE); - static final Set SUPPORT_RVE_SET = SQLDialect.supportedBy(H2, HSQLDB, POSTGRES, YUGABYTEDB); - static final Set REQUIRE_RVE_ROW_CLAUSE = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB); + static final Set SUPPORT_RVE_SET = SQLDialect.supportedBy(H2, HSQLDB, POSTGRES, YUGABYTEDB); + static final Set REQUIRE_RVE_ROW_CLAUSE = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB); - private final Table table; - private final Clause assignmentClause; + final Table table; + final SetClause setClause; + final Clause assignmentClause; - FieldMapForUpdate(Table table, Clause assignmentClause) { + FieldMapForUpdate(Table table, SetClause setClause, Clause assignmentClause) { this.table = table; + this.setClause = setClause; this.assignmentClause = assignmentClause; } @@ -157,12 +174,7 @@ final class FieldMapForUpdate extends AbstractQueryPartMap ctx, boolean supportsQualify, FieldOrRow key, - FieldOrRowOrSelect value + FieldOrRowOrSelect value, + String separator ) { + if (!"".equals(separator)) + ctx.sql(separator) + .formatSeparator(); + if (assignmentClause != null) ctx.start(assignmentClause); @@ -189,7 +206,7 @@ final class FieldMapForUpdate extends AbstractQueryPartMap k = multiRow.field(i); @@ -198,7 +215,7 @@ final class FieldMapForUpdate extends AbstractQueryPartMap select; + + + + + + + select = multiSelect; + + // [#10523] Simplify special case + if (size == 1) { + ctx.qualify(false, c -> c.visit(row.field(0))) + .sql(" = "); + + visitSubquery(ctx, select, false, false, false); + } + else { + Field[] f = Tools.fields(size); + + for (int i = 0; i < size; i++) { + FieldMapForUpdate mu = new FieldMapForUpdate(table, setClause, null); + separator = mu.acceptAssignmentClause(ctx, + supportsQualify, + row.field(i), + field(select(f[i]).from(select.asTable(table(name("t")), f))), + separator + ); + } + } + } else { Row row = removeReadonly(ctx, multiRow); @@ -270,13 +323,8 @@ final class FieldMapForUpdate extends AbstractQueryPartMap ctx, FieldMapForUpdate updateMap) { - ctx.formatIndentStart() - .formatSeparator() - .visit(updateMap) - .formatIndentEnd(); + return ","; } static final Row removeReadonly(Context ctx, Row row) { @@ -293,8 +341,13 @@ final class FieldMapForUpdate extends AbstractQueryPartMap map) { map.forEach((k, v) -> { - Field field = Tools.tableField(table, k); - put(field, Tools.field(v, field)); + if (k instanceof Row) { Row r = (Row) k; + put(r, (FieldOrRowOrSelect) v); + } + else { + Field field = Tools.tableField(table, k); + put(field, Tools.field(v, field)); + } }); } @@ -315,4 +368,27 @@ final class FieldMapForUpdate extends AbstractQueryPartMap into) { super(configuration, with, into); - this.updateMap = new FieldMapForUpdate(into, INSERT_ON_DUPLICATE_KEY_UPDATE_ASSIGNMENT); + this.updateMap = new FieldMapForUpdate(into, SetClause.INSERT, INSERT_ON_DUPLICATE_KEY_UPDATE_ASSIGNMENT); this.insertMaps = new FieldMapsForInsert(into); this.onConflictWhere = new ConditionProviderImpl(); this.condition = new ConditionProviderImpl(); @@ -224,7 +227,7 @@ implements onConflictOnConstraint0(constraint(constraint)); } - private void onConflictOnConstraint0(Constraint constraint) { + private final void onConflictOnConstraint0(Constraint constraint) { this.onConstraint = constraint; if (onConstraintUniqueKey == null) @@ -554,7 +557,7 @@ implements // CUBRID can emulate this using ON DUPLICATE KEY UPDATE case CUBRID: { - FieldMapForUpdate update = new FieldMapForUpdate(table(), INSERT_ON_DUPLICATE_KEY_UPDATE_ASSIGNMENT); + FieldMapForUpdate update = new FieldMapForUpdate(table(), SetClause.INSERT, INSERT_ON_DUPLICATE_KEY_UPDATE_ASSIGNMENT); Field field = table().field(0); update.put(field, field); @@ -876,6 +879,15 @@ implements if (s == null) s = select(map(f, x -> x.getDataType().defaulted() ? x.getDataType().default_() : DSL.NULL(x))); + + + + + + + + + // [#6375] INSERT .. VALUES and INSERT .. SELECT distinction also in MERGE t = s.asTable("t", map(f, Field::getName, String[]::new)); @@ -894,10 +906,23 @@ implements // [#1295] Use UPDATE clause only when with ON DUPLICATE KEY UPDATE, // not with ON DUPLICATE KEY IGNORE MergeNotMatchedStep notMatched = on; - if (onDuplicateKeyUpdate) + if (onDuplicateKeyUpdate) { + final FieldMapForUpdate um; + + + + + + + + + + um = updateMap; + notMatched = condition.hasWhere() - ? on.whenMatchedAnd(condition.getWhere()).thenUpdate().set(updateMap) - : on.whenMatchedThenUpdate().set(updateMap); + ? on.whenMatchedAnd(condition.getWhere()).thenUpdate().set(um) + : on.whenMatchedThenUpdate().set(um); + } return t != null ? notMatched.whenNotMatchedThenInsert(f).values(t.fields()) diff --git a/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java b/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java index f95877532e..7c9bd157d7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/MergeImpl.java @@ -175,6 +175,7 @@ import org.jooq.Table; import org.jooq.TableLike; import org.jooq.UniqueKey; import org.jooq.exception.DataTypeException; +import org.jooq.impl.FieldMapForUpdate.SetClause; import org.jooq.impl.QOM.UNotYetImplemented; import org.jooq.impl.Tools.DataExtendedKey; import org.jooq.tools.StringUtils; @@ -1796,7 +1797,7 @@ implements } MatchedClause(Condition condition, boolean delete) { - this(condition, delete, new FieldMapForUpdate(table, MERGE_SET_ASSIGNMENT)); + this(condition, delete, new FieldMapForUpdate(table, SetClause.MERGE, MERGE_SET_ASSIGNMENT)); } MatchedClause(Condition condition, boolean delete, FieldMapForUpdate updateMap) { diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 1025a89551..7cb7d339d6 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -185,6 +185,7 @@ import static org.jooq.impl.SQLDataType.OTHER; import static org.jooq.impl.SQLDataType.SMALLINT; import static org.jooq.impl.SQLDataType.VARCHAR; import static org.jooq.impl.SQLDataType.XML; +import static org.jooq.impl.Tools.anyMatch; import static org.jooq.impl.Tools.DataKey.DATA_BLOCK_NESTING; import static org.jooq.tools.StringUtils.defaultIfNull; @@ -5301,6 +5302,11 @@ final class Tools { + + + + + diff --git a/jOOQ/src/main/java/org/jooq/impl/UpdateQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/UpdateQueryImpl.java index b871b8eb0f..5b6a46dde1 100644 --- a/jOOQ/src/main/java/org/jooq/impl/UpdateQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/UpdateQueryImpl.java @@ -157,6 +157,7 @@ import org.jooq.Select; import org.jooq.Table; import org.jooq.TableLike; import org.jooq.UpdateQuery; +import org.jooq.impl.FieldMapForUpdate.SetClause; import org.jooq.impl.QOM.UNotYetImplemented; /** @@ -193,7 +194,7 @@ implements UpdateQueryImpl(Configuration configuration, WithImpl with, Table table) { super(configuration, with, table); - this.updateMap = new FieldMapForUpdate(table, UPDATE_SET_ASSIGNMENT); + this.updateMap = new FieldMapForUpdate(table, SetClause.UPDATE, UPDATE_SET_ASSIGNMENT); this.from = new TableList(); this.condition = new ConditionProviderImpl(); this.orderBy = new SortFieldList(); @@ -542,15 +543,6 @@ implements - - - - - - - - - accept1(ctx); } @@ -598,10 +590,12 @@ implements ctx.formatSeparator() .start(UPDATE_SET) .visit(K_SET) - .separatorRequired(true); - - FieldMapForUpdate.toSQLUpdateMap(ctx, updateMap); - ctx.end(UPDATE_SET); + .separatorRequired(true) + .formatIndentStart() + .formatSeparator() + .visit(updateMap) + .formatIndentEnd() + .end(UPDATE_SET);