org.jooq.impl.InsertQueryImpl Maven / Gradle / Ivy
/*
* 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
* Apache-2.0 license and offer limited warranties, support, maintenance, and
* commercial database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.impl;
import static java.lang.Boolean.TRUE;
import static java.util.Arrays.asList;
import static java.util.Collections.nCopies;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
import static org.jooq.Clause.INSERT;
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.SQLDialect.DERBY;
import static org.jooq.SQLDialect.H2;
import static org.jooq.SQLDialect.MARIADB;
// ...
import static org.jooq.SQLDialect.MYSQL;
// ...
// ...
import static org.jooq.SQLDialect.POSTGRES;
import static org.jooq.SQLDialect.SQLITE;
// ...
// ...
import static org.jooq.impl.DSL.constraint;
import static org.jooq.impl.DSL.falseCondition;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DSL.one;
import static org.jooq.impl.DSL.row;
import static org.jooq.impl.DSL.select;
import static org.jooq.impl.DSL.selectFrom;
import static org.jooq.impl.DSL.selectOne;
import static org.jooq.impl.FieldMapsForInsert.toSQLInsertSelect;
import static org.jooq.impl.Keywords.K_AS;
import static org.jooq.impl.Keywords.K_DEFAULT;
import static org.jooq.impl.Keywords.K_DEFAULT_VALUES;
import static org.jooq.impl.Keywords.K_DO_NOTHING;
import static org.jooq.impl.Keywords.K_DO_UPDATE;
import static org.jooq.impl.Keywords.K_IGNORE;
import static org.jooq.impl.Keywords.K_INSERT;
import static org.jooq.impl.Keywords.K_INTO;
import static org.jooq.impl.Keywords.K_ON_CONFLICT;
import static org.jooq.impl.Keywords.K_ON_CONSTRAINT;
import static org.jooq.impl.Keywords.K_ON_DUPLICATE_KEY_UPDATE;
import static org.jooq.impl.Keywords.K_SET;
import static org.jooq.impl.Keywords.K_VALUES;
import static org.jooq.impl.Keywords.K_WHERE;
import static org.jooq.impl.QueryPartListView.wrap;
import static org.jooq.impl.Tools.aliasedFields;
import static org.jooq.impl.Tools.anyMatch;
import static org.jooq.impl.Tools.collect;
import static org.jooq.impl.Tools.degree;
import static org.jooq.impl.Tools.flattenCollection;
import static org.jooq.impl.Tools.map;
import static org.jooq.impl.Tools.orElse;
import static org.jooq.impl.Tools.qualify;
import static org.jooq.impl.Tools.unqualified;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_CONSTRAINT_REFERENCE;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_INSERT_SELECT;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_INSERT_SELECT_WITHOUT_INSERT_COLUMN_LIST;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_MANDATORY_WHERE_CLAUSE;
import static org.jooq.impl.Tools.SimpleDataKey.DATA_ON_DUPLICATE_KEY_WHERE;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.jooq.Clause;
import org.jooq.Condition;
import org.jooq.Configuration;
import org.jooq.Constraint;
import org.jooq.Context;
import org.jooq.Field;
import org.jooq.GeneratorStatementType;
import org.jooq.Identity;
import org.jooq.InsertQuery;
import org.jooq.MergeNotMatchedStep;
import org.jooq.MergeOnConditionStep;
import org.jooq.Name;
import org.jooq.Operator;
// ...
import org.jooq.QueryPart;
import org.jooq.Record;
import org.jooq.SQLDialect;
import org.jooq.Scope;
import org.jooq.Select;
import org.jooq.Table;
import org.jooq.TableField;
import org.jooq.UniqueKey;
import org.jooq.conf.WriteIfReadonly;
import org.jooq.impl.FieldMapForUpdate.SetClause;
import org.jooq.impl.QOM.UNotYetImplemented;
import org.jooq.impl.QOM.UnmodifiableList;
import org.jooq.impl.Tools.BooleanDataKey;
import org.jooq.impl.Tools.ExtendedDataKey;
import org.jooq.tools.StringUtils;
/**
* @author Lukas Eder
*/
final class InsertQueryImpl
extends
AbstractStoreQuery, Field>>
implements
InsertQuery,
UNotYetImplemented
{
static final Clause[] CLAUSES = { INSERT };
static final Set SUPPORT_INSERT_IGNORE = SQLDialect.supportedBy(MARIADB, MYSQL);
static final Set SUPPORTS_OPTIONAL_DO_UPDATE_CONFLICT_TARGETS = SQLDialect.supportedBy(SQLITE);
static final Set NO_SUPPORT_DERIVED_COLUMN_LIST_IN_MERGE_USING = SQLDialect.supportedBy(DERBY, H2);
static final Set NO_SUPPORT_SUBQUERY_IN_MERGE_USING = SQLDialect.supportedBy(DERBY);
static final Set REQUIRE_NEW_MYSQL_EXCLUDED_EMULATION = SQLDialect.supportedBy(MYSQL);
final FieldMapForUpdate updateMap;
final FieldMapsForInsert insertMaps;
Select> select;
boolean defaultValues;
boolean onDuplicateKeyUpdate;
boolean onDuplicateKeyIgnore;
Constraint onConstraint;
UniqueKey onConstraintUniqueKey;
QueryPartList> onConflict;
final ConditionProviderImpl onConflictWhere;
final ConditionProviderImpl condition;
InsertQueryImpl(Configuration configuration, WithImpl with, Table into) {
super(configuration, with, into);
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();
}
@Override
public final void newRecord() {
insertMaps.newRecord();
}
@Override
protected final Map, Field>> getValues() {
return insertMaps.lastMap();
}
final FieldMapsForInsert getInsertMaps() {
return insertMaps;
}
final Select> getSelect() {
return select;
}
@Override
public final void addRecord(R record) {
newRecord();
setRecord(record);
}
@Override
public final void onConflict(Field>... fields) {
onConflict(Arrays.asList(fields));
}
@Override
public final void onConflict(Collection extends Field>> fields) {
this.onConflict = new QueryPartList>(fields).qualify(false);
}
@Override
public final void onConflictWhere(Condition conditions) {
onConflictWhere.addConditions(conditions);
}
@Override
public final void onConflictOnConstraint(Constraint constraint) {
onConflictOnConstraint0(constraint);
}
@Override
public void onConflictOnConstraint(UniqueKey constraint) {
if (StringUtils.isEmpty(constraint.getName()))
throw new IllegalArgumentException("UniqueKey's name is not specified");
this.onConstraintUniqueKey = constraint;
onConflictOnConstraint0(constraint(name(constraint.getName())));
}
@Override
public final void onConflictOnConstraint(Name constraint) {
onConflictOnConstraint0(constraint(constraint));
}
private final void onConflictOnConstraint0(Constraint constraint) {
this.onConstraint = constraint;
if (onConstraintUniqueKey == null)
onConstraintUniqueKey = Tools.findAny(table().getKeys(), key -> constraint.getName().equals(key.getName()));
}
@Override
public final void onDuplicateKeyUpdate(boolean flag) {
this.onDuplicateKeyIgnore = false;
this.onDuplicateKeyUpdate = flag;
}
@Override
public final void onDuplicateKeyIgnore(boolean flag) {
this.onDuplicateKeyUpdate = false;
this.onDuplicateKeyIgnore = flag;
}
@Override
public final void addValueForUpdate(Field field, T value) {
updateMap.put(field, Tools.field(value, field));
}
@Override
public final void addValueForUpdate(Field field, Field value) {
updateMap.put(field, Tools.field(value, field));
}
@Override
public final void addValuesForUpdate(Map, ?> map) {
updateMap.set(map);
}
@Override
public final void addConditions(Condition conditions) {
condition.addConditions(conditions);
}
@Override
public final void addConditions(Condition... conditions) {
condition.addConditions(conditions);
}
@Override
public final void addConditions(Collection extends Condition> conditions) {
condition.addConditions(conditions);
}
@Override
public final void addConditions(Operator operator, Condition conditions) {
condition.addConditions(operator, conditions);
}
@Override
public final void addConditions(Operator operator, Condition... conditions) {
condition.addConditions(operator, conditions);
}
@Override
public final void addConditions(Operator operator, Collection extends Condition> conditions) {
condition.addConditions(operator, conditions);
}
@Override
public final void setDefaultValues() {
defaultValues = true;
}
private final boolean defaultValues(Configuration c) {
return defaultValues;
}
@Override
public final void setSelect(Field>[] f, Select> s) {
setSelect(Arrays.asList(f), s);
}
@Override
public final void setSelect(Collection extends Field>> f, Select> s) {
insertMaps.addFields(f);
select = s;
}
@Override
public final void addValues(Map, ?> map) {
insertMaps.set(map);
}
@Override
final void accept0(Context> ctx) {
// ON DUPLICATE KEY UPDATE clause
// ------------------------------
if (onDuplicateKeyUpdate) {
switch (ctx.family()) {
case POSTGRES:
case SQLITE:
case YUGABYTEDB: {
ctx.data(DATA_MANDATORY_WHERE_CLAUSE, ctx.family() == SQLITE, c -> toSQLInsert(c, false));
ctx.formatSeparator()
.start(INSERT_ON_DUPLICATE_KEY_UPDATE)
.visit(K_ON_CONFLICT)
.sql(' ');
if (onConstraint != null ) {
ctx.data(DATA_CONSTRAINT_REFERENCE, true);
ctx.visit(K_ON_CONSTRAINT)
.sql(' ')
.visit(onConstraint);
ctx.data().remove(DATA_CONSTRAINT_REFERENCE);
}
else {
if (onConflict != null && onConflict.size() > 0)
ctx.sql('(').visit(onConflict).sql(')');
// [#13273] SQLite 3.38 has started supporting optional on conflict targets
else if (SUPPORTS_OPTIONAL_DO_UPDATE_CONFLICT_TARGETS.contains(ctx.dialect()) && !onConflictWhere.hasWhere())
;
// [#6462] There is no way to emulate MySQL's ON DUPLICATE KEY UPDATE
// where all UNIQUE keys are considered for conflicts. PostgreSQL
// doesn't allow ON CONFLICT DO UPDATE without either a conflict
// column list or a constraint reference.
else if (table().getPrimaryKey() == null)
ctx.sql("[unknown primary key]");
else
ctx.sql('(').qualify(false, c -> c.visit(new FieldsImpl<>(table().getPrimaryKey().getFields()))).sql(')');
}
acceptOnConflictWhere(ctx);
ctx.formatSeparator()
.visit(K_DO_UPDATE)
.formatSeparator()
.visit(K_SET)
.formatIndentStart()
.formatSeparator()
.visit(updateMapComputedOnClientStored(ctx))
.formatIndentEnd();
if (condition.hasWhere())
ctx.formatSeparator()
.visit(K_WHERE)
.sql(' ')
.visit(condition);
ctx.end(INSERT_ON_DUPLICATE_KEY_UPDATE);
break;
}
// Some databases allow for emulating this clause using a MERGE statement
case DERBY:
case FIREBIRD:
case H2:
case HSQLDB: {
ctx.visit(toMerge(ctx));
break;
}
// MySQL has a nice syntax for this
default: {
// [#2508] In H2, this syntax is supported in MySQL MODE (we're assuming users will
// set this mode in order to profit from this functionality). Up until
// H2 1.4.197, qualification of columns in the ON DUPLICATE KEY UPDATE clause
// wasn't supported (see https://github.com/h2database/h2database/issues/1027)
boolean oldQualify = ctx.qualify();
boolean newQualify = ctx.family() != H2 && oldQualify;
FieldMapForUpdate um = updateMapComputedOnClientStored(ctx);
boolean requireNewMySQLExcludedEmulation = REQUIRE_NEW_MYSQL_EXCLUDED_EMULATION.contains(ctx.dialect()) && anyMatch(um.values(), v -> v instanceof Excluded);
Set> keys = toSQLInsert(ctx, requireNewMySQLExcludedEmulation);
// [#5214] The alias only applies with INSERT .. VALUES
if (requireNewMySQLExcludedEmulation && select == null)
ctx.formatSeparator()
.visit(K_AS).sql(' ').visit(name("t"));
ctx.formatSeparator()
.start(INSERT_ON_DUPLICATE_KEY_UPDATE)
.visit(K_ON_DUPLICATE_KEY_UPDATE)
.formatIndentStart()
.formatSeparator()
.qualify(newQualify);
// [#8479] Emulate WHERE clause using CASE
if (condition.hasWhere())
ctx.data(DATA_ON_DUPLICATE_KEY_WHERE, condition.getWhere());
if (requireNewMySQLExcludedEmulation) {
um.replaceAll((k, v) -> {
if (v instanceof Excluded> e) {
return keys.contains(e.$field()) ? v : qualify(table(), e.$field());
}
else
return v;
});
}
ctx.visit(um);
if (condition.hasWhere())
ctx.data().remove(DATA_ON_DUPLICATE_KEY_WHERE);
ctx.qualify(oldQualify)
.formatIndentEnd()
.end(INSERT_ON_DUPLICATE_KEY_UPDATE);
break;
}
}
}
// ON DUPLICATE KEY IGNORE clause
// ------------------------------
else if (onDuplicateKeyIgnore) {
switch (ctx.family()) {
// The default emulation
case FIREBIRD:
case IGNITE: {
ctx.visit(toInsertSelect(ctx));
break;
}
case POSTGRES:
case SQLITE:
case YUGABYTEDB: {
ctx.data(DATA_MANDATORY_WHERE_CLAUSE, ctx.family() == SQLITE, c -> toSQLInsert(c, false));
ctx.formatSeparator()
.start(INSERT_ON_DUPLICATE_KEY_UPDATE)
.visit(K_ON_CONFLICT);
if (onConstraint != null ) {
ctx.data(DATA_CONSTRAINT_REFERENCE, true, c -> c
.sql(' ')
.visit(K_ON_CONSTRAINT)
.sql(' ')
.visit(onConstraint)
);
}
else {
if (onConflict != null && onConflict.size() > 0) {
ctx.sql(" (").visit(onConflict).sql(')');
acceptOnConflictWhere(ctx);
}
}
ctx.formatSeparator()
.visit(K_DO_NOTHING)
.end(INSERT_ON_DUPLICATE_KEY_UPDATE);
break;
}
// CUBRID can emulate this using ON DUPLICATE KEY UPDATE
case CUBRID: {
FieldMapForUpdate update = new FieldMapForUpdate(table(), SetClause.INSERT, INSERT_ON_DUPLICATE_KEY_UPDATE_ASSIGNMENT);
Field> field = table().field(0);
update.put(field, field);
toSQLInsert(ctx, false);
ctx.formatSeparator()
.start(INSERT_ON_DUPLICATE_KEY_UPDATE)
.visit(K_ON_DUPLICATE_KEY_UPDATE)
.sql(' ')
.visit(update)
.end(INSERT_ON_DUPLICATE_KEY_UPDATE);
break;
}
// Some databases allow for emulating this clause using a MERGE statement
case H2:
case HSQLDB: {
ctx.visit(toMerge(ctx));
break;
}
case DERBY: {
// [#10989] Cannot use MERGE with SELECT: [42XAL]: The source table of a MERGE statement must be a base table or table function.
if (select != null)
ctx.visit(toInsertSelect(ctx));
else
ctx.visit(toMerge(ctx));
break;
}
// MySQL has a nice, native syntax for this
default: {
toSQLInsert(ctx, false);
ctx.start(INSERT_ON_DUPLICATE_KEY_UPDATE)
.end(INSERT_ON_DUPLICATE_KEY_UPDATE);
break;
}
}
}
// Default mode
// ------------
else {
toSQLInsert(ctx, false);
ctx.start(INSERT_ON_DUPLICATE_KEY_UPDATE)
.end(INSERT_ON_DUPLICATE_KEY_UPDATE);
}
ctx.start(INSERT_RETURNING);
toSQLReturning(ctx);
ctx.end(INSERT_RETURNING);
}
private final void acceptOnConflictWhere(Context> ctx) {
if (onConflictWhere.hasWhere())
// [#11732] [#13660] Avoid qualification, which wasn't supported in older PG versions
ctx.qualify(false, c -> c
.formatSeparator()
.visit(K_WHERE)
.sql(' ')
.visit(onConflictWhere.getWhere()));
}
@Override
public final Clause[] clauses(Context> ctx) {
return CLAUSES;
}
private final Set> toSQLInsert(Context> ctx, boolean requireNewMySQLExcludedEmulation) {
ctx.start(INSERT_INSERT_INTO)
.visit(K_INSERT)
.sql(' ');
// [#1295] MySQL dialects have native syntax for INSERT IGNORE
// [#4376] [#8433] for SQLite render using ON CONFLICT DO NOTHING
// rather than INSERT OR IGNORE
if (onDuplicateKeyIgnore)
if (SUPPORT_INSERT_IGNORE.contains(ctx.dialect()))
ctx.visit(K_IGNORE).sql(' ');
ctx.visit(K_INTO)
.sql(' ')
.declareTables(true, c -> c.visit(table(c)));
Set> fields = insertMaps.toSQLReferenceKeys(ctx);
ctx.end(INSERT_INSERT_INTO);
if (select != null) {
Set> keysFlattened = insertMaps.keysFlattened(ctx, GeneratorStatementType.INSERT);
// [#2995] Prevent the generation of wrapping parentheses around the
// INSERT .. SELECT statement's SELECT because they would be
// interpreted as the (missing) INSERT column list's parens.
if (keysFlattened.size() == 0)
ctx.data(DATA_INSERT_SELECT_WITHOUT_INSERT_COLUMN_LIST, true);
ctx.data(DATA_INSERT_SELECT, true);
Select> s = select;
if (requireNewMySQLExcludedEmulation)
s = selectFrom(s.asTable(DSL.table(name("t")), keysFlattened));
// [#8353] TODO: Support overlapping embeddables
toSQLInsertSelect(ctx, s);
ctx.data().remove(DATA_INSERT_SELECT_WITHOUT_INSERT_COLUMN_LIST);
ctx.data().remove(DATA_INSERT_SELECT);
}
else if (defaultValues(ctx.configuration())) {
switch (ctx.family()) {
case DERBY:
case MARIADB:
case MYSQL:
acceptDefaultValuesEmulation(ctx, table().fields().length);
break;
default:
ctx.formatSeparator()
.visit(K_DEFAULT_VALUES);
break;
}
}
else
ctx.visit(insertMaps);
return fields;
}
private final void acceptDefaultValuesEmulation(Context> ctx, int length) {
ctx.formatSeparator()
.visit(K_VALUES)
.sql(" (")
.visit(wrap(nCopies(length, K_DEFAULT)))
.sql(')');
}
private final List>> conflictingKeys(Context> ctx) {
// [#7365] PostgreSQL ON CONFLICT (conflict columns) clause
if (onConflict != null && onConflict.size() > 0)
return singletonList(onConflict);
// [#7409] PostgreSQL ON CONFLICT ON CONSTRAINT clause
else if (onConstraintUniqueKey != null)
return singletonList(onConstraintUniqueKey.getFields());
// [#6462] MySQL ON DUPLICATE KEY UPDATE clause
// Flag for backwards compatibility considers only PRIMARY KEY
else if (TRUE.equals(ctx.settings().isEmulateOnDuplicateKeyUpdateOnPrimaryKeyOnly()))
return singletonList(table().getPrimaryKey().getFields());
// [#6462] MySQL ON DUPLICATE KEY UPDATE clause
// All conflicting keys are considered
else
return map(table().getKeys(), k -> k.getFields());
}
@SuppressWarnings("unchecked")
private final QueryPart toInsertSelect(Context> ctx) {
List>> keys = conflictingKeys(ctx);
if (!keys.isEmpty()) {
Select rows = null;
Set> fields = insertMaps.keysFlattened(ctx, GeneratorStatementType.INSERT);
// [#10989] INSERT .. SELECT .. ON DUPLICATE KEY IGNORE
if (select != null) {
Map, Field>> map = new HashMap<>();
Field>[] names = Tools.fields(degree(select));
List> f = new ArrayList<>(fields);
for (int i = 0; i < fields.size() && i < names.length; i++)
map.put(f.get(i), names[i]);
rows = (Select) selectFrom(select.asTable(DSL.table(name("t")), names))
.whereNotExists(
selectOne()
.from(table())
.where(matchByConflictingKeys(ctx, map))
);
}
// [#5089] Multi-row inserts need to explicitly generate UNION ALL
// here. TODO: Refactor this logic to be more generally
// reusable - i.e. ordinary UNION ALL emulation should be
// re-used.
else {
for (Map, Field>> map : insertMaps.maps()) {
Select row =
select(aliasedFields(map.entrySet().stream().filter(e -> fields.contains(e.getKey())).map(Entry::getValue).collect(toList())))
.whereNotExists(
selectOne()
.from(table())
.where(matchByConflictingKeys(ctx, map))
);
if (rows == null)
rows = row;
else
rows = rows.unionAll(row);
}
}
return ctx.dsl()
.insertInto(table())
.columns(fields)
.select(selectFrom(rows.asTable("t")));
}
else
return DSL.sql("[ The ON DUPLICATE KEY IGNORE/UPDATE clause cannot be emulated when inserting into tables without any known keys : " + table() + " ]");
}
private final QueryPart toMerge(Context> ctx) {
if ((onConflict != null && onConflict.size() > 0)
|| onConstraint != null
|| !table().getKeys().isEmpty()) {
Table> t;
Set> k = insertMaps.keysFlattened(ctx, null);
Collection> f = null;
// [#15668] The no-subquery-in-MERGE-USING emulation only applies to single row INSERTs
if (!NO_SUPPORT_SUBQUERY_IN_MERGE_USING.contains(ctx.dialect()) || select != null || insertMaps.rows > 1) {
f = k.isEmpty() ? asList(table().fields()) : k;
// [#10461] Multi row inserts need to be emulated using select
// [#11770] [#11880] Single row inserts also do, in some dialects
Select> s = select != null
? select
: insertMaps.insertSelect(ctx, null);
// [#8937] With DEFAULT VALUES, there is no SELECT. Create one from
// known DEFAULT expressions, or use NULL.
if (s == null)
s = select(map(f, (Field> x) -> x.getDataType().defaulted() ? x.getDataType().default_() : DSL.NULL(x)));
// [#6375] INSERT .. VALUES and INSERT .. SELECT distinction also in MERGE
if (NO_SUPPORT_DERIVED_COLUMN_LIST_IN_MERGE_USING.contains(ctx.dialect()))
t = new AliasedSelect((Select) s, true, true, false, map(f, Field::getUnqualifiedName, Name[]::new)).as("t");
else
t = s.asTable("t", map(f, Field::getName, String[]::new));
}
else
t = null;
MergeOnConditionStep on = t != null
? ctx.dsl().mergeInto(table())
.using(t)
.on(matchByConflictingKeys(ctx, t))
: ctx.dsl().mergeInto(table())
.usingDual()
.on(matchByConflictingKeys(ctx, insertMaps.lastMap()));
// [#1295] Use UPDATE clause only when with ON DUPLICATE KEY UPDATE,
// not with ON DUPLICATE KEY IGNORE
MergeNotMatchedStep notMatched = on;
if (onDuplicateKeyUpdate) {
FieldMapForUpdate um = new FieldMapForUpdate(updateMap, SetClause.INSERT);
// [#5214] [#13571] PostgreSQL EXCLUDED pseudo table emulation
// The InsertQueryImpl uses "t" as table name
um.replaceAll((key, v) -> {
if (v instanceof Excluded> e) {
if (t != null)
// If the field isn't part of the USING clause, just
// set the field to itself to maintain the user's
// intended (?) touch semantics, which might affect
// triggers
return orElse(t.field(e.$field()), () -> qualify(table(), e.$field()));
else
return orElse(insertMaps.lastMap().get(e.$field()), () -> qualify(table(), e.$field()));
}
else
return v;
});
// [#9879] EXCLUDED emulation must happen before client side
// computed column emulation
um = updateMapComputedOnClientStored(ctx, um);
notMatched = condition.hasWhere()
? on.whenMatchedAnd(condition.getWhere()).thenUpdate().set(um)
: on.whenMatchedThenUpdate().set(um);
}
return t != null
? notMatched.whenNotMatchedThenInsert(f).values(t.fields())
: notMatched.whenNotMatchedThenInsert(k).values(insertMaps.lastMap().entrySet().stream().filter(e -> k.contains(e.getKey())).map(Entry::getValue).collect(toList()));
}
else
return DSL.sql("[ The ON DUPLICATE KEY IGNORE/UPDATE clause cannot be emulated when inserting into non-updatable tables : " + table() + " ]");
}
private final FieldMapForUpdate updateMapComputedOnClientStored(Context> ctx) {
// [#5214] Always make a copy to benefit other emulations
return updateMapComputedOnClientStored(ctx, new FieldMapForUpdate(updateMap, SetClause.INSERT));
}
private final FieldMapForUpdate updateMapComputedOnClientStored(Context> ctx, FieldMapForUpdate um) {
return um;
}
/**
* Produce a {@link Condition} that matches existing rows by the inserted or
* updated primary key values.
*/
@SuppressWarnings("unchecked")
private final Condition matchByConflictingKeys(Context> ctx, Map, Field>> map) {
Condition or = null;
// [#7365] The ON CONFLICT clause can be emulated using MERGE by joining
// the MERGE's target and source tables on the conflict columns
// [#7409] The ON CONFLICT ON CONSTRAINT clause can be emulated using MERGE by
// joining the MERGE's target and source tables on the constraint columns
// [#6462] The ON DUPLICATE KEY UPDATE clause is emulated using the primary key.
// To properly reflect MySQL behaviour, it should use all the known unique keys.
if (onConstraint != null && onConstraintUniqueKey == null)
return DSL.condition("[ cannot create predicate from constraint with unknown columns ]");
for (List extends Field>> fields : conflictingKeys(ctx)) {
Condition and = null;
for (Field> field : fields) {
Field
© 2015 - 2024 Weber Informatics LLC | Privacy Policy