From efb869e4a38565d91e01a8d6103a7196305fc4db Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 7 Oct 2025 14:52:41 +0200 Subject: [PATCH] [jOOQ/jOOQ#19137] Support binding a Result value as a MultisetDataType This includes: - [jOOQ/jOOQ#14462] inline(null, multisetType) doesn't work --- .../java/org/jooq/impl/AbstractParam.java | 4 ++ .../java/org/jooq/impl/DefaultBinding.java | 67 ++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractParam.java b/jOOQ/src/main/java/org/jooq/impl/AbstractParam.java index b9ac5f540a..f84487eaf0 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractParam.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractParam.java @@ -59,6 +59,7 @@ import org.jooq.Name; import org.jooq.Param; import org.jooq.ParamMode; import org.jooq.QualifiedRecord; +import org.jooq.Result; import org.jooq.SQLDialect; import org.jooq.conf.ParamType; import org.jooq.impl.DefaultBinding.InternalBinding; @@ -109,6 +110,9 @@ abstract class AbstractParam extends AbstractParamX implements SimpleQuery paramName != null ? paramName + : value instanceof Result r + ? r.fieldsRow().toString() + // [#3707] Protect value.toString call for certain jOOQ types. : value instanceof QualifiedRecord q ? q.getQualifier().getName() diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java index 63fdb4f0bd..da273fb82b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java @@ -54,6 +54,8 @@ import static org.jooq.Decfloat.decfloat; import static org.jooq.Decfloat.decfloatOrNull; import static org.jooq.Geography.geography; import static org.jooq.Geometry.geometry; +import static org.jooq.JSON.json; +import static org.jooq.JSONB.jsonb; // ... // ... // ... @@ -92,11 +94,13 @@ import static org.jooq.SQLDialect.SQLITE; import static org.jooq.SQLDialect.TRINO; // ... import static org.jooq.SQLDialect.YUGABYTEDB; +import static org.jooq.XML.xml; import static org.jooq.conf.ParamType.INLINED; import static org.jooq.impl.Array.NO_SUPPORT_SQUARE_BRACKETS; import static org.jooq.impl.BlobBinding.readBlob; import static org.jooq.impl.Convert.convert; import static org.jooq.impl.Convert.patchIso8601Timestamp; +import static org.jooq.impl.DSL.array; import static org.jooq.impl.DSL.cast; import static org.jooq.impl.DSL.field; import static org.jooq.impl.DSL.inline; @@ -118,6 +122,7 @@ import static org.jooq.impl.DefaultDataType.unsupportedDatetimePrecision; import static org.jooq.impl.DefaultExecuteContext.localExecuteContext; import static org.jooq.impl.DefaultExecuteContext.localTargetConnection; import static org.jooq.impl.Internal.arrayType; +import static org.jooq.impl.JSONReader.ENCODE_BINARY_AS_HEX; import static org.jooq.impl.Keywords.K_ARRAY; import static org.jooq.impl.Keywords.K_AS; import static org.jooq.impl.Keywords.K_BLOB; @@ -276,6 +281,8 @@ import org.jooq.Geography; import org.jooq.Geometry; import org.jooq.JSON; import org.jooq.JSONB; +import org.jooq.JSONFormat; +import org.jooq.JSONFormat.BinaryFormat; import org.jooq.Package; import org.jooq.Param; // ... @@ -295,6 +302,9 @@ import org.jooq.UDT; import org.jooq.UDTField; import org.jooq.UDTRecord; import org.jooq.XML; +import org.jooq.XMLFormat; +import org.jooq.XMLFormat.ArrayFormat; +import org.jooq.XMLFormat.NullFormat; import org.jooq.conf.NestedCollectionEmulation; import org.jooq.exception.ControlFlowSignal; import org.jooq.exception.DataAccessException; @@ -4820,13 +4830,68 @@ public class DefaultBinding implements Binding { static final class DefaultResultBinding extends InternalBinding, U> { + static final JSONFormat JSON_FORMAT_BASE64 = JSONFormat.DEFAULT_FOR_RECORDS.recordFormat(JSONFormat.RecordFormat.ARRAY).nanAsString(true).infinityAsString(true); + static final JSONFormat JSON_FORMAT_HEX = JSON_FORMAT_BASE64.binaryFormat(BinaryFormat.HEX); + static final XMLFormat XML_FORMAT = XMLFormat.DEFAULT_FOR_RECORDS.recordFormat(XMLFormat.RecordFormat.COLUMN_NAME_ELEMENTS).nullFormat(NullFormat.XSI_NIL).arrayFormat(ArrayFormat.ELEMENTS); + + final DefaultXMLBinding xmlBinding; + final DefaultJSONBinding jsonBinding; + final DefaultJSONBBinding jsonbBinding; + DefaultResultBinding(DataType> dataType, Converter, U> converter) { super(dataType, converter); + + xmlBinding = new DefaultXMLBinding<>(SQLDataType.XML, Converters.identity(XML.class)); + jsonBinding = new DefaultJSONBinding<>(SQLDataType.JSON, Converters.identity(JSON.class)); + jsonbBinding = new DefaultJSONBBinding<>(SQLDataType.JSONB, Converters.identity(JSONB.class)); + } + + final JSONFormat jsonFormat(Scope ctx) { + return ENCODE_BINARY_AS_HEX.contains(ctx.dialect()) ? JSON_FORMAT_HEX : JSON_FORMAT_BASE64; + } + + @Override + final void sqlInline0(BindingSQLContext ctx, Result value) throws SQLException { + switch (emulateMultiset(ctx.configuration())) { + case JSON: + jsonBinding.sqlInline0((BindingSQLContext) ctx, json(value.formatJSON(jsonFormat(ctx)))); + break; + + case JSONB: + jsonbBinding.sqlInline0((BindingSQLContext) ctx, jsonb(value.formatJSON(jsonFormat(ctx)))); + break; + + case XML: + xmlBinding.sqlInline0((BindingSQLContext) ctx, xml(value.formatXML(XML_FORMAT))); + break; + + case NATIVE: + ctx.render().visit(array(map(value, r -> new RowAsField<>(r.valuesRow())))); + break; + + default: + throw new UnsupportedOperationException("Cannot inline a value of type Result using " + emulateMultiset(ctx.configuration()) + " MULTISET emulation."); + } } @Override final void set0(BindingSetStatementContext ctx, Result value) throws SQLException { - throw new UnsupportedOperationException("Cannot bind a value of type Result to a PreparedStatement"); + switch (emulateMultiset(ctx.configuration())) { + case JSON: + jsonBinding.set0((BindingSetStatementContext) ctx, json(value.formatJSON(jsonFormat(ctx)))); + break; + + case JSONB: + jsonbBinding.set0((BindingSetStatementContext) ctx, jsonb(value.formatJSON(jsonFormat(ctx)))); + break; + + case XML: + xmlBinding.set0((BindingSetStatementContext) ctx, xml(value.formatXML(XML_FORMAT))); + break; + + default: + throw new UnsupportedOperationException("Cannot bind a value of type Result using " + emulateMultiset(ctx.configuration()) + " MULTISET emulation."); + } } @Override