[#7409] Emulate PostgreSQL's ON CONFLICT .. ON CONSTRAINT clause with MERGE

This commit is contained in:
lukaseder 2018-04-12 11:02:55 +02:00
parent 345d970810
commit f5344c4bcf
8 changed files with 57 additions and 24 deletions

View File

@ -85,8 +85,7 @@ extends
GroupField,
OrderField<T>,
FieldOrRow,
FieldOrConstraint,
Named {
FieldOrConstraint {
// ------------------------------------------------------------------------
// API

View File

@ -45,6 +45,6 @@ package org.jooq;
*
* @author Lukas Eder
*/
public interface FieldOrConstraint extends QueryPart {
public interface FieldOrConstraint extends Named {
}

View File

@ -74,19 +74,19 @@ public interface InsertOnDuplicateStep<R extends Record> extends InsertReturning
/**
* Add a <code>ON CONFLICT ON CONSTRAINT</code> clause to this query.
*/
@Support({ POSTGRES_9_5 })
@Support({ CUBRID, FIREBIRD_3_0, HSQLDB, POSTGRES_9_5 })
InsertOnConflictDoUpdateStep<R> onConflictOnConstraint(Constraint constraint);
/**
* Add a <code>ON CONFLICT ON CONSTRAINT</code> clause to this query.
*/
@Support({ POSTGRES_9_5 })
@Support({ CUBRID, FIREBIRD_3_0, HSQLDB, POSTGRES_9_5 })
InsertOnConflictDoUpdateStep<R> onConflictOnConstraint(Name constraint);
/**
* Add a <code>ON CONFLICT ON CONSTRAINT</code> clause to this query.
*/
@Support({ POSTGRES_9_5 })
@Support({ CUBRID, FIREBIRD_3_0, HSQLDB, POSTGRES_9_5 })
InsertOnConflictDoUpdateStep<R> onConflictOnConstraint(UniqueKey<R> constraint);
/**

View File

@ -123,7 +123,7 @@ public interface InsertQuery<R extends Record> extends StoreQuery<R>, Insert<R>
* <code>ON CONFLICT ON CONSTRAINT</code> clause in this <code>INSERT</code>
* statement.
*/
@Support({ POSTGRES_9_5 })
@Support({ CUBRID, FIREBIRD_3_0, HSQLDB, POSTGRES_9_5 })
void onConflictOnConstraint(Constraint constraint);
/**
@ -131,7 +131,7 @@ public interface InsertQuery<R extends Record> extends StoreQuery<R>, Insert<R>
* <code>ON CONFLICT ON CONSTRAINT</code> clause in this <code>INSERT</code>
* statement.
*/
@Support({ POSTGRES_9_5 })
@Support({ CUBRID, FIREBIRD_3_0, HSQLDB, POSTGRES_9_5 })
void onConflictOnConstraint(UniqueKey<R> constraint);
/**
@ -139,7 +139,7 @@ public interface InsertQuery<R extends Record> extends StoreQuery<R>, Insert<R>
* <code>ON CONFLICT ON CONSTRAINT</code> clause in this <code>INSERT</code>
* statement.
*/
@Support({ POSTGRES_9_5 })
@Support({ CUBRID, FIREBIRD_3_0, HSQLDB, POSTGRES_9_5 })
void onConflictOnConstraint(Name constraint);
/**

View File

@ -64,6 +64,7 @@ abstract class AbstractName extends AbstractQueryPart implements Name {
* Generated UID
*/
private static final long serialVersionUID = 8562325639223483938L;
static final Name NO_NAME = DSL.name("");
@Override
public final Name append(String name) {

View File

@ -53,7 +53,7 @@ abstract class AbstractNamed extends AbstractQueryPart implements Named {
private final Comment comment;
AbstractNamed(Name name, Comment comment) {
this.name = name;
this.name = name == null ? AbstractName.NO_NAME : name;
this.comment = comment == null ? CommentImpl.NO_COMMENT : comment;
}

View File

@ -97,7 +97,7 @@ import org.jooq.exception.DataAccessException;
* @author Lukas Eder
*/
@SuppressWarnings("rawtypes")
final class ConstraintImpl extends AbstractQueryPart
final class ConstraintImpl extends AbstractNamed
implements
ConstraintTypeStep
, ConstraintForeignKeyOnStep
@ -137,7 +137,6 @@ implements
private static final long serialVersionUID = 1018023703769802616L;
private static final Clause[] CLAUSES = { CONSTRAINT };
private final Name name;
private Field<?>[] unique;
private Field<?>[] primaryKey;
private Field<?>[] foreignKey;
@ -152,9 +151,13 @@ implements
}
ConstraintImpl(Name name) {
this.name = name;
super(name, null);
}
// ------------------------------------------------------------------------
// XXX: QueryPart API
// ------------------------------------------------------------------------
@Override
public final Clause[] clauses(Context<?> ctx) {
return CLAUSES;
@ -163,18 +166,18 @@ implements
@Override
public final void accept(Context<?> ctx) {
if (ctx.data(DATA_CONSTRAINT_REFERENCE) != null) {
if (name == null)
if (getQualifiedName() == AbstractName.NO_NAME)
throw new DataAccessException("Cannot ALTER or DROP CONSTRAINT without name");
ctx.visit(name);
ctx.visit(getQualifiedName());
}
else {
boolean qualify = ctx.qualify();
if (name != null)
if (getQualifiedName() == AbstractName.NO_NAME)
ctx.visit(K_CONSTRAINT)
.sql(' ')
.visit(name)
.visit(getQualifiedName())
.formatIndentStart()
.formatSeparator();
@ -230,11 +233,15 @@ implements
.sql(')');
}
if (name != null)
if (getQualifiedName() == AbstractName.NO_NAME)
ctx.formatIndentEnd();
}
}
// ------------------------------------------------------------------------
// XXX: Constraint API
// ------------------------------------------------------------------------
@Override
public final ConstraintImpl unique(String... fields) {
return unique(fieldsByName(fields));

View File

@ -120,6 +120,7 @@ final class InsertQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
private boolean onDuplicateKeyUpdate;
private boolean onDuplicateKeyIgnore;
private Constraint onConstraint;
private UniqueKey<R> onConstraintUniqueKey;
private QueryPartList<Field<?>> onConflict;
private final ConditionProviderImpl condition;
@ -160,6 +161,15 @@ final class InsertQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
@Override
public final void onConflictOnConstraint(Constraint constraint) {
this.onConstraint = constraint;
if (onConstraintUniqueKey == null) {
for (UniqueKey<R> key : table.getKeys()) {
if (constraint.getName().equals(key.getName())) {
onConstraintUniqueKey = key;
break;
}
}
}
}
@Override
@ -167,6 +177,7 @@ final class InsertQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
if (StringUtils.isEmpty(constraint.getName()))
throw new IllegalArgumentException("UniqueKey's name is not specified");
this.onConstraintUniqueKey = constraint;
onConflictOnConstraint(name(constraint.getName()));
}
@ -632,8 +643,9 @@ final class InsertQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
}
private final Merge<R> toMerge(Configuration configuration) {
if ((onConflict != null && onConflict.size() > 0) ||
table.getPrimaryKey() != null) {
if ((onConflict != null && onConflict.size() > 0)
|| onConstraint != null
|| table.getPrimaryKey() != null) {
// [#6375] INSERT .. VALUES and INSERT .. SELECT distinction also in MERGE
Table<?> t = select == null
@ -681,12 +693,19 @@ final class InsertQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
private final Condition matchByConflictingKey(Map<Field<?>, Field<?>> map) {
Condition result = null;
// [#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.
// [#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 (Field<?> f : (onConflict != null && onConflict.size() > 0)
? onConflict
: onConstraintUniqueKey != null
? onConstraintUniqueKey.getFields()
: table.getPrimaryKey().getFields()) {
Field<Object> field = (Field<Object>) f;
Field<Object> value = (Field<Object>) map.get(field);
@ -706,12 +725,19 @@ final class InsertQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
private final Condition matchByConflictingKey(Table<?> s) {
Condition result = null;
// [#7365] The ON CONFLICT (column list) 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.
// [#7365] The ON CONFLICT clause can be emulated using MERGE by joining
// the MERGE's target and source tables on the conflict columns
if (onConstraint != null && onConstraintUniqueKey == null)
return DSL.condition("[ cannot create predicate from constraint with unknown columns ]");
for (Field<?> f : (onConflict != null && onConflict.size() > 0)
? onConflict
: onConstraintUniqueKey != null
? onConstraintUniqueKey.getFields()
: table.getPrimaryKey().getFields()) {
Field<Object> field = (Field<Object>) f;
Field<Object> value = s.field(field);