[#7409] Emulate PostgreSQL's ON CONFLICT .. ON CONSTRAINT clause with MERGE
This commit is contained in:
parent
345d970810
commit
f5344c4bcf
@ -85,8 +85,7 @@ extends
|
||||
GroupField,
|
||||
OrderField<T>,
|
||||
FieldOrRow,
|
||||
FieldOrConstraint,
|
||||
Named {
|
||||
FieldOrConstraint {
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// API
|
||||
|
||||
@ -45,6 +45,6 @@ package org.jooq;
|
||||
*
|
||||
* @author Lukas Eder
|
||||
*/
|
||||
public interface FieldOrConstraint extends QueryPart {
|
||||
public interface FieldOrConstraint extends Named {
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
/**
|
||||
|
||||
@ -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);
|
||||
|
||||
/**
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user