[jOOQ/jOOQ#13325] Refactor internal FieldMapForUpdate to support the row assignments, too

This includes:

- [jOOQ/jOOQ#13329] Add a FieldOrRowOrSelect nominal union type
This commit is contained in:
Lukas Eder 2022-03-23 12:37:01 +01:00
parent 953501b380
commit c93aa4b840
14 changed files with 311 additions and 199 deletions

View File

@ -151,6 +151,7 @@ extends
GroupField,
OrderField<T>,
FieldOrRow,
FieldOrRowOrSelect,
FieldOrConstraint,
TableElement
{

View File

@ -0,0 +1,57 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq;
/**
* A common base type for {@link Field}, {@link Row}, and {@link Select} where
* DSL API accepts all types alike.
* <p>
* Instances of this type cannot be created directly, only of its subtypes.
*
* @author Lukas Eder
*/
public /* sealed */ interface FieldOrRowOrSelect
extends
QueryPart
/* permits
Field,
Row,
Select */
{
}

View File

@ -80,7 +80,12 @@ import org.jetbrains.annotations.ApiStatus.Experimental;
*
* @author Lukas Eder
*/
public /* non-sealed */ interface Row extends Fields, FieldOrRow {
public /* non-sealed */ interface Row
extends
Fields,
FieldOrRow,
FieldOrRowOrSelect
{
/**
* Get the degree of this row value expression.

View File

@ -97,7 +97,13 @@ import org.jetbrains.annotations.ApiStatus.Experimental;
* @param <R> The record type being returned by this query
* @author Lukas Eder
*/
public /* non-sealed */ interface Select<R extends Record> extends ResultQuery<R>, TableLike<R>, FieldLike {
public /* non-sealed */ interface Select<R extends Record>
extends
ResultQuery<R>,
TableLike<R>,
FieldLike,
FieldOrRowOrSelect
{
/**
* Apply the <code>UNION</code> set operation.

View File

@ -46,7 +46,6 @@ import org.jooq.CloseableQuery;
import org.jooq.Configuration;
import org.jooq.Context;
import org.jooq.Param;
import org.jooq.Query;
import org.jooq.Record;
import org.jooq.conf.ParamType;
import org.jooq.impl.QOM.UProxy;

View File

@ -49,8 +49,11 @@ import org.jooq.QueryPart;
* @author Lukas Eder
*/
abstract class AbstractQueryPartMap<K extends QueryPart, V extends QueryPart>
extends AbstractQueryPart
implements Map<K, V> {
extends
AbstractQueryPart
implements
Map<K, V>
{
private final Map<K, V> map;

View File

@ -43,10 +43,11 @@ import org.jooq.Configuration;
import org.jooq.Context;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.FieldOrRow;
import org.jooq.FieldOrRowOrSelect;
import org.jooq.Record;
import org.jooq.StoreQuery;
import org.jooq.Table;
import org.jooq.impl.QOM.UEmpty;
import org.jooq.impl.QOM.UTransient;
/**
@ -54,13 +55,18 @@ import org.jooq.impl.QOM.UTransient;
*
* @author Lukas Eder
*/
abstract class AbstractStoreQuery<R extends Record> extends AbstractDMLQuery<R> implements StoreQuery<R> {
abstract class AbstractStoreQuery<R extends Record, K extends FieldOrRow, V extends FieldOrRowOrSelect>
extends
AbstractDMLQuery<R>
implements
StoreQuery<R>
{
AbstractStoreQuery(Configuration configuration, WithImpl with, Table<R> table) {
super(configuration, with, table);
}
protected abstract Map<Field<?>, Field<?>> getValues();
protected abstract Map<K, V> getValues();
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
@ -87,7 +93,7 @@ abstract class AbstractStoreQuery<R extends Record> extends AbstractDMLQuery<R>
else
addValue(new UnknownField<T>(getValues().size()), value);
else
getValues().put(field, Tools.field(value, field));
getValues().put((K) field, (V) Tools.field(value, field));
}
final <T> void addValue(Field<T> field, int index, Field<T> value) {
@ -97,7 +103,7 @@ abstract class AbstractStoreQuery<R extends Record> extends AbstractDMLQuery<R>
else
addValue(new UnknownField<T>(getValues().size()), value);
else
getValues().put(field, Tools.field(value, field));
getValues().put((K) field, (V) Tools.field(value, field));
}
static class UnknownField<T> extends AbstractField<T> implements UTransient {

View File

@ -37,25 +37,47 @@
*/
package org.jooq.impl;
import static org.jooq.Clause.UPDATE_SET_ASSIGNMENT;
// ...
// ...
// ...
// ...
// ...
import static org.jooq.SQLDialect.H2;
// ...
import static org.jooq.SQLDialect.HSQLDB;
// ...
// ...
import static org.jooq.SQLDialect.POSTGRES;
// ...
// ...
// ...
// ...
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.name;
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.Keywords.K_ROW;
import static org.jooq.impl.Tools.anyMatch;
import static org.jooq.impl.Tools.collect;
import static org.jooq.impl.Tools.fieldName;
import static org.jooq.impl.Tools.filter;
import static org.jooq.impl.Tools.flattenEntrySet;
import static org.jooq.impl.Tools.map;
import static org.jooq.impl.Tools.row0;
import static org.jooq.impl.Tools.unqualified;
import static org.jooq.impl.Tools.visitSubquery;
import static org.jooq.impl.Tools.DataKey.DATA_ON_DUPLICATE_KEY_WHERE;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -63,10 +85,13 @@ import org.jooq.Clause;
import org.jooq.Condition;
import org.jooq.Context;
import org.jooq.Field;
import org.jooq.FieldOrRow;
import org.jooq.FieldOrRowOrSelect;
// ...
import org.jooq.RenderContext.CastMode;
import org.jooq.Row;
import org.jooq.SQLDialect;
import org.jooq.Select;
import org.jooq.Table;
import org.jooq.exception.DataTypeException;
import org.jooq.impl.QOM.UNotYetImplemented;
@ -74,9 +99,18 @@ import org.jooq.impl.QOM.UNotYetImplemented;
/**
* @author Lukas Eder
*/
final class FieldMapForUpdate extends AbstractQueryPartMap<Field<?>, Field<?>> implements UNotYetImplemented {
private static final Set<SQLDialect> CASTS_NEEDED = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB);
private static final Set<SQLDialect> NO_SUPPORT_QUALIFY = SQLDialect.supportedBy(POSTGRES, SQLITE, YUGABYTEDB);
final class FieldMapForUpdate extends AbstractQueryPartMap<FieldOrRow, FieldOrRowOrSelect> implements UNotYetImplemented {
static final Set<SQLDialect> CASTS_NEEDED = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB);
static final Set<SQLDialect> NO_SUPPORT_QUALIFY = SQLDialect.supportedBy(POSTGRES, SQLITE, YUGABYTEDB);
static final Set<SQLDialect> SUPPORT_RVE_SET = SQLDialect.supportedBy(H2, HSQLDB, POSTGRES, YUGABYTEDB);
static final Set<SQLDialect> REQUIRE_RVE_ROW_CLAUSE = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB);
private final Table<?> table;
private final Clause assignmentClause;
@ -86,7 +120,6 @@ final class FieldMapForUpdate extends AbstractQueryPartMap<Field<?>, Field<?>> i
this.assignmentClause = assignmentClause;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public final void accept(Context<?> ctx) {
if (size() > 0) {
@ -106,24 +139,29 @@ final class FieldMapForUpdate extends AbstractQueryPartMap<Field<?>, Field<?>> i
if (!CASTS_NEEDED.contains(ctx.dialect()))
ctx.castMode(CastMode.NEVER);
for (Entry<Field<?>, Field<?>> entry : removeReadonly(ctx, flattenEntrySet(entrySet(), true))) {
entryLoop:
for (Entry<FieldOrRow, FieldOrRowOrSelect> entry : flattenEntrySet(entrySet(), true)) {
FieldOrRow key = entry.getKey();
FieldOrRowOrSelect value = entry.getValue();
if (!"".equals(separator))
ctx.sql(separator)
.formatSeparator();
ctx.start(assignmentClause)
.qualify(supportsQualify, c -> c.visit(entry.getKey()))
.sql(" = ");
// [#8479] Emulate WHERE clause using CASE
Condition condition = (Condition) ctx.data(DATA_ON_DUPLICATE_KEY_WHERE);
if (condition != null)
ctx.visit(when(condition, (Field) entry.getValue()).else_(entry.getKey()));
else
ctx.visit(entry.getValue());
ctx.end(assignmentClause);
acceptAssignmentClause(ctx, supportsQualify, key, value);
separator = ",";
}
@ -134,12 +172,114 @@ final class FieldMapForUpdate extends AbstractQueryPartMap<Field<?>, Field<?>> i
ctx.sql("[ no fields are updated ]");
}
static final Iterable<Entry<Field<?>, Field<?>>> removeReadonly(Context<?> ctx, Iterable<Entry<Field<?>, Field<?>>> it) {
@SuppressWarnings("unchecked")
private final void acceptAssignmentClause(
Context<?> ctx,
boolean supportsQualify,
FieldOrRow key,
FieldOrRowOrSelect value
) {
ctx.start(assignmentClause);
// A multi-row update was specified
if (key instanceof Row) { Row multiRow = (Row) key;
Row multiValue = value instanceof Row ? (Row) value : null;
Select<?> multiSelect = value instanceof Select ? (Select<?>) value : null;
// [#6884] This syntax can be emulated trivially, if the RHS is not a SELECT subquery
if (multiValue != null && !SUPPORT_RVE_SET.contains(ctx.dialect())) {
FieldMapForUpdate map = new FieldMapForUpdate(table(), UPDATE_SET_ASSIGNMENT);
for (int i = 0; i < multiRow.size(); i++) {
Field<?> k = multiRow.field(i);
Field<?> v = multiValue.field(i);
map.put(k, Tools.field(v, k));
}
toSQLUpdateMap(ctx, map);
}
return it;
else {
Row row = removeReadonly(ctx, multiRow);
ctx.start(UPDATE_SET_ASSIGNMENT)
.formatIndentStart()
.formatSeparator()
.qualify(false, c -> c.visit(row))
.sql(" = ");
// Some dialects don't really support row value expressions on the
// right hand side of a SET clause
if (multiValue != null
) {
// [#6763] Incompatible change in PostgreSQL 10 requires ROW() constructor for
// single-degree rows. Let's just always render it, here.
if (REQUIRE_RVE_ROW_CLAUSE.contains(ctx.dialect()))
ctx.visit(K_ROW).sql(" ");
ctx.visit(removeReadonly(ctx, multiRow, multiValue));
}
// Subselects or subselect emulations of row value expressions
else if (multiSelect != null) {
Select<?> select;
if (multiValue != null)
select = select(removeReadonly(ctx, multiRow, multiValue).fields());
else
select = multiSelect;
visitSubquery(ctx, select, false, false, false);
}
ctx.formatIndentEnd().end(UPDATE_SET_ASSIGNMENT);
}
}
// A regular (non-multi-row) update was specified
else {
ctx.qualify(supportsQualify, c -> c.visit(key))
.sql(" = ");
// [#8479] Emulate WHERE clause using CASE
Condition condition = (Condition) ctx.data(DATA_ON_DUPLICATE_KEY_WHERE);
if (condition != null)
ctx.visit(when(condition, (Field) value).else_(key));
else
ctx.visit(value);
}
ctx.end(assignmentClause);
}
static final void toSQLUpdateMap(Context<?> ctx, FieldMapForUpdate updateMap) {
ctx.formatIndentStart()
.formatSeparator()
.visit(updateMap)
.formatIndentEnd();
}
static final Row removeReadonly(Context<?> ctx, Row row) {
@ -177,4 +317,5 @@ final class FieldMapForUpdate extends AbstractQueryPartMap<Field<?>, Field<?>> i
}

View File

@ -38,7 +38,6 @@
package org.jooq.impl;
import static java.lang.Boolean.TRUE;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static org.jooq.Clause.FIELD_ROW;
import static org.jooq.Clause.INSERT_SELECT;
@ -56,10 +55,8 @@ import static org.jooq.impl.Keywords.K_VALUES;
import static org.jooq.impl.QueryPartCollectionView.wrap;
import static org.jooq.impl.Tools.EMPTY_FIELD;
import static org.jooq.impl.Tools.anyMatch;
import static org.jooq.impl.Tools.collect;
import static org.jooq.impl.Tools.filter;
import static org.jooq.impl.Tools.flatten;
import static org.jooq.impl.Tools.flattenCollection;
import static org.jooq.impl.Tools.lazy;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_EMULATE_BULK_INSERT_RETURNING;
@ -67,7 +64,6 @@ import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
@ -83,21 +79,19 @@ import org.jooq.Configuration;
import org.jooq.Context;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.FieldOrRow;
import org.jooq.Param;
// ...
import org.jooq.Record;
import org.jooq.RenderContext.CastMode;
import org.jooq.SQLDialect;
import org.jooq.Select;
import org.jooq.SelectJoinStep;
import org.jooq.Table;
import org.jooq.conf.WriteIfReadonly;
import org.jooq.exception.DataTypeException;
import org.jooq.impl.AbstractStoreQuery.UnknownField;
import org.jooq.impl.QOM.UNotYetImplemented;
import org.jetbrains.annotations.NotNull;
/**
* @author Lukas Eder
*/
@ -219,7 +213,7 @@ final class FieldMapsForInsert extends AbstractQueryPart implements UNotYetImple
.end(INSERT_SELECT);
}
static final Set<Field<?>> keysAndComputedOnClient(Set<Field<?>> keys, Table<?> table) {
static final <K extends FieldOrRow> Set<K> keysAndComputedOnClient(Set<K> keys, Table<?> table) {
@ -289,6 +283,8 @@ final class FieldMapsForInsert extends AbstractQueryPart implements UNotYetImple

View File

@ -56,7 +56,6 @@ import org.jooq.FieldLike;
import org.jooq.InsertOnConflictConditionStep;
import org.jooq.InsertOnConflictWhereIndexPredicateStep;
import org.jooq.InsertOnDuplicateSetMoreStep;
import org.jooq.InsertQuery;
import org.jooq.InsertResultStep;
import org.jooq.InsertSetMoreStep;
import org.jooq.InsertSetStep;
@ -822,21 +821,21 @@ final class InsertImpl<R extends Record, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
return values(values.toArray());
}
private final <T> void addValue(InsertQuery<R> delegate, Field<T> field, int index, Object object) {
private final <T> void addValue(InsertQueryImpl<R> delegate, Field<T> field, int index, Object object) {
// [#1343] Only convert non-jOOQ objects
// [#8606] The column index is relevant when adding a value to a plain SQL multi row INSERT
// statement that does not have any field list.
if (object instanceof Field)
((AbstractStoreQuery<R>) delegate).addValue(field, index, (Field) object);
delegate.addValue(field, index, (Field) object);
else if (object instanceof FieldLike)
((AbstractStoreQuery<R>) delegate).addValue(field, index, ((FieldLike) object).asField());
delegate.addValue(field, index, ((FieldLike) object).asField());
else if (field != null)
((AbstractStoreQuery<R>) delegate).addValue(field, index, field.getDataType().convert(object));
delegate.addValue(field, index, field.getDataType().convert(object));
// [#4629] Plain SQL INSERT INTO t VALUES (a, b, c) statements don't know the insert columns
else
((AbstractStoreQuery<R>) delegate).addValue(field, index, (T) object);
delegate.addValue(field, index, (T) object);
}
@Override

View File

@ -48,7 +48,6 @@ import static org.jooq.Clause.INSERT_INSERT_INTO;
import static org.jooq.Clause.INSERT_ON_DUPLICATE_KEY_UPDATE;
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.SQLDialect.DERBY;
@ -102,7 +101,6 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import org.jooq.Clause;
import org.jooq.Condition;
@ -112,7 +110,6 @@ import org.jooq.Context;
import org.jooq.Field;
import org.jooq.Identity;
import org.jooq.InsertQuery;
import org.jooq.Merge;
import org.jooq.MergeNotMatchedStep;
import org.jooq.MergeOnConditionStep;
import org.jooq.Name;
@ -134,7 +131,13 @@ import org.jooq.tools.StringUtils;
/**
* @author Lukas Eder
*/
final class InsertQueryImpl<R extends Record> extends AbstractStoreQuery<R> implements InsertQuery<R>, UNotYetImplemented {
final class InsertQueryImpl<R extends Record>
extends
AbstractStoreQuery<R, Field<?>, Field<?>>
implements
InsertQuery<R>,
UNotYetImplemented
{
private static final Clause[] CLAUSES = { INSERT };
private static final Set<SQLDialect> SUPPORT_INSERT_IGNORE = SQLDialect.supportedBy(MARIADB, MYSQL);

View File

@ -109,6 +109,8 @@ import org.jooq.Condition;
import org.jooq.Configuration;
import org.jooq.Context;
import org.jooq.Field;
import org.jooq.FieldOrRow;
import org.jooq.FieldOrRowOrSelect;
import org.jooq.MergeKeyStep1;
import org.jooq.MergeKeyStep10;
import org.jooq.MergeKeyStep11;
@ -1627,11 +1629,13 @@ implements
if (update == null)
update = new MatchedClause(noCondition());
for (Entry<Field<?>, Field<?>> e : m.updateMap.entrySet()) {
Field<?> exp = update.updateMap.get(e.getKey());
for (Entry<FieldOrRow, FieldOrRowOrSelect> e : m.updateMap.entrySet()) {
FieldOrRowOrSelect exp = update.updateMap.get(e.getKey());
if (exp instanceof CaseConditionStepImpl)
((CaseConditionStepImpl) exp).when(negate.and(condition), e.getValue());
// [#10523] [#13325] TODO: ClassCastException when we're using Row here, once supported
else
update.updateMap.put(e.getKey(), when(negate.and(condition), (Field) e.getValue()).else_(e.getKey()));
}

View File

@ -268,6 +268,7 @@ import org.jooq.ExecuteContext;
import org.jooq.ExecuteListener;
import org.jooq.Field;
import org.jooq.FieldOrRow;
import org.jooq.FieldOrRowOrSelect;
import org.jooq.Fields;
import org.jooq.ForeignKey;
import org.jooq.JSON;
@ -5676,6 +5677,13 @@ final class Tools {
return map(orderFields, (OrderField<?> f) -> field(f));
}
static final List<Field<?>> fields(FieldOrRow fr) {
if (fr instanceof Field)
return singletonList((Field<?>) fr);
else
return asList(((Row) fr).fields());
}
static final <T> Field<T> unalias(Field<T> field) {
Field<T> result = aliased(field);
return result != null ? result : field;
@ -6099,23 +6107,25 @@ final class Tools {
* set iterable, making sure no duplicate keys resulting from overlapping
* embeddables will be produced.
*/
static final Iterable<Entry<Field<?>, Field<?>>> flattenEntrySet(
final Iterable<Entry<Field<?>, Field<?>>> iterable,
static final Iterable<Entry<FieldOrRow, FieldOrRowOrSelect>> flattenEntrySet(
final Iterable<Entry<FieldOrRow, FieldOrRowOrSelect>> iterable,
final boolean removeDuplicates
) {
// [#2530] [#6124] [#10481] TODO: Refactor and optimise these flattening algorithms
// [#11729] Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=572873
return () -> new FlatteningIterator<>(iterable.iterator(), (e, duplicates) -> {
if (e.getKey() instanceof EmbeddableTableField) {
List<Entry<Field<?>, Field<?>>> result = new ArrayList<>();
Field<?>[] keys = embeddedFields(e.getKey());
Field<?>[] values = embeddedFields(e.getValue());
// [#9879] [#13325] TODO: Support also UPDATE .. SET ROW = ...
if (e.getKey() instanceof EmbeddableTableField) { EmbeddableTableField<?, ?> key = (EmbeddableTableField<?, ?>) e.getKey();
List<Entry<FieldOrRow, FieldOrRowOrSelect>> result = new ArrayList<>();
Field<?>[] keys = embeddedFields(key);
Field<?>[] values = embeddedFields((Field<?>) e.getValue());
for (int i = 0; i < keys.length; i++)
result.add(new SimpleImmutableEntry<Field<?>, Field<?>>(
result.add(new SimpleImmutableEntry<FieldOrRow, FieldOrRowOrSelect>(
keys[i], values[i]
));

View File

@ -52,7 +52,6 @@ import static org.jooq.Clause.UPDATE_WHERE;
// ...
import static org.jooq.SQLDialect.CUBRID;
// ...
// ...
import static org.jooq.SQLDialect.DERBY;
// ...
import static org.jooq.SQLDialect.FIREBIRD;
@ -68,8 +67,6 @@ import static org.jooq.SQLDialect.POSTGRES;
// ...
// ...
// ...
// ...
// ...
import static org.jooq.SQLDialect.SQLITE;
// ...
// ...
@ -77,30 +74,22 @@ import static org.jooq.SQLDialect.SQLITE;
// ...
import static org.jooq.SQLDialect.YUGABYTEDB;
import static org.jooq.conf.SettingsTools.getExecuteUpdateWithoutWhere;
import static org.jooq.conf.WriteIfReadonly.THROW;
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.trueCondition;
import static org.jooq.impl.FieldMapForUpdate.removeReadonly;
import static org.jooq.impl.FieldMapsForInsert.keysAndComputedOnClient;
import static org.jooq.impl.Keywords.K_FROM;
import static org.jooq.impl.Keywords.K_LIMIT;
import static org.jooq.impl.Keywords.K_ORDER_BY;
import static org.jooq.impl.Keywords.K_ROW;
import static org.jooq.impl.Keywords.K_SET;
import static org.jooq.impl.Keywords.K_UPDATE;
import static org.jooq.impl.Keywords.K_WHERE;
import static org.jooq.impl.Tools.EMPTY_FIELD;
import static org.jooq.impl.Tools.fieldName;
import static org.jooq.impl.Tools.map;
import static org.jooq.impl.Tools.unqualified;
import static org.jooq.impl.Tools.visitSubquery;
import static org.jooq.impl.Tools.anyMatch;
import static org.jooq.impl.Tools.findAny;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
@ -110,6 +99,8 @@ import org.jooq.Condition;
import org.jooq.Configuration;
import org.jooq.Context;
import org.jooq.Field;
import org.jooq.FieldOrRow;
import org.jooq.FieldOrRowOrSelect;
import org.jooq.Operator;
import org.jooq.OrderField;
// ...
@ -166,13 +157,18 @@ import org.jooq.Select;
import org.jooq.Table;
import org.jooq.TableLike;
import org.jooq.UpdateQuery;
import org.jooq.exception.DataTypeException;
import org.jooq.impl.QOM.UNotYetImplemented;
/**
* @author Lukas Eder
*/
final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> implements UpdateQuery<R>, UNotYetImplemented {
final class UpdateQueryImpl<R extends Record>
extends
AbstractStoreQuery<R, FieldOrRow, FieldOrRowOrSelect>
implements
UpdateQuery<R>,
UNotYetImplemented
{
private static final Clause[] CLAUSES = { UPDATE };
@ -182,13 +178,6 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
private static final Set<SQLDialect> SUPPORT_RVE_SET = SQLDialect.supportedBy(H2, HSQLDB, POSTGRES, YUGABYTEDB);
private static final Set<SQLDialect> REQUIRE_RVE_ROW_CLAUSE = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB);
// LIMIT is not supported at all
private static final Set<SQLDialect> NO_SUPPORT_LIMIT = SQLDialect.supportedUntil(CUBRID, DERBY, FIREBIRD, H2, HSQLDB, POSTGRES, SQLITE, YUGABYTEDB);
@ -198,9 +187,6 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
private final FieldMapForUpdate updateMap;
private final TableList from;
private final ConditionProviderImpl condition;
private Row multiRow;
private Row multiValue;
private Select<?> multiSelect;
private final SortFieldList orderBy;
private Field<? extends Number> limit;
@ -453,13 +439,11 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
final void addValues0(Row row, Row value) {
multiRow = row;
multiValue = value;
updateMap.put(row, value);
}
final void addValues0(Row row, Select<?> select) {
multiRow = row;
multiSelect = select;
updateMap.put(row, select);
}
@Override
@ -564,9 +548,6 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
@ -580,9 +561,6 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
u.setReturning(returning);
u.updateMap.putAll(updateMap);
u.multiRow = multiRow;
u.multiSelect = multiSelect;
u.multiValue = multiValue;
u.from.addAll(from);
u.condition.setWhere(condition.getWhere());
u.orderBy.addAll(orderBy);
@ -622,86 +600,7 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
.visit(K_SET)
.separatorRequired(true);
// A multi-row update was specified
if (multiRow != null) {
// [#6884] This syntax can be emulated trivially, if the RHS is not a SELECT subquery
if (multiValue != null && !SUPPORT_RVE_SET.contains(ctx.dialect())) {
FieldMapForUpdate map = new FieldMapForUpdate(table(), UPDATE_SET_ASSIGNMENT);
for (int i = 0; i < multiRow.size(); i++) {
Field<?> k = multiRow.field(i);
Field<?> v = multiValue.field(i);
map.put(k, Tools.field(v, k));
}
toSQLUpdateMap(ctx, map);
}
else {
Row row = removeReadonly(ctx, multiRow);
ctx.start(UPDATE_SET_ASSIGNMENT)
.formatIndentStart()
.formatSeparator()
.qualify(false, c -> c.visit(row))
.sql(" = ");
// Some dialects don't really support row value expressions on the
// right hand side of a SET clause
if (multiValue != null
) {
// [#6763] Incompatible change in PostgreSQL 10 requires ROW() constructor for
// single-degree rows. Let's just always render it, here.
if (REQUIRE_RVE_ROW_CLAUSE.contains(ctx.dialect()))
ctx.visit(K_ROW).sql(" ");
ctx.visit(removeReadonly(ctx, multiRow, multiValue));
}
// Subselects or subselect emulations of row value expressions
else {
Select<?> select;
if (multiValue != null)
select = select(removeReadonly(ctx, multiRow, multiValue).fields());
else
select = multiSelect;
visitSubquery(ctx, select, false, false, false);
}
ctx.formatIndentEnd().end(UPDATE_SET_ASSIGNMENT);
}
}
// A regular (non-multi-row) update was specified
else
toSQLUpdateMap(ctx, updateMap);
FieldMapForUpdate.toSQLUpdateMap(ctx, updateMap);
ctx.end(UPDATE_SET);
@ -763,32 +662,6 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
ctx.end(UPDATE_RETURNING);
}
private static final void toSQLUpdateMap(Context<?> ctx, FieldMapForUpdate updateMap) {
ctx.formatIndentStart()
.formatSeparator()
.visit(updateMap)
.formatIndentEnd();
}
private final void acceptFrom(Context<?> ctx) {
ctx.start(UPDATE_FROM);
@ -801,6 +674,8 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
f = from;
if (!f.isEmpty())
@ -823,7 +698,7 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
if (!condition.hasWhere())
executeWithoutWhere("UPDATE without WHERE", getExecuteUpdateWithoutWhere(configuration().settings()));
return updateMap.size() > 0 || multiRow != null;
return updateMap.size() > 0;
}
@ -835,4 +710,11 @@ final class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
}