[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:
Lukas Eder 2022-07-04 17:49:05 +02:00
parent 3c0b328acf
commit eba80bb09b
4 changed files with 106 additions and 87 deletions

View File

@ -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")

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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,