[jOOQ/jOOQ#12168] Deeply nested MULTISET produce quoted JSON in MariaDB

This commit is contained in:
Lukas Eder 2021-07-13 17:28:53 +02:00
parent f22489e93e
commit 4bae0a5129
6 changed files with 51 additions and 27 deletions

View File

@ -23843,7 +23843,7 @@ public class DSL {
* {@link SelectForStep#forXML()}. jOOQ produces an XML encoding that is
* compatible with {@link DSLContext#fetchFromXML(String)}. Future jOOQ
* versions will make this format configurable according to
* {@link XMLFormat.RecordFormat}.</li>
* {@link XMLFormat.RecordFormau}.</li>
* <li>{@link NestedCollectionEmulation#NATIVE}: A few dialects have native
* support for MULTISET.</li>
* <li>{@link NestedCollectionEmulation#DEFAULT}: By default, jOOQ chooses

View File

@ -39,15 +39,14 @@ package org.jooq.impl;
// ...
import static org.jooq.SQLDialect.MARIADB;
// ...
import static org.jooq.SQLDialect.MYSQL;
// ...
// ...
import static org.jooq.impl.DSL.function;
import static org.jooq.impl.DSL.groupConcat;
import static org.jooq.impl.DSL.inline;
import static org.jooq.impl.DSL.noCondition;
import static org.jooq.impl.JSONEntryImpl.jsonCastMapper;
import static org.jooq.impl.JSONEntryImpl.jsonMerge;
import static org.jooq.impl.JSONOnNull.ABSENT_ON_NULL;
import static org.jooq.impl.JSONOnNull.NULL_ON_NULL;
import static org.jooq.impl.Names.N_GROUP_CONCAT;
@ -55,8 +54,6 @@ import static org.jooq.impl.Names.N_JSONB_AGG;
import static org.jooq.impl.Names.N_JSON_AGG;
import static org.jooq.impl.Names.N_JSON_ARRAYAGG;
import static org.jooq.impl.Names.N_JSON_GROUP_ARRAY;
import static org.jooq.impl.Names.N_JSON_MERGE;
import static org.jooq.impl.Names.N_JSON_MERGE_PRESERVE;
import static org.jooq.impl.Names.N_JSON_QUOTE;
import static org.jooq.impl.SQLDataType.JSON;
import static org.jooq.impl.SQLDataType.VARCHAR;
@ -88,7 +85,6 @@ extends AbstractAggregateFunction<J>
implements JSONArrayAggOrderByStep<J> {
static final Set<SQLDialect> EMULATE_WITH_GROUP_CONCAT = SQLDialect.supportedBy(MARIADB, MYSQL);
static final Set<SQLDialect> SUPPORT_JSON_MERGE_PRESERVE = SQLDialect.supportedBy(MARIADB, MYSQL);
@ -102,13 +98,13 @@ implements JSONArrayAggOrderByStep<J> {
}
@Override
public void accept(Context<?> ctx) {
public final void accept(Context<?> ctx) {
switch (ctx.family()) {
case MARIADB:
case MYSQL: {
// Workaround for https://jira.mariadb.org/browse/MDEV-21912,
// https://jira.mariadb.org/browse/MDEV-21914, and other issues
ctx.visit(SUPPORT_JSON_MERGE_PRESERVE.contains(ctx.dialect()) ? N_JSON_MERGE_PRESERVE : N_JSON_MERGE).sql('(').visit(inline("[]")).sql(", ").visit(groupConcatEmulation(ctx)).sql(')');
ctx.visit(jsonMerge(ctx, "[]", groupConcatEmulation(ctx)));
break;
}

View File

@ -39,6 +39,10 @@ package org.jooq.impl;
import static java.lang.Boolean.TRUE;
// ...
import static org.jooq.SQLDialect.MARIADB;
// ...
import static org.jooq.SQLDialect.MYSQL;
// ...
import static org.jooq.impl.DSL.NULL;
import static org.jooq.impl.DSL.coalesce;
import static org.jooq.impl.DSL.condition;
@ -53,11 +57,15 @@ import static org.jooq.impl.Keywords.K_JSON;
import static org.jooq.impl.Keywords.K_KEY;
import static org.jooq.impl.Keywords.K_VALUE;
import static org.jooq.impl.Names.N_JSON;
import static org.jooq.impl.Names.N_JSON_MERGE;
import static org.jooq.impl.Names.N_JSON_MERGE_PRESERVE;
import static org.jooq.impl.Names.N_JSON_QUERY;
import static org.jooq.impl.SQLDataType.VARCHAR;
import static org.jooq.impl.Tools.combine;
import static org.jooq.impl.Tools.emulateMultiset;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_MULTISET_CONTENT;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
@ -69,6 +77,7 @@ import org.jooq.JSONEntryValueStep;
import org.jooq.Param;
// ...
import org.jooq.Record1;
import org.jooq.SQLDialect;
import org.jooq.Scope;
import org.jooq.Select;
import org.jooq.conf.NestedCollectionEmulation;
@ -80,8 +89,12 @@ import org.jooq.conf.NestedCollectionEmulation;
* @author Lukas Eder
*/
final class JSONEntryImpl<T> extends AbstractQueryPart implements JSONEntry<T>, JSONEntryValueStep {
private final Field<String> key;
private final Field<T> value;
static final Set<SQLDialect> REQUIRE_JSON_MERGE = SQLDialect.supportedBy(MARIADB, MYSQL);
static final Set<SQLDialect> SUPPORT_JSON_MERGE_PRESERVE = SQLDialect.supportedBy(MARIADB, MYSQL);
private final Field<String> key;
private final Field<T> value;
JSONEntryImpl(Field<String> key) {
this(key, null);
@ -248,4 +261,12 @@ final class JSONEntryImpl<T> extends AbstractQueryPart implements JSONEntry<T>,
static final Field<?> jsonMerge(Scope scope, String empty, Field<?>... fields) {
return function(
SUPPORT_JSON_MERGE_PRESERVE.contains(scope.dialect()) ? N_JSON_MERGE_PRESERVE : N_JSON_MERGE,
fields[0].getDataType(),
combine(inline(empty), fields)
);
}
}

View File

@ -182,23 +182,14 @@ implements
case MARIADB: {
JSONEntry<?> first;
Name name = JSONArrayAgg.SUPPORT_JSON_MERGE_PRESERVE.contains(ctx.dialect()) ? N_JSON_MERGE_PRESERVE : N_JSON_MERGE;
// Workaround for https://jira.mariadb.org/browse/MDEV-13701
if (entries.size() > 1) {
ctx.visit(name).sql('(').visit(inline("{}"))
.formatIndentStart();
for (JSONEntry<?> entry : entries)
ctx.sql(',').formatSeparator().visit(jsonObject(entry));
ctx.formatIndentEnd()
.formatNewLine()
.sql(')');
ctx.visit(jsonMerge(ctx, "{}", Tools.map(entries, e -> jsonObject(e), Field[]::new)));
}
else if (!entries.isEmpty() && isJSONArray((first = entries.iterator().next()).value())) {
ctx.visit(jsonObject(
key(first.key()).value(DSL.field("{0}({1}, {2})", getDataType(), name, inline("[]"), first.value()))
key(first.key()).value(jsonMerge(ctx, "[]", first.value()))
));
}
else

View File

@ -41,7 +41,6 @@ import static org.jooq.SQLDialect.MARIADB;
import static org.jooq.impl.DSL.groupConcat;
import static org.jooq.impl.DSL.inline;
import static org.jooq.impl.DSL.jsonObject;
import static org.jooq.impl.DSL.jsonValue;
import static org.jooq.impl.DSL.noCondition;
import static org.jooq.impl.DSL.when;
import static org.jooq.impl.JSONOnNull.ABSENT_ON_NULL;
@ -60,11 +59,8 @@ import org.jooq.Field;
import org.jooq.JSON;
import org.jooq.JSONEntry;
import org.jooq.JSONObjectAggNullStep;
import org.jooq.JSONObjectNullStep;
// ...
import org.jetbrains.annotations.NotNull;
/**
* The JSON object constructor.

View File

@ -42,6 +42,8 @@ import static java.lang.Boolean.TRUE;
// ...
// ...
import static org.jooq.SQLDialect.POSTGRES;
import static org.jooq.impl.DSL.function;
import static org.jooq.impl.DSL.inline;
import static org.jooq.impl.DSL.jsonArray;
import static org.jooq.impl.DSL.jsonArrayAgg;
import static org.jooq.impl.DSL.jsonEntry;
@ -55,7 +57,13 @@ import static org.jooq.impl.DSL.xmlagg;
import static org.jooq.impl.DSL.xmlelement;
import static org.jooq.impl.DSL.xmlserializeContent;
import static org.jooq.impl.JSONArrayAgg.patchOracleArrayAggBug;
import static org.jooq.impl.JSONEntryImpl.REQUIRE_JSON_MERGE;
import static org.jooq.impl.JSONEntryImpl.SUPPORT_JSON_MERGE_PRESERVE;
import static org.jooq.impl.JSONEntryImpl.isJSON;
import static org.jooq.impl.JSONEntryImpl.jsonMerge;
import static org.jooq.impl.Keywords.K_MULTISET;
import static org.jooq.impl.Names.N_JSON_MERGE;
import static org.jooq.impl.Names.N_JSON_MERGE_PRESERVE;
import static org.jooq.impl.Names.N_MULTISET;
import static org.jooq.impl.Names.N_RECORD;
import static org.jooq.impl.Names.N_RESULT;
@ -335,7 +343,7 @@ final class Multiset<R extends Record> extends AbstractField<Result<R>> {
default:
return jsonArrayAgg(
returningClob(ctx, jsonArray(
map(fields.fields(), (f, i) -> agg ? f : DSL.field(fieldName(i), f.getDataType()))
map(fields.fields(), (f, i) -> jsonMerge(ctx, "[]", agg ? f : DSL.field(fieldName(i), f.getDataType())))
).nullOnNull())
);
}
@ -359,12 +367,24 @@ final class Multiset<R extends Record> extends AbstractField<Result<R>> {
default:
return jsonbArrayAgg(
returningClob(ctx, jsonbArray(
map(fields.fields(), (f, i) -> agg ? f : DSL.field(fieldName(i), f.getDataType()))
map(fields.fields(), (f, i) -> jsonMerge(ctx, "[]", agg ? f : DSL.field(fieldName(i), f.getDataType())))
).nullOnNull())
);
}
}
private static final Field<?> jsonMerge(Scope scope, String empty, Field<?> field) {
// [#12168] Yet another MariaDB JSON un-escaping workaround https://jira.mariadb.org/browse/MDEV-26134
return REQUIRE_JSON_MERGE.contains(scope.dialect()) && isJSON(scope, field.getDataType())
? function(
SUPPORT_JSON_MERGE_PRESERVE.contains(scope.dialect()) ? N_JSON_MERGE_PRESERVE : N_JSON_MERGE,
field.getDataType(),
inline(empty),
field
)
: field;
}
static final XMLAggOrderByStep<XML> xmlaggEmulation(Fields fields, boolean multisetCondition, boolean agg) {
return xmlagg(
xmlelement(N_RECORD,