[jOOQ/jOOQ#12134] Support deserialising binary data in MULTISET

emulations

- Added SQL/JSON emulation support for H2, SQLite, MySQL, MariaDB
- Added SQL/XML emulation support for Db2, Oracle
This commit is contained in:
Lukas Eder 2022-03-31 16:50:52 +02:00
parent fd05a97f92
commit ce4115dac9
3 changed files with 59 additions and 8 deletions

View File

@ -44,6 +44,7 @@ import static org.jooq.SQLDialect.MYSQL;
import static org.jooq.impl.AbstractRowAsField.forceMultisetContent;
import static org.jooq.impl.DSL.NULL;
import static org.jooq.impl.DSL.case_;
import static org.jooq.impl.DSL.castNull;
import static org.jooq.impl.DSL.coalesce;
import static org.jooq.impl.DSL.field;
import static org.jooq.impl.DSL.function;
@ -51,17 +52,22 @@ import static org.jooq.impl.DSL.inline;
import static org.jooq.impl.DSL.inlined;
import static org.jooq.impl.DSL.nvl;
import static org.jooq.impl.DSL.toChar;
import static org.jooq.impl.DSL.when;
import static org.jooq.impl.DSL.xmlelement;
import static org.jooq.impl.DSL.xmlquery;
import static org.jooq.impl.Keywords.K_FORMAT;
import static org.jooq.impl.Keywords.K_JSON;
import static org.jooq.impl.Keywords.K_KEY;
import static org.jooq.impl.Keywords.K_VALUE;
import static org.jooq.impl.Names.N_HEX;
import static org.jooq.impl.Names.N_JSON;
import static org.jooq.impl.Names.N_JSON_EXTRACT;
import static org.jooq.impl.Names.N_JSON_MERGE;
import static org.jooq.impl.Names.N_JSON_MERGE_PRESERVE;
import static org.jooq.impl.Names.N_JSON_QUERY;
import static org.jooq.impl.Names.N_RAWTOHEX;
import static org.jooq.impl.RowAsField.NO_NATIVE_SUPPORT;
import static org.jooq.impl.SQLDataType.BIT;
import static org.jooq.impl.SQLDataType.*;
import static org.jooq.impl.SQLDataType.BOOLEAN;
import static org.jooq.impl.SQLDataType.VARCHAR;
import static org.jooq.impl.Tools.combine;
@ -192,6 +198,10 @@ final class JSONEntryImpl<T> extends AbstractQueryPart implements JSONEntry<T>,
else if (type.isTemporal())
return field.cast(VARCHAR);
// [#12134] No auto conversion available yet
else if (type.isBinary())
return function(N_RAWTOHEX, VARCHAR, field);
break;
// [#11025] These don't have boolean support outside of JSON
@ -210,6 +220,8 @@ final class JSONEntryImpl<T> extends AbstractQueryPart implements JSONEntry<T>,
case SQLITE:
if (isType(type, Boolean.class))
return function(N_JSON, SQLDataType.JSON, case_((Field<Boolean>) field).when(inline(true), inline("true")).when(inline(false), inline("false")));
else if (type.isBinary())
return when(field.isNotNull(), function(N_HEX, VARCHAR, field));
break;
@ -241,6 +253,12 @@ final class JSONEntryImpl<T> extends AbstractQueryPart implements JSONEntry<T>,

View File

@ -39,6 +39,15 @@ package org.jooq.impl;
import static java.lang.Integer.parseInt;
import static java.util.Arrays.asList;
// ...
// ...
// ...
import static org.jooq.SQLDialect.H2;
import static org.jooq.SQLDialect.MARIADB;
import static org.jooq.SQLDialect.POSTGRES;
// ...
import static org.jooq.SQLDialect.SQLITE;
import static org.jooq.SQLDialect.YUGABYTEDB;
import static org.jooq.impl.DSL.field;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DefaultDataType.getDataType;
@ -56,12 +65,14 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.Fields;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.SQLDialect;
import org.jooq.tools.json.ContainerFactory;
import org.jooq.tools.json.JSONParser;
@ -226,6 +237,9 @@ final class JSONReader<R extends Record> {
return result;
}
private static final Set<SQLDialect> ENCODE_BINARY_AS_HEX = SQLDialect.supportedBy(H2, POSTGRES, SQLITE, YUGABYTEDB);
private static final Set<SQLDialect> ENCODE_BINARY_AS_TEXT = SQLDialect.supportedBy(MARIADB);
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);
@ -235,9 +249,20 @@ final class JSONReader<R extends Record> {
if (field.getType() == byte[].class && record.get(i) instanceof String) {
String s = (String) record.get(i);
// [#12134] SQL/JSON MULTISET and other forms of serialisation may produce hex encoded binary data
if (s.startsWith("\\x"))
record.set(i, convertHexToBytes(s, 1, Integer.MAX_VALUE));
// [#12134] PostgreSQL encodes binary data as hex
if (ENCODE_BINARY_AS_HEX.contains(ctx.dialect()))
if (s.startsWith("\\x"))
record.set(i, convertHexToBytes(s, 1, Integer.MAX_VALUE));
else
record.set(i, convertHexToBytes(s));
// [#12134] MariaDB encodes binary data as text (?)
else if (ENCODE_BINARY_AS_TEXT.contains(ctx.dialect()))
record.set(i, s);
// [#12134] MySQL encodes binary data as prefixed base64
else if (s.startsWith("base64:type15:"))
record.set(i, Base64.getDecoder().decode(s.substring(14)));
else
record.set(i, Base64.getDecoder().decode(s));
}

View File

@ -37,6 +37,7 @@
*/
package org.jooq.impl;
// ...
import static org.jooq.impl.DSL.field;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DefaultDataType.getDataType;
@ -83,6 +84,7 @@ final class XMLHandler<R extends Record> extends DefaultHandler {
private State<R> s;
private static class State<R extends Record> {
final DSLContext ctx;
AbstractRow<R> row;
final Class<? extends R> recordType;
boolean inResult;
@ -95,7 +97,8 @@ final class XMLHandler<R extends Record> extends DefaultHandler {
int column;
@SuppressWarnings("unchecked")
State(AbstractRow<R> row, Class<? extends R> recordType) {
State(DSLContext ctx, AbstractRow<R> row, Class<? extends R> recordType) {
this.ctx = ctx;
this.row = row;
this.recordType = recordType != null ? recordType : (Class<? extends R>) Record.class;
this.fields = new ArrayList<>();
@ -108,6 +111,11 @@ final class XMLHandler<R extends Record> extends DefaultHandler {
for (int i = 0; i < fields.size(); i++) {
if (fields.get(i).getDataType().isBinary()) {
if (values.get(i) instanceof String) { String s = (String) values.get(i);
values.set(i, Base64.getDecoder().decode(s));
}
}
@ -121,7 +129,7 @@ final class XMLHandler<R extends Record> extends DefaultHandler {
XMLHandler(DSLContext ctx, AbstractRow<R> row, Class<? extends R> recordType) {
this.ctx = ctx;
this.states = new ArrayDeque<>();
this.s = new State<>(row, recordType);
this.s = new State<>(ctx, row, recordType);
}
Result<R> read(String string) {
@ -180,7 +188,7 @@ final class XMLHandler<R extends Record> extends DefaultHandler {
if (f.getDataType().isMultiset()) {
states.push(s);
s = new State<>((AbstractRow<R>) f.getDataType().getRow(), (Class<R>) f.getDataType().getRecordType());
s = new State<>(ctx, (AbstractRow<R>) f.getDataType().getRow(), (Class<R>) f.getDataType().getRecordType());
s.inResult = true;
}
else
@ -207,7 +215,7 @@ final class XMLHandler<R extends Record> extends DefaultHandler {
if (f.getDataType().isRecord()) {
states.push(s);
s = new State<>((AbstractRow<R>) f.getDataType().getRow(), (Class<R>) f.getDataType().getRecordType());
s = new State<>(ctx, (AbstractRow<R>) f.getDataType().getRow(), (Class<R>) f.getDataType().getRecordType());
s.inResult = true;
}
else