[jOOQ/jOOQ#12155] Nested multiset record mapping fails at the 3rd level

of nesting
This commit is contained in:
Lukas Eder 2021-07-13 12:26:23 +02:00
parent 888b1cf143
commit cea2f300aa
3 changed files with 85 additions and 42 deletions

View File

@ -46,6 +46,7 @@ import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.Nullability;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.Row;
import org.jooq.SQLDialect;
@ -214,6 +215,10 @@ final class ConvertedDataType<T, U> extends AbstractDataTypeX<U> {
if (getConverter().toType().isInstance(object))
return (U) object;
// [#12155] Avoid double conversion passes between Result and custom List<UserType>
else if (delegate.isMultiset() && !(object instanceof Result))
return (U) object;
// [#3200] Try to convert arbitrary objects to T
else
return ((Converter<T, U>) getConverter()).from(delegate.convert(object));

View File

@ -60,6 +60,7 @@ import javax.xml.bind.DatatypeConverter;
import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.Fields;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.tools.json.ContainerFactory;
@ -71,7 +72,7 @@ import org.jooq.tools.json.JSONParser;
* @author Johannes Bühler
* @author Lukas Eder
*/
@SuppressWarnings({ "unchecked" })
@SuppressWarnings({ "unchecked", "rawtypes" })
final class JSONReader<R extends Record> {
private final DSLContext ctx;
@ -92,7 +93,6 @@ final class JSONReader<R extends Record> {
return read(reader, false);
}
@SuppressWarnings("rawtypes")
final Result<R> read(final Reader reader, boolean multiset) {
try {
Object root = new JSONParser().parse(reader, new ContainerFactory() {
@ -107,39 +107,53 @@ final class JSONReader<R extends Record> {
}
});
AbstractRow<R> actualRow = row;
List<Field<?>> header = new ArrayList<>();
return read(ctx, row, recordType, multiset, root);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
List<?> records;
Result<R> result = null;
private static final <R extends Record> Result<R> read(
DSLContext ctx,
AbstractRow<R> actualRow,
Class<? extends R> recordType,
boolean multiset,
Object root
) {
List<Field<?>> header = new ArrayList<>();
if (root instanceof Map) {
Map<String, Object> o1 = (Map<String, Object>) root;
List<Map<String, String>> fields = (List<Map<String, String>>) o1.get("fields");
List<?> records;
Result<R> result = null;
if (fields != null) {
for (Map<String, String> field : fields) {
String catalog = field.get("catalog");
String schema = field.get("schema");
String table = field.get("table");
String name = field.get("name");
String type = field.get("type");
if (root instanceof Map) {
Map<String, Object> o1 = (Map<String, Object>) root;
List<Map<String, String>> fields = (List<Map<String, String>>) o1.get("fields");
header.add(field(name(catalog, schema, table, name), getDataType(ctx.dialect(), defaultIfBlank(type, "VARCHAR"))));
}
if (fields != null) {
for (Map<String, String> field : fields) {
String catalog = field.get("catalog");
String schema = field.get("schema");
String table = field.get("table");
String name = field.get("name");
String type = field.get("type");
header.add(field(name(catalog, schema, table, name), getDataType(ctx.dialect(), defaultIfBlank(type, "VARCHAR"))));
}
records = (List<?>) o1.get("records");
}
else
records = (List<?>) root;
if (actualRow == null && !header.isEmpty())
actualRow = (AbstractRow<R>) Tools.row0(header);
records = (List<?>) o1.get("records");
}
else
records = (List<?>) root;
if (actualRow != null)
result = new ResultImpl<>(ctx.configuration(), actualRow);
if (actualRow == null && !header.isEmpty())
actualRow = (AbstractRow<R>) Tools.row0(header);
if (actualRow != null)
result = new ResultImpl<>(ctx.configuration(), actualRow);
if (records != null) {
for (Object o3 : records) {
if (o3 instanceof Map) {
Map<String, Object> record = (Map<String, Object>) o3;
@ -152,11 +166,20 @@ final class JSONReader<R extends Record> {
result = new ResultImpl<>(ctx.configuration(), actualRow = (AbstractRow<R>) Tools.row0(header));
}
result.add(newRecord(true, recordType, actualRow, ctx.configuration()).operate(r -> {
List<Object> list = multiset
? patchRecord(
ctx,
multiset,
actualRow,
// This sort is required if we use the JSONFormat.RecordFormat.OBJECT encoding (e.g. in SQL Server)
// This sort is required if we use the JSONFormat.RecordFormat.OBJECT encoding (e.g. in SQL Server)
record.entrySet().stream().sorted(comparing(Entry::getKey)).map(Entry::getValue).collect(toList())
)
: null;
result.add(newRecord(true, recordType, actualRow, ctx.configuration()).operate(r -> {
if (multiset)
r.from(record.entrySet().stream().sorted(comparing(Entry::getKey)).map(Entry::getValue).collect(toList()));
r.from(list);
else
r.fromMap(record);
@ -164,7 +187,7 @@ final class JSONReader<R extends Record> {
}));
}
else {
List record = (List) o3;
List<Object> record = (List<Object>) o3;
if (result == null) {
if (header.isEmpty())
@ -173,24 +196,39 @@ final class JSONReader<R extends Record> {
result = new ResultImpl<>(ctx.configuration(), actualRow = (AbstractRow<R>) Tools.row0(header));
}
// [#8829] LoaderImpl expects binary data to be encoded in base64,
// not according to org.jooq.tools.Convert
for (int i = 0; i < result.fields().length; i++)
if (result.field(i).getType() == byte[].class && record.get(i) instanceof String)
record.set(i, DatatypeConverter.parseBase64Binary((String) record.get(i)));
patchRecord(ctx, multiset, actualRow, record);
result.add(newRecord(true, recordType, actualRow, ctx.configuration()).operate(r -> {
r.from(record);
return r;
}));
}
}
}
return result;
}
catch (Exception e) {
throw new RuntimeException(e);
return result;
}
private static final List<Object> patchRecord(DSLContext ctx, boolean multiset, Fields result, List<Object> record) {
for (int i = 0; i < result.fields().length; i++) {
Field<?> field = result.field(i);
// [#8829] LoaderImpl expects binary data to be encoded in base64,
// not according to org.jooq.tools.Convert
if (field.getType() == byte[].class && record.get(i) instanceof String)
record.set(i, DatatypeConverter.parseBase64Binary((String) record.get(i)));
// [#12155] Recurse for nested data types
else if (multiset && field.getDataType().isMultiset())
record.set(i, read(
ctx,
(AbstractRow) field.getDataType().getRow(),
(Class) field.getDataType().getRecordType(),
multiset,
record.get(i)
));
}
return record;
}
}

View File

@ -174,7 +174,7 @@ final class RowField<ROW extends Row, REC extends Record> extends AbstractField<
default:
ctx.visit(alias(ctx, returningClob(ctx, jsonArray(row.fields()))));
ctx.visit(alias(ctx, returningClob(ctx, jsonArray(row.fields()).nullOnNull())));
break;
}
@ -199,7 +199,7 @@ final class RowField<ROW extends Row, REC extends Record> extends AbstractField<
default:
ctx.visit(alias(ctx, returningClob(ctx, jsonbArray(row.fields()))));
ctx.visit(alias(ctx, returningClob(ctx, jsonbArray(row.fields()).nullOnNull())));
break;
}