From 4bae0a5129d0170b3d15e4f6929daa358e1407f0 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 13 Jul 2021 17:28:53 +0200 Subject: [PATCH] [jOOQ/jOOQ#12168] Deeply nested MULTISET produce quoted JSON in MariaDB --- jOOQ/src/main/java/org/jooq/impl/DSL.java | 2 +- .../main/java/org/jooq/impl/JSONArrayAgg.java | 10 +++----- .../java/org/jooq/impl/JSONEntryImpl.java | 25 +++++++++++++++++-- .../main/java/org/jooq/impl/JSONObject.java | 13 ++-------- .../java/org/jooq/impl/JSONObjectAgg.java | 4 --- .../src/main/java/org/jooq/impl/Multiset.java | 24 ++++++++++++++++-- 6 files changed, 51 insertions(+), 27 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/impl/DSL.java b/jOOQ/src/main/java/org/jooq/impl/DSL.java index fd1668c581..b0af91d641 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DSL.java +++ b/jOOQ/src/main/java/org/jooq/impl/DSL.java @@ -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}. + * {@link XMLFormat.RecordFormau}. *
  • {@link NestedCollectionEmulation#NATIVE}: A few dialects have native * support for MULTISET.
  • *
  • {@link NestedCollectionEmulation#DEFAULT}: By default, jOOQ chooses diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONArrayAgg.java b/jOOQ/src/main/java/org/jooq/impl/JSONArrayAgg.java index e189afce18..20446e8608 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JSONArrayAgg.java +++ b/jOOQ/src/main/java/org/jooq/impl/JSONArrayAgg.java @@ -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 implements JSONArrayAggOrderByStep { static final Set EMULATE_WITH_GROUP_CONCAT = SQLDialect.supportedBy(MARIADB, MYSQL); - static final Set SUPPORT_JSON_MERGE_PRESERVE = SQLDialect.supportedBy(MARIADB, MYSQL); @@ -102,13 +98,13 @@ implements JSONArrayAggOrderByStep { } @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; } diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONEntryImpl.java b/jOOQ/src/main/java/org/jooq/impl/JSONEntryImpl.java index 9882e3fbc5..7c4ccbd83a 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JSONEntryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/JSONEntryImpl.java @@ -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 extends AbstractQueryPart implements JSONEntry, JSONEntryValueStep { - private final Field key; - private final Field value; + + static final Set REQUIRE_JSON_MERGE = SQLDialect.supportedBy(MARIADB, MYSQL); + static final Set SUPPORT_JSON_MERGE_PRESERVE = SQLDialect.supportedBy(MARIADB, MYSQL); + + private final Field key; + private final Field value; JSONEntryImpl(Field key) { this(key, null); @@ -248,4 +261,12 @@ final class JSONEntryImpl extends AbstractQueryPart implements JSONEntry, + + 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) + ); + } } diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONObject.java b/jOOQ/src/main/java/org/jooq/impl/JSONObject.java index 079f971141..de69e61ab1 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JSONObject.java +++ b/jOOQ/src/main/java/org/jooq/impl/JSONObject.java @@ -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 diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONObjectAgg.java b/jOOQ/src/main/java/org/jooq/impl/JSONObjectAgg.java index 9616dd75f8..3f2824fc6e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JSONObjectAgg.java +++ b/jOOQ/src/main/java/org/jooq/impl/JSONObjectAgg.java @@ -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. diff --git a/jOOQ/src/main/java/org/jooq/impl/Multiset.java b/jOOQ/src/main/java/org/jooq/impl/Multiset.java index 9bb5ad691f..955412dbaf 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Multiset.java +++ b/jOOQ/src/main/java/org/jooq/impl/Multiset.java @@ -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 extends AbstractField> { 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 extends AbstractField> { 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 xmlaggEmulation(Fields fields, boolean multisetCondition, boolean agg) { return xmlagg( xmlelement(N_RECORD,