[jOOQ/jOOQ#13358] Add SQLDialect.POSTGRES_15 (Support MERGE)
This also fixes: - [jOOQ/jOOQ#7552] INSERT .. ON DUPLICATE KEY { IGNORE | UPDATE } emulation should consider all UNIQUE keys on PostgreSQL - [jOOQ/jOOQ#13575] Support EXCLUDED table emulation also in expressions
This commit is contained in:
parent
3c0b328acf
commit
eba80bb09b
@ -3694,8 +3694,8 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
|
||||
void sqlBind0(BindingSQLContext<U> ctx, Record value) throws SQLException {
|
||||
Cast.renderCastIf(ctx.render(),
|
||||
c -> super.sqlBind0(ctx, value),
|
||||
c -> pgRenderRecordCast(ctx.render(), value),
|
||||
() -> REQUIRE_RECORD_CAST.contains(ctx.dialect()) && value != null
|
||||
c -> pgRenderRecordCast(ctx.render()),
|
||||
() -> REQUIRE_RECORD_CAST.contains(ctx.dialect())
|
||||
);
|
||||
}
|
||||
|
||||
@ -3708,7 +3708,7 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
|
||||
else
|
||||
ctx.render().sql("[UDT]");
|
||||
},
|
||||
c -> pgRenderRecordCast(ctx.render(), value),
|
||||
c -> pgRenderRecordCast(ctx.render()),
|
||||
() -> REQUIRE_RECORD_CAST.contains(ctx.dialect())
|
||||
);
|
||||
}
|
||||
@ -3828,11 +3828,8 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
|
||||
// interfaces. Instead, a string representation of a UDT has to be parsed
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static final void pgRenderRecordCast(Context<?> ctx, Record value) {
|
||||
if (value instanceof UDTRecord<?> u)
|
||||
ctx.visit(u.getUDT().getQualifiedName());
|
||||
else if (value instanceof TableRecord<?> t)
|
||||
ctx.visit(t.getTable().getQualifiedName());
|
||||
final void pgRenderRecordCast(Context<?> ctx) {
|
||||
ctx.visit(dataType.getQualifiedName());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@ -116,7 +116,15 @@ implements
|
||||
}
|
||||
|
||||
default:
|
||||
ctx.visit(N_EXCLUDED).sql('.').qualify(false, c -> c.visit(field));
|
||||
|
||||
// [#7552] When emulating INSERT .. ON DUPLICATE KEY UPDATE using
|
||||
// MERGE, the EXCLUDED pseudo table is called "t", instead
|
||||
ctx.visit(ctx.data(ExtendedDataKey.DATA_INSERT_ON_DUPLICATE_KEY_UPDATE) != null
|
||||
? name("t")
|
||||
: N_EXCLUDED)
|
||||
.sql('.')
|
||||
.qualify(false, c -> c.visit(field));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,6 +57,7 @@ import static org.jooq.SQLDialect.MARIADB;
|
||||
import static org.jooq.SQLDialect.MYSQL;
|
||||
// ...
|
||||
// ...
|
||||
import static org.jooq.SQLDialect.POSTGRES;
|
||||
import static org.jooq.SQLDialect.SQLITE;
|
||||
// ...
|
||||
// ...
|
||||
@ -344,66 +345,79 @@ implements
|
||||
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);
|
||||
// [#7552] Dialects supporting both MERGE and ON CONFLICT should
|
||||
// generate MERGE to emulate MySQL's ON DUPLICATE KEY UPDATE
|
||||
// if there are multiple known unique constraints.
|
||||
if (ctx.dialect().supports(POSTGRES_15)
|
||||
&& onConstraint == null
|
||||
&& onConflict == null
|
||||
&& returning.isEmpty()
|
||||
&& table().getKeys().size() > 1) {
|
||||
acceptMerge(ctx);
|
||||
}
|
||||
else {
|
||||
if (onConflict != null && onConflict.size() > 0)
|
||||
ctx.sql('(').visit(onConflict).sql(')');
|
||||
ctx.data(DATA_MANDATORY_WHERE_CLAUSE, ctx.family() == SQLITE, c -> toSQLInsert(c, false));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// [#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);
|
||||
.start(INSERT_ON_DUPLICATE_KEY_UPDATE)
|
||||
.visit(K_ON_CONFLICT)
|
||||
.sql(' ');
|
||||
|
||||
ctx.end(INSERT_ON_DUPLICATE_KEY_UPDATE);
|
||||
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;
|
||||
}
|
||||
@ -418,17 +432,11 @@ implements
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
case DERBY:
|
||||
case FIREBIRD:
|
||||
case H2:
|
||||
case HSQLDB: {
|
||||
ctx.visit(toMerge(ctx));
|
||||
acceptMerge(ctx);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -505,7 +513,7 @@ implements
|
||||
|
||||
case FIREBIRD:
|
||||
case IGNITE: {
|
||||
ctx.visit(toInsertSelect(ctx));
|
||||
acceptInsertSelect(ctx);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -578,7 +586,7 @@ implements
|
||||
|
||||
case H2:
|
||||
case HSQLDB: {
|
||||
ctx.visit(toMerge(ctx));
|
||||
acceptMerge(ctx);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -586,9 +594,9 @@ implements
|
||||
|
||||
// [#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));
|
||||
acceptInsertSelect(ctx);
|
||||
else
|
||||
ctx.visit(toMerge(ctx));
|
||||
acceptMerge(ctx);
|
||||
|
||||
break;
|
||||
}
|
||||
@ -794,7 +802,7 @@ implements
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private final QueryPart toInsertSelect(Context<?> ctx) {
|
||||
private final void acceptInsertSelect(Context<?> ctx) {
|
||||
List<List<? extends Field<?>>> keys = conflictingKeys(ctx);
|
||||
|
||||
if (!keys.isEmpty()) {
|
||||
@ -838,16 +846,21 @@ implements
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.dsl()
|
||||
ctx.visit(ctx.dsl()
|
||||
.insertInto(table())
|
||||
.columns(fields)
|
||||
.select(selectFrom(rows.asTable("t")));
|
||||
.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() + " ]");
|
||||
ctx.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) {
|
||||
private final void acceptMerge(Context<?> ctx) {
|
||||
ctx.data(ExtendedDataKey.DATA_INSERT_ON_DUPLICATE_KEY_UPDATE, this, c -> acceptMerge0(c));
|
||||
}
|
||||
|
||||
private final void acceptMerge0(Context<?> ctx) {
|
||||
if ((onConflict != null && onConflict.size() > 0)
|
||||
|| onConstraint != null
|
||||
|| !table().getKeys().isEmpty()) {
|
||||
@ -929,12 +942,13 @@ implements
|
||||
: on.whenMatchedThenUpdate().set(um);
|
||||
}
|
||||
|
||||
return t != null
|
||||
ctx.visit(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()));
|
||||
: 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() + " ]");
|
||||
ctx.sql("[ The ON DUPLICATE KEY IGNORE/UPDATE clause cannot be emulated when inserting into non-updatable tables : " + table() + " ]");
|
||||
}
|
||||
|
||||
private final FieldMapForUpdate updateMapComputedOnClientStored(Context<?> ctx) {
|
||||
|
||||
@ -850,11 +850,11 @@ final class Tools {
|
||||
*/
|
||||
enum ExtendedDataKey implements DataKey {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* [#4498] [#7552] The original INSERT ON DUPLICATE KEY UPDATE query
|
||||
* that produced a MERGE statement for its emulation.
|
||||
*/
|
||||
DATA_INSERT_ON_DUPLICATE_KEY_UPDATE,
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user