From ce4115dac9da519b6b393ee10e17a4928a402bfc Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Thu, 31 Mar 2022 16:50:52 +0200 Subject: [PATCH] [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 --- .../java/org/jooq/impl/JSONEntryImpl.java | 20 +++++++++++- .../main/java/org/jooq/impl/JSONReader.java | 31 +++++++++++++++++-- .../main/java/org/jooq/impl/XMLHandler.java | 16 +++++++--- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONEntryImpl.java b/jOOQ/src/main/java/org/jooq/impl/JSONEntryImpl.java index 426b697349..9e67929196 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JSONEntryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/JSONEntryImpl.java @@ -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 extends AbstractQueryPart implements JSONEntry, 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 extends AbstractQueryPart implements JSONEntry, case SQLITE: if (isType(type, Boolean.class)) return function(N_JSON, SQLDataType.JSON, case_((Field) 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 extends AbstractQueryPart implements JSONEntry, + + + + + + diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONReader.java b/jOOQ/src/main/java/org/jooq/impl/JSONReader.java index 9a97373f1c..14856af949 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JSONReader.java +++ b/jOOQ/src/main/java/org/jooq/impl/JSONReader.java @@ -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 { return result; } + private static final Set ENCODE_BINARY_AS_HEX = SQLDialect.supportedBy(H2, POSTGRES, SQLITE, YUGABYTEDB); + private static final Set ENCODE_BINARY_AS_TEXT = SQLDialect.supportedBy(MARIADB); + private static final List patchRecord(DSLContext ctx, boolean multiset, Fields result, List record) { for (int i = 0; i < result.fields().length; i++) { Field field = result.field(i); @@ -235,9 +249,20 @@ final class JSONReader { 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)); } diff --git a/jOOQ/src/main/java/org/jooq/impl/XMLHandler.java b/jOOQ/src/main/java/org/jooq/impl/XMLHandler.java index f5235868ed..30ed1c8a6e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/XMLHandler.java +++ b/jOOQ/src/main/java/org/jooq/impl/XMLHandler.java @@ -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 extends DefaultHandler { private State s; private static class State { + final DSLContext ctx; AbstractRow row; final Class recordType; boolean inResult; @@ -95,7 +97,8 @@ final class XMLHandler extends DefaultHandler { int column; @SuppressWarnings("unchecked") - State(AbstractRow row, Class recordType) { + State(DSLContext ctx, AbstractRow row, Class recordType) { + this.ctx = ctx; this.row = row; this.recordType = recordType != null ? recordType : (Class) Record.class; this.fields = new ArrayList<>(); @@ -108,6 +111,11 @@ final class XMLHandler 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 extends DefaultHandler { XMLHandler(DSLContext ctx, AbstractRow row, Class recordType) { this.ctx = ctx; this.states = new ArrayDeque<>(); - this.s = new State<>(row, recordType); + this.s = new State<>(ctx, row, recordType); } Result read(String string) { @@ -180,7 +188,7 @@ final class XMLHandler extends DefaultHandler { if (f.getDataType().isMultiset()) { states.push(s); - s = new State<>((AbstractRow) f.getDataType().getRow(), (Class) f.getDataType().getRecordType()); + s = new State<>(ctx, (AbstractRow) f.getDataType().getRow(), (Class) f.getDataType().getRecordType()); s.inResult = true; } else @@ -207,7 +215,7 @@ final class XMLHandler extends DefaultHandler { if (f.getDataType().isRecord()) { states.push(s); - s = new State<>((AbstractRow) f.getDataType().getRow(), (Class) f.getDataType().getRecordType()); + s = new State<>(ctx, (AbstractRow) f.getDataType().getRow(), (Class) f.getDataType().getRecordType()); s.inResult = true; } else