From 3b6045ff4307b83b199228b9098f1d02bfcadd54 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Wed, 27 Mar 2024 17:34:18 +0100 Subject: [PATCH] [jOOQ/jOOQ#15732] Fix MULTISET equality semantics --- .../java/org/jooq/impl/ArrayDataType.java | 1 + .../java/org/jooq/impl/DefaultBinding.java | 3 +- .../src/main/java/org/jooq/impl/Multiset.java | 23 ++++++- .../main/java/org/jooq/impl/MultisetAgg.java | 32 +++------ .../java/org/jooq/tools/json/JSONValue.java | 66 +++++++------------ 5 files changed, 58 insertions(+), 67 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/impl/ArrayDataType.java b/jOOQ/src/main/java/org/jooq/impl/ArrayDataType.java index d084f696a4..0a0403b5f2 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ArrayDataType.java +++ b/jOOQ/src/main/java/org/jooq/impl/ArrayDataType.java @@ -178,6 +178,7 @@ final class ArrayDataType extends DefaultDataType { switch (configuration.family()) { + case DUCKDB: case POSTGRES: case YUGABYTEDB: return dataType + "[]"; diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java index b1375a7f77..c3387757bb 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java @@ -296,6 +296,7 @@ import org.jooq.tools.jdbc.JDBCUtils; import org.jooq.tools.jdbc.MockArray; import org.jooq.tools.jdbc.MockResultSet; import org.jooq.tools.json.JSONArray; +import org.jooq.tools.json.JSONValue; import org.jooq.types.DayToSecond; import org.jooq.types.UByte; import org.jooq.types.UInteger; @@ -1420,7 +1421,7 @@ public class DefaultBinding implements Binding { // [#15732] Use JSON as a workaround to bind array types for now. case DUCKDB: { - ctx.statement().setString(ctx.index(), new JSONArray(asList(value)).toString()); + ctx.statement().setString(ctx.index(), JSONValue.toJSONString(value)); break; } diff --git a/jOOQ/src/main/java/org/jooq/impl/Multiset.java b/jOOQ/src/main/java/org/jooq/impl/Multiset.java index 2db6ad47dd..ee6859c0e3 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Multiset.java +++ b/jOOQ/src/main/java/org/jooq/impl/Multiset.java @@ -45,6 +45,7 @@ import static org.jooq.SQLDialect.MYSQL; import static org.jooq.SQLDialect.*; import static org.jooq.SQLDialect.POSTGRES; import static org.jooq.SQLDialect.YUGABYTEDB; +import static org.jooq.impl.DSL.arrayAgg; import static org.jooq.impl.DSL.field; import static org.jooq.impl.DSL.function; import static org.jooq.impl.DSL.inline; @@ -97,6 +98,7 @@ import java.util.Set; import java.util.function.Function; import org.jooq.AggregateFilterStep; +import org.jooq.ArrayAggOrderByStep; import org.jooq.Context; import org.jooq.DataType; import org.jooq.Field; @@ -119,6 +121,8 @@ import org.jooq.Result; import org.jooq.SQLDialect; import org.jooq.Scope; import org.jooq.Select; +import org.jooq.SelectJoinStep; +import org.jooq.SelectOrderByStep; import org.jooq.Spatial; import org.jooq.Table; import org.jooq.TableLike; @@ -126,6 +130,8 @@ import org.jooq.TableLike; import org.jooq.XML; import org.jooq.XMLAggOrderByStep; +import org.jetbrains.annotations.NotNull; + /** * @author Lukas Eder */ @@ -394,9 +400,14 @@ final class Multiset extends AbstractField> implemen case NATIVE: switch (ctx.family()) { - case DUCKDB: - visitSubquery(ctx.visit(K_ARRAY), select(DSL.field(N_T)).from(select.asTable(N_T))); + case DUCKDB: { + SelectOrderByStep s = select(DSL.field(N_T)).from(select.asTable(N_T)); + visitSubquery(ctx.visit(K_ARRAY), multisetCondition + ? s.orderBy(DSL.field(N_T)) + : s + ); break; + } default: visitSubquery(ctx.visit(K_MULTISET), select); @@ -603,6 +614,14 @@ final class Multiset extends AbstractField> implemen ); } + static final ArrayAggOrderByStep arrayAggEmulation(Fields fields, boolean agg) { + return arrayAgg( + new RowAsField<>(row( + map(fields.fields(), (f, i) -> agg ? f : DSL.field(fieldName(i), f.getDataType())) + )) + ); + } + // ------------------------------------------------------------------------- // XXX: Query Object Model // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/MultisetAgg.java b/jOOQ/src/main/java/org/jooq/impl/MultisetAgg.java index 7ffd1c41db..c5c3c13d78 100644 --- a/jOOQ/src/main/java/org/jooq/impl/MultisetAgg.java +++ b/jOOQ/src/main/java/org/jooq/impl/MultisetAgg.java @@ -39,31 +39,26 @@ package org.jooq.impl; import static java.lang.Boolean.TRUE; // ... -import static org.jooq.impl.DSL.row; import static org.jooq.impl.DSL.xmlelement; import static org.jooq.impl.DSL.xmlserializeContent; import static org.jooq.impl.Multiset.NO_SUPPORT_JSONB_COMPARE; import static org.jooq.impl.Multiset.NO_SUPPORT_JSON_COMPARE; import static org.jooq.impl.Multiset.NO_SUPPORT_XML_COMPARE; +import static org.jooq.impl.Multiset.arrayAggEmulation; import static org.jooq.impl.Multiset.jsonArrayaggEmulation; import static org.jooq.impl.Multiset.jsonbArrayaggEmulation; import static org.jooq.impl.Multiset.nResult; import static org.jooq.impl.Multiset.returningClob; import static org.jooq.impl.Multiset.xmlaggEmulation; -import static org.jooq.impl.Names.N_ARRAY_AGG; import static org.jooq.impl.Names.N_MULTISET_AGG; -import static org.jooq.impl.Names.N_RESULT; import static org.jooq.impl.SQLDataType.VARCHAR; import static org.jooq.impl.Tools.emulateMultiset; import static org.jooq.impl.Tools.BooleanDataKey.DATA_MULTISET_CONDITION; import static org.jooq.impl.Tools.BooleanDataKey.DATA_MULTISET_CONTENT; -import java.util.function.BiFunction; -import java.util.function.Predicate; - +import org.jooq.ArrayAggOrderByStep; import org.jooq.Context; import org.jooq.Field; -import org.jooq.Function1; import org.jooq.JSON; import org.jooq.JSONArrayAggOrderByStep; import org.jooq.JSONB; @@ -164,23 +159,16 @@ final class MultisetAgg extends AbstractAggregateFunction order = arrayAggEmulation(row, true); + + ctx.visit(multisetCondition + ? fo(order.orderBy(row.fields())) + : ofo((AbstractAggregateFunction) order) + ); - ctx.sql('('); - acceptArguments1(ctx, new QueryPartListView<>(new RowAsField<>(DSL.row(arguments)))); - acceptOrderBy(ctx); - ctx.sql(')'); - acceptFilterClause(ctx); - acceptOverClause(ctx); break; + } } } diff --git a/jOOQ/src/main/java/org/jooq/tools/json/JSONValue.java b/jOOQ/src/main/java/org/jooq/tools/json/JSONValue.java index 4868247347..76e80f9004 100644 --- a/jOOQ/src/main/java/org/jooq/tools/json/JSONValue.java +++ b/jOOQ/src/main/java/org/jooq/tools/json/JSONValue.java @@ -22,7 +22,9 @@ package org.jooq.tools.json; import java.io.IOException; import java.io.Reader; +import java.io.StringWriter; import java.io.Writer; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -43,26 +45,28 @@ public class JSONValue { return; } - if (value instanceof String) { + if (value instanceof String s) { out.write('\"'); - out.write(escape((String) value)); + out.write(escape(s)); out.write('\"'); return; } - if (value instanceof Double) { - if (((Double) value).isInfinite() || ((Double) value).isNaN()) + if (value instanceof Double d) { + if (d.isInfinite() || d.isNaN()) out.write("null"); else out.write(value.toString()); + return; } - if (value instanceof Float) { - if (((Float) value).isInfinite() || ((Float) value).isNaN()) + if (value instanceof Float f) { + if (f.isInfinite() || f.isNaN()) out.write("null"); else out.write(value.toString()); + return; } @@ -76,13 +80,18 @@ public class JSONValue { return; } - if (value instanceof Map) { - JSONObject.writeJSONString((Map) value, out); + if (value instanceof Map m) { + JSONObject.writeJSONString(m, out); return; } - if (value instanceof List) { - JSONArray.writeJSONString((List) value, out); + if (value instanceof List l) { + JSONArray.writeJSONString(l, out); + return; + } + + if (value instanceof Object[] a) { + JSONArray.writeJSONString(Arrays.asList(a), out); return; } @@ -102,41 +111,14 @@ public class JSONValue { * number. */ public static String toJSONString(Object value) { - if (value == null) - return "null"; + Writer w = new StringWriter(); - if (value instanceof String) - return "\"" + escape((String) value) + "\""; - - if (value instanceof Double) { - if (((Double) value).isInfinite() || ((Double) value).isNaN()) - return "null"; - else - return value.toString(); + try { + writeJSONString(value, w); } + catch (IOException ignore) {} - if (value instanceof Float) { - if (((Float) value).isInfinite() || ((Float) value).isNaN()) - return "null"; - else - return value.toString(); - } - - if (value instanceof Number) - return value.toString(); - - if (value instanceof Boolean) - return value.toString(); - - if (value instanceof Map) - return JSONObject.toJSONString((Map) value); - - if (value instanceof List) - return JSONArray.toJSONString((List) value); - - // Patched original according to issue 27 of JSON-simple - // http://code.google.com/p/json-simple/issues/detail?id=27 - return "\"" + escape(value.toString()) + "\""; + return w.toString(); } /**