[jOOQ/jOOQ#3884] Support ambiguous column names in MULTISET (MSSQL)
This commit is contained in:
parent
5d60eb36f1
commit
04a5a0ffe7
@ -133,8 +133,6 @@ import static org.jooq.impl.Tools.findAny;
|
||||
import static org.jooq.impl.Tools.getMappedUDTName;
|
||||
import static org.jooq.impl.Tools.map;
|
||||
import static org.jooq.impl.Tools.needsBackslashEscaping;
|
||||
import static org.jooq.impl.Tools.newRecord;
|
||||
import static org.jooq.impl.Tools.row0;
|
||||
import static org.jooq.impl.Tools.uncoerce;
|
||||
import static org.jooq.tools.StringUtils.leftPad;
|
||||
import static org.jooq.tools.jdbc.JDBCUtils.safeFree;
|
||||
@ -146,6 +144,7 @@ import static org.jooq.util.postgres.PostgresUtils.toPGArrayString;
|
||||
import static org.jooq.util.postgres.PostgresUtils.toPGInterval;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
@ -214,7 +213,6 @@ import org.jooq.RowId;
|
||||
import org.jooq.SQLDialect;
|
||||
import org.jooq.Schema;
|
||||
import org.jooq.Scope;
|
||||
import org.jooq.Select;
|
||||
import org.jooq.TableRecord;
|
||||
import org.jooq.UDTRecord;
|
||||
import org.jooq.XML;
|
||||
@ -241,8 +239,6 @@ import org.jooq.types.YearToMonth;
|
||||
import org.jooq.types.YearToSecond;
|
||||
import org.jooq.util.postgres.PostgresUtils;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
// ...
|
||||
|
||||
// ...
|
||||
@ -3787,12 +3783,12 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
|
||||
Field<?> field = uncoerce(ctx.field());
|
||||
|
||||
if (field.getDataType() instanceof MultisetDataType)
|
||||
return extracted(ctx, (MultisetDataType<?>) field.getDataType());
|
||||
return readMultiset(ctx, (MultisetDataType<?>) field.getDataType());
|
||||
else
|
||||
return ctx.configuration().dsl().fetch(convert(ctx.resultSet().getObject(ctx.index()), ResultSet.class));
|
||||
}
|
||||
|
||||
private final <R extends Record> Result<R> extracted(BindingGetResultSetContext<U> ctx, MultisetDataType<R> type) throws SQLException {
|
||||
private final <R extends Record> Result<R> readMultiset(BindingGetResultSetContext<U> ctx, MultisetDataType<R> type) throws SQLException {
|
||||
NestedCollectionEmulation emulation = emulateMultiset(ctx.configuration());
|
||||
|
||||
switch (emulation) {
|
||||
@ -3801,7 +3797,7 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
|
||||
|
||||
case JSON:
|
||||
case JSONB:
|
||||
return new JSONReader<>(ctx.dsl(), type.row, type.recordType).read(ctx.resultSet().getString(ctx.index()));
|
||||
return new JSONReader<>(ctx.dsl(), type.row, type.recordType).read(new StringReader(ctx.resultSet().getString(ctx.index())), true);
|
||||
|
||||
case XML:
|
||||
return new XMLHandler<>(ctx.dsl(), type.row, type.recordType).read(ctx.resultSet().getString(ctx.index()));
|
||||
|
||||
@ -38,6 +38,8 @@
|
||||
package org.jooq.impl;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jooq.impl.DSL.field;
|
||||
import static org.jooq.impl.DSL.name;
|
||||
import static org.jooq.impl.DefaultDataType.getDataType;
|
||||
@ -52,6 +54,7 @@ import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
|
||||
@ -85,8 +88,12 @@ final class JSONReader<R extends Record> {
|
||||
return read(new StringReader(string));
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
final Result<R> read(final Reader reader) {
|
||||
return read(reader, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
final Result<R> read(final Reader reader, boolean multiset) {
|
||||
try {
|
||||
Object root = new JSONParser().parse(reader, new ContainerFactory() {
|
||||
@Override
|
||||
@ -146,7 +153,13 @@ final class JSONReader<R extends Record> {
|
||||
}
|
||||
|
||||
result.add(newRecord(true, recordType, actualRow, ctx.configuration()).operate(r -> {
|
||||
r.fromMap(record);
|
||||
|
||||
// This sort is required if we use the JSONFormat.RecordFormat.OBJECT encoding (e.g. in SQL Server)
|
||||
if (multiset)
|
||||
r.from(record.entrySet().stream().sorted(comparing(Entry::getKey)).map(Entry::getValue).collect(toList()));
|
||||
else
|
||||
r.fromMap(record);
|
||||
|
||||
return r;
|
||||
}));
|
||||
}
|
||||
|
||||
@ -63,10 +63,13 @@ import static org.jooq.impl.SQLDataType.CLOB;
|
||||
import static org.jooq.impl.SQLDataType.VARCHAR;
|
||||
import static org.jooq.impl.Tools.emulateMultiset;
|
||||
import static org.jooq.impl.Tools.fieldName;
|
||||
import static org.jooq.impl.Tools.fieldNameString;
|
||||
import static org.jooq.impl.Tools.fieldNameStrings;
|
||||
import static org.jooq.impl.Tools.map;
|
||||
import static org.jooq.impl.Tools.visitSubquery;
|
||||
import static org.jooq.impl.Tools.BooleanDataKey.DATA_MULTISET_CONDITION;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jooq.AggregateFilterStep;
|
||||
@ -78,7 +81,6 @@ import org.jooq.JSONArrayAggOrderByStep;
|
||||
import org.jooq.JSONArrayAggReturningStep;
|
||||
import org.jooq.JSONArrayReturningStep;
|
||||
import org.jooq.JSONB;
|
||||
import org.jooq.JSONObjectReturningStep;
|
||||
import org.jooq.Record;
|
||||
import org.jooq.Record1;
|
||||
import org.jooq.Result;
|
||||
@ -101,7 +103,8 @@ final class Multiset<R extends Record> extends AbstractField<Result<R>> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Multiset(Select<R> select) {
|
||||
super(N_MULTISET, new MultisetDataType<>((AbstractRow<R>) select.fieldsRow(), select.getRecordType()));
|
||||
// [#12100] Can't use select.fieldsRow() here.
|
||||
super(N_MULTISET, new MultisetDataType<>((AbstractRow<R>) DSL.row(select.getSelect()), select.getRecordType()));
|
||||
|
||||
this.select = select;
|
||||
}
|
||||
@ -131,7 +134,7 @@ final class Multiset<R extends Record> extends AbstractField<Result<R>> {
|
||||
private final void accept0(Context<?> ctx, boolean multisetCondition) {
|
||||
switch (emulateMultiset(ctx.configuration())) {
|
||||
case JSON: {
|
||||
Table<?> t = select.asTable("t");
|
||||
Table<?> t = select.asTable("t", fieldNameStrings(select.getSelect().size()));
|
||||
|
||||
switch (ctx.family()) {
|
||||
|
||||
@ -147,7 +150,7 @@ final class Multiset<R extends Record> extends AbstractField<Result<R>> {
|
||||
JSONArrayAggOrderByStep<JSON> order;
|
||||
JSONArrayAggReturningStep<JSON> returning;
|
||||
|
||||
returning = order = jsonArrayaggEmulation(ctx, t, multisetCondition);
|
||||
returning = order = jsonArrayaggEmulation(ctx, select, false);
|
||||
|
||||
// TODO: Re-apply derived table's ORDER BY clause as aggregate ORDER BY
|
||||
if (multisetCondition)
|
||||
@ -174,7 +177,7 @@ final class Multiset<R extends Record> extends AbstractField<Result<R>> {
|
||||
}
|
||||
|
||||
case JSONB: {
|
||||
Table<?> t = select.asTable("t");
|
||||
Table<?> t = select.asTable("t", fieldNameStrings(select.getSelect().size()));
|
||||
|
||||
switch (ctx.family()) {
|
||||
|
||||
@ -190,7 +193,7 @@ final class Multiset<R extends Record> extends AbstractField<Result<R>> {
|
||||
JSONArrayAggOrderByStep<JSONB> order;
|
||||
JSONArrayAggReturningStep<JSONB> returning;
|
||||
|
||||
returning = order = jsonbArrayaggEmulation(ctx, t, multisetCondition);
|
||||
returning = order = jsonbArrayaggEmulation(ctx, select, false);
|
||||
|
||||
// TODO: Re-apply derived table's ORDER BY clause as aggregate ORDER BY
|
||||
if (multisetCondition)
|
||||
@ -213,7 +216,8 @@ final class Multiset<R extends Record> extends AbstractField<Result<R>> {
|
||||
}
|
||||
|
||||
case XML: {
|
||||
Table<?> t = select.asTable("t");
|
||||
List<Field<?>> fields = select.getSelect();
|
||||
Table<?> t = select.asTable("t", fieldNameStrings(fields.size()));
|
||||
|
||||
switch (ctx.family()) {
|
||||
|
||||
@ -230,11 +234,15 @@ final class Multiset<R extends Record> extends AbstractField<Result<R>> {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
default: {
|
||||
XMLAggOrderByStep<XML> order;
|
||||
AggregateFilterStep<XML> filter;
|
||||
|
||||
filter = order = xmlaggEmulation(t, multisetCondition);
|
||||
filter = order = xmlaggEmulation(select, multisetCondition, false);
|
||||
|
||||
// TODO: Re-apply derived table's ORDER BY clause as aggregate ORDER BY
|
||||
if (multisetCondition)
|
||||
@ -260,18 +268,6 @@ final class Multiset<R extends Record> extends AbstractField<Result<R>> {
|
||||
}
|
||||
}
|
||||
|
||||
static final <J> Field<J> returningClob(Scope ctx, JSONObjectReturningStep<J> jsonObject) {
|
||||
switch (ctx.family()) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
default:
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
||||
|
||||
static final <J> Field<J> returningClob(Scope ctx, JSONArrayReturningStep<J> jsonArray) {
|
||||
switch (ctx.family()) {
|
||||
|
||||
@ -296,24 +292,35 @@ final class Multiset<R extends Record> extends AbstractField<Result<R>> {
|
||||
}
|
||||
}
|
||||
|
||||
static final JSONArrayAggOrderByStep<JSON> jsonArrayaggEmulation(Scope ctx, Fields fields, boolean multisetCondition) {
|
||||
// The emulations use the less intuitive JSONFormat.RecordFormat.ARRAY encoding:
|
||||
// - It uses less bandwidth
|
||||
// - It is column name agnostic (supporting ambiguous column names)
|
||||
// - The JSON never leaks outside of the emulation into user code
|
||||
|
||||
static final JSONArrayAggOrderByStep<JSON> jsonArrayaggEmulation(Scope ctx, Fields fields, boolean agg) {
|
||||
return jsonArrayAgg(
|
||||
returningClob(ctx, jsonObject(
|
||||
map(fields.fields(), (f, i) -> jsonEntry(multisetCondition ? Tools.fieldNameString(i) : f.getName(), f))
|
||||
returningClob(ctx, jsonArray(
|
||||
map(fields.fields(), (f, i) -> agg ? f : DSL.field(fieldName(i), SQLDataType.JSON))
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
static final JSONArrayAggOrderByStep<JSONB> jsonbArrayaggEmulation(Scope ctx, Fields fields, boolean multisetCondition) {
|
||||
static final JSONArrayAggOrderByStep<JSONB> jsonbArrayaggEmulation(Scope ctx, Fields fields, boolean agg) {
|
||||
return jsonbArrayAgg(
|
||||
returningClob(ctx, jsonbObject(
|
||||
map(fields.fields(), (f, i) -> jsonEntry(multisetCondition ? Tools.fieldNameString(i) : f.getName(), f))
|
||||
returningClob(ctx, jsonbArray(
|
||||
map(fields.fields(), (f, i) -> agg ? f : DSL.field(fieldName(i), SQLDataType.JSONB))
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
static final XMLAggOrderByStep<XML> xmlaggEmulation(Fields fields, boolean multisetCondition) {
|
||||
return xmlagg(xmlelement(N_RECORD, map(fields.fields(), (f, i) -> xmlelement(multisetCondition ? fieldName(i) : f.getUnqualifiedName(), f))));
|
||||
static final XMLAggOrderByStep<XML> xmlaggEmulation(Fields fields, boolean multisetCondition, boolean agg) {
|
||||
return xmlagg(
|
||||
xmlelement(N_RECORD,
|
||||
map(fields.fields(), (f, i) -> xmlelement(
|
||||
multisetCondition ? fieldNameString(i) : f.getName(),
|
||||
agg ? f : DSL.field(fieldName(i))
|
||||
))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -113,7 +113,7 @@ final class MultisetAgg<R extends Record> extends DefaultAggregateFunction<Resul
|
||||
private final void accept0(Context<?> ctx, boolean multisetCondition) {
|
||||
switch (emulateMultiset(ctx.configuration())) {
|
||||
case JSON: {
|
||||
JSONArrayAggOrderByStep<JSON> order = jsonArrayaggEmulation(ctx, row, multisetCondition);
|
||||
JSONArrayAggOrderByStep<JSON> order = jsonArrayaggEmulation(ctx, row, true);
|
||||
|
||||
Field<?> f = multisetCondition
|
||||
? fo((AbstractAggregateFunction<?>) returningClob(ctx, order.orderBy(row.fields())))
|
||||
@ -128,7 +128,7 @@ final class MultisetAgg<R extends Record> extends DefaultAggregateFunction<Resul
|
||||
}
|
||||
|
||||
case JSONB: {
|
||||
JSONArrayAggOrderByStep<JSONB> order = jsonbArrayaggEmulation(ctx, row, multisetCondition);
|
||||
JSONArrayAggOrderByStep<JSONB> order = jsonbArrayaggEmulation(ctx, row, true);
|
||||
|
||||
Field<?> f = multisetCondition
|
||||
? fo((AbstractAggregateFunction<?>) returningClob(ctx, order.orderBy(row.fields())))
|
||||
@ -139,7 +139,7 @@ final class MultisetAgg<R extends Record> extends DefaultAggregateFunction<Resul
|
||||
}
|
||||
|
||||
case XML: {
|
||||
XMLAggOrderByStep<XML> order = xmlaggEmulation(row, multisetCondition);
|
||||
XMLAggOrderByStep<XML> order = xmlaggEmulation(row, multisetCondition, true);
|
||||
|
||||
Field<XML> f = xmlelement(N_RESULT,
|
||||
multisetCondition
|
||||
|
||||
@ -37,10 +37,14 @@
|
||||
*/
|
||||
package org.jooq.impl;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jooq.impl.Tools.CTX;
|
||||
import static org.jooq.impl.Tools.newRecord;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.jooq.CharacterSet;
|
||||
import org.jooq.Collation;
|
||||
@ -124,6 +128,7 @@ final class MultisetDataType<R extends Record> extends DefaultDataType<Result<R>
|
||||
return row;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@Override
|
||||
public Result<R> convert(Object object) {
|
||||
|
||||
@ -137,7 +142,11 @@ final class MultisetDataType<R extends Record> extends DefaultDataType<Result<R>
|
||||
|
||||
// [#12014] TODO: Fix this and remove workaround
|
||||
if (record instanceof Record)
|
||||
((AbstractRecord) r).from((Record) record);
|
||||
((AbstractRecord) r).fromArray(((Record) record).intoArray());
|
||||
|
||||
// This sort is required if we use the JSONFormat.RecordFormat.OBJECT encoding (e.g. in SQL Server)
|
||||
else if (record instanceof Map)
|
||||
r.from(((Map<String, ?>) record).entrySet().stream().sorted(comparing(Entry::getKey)).map(Entry::getValue).collect(toList()));
|
||||
else
|
||||
r.from(record);
|
||||
|
||||
|
||||
@ -5890,7 +5890,6 @@ final class Tools {
|
||||
|
||||
|
||||
|
||||
|
||||
default:
|
||||
return NestedCollectionEmulation.NATIVE;
|
||||
}
|
||||
|
||||
@ -261,7 +261,7 @@ final class XMLHandler<R extends Record> extends DefaultHandler {
|
||||
if (fields.size() <= 1)
|
||||
return false;
|
||||
else
|
||||
return !anyMatch(fields, f -> !"value".equals(f.getName()));
|
||||
return !anyMatch(fields, f -> !"value".equalsIgnoreCase(f.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Loading…
Reference in New Issue
Block a user