[jOOQ/jOOQ#19137] Support binding a Result value as a MultisetDataType

This includes:

- [jOOQ/jOOQ#14462] inline(null, multisetType) doesn't work
This commit is contained in:
Lukas Eder 2025-10-07 14:52:41 +02:00
parent 3cb53b66aa
commit efb869e4a3
2 changed files with 70 additions and 1 deletions

View File

@ -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<T> extends AbstractParamX<T> 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()

View File

@ -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<T, U> implements Binding<T, U> {
static final class DefaultResultBinding<U> extends InternalBinding<org.jooq.Result<?>, 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<XML> xmlBinding;
final DefaultJSONBinding<JSON> jsonBinding;
final DefaultJSONBBinding<JSONB> jsonbBinding;
DefaultResultBinding(DataType<Result<?>> dataType, Converter<Result<?>, 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<U> 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<U> 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