From ed6d56b3c97dc2c4c03da37ea8067dc75657067c Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Fri, 18 Jun 2021 09:54:38 +0200 Subject: [PATCH] [jOOQ/jOOQ#3884] Support nesting MULTISET This includes: - [jOOQ/jOOQ#10170] Added some missing FOR XML API - [jOOQ/jOOQ#12012] Internal API added - [jOOQ/jOOQ#12020] Result.formatXML() does not correctly format nested records --- jOOQ/src/main/java/org/jooq/JSONFormat.java | 47 +++++- .../SelectForXMLCommonDirectivesStep.java | 8 + .../jooq/SelectForXMLPathDirectivesStep.java | 16 ++ .../jooq/SelectForXMLRawDirectivesStep.java | 16 ++ jOOQ/src/main/java/org/jooq/SelectQuery.java | 21 +++ .../java/org/jooq/impl/AbstractResult.java | 68 +++++--- jOOQ/src/main/java/org/jooq/impl/ForXML.java | 13 ++ .../src/main/java/org/jooq/impl/Keywords.java | 1 + .../src/main/java/org/jooq/impl/Multiset.java | 1 - .../java/org/jooq/impl/MultisetDataType.java | 158 ++++++++++++++++++ .../main/java/org/jooq/impl/ParserImpl.java | 7 + .../main/java/org/jooq/impl/SelectImpl.java | 30 ++++ .../java/org/jooq/impl/SelectQueryImpl.java | 22 +++ 13 files changed, 380 insertions(+), 28 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/impl/MultisetDataType.java diff --git a/jOOQ/src/main/java/org/jooq/JSONFormat.java b/jOOQ/src/main/java/org/jooq/JSONFormat.java index 03d53d4d7e..a5e01a4dd7 100644 --- a/jOOQ/src/main/java/org/jooq/JSONFormat.java +++ b/jOOQ/src/main/java/org/jooq/JSONFormat.java @@ -73,6 +73,7 @@ public final class JSONFormat { final boolean format; final String newline; + final int globalIndent; final int indent; final String[] indented; final boolean header; @@ -84,6 +85,7 @@ public final class JSONFormat { this( false, "\n", + 0, 2, null, true, @@ -96,6 +98,7 @@ public final class JSONFormat { private JSONFormat( boolean format, String newline, + int globalIndent, int indent, String[] indented, boolean header, @@ -105,6 +108,7 @@ public final class JSONFormat { ) { this.format = format; this.newline = newline; + this.globalIndent = globalIndent; this.indent = indent; this.indented = indented != null ? indented : new String[] { "", @@ -125,6 +129,7 @@ public final class JSONFormat { return new JSONFormat( newFormat, newline, + globalIndent, indent, null, header, @@ -148,6 +153,7 @@ public final class JSONFormat { return new JSONFormat( format, newNewline, + globalIndent, indent, indented, header, @@ -165,12 +171,37 @@ public final class JSONFormat { } /** - * The new indentation value, defaulting to 2. + * The new global indentation size applied on all levels, defaulting to 0. + */ + public final JSONFormat globalIndent(int newGlobalIndent) { + return new JSONFormat( + format, + newline, + newGlobalIndent, + indent, + null, + header, + recordFormat, + wrapSingleColumnRecords, + quoteNested + ); + } + + /** + * The global indentation applied on all levels. + */ + public final int globalIndent() { + return globalIndent; + } + + /** + * The new indentation size per level value, defaulting to 2. */ public final JSONFormat indent(int newIndent) { return new JSONFormat( format, newline, + globalIndent, newIndent, null, header, @@ -181,7 +212,7 @@ public final class JSONFormat { } /** - * The indentation. + * The indentation size per level. */ public final int indent() { return indent; @@ -191,10 +222,12 @@ public final class JSONFormat { * Convenience method to get an indentation string at a given level. */ public final String indentString(int level) { - if (level < indented.length) - return indented[level]; + int i = level + globalIndent / indent; + + if (i < indented.length) + return indented[i]; else if (format) - return rightPad("", indent * level); + return rightPad("", globalIndent + indent * level); else return ""; } @@ -207,6 +240,7 @@ public final class JSONFormat { return new JSONFormat( format, newline, + globalIndent, indent, indented, newHeader, @@ -232,6 +266,7 @@ public final class JSONFormat { return new JSONFormat( format, newline, + globalIndent, indent, indented, header, @@ -256,6 +291,7 @@ public final class JSONFormat { return new JSONFormat( format, newline, + globalIndent, indent, indented, header, @@ -280,6 +316,7 @@ public final class JSONFormat { return new JSONFormat( format, newline, + globalIndent, indent, indented, header, diff --git a/jOOQ/src/main/java/org/jooq/SelectForXMLCommonDirectivesStep.java b/jOOQ/src/main/java/org/jooq/SelectForXMLCommonDirectivesStep.java index 149ea20014..58756d4b95 100644 --- a/jOOQ/src/main/java/org/jooq/SelectForXMLCommonDirectivesStep.java +++ b/jOOQ/src/main/java/org/jooq/SelectForXMLCommonDirectivesStep.java @@ -121,6 +121,14 @@ import static org.jooq.SQLDialect.POSTGRES; + + + + + + + + diff --git a/jOOQ/src/main/java/org/jooq/SelectForXMLPathDirectivesStep.java b/jOOQ/src/main/java/org/jooq/SelectForXMLPathDirectivesStep.java index c3de861fa0..69e79212dd 100644 --- a/jOOQ/src/main/java/org/jooq/SelectForXMLPathDirectivesStep.java +++ b/jOOQ/src/main/java/org/jooq/SelectForXMLPathDirectivesStep.java @@ -101,6 +101,22 @@ import org.jetbrains.annotations.*; + + + + + + + + + + + + + + + + diff --git a/jOOQ/src/main/java/org/jooq/SelectForXMLRawDirectivesStep.java b/jOOQ/src/main/java/org/jooq/SelectForXMLRawDirectivesStep.java index c1ab312cef..dd86c6e7ac 100644 --- a/jOOQ/src/main/java/org/jooq/SelectForXMLRawDirectivesStep.java +++ b/jOOQ/src/main/java/org/jooq/SelectForXMLRawDirectivesStep.java @@ -106,6 +106,22 @@ import static org.jooq.SQLDialect.POSTGRES; + + + + + + + + + + + + + + + + diff --git a/jOOQ/src/main/java/org/jooq/SelectQuery.java b/jOOQ/src/main/java/org/jooq/SelectQuery.java index 43d79ccbfe..d1156534d0 100644 --- a/jOOQ/src/main/java/org/jooq/SelectQuery.java +++ b/jOOQ/src/main/java/org/jooq/SelectQuery.java @@ -1305,6 +1305,27 @@ public interface SelectQuery extends Select, ConditionProvi + + + + + + + + + + + + + + + + + + + + + diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractResult.java b/jOOQ/src/main/java/org/jooq/impl/AbstractResult.java index 833346f834..a943914cb0 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractResult.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractResult.java @@ -461,6 +461,7 @@ abstract class AbstractResult extends AbstractFormattable impl try { String separator; int recordLevel = format.header() ? 2 : 1; + boolean hasRecords = false; if (format.header()) { if (format.format()) @@ -552,6 +553,7 @@ abstract class AbstractResult extends AbstractFormattable impl switch (format.recordFormat()) { case ARRAY: for (Record record : this) { + hasRecords = true; writer.append(separator); if (format.format()) @@ -564,6 +566,7 @@ abstract class AbstractResult extends AbstractFormattable impl break; case OBJECT: for (Record record : this) { + hasRecords = true; writer.append(separator); if (format.format()) @@ -578,17 +581,23 @@ abstract class AbstractResult extends AbstractFormattable impl throw new IllegalArgumentException("Format not supported: " + format); } - if (format.format()) { + if (format.format() && hasRecords) { writer.append(format.newline()); if (format.header()) writer.append(format.indentString(1)); + else + writer.append(format.indentString(0)); } writer.append(']'); - if (format.header()) - writer.append(format.newline()).append('}'); + if (format.header()) { + if (format.format()) + writer.append(format.newline()).append(format.indentString(0)); + + writer.append('}'); + } writer.flush(); } @@ -647,17 +656,20 @@ abstract class AbstractResult extends AbstractFormattable impl int size = fields.size(); boolean wrapRecords = format.wrapSingleColumnRecords() || size > 1; + if (format.format()) + writer.append(format.indentString(recordLevel)); + if (wrapRecords) - if (format.format()) - writer.append(format.indentString(recordLevel)).append('{'); - else - writer.append('{'); + writer.append('{'); for (int index = 0; index < size; index++) { writer.append(separator); if (format.format()) - writer.append(format.newline()).append(format.indentString(recordLevel + 1)); + if (size > 1) + writer.append(format.newline()).append(format.indentString(recordLevel + 1)); + else if (format.wrapSingleColumnRecords()) + writer.append(' '); if (wrapRecords) { JSONValue.writeJSONString(record.field(index).getName(), writer); @@ -667,12 +679,16 @@ abstract class AbstractResult extends AbstractFormattable impl writer.append(' '); } - formatJSON0(record.get(index), writer, format); + formatJSON0(record.get(index), writer, format.globalIndent(format.globalIndent() + format.indent() * (recordLevel + 1))); + + if (format.format() && format.wrapSingleColumnRecords() && size == 1) + writer.append(' '); + separator = ","; } if (wrapRecords) - if (format.format()) + if (format.format() && size > 1) writer.append(format.newline()).append(format.indentString(recordLevel)).append('}'); else writer.append('}'); @@ -686,26 +702,34 @@ abstract class AbstractResult extends AbstractFormattable impl Writer writer ) throws java.io.IOException { String separator = ""; - int size = fields.size(); - if (format.wrapSingleColumnRecords() || size > 1) - if (format.format()) - writer.append(format.indentString(recordLevel)).append('['); - else - writer.append('['); + boolean wrapRecords = format.wrapSingleColumnRecords() || size > 1; + + if (format.format()) + writer.append(format.indentString(recordLevel)); + + if (wrapRecords) + writer.append('['); for (int index = 0; index < size; index++) { writer.append(separator); if (format.format()) - writer.append(format.newline()).append(format.indentString(recordLevel + 1)); + if (size > 1) + writer.append(format.newline()).append(format.indentString(recordLevel + 1)); + else if (format.wrapSingleColumnRecords()) + writer.append(' '); + + formatJSON0(record.get(index), writer, format.globalIndent(format.globalIndent() + format.indent() * (recordLevel + 1))); + + if (format.format() && format.wrapSingleColumnRecords() && size == 1) + writer.append(' '); - formatJSON0(record.get(index), writer, format); separator = ","; } - if (format.wrapSingleColumnRecords() || size > 1) - if (format.format()) + if (wrapRecords) + if (format.format() && size > 1) writer.append(format.newline()).append(format.indentString(recordLevel)).append(']'); else writer.append(']'); @@ -771,7 +795,7 @@ abstract class AbstractResult extends AbstractFormattable impl if (format.header()) writer.append(newline).append(format.indentString(1)).append(""); - writer.append(newline).append(""); + writer.append(newline).append(format.indentString(0)).append(""); writer.flush(); } catch (java.io.IOException e) { @@ -818,7 +842,7 @@ abstract class AbstractResult extends AbstractFormattable impl if (value instanceof Formattable) { writer.append(newline).append(format.indentString(recordLevel + 2)); - ((Formattable) value).formatXML(writer, format.globalIndent(format.indent() * (recordLevel + 2))); + ((Formattable) value).formatXML(writer, format.globalIndent(format.globalIndent() + format.indent() * (recordLevel + 2))); writer.append(newline).append(format.indentString(recordLevel + 1)); } else if (value instanceof XML && !format.quoteNested()) diff --git a/jOOQ/src/main/java/org/jooq/impl/ForXML.java b/jOOQ/src/main/java/org/jooq/impl/ForXML.java index 7b0dded612..d856dc4531 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ForXML.java +++ b/jOOQ/src/main/java/org/jooq/impl/ForXML.java @@ -93,6 +93,19 @@ package org.jooq.impl; + + + + + + + + + + + + + diff --git a/jOOQ/src/main/java/org/jooq/impl/Keywords.java b/jOOQ/src/main/java/org/jooq/impl/Keywords.java index c083618d61..36358d4abc 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Keywords.java +++ b/jOOQ/src/main/java/org/jooq/impl/Keywords.java @@ -452,6 +452,7 @@ final class Keywords { static final Keyword K_XML = keyword("xml"); static final Keyword K_XMLEXISTS = keyword("xmlexists"); static final Keyword K_XMLTABLE = keyword("xmltable"); + static final Keyword K_XSINIL = keyword("xsinil"); static final Keyword K_YEAR = keyword("year"); static final Keyword K_YEAR_MONTH = keyword("year_month"); static final Keyword K_YEAR_TO_DAY = keyword("year to day"); diff --git a/jOOQ/src/main/java/org/jooq/impl/Multiset.java b/jOOQ/src/main/java/org/jooq/impl/Multiset.java index 07445d5884..616e05fda5 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Multiset.java +++ b/jOOQ/src/main/java/org/jooq/impl/Multiset.java @@ -77,7 +77,6 @@ final class Multiset extends AbstractField> { this.select = select; } - @SuppressWarnings("unchecked") @Override public final void accept(Context ctx) { switch (emulateMultiset(ctx.configuration())) { diff --git a/jOOQ/src/main/java/org/jooq/impl/MultisetDataType.java b/jOOQ/src/main/java/org/jooq/impl/MultisetDataType.java new file mode 100644 index 0000000000..a8fec74240 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/MultisetDataType.java @@ -0,0 +1,158 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +import static org.jooq.impl.Tools.CTX; +import static org.jooq.impl.Tools.newRecord; +import static org.jooq.impl.Tools.recordType; +import static org.jooq.impl.Tools.row0; + +import java.util.List; + +import org.jooq.CharacterSet; +import org.jooq.Collation; +import org.jooq.Field; +import org.jooq.Nullability; +import org.jooq.Record; +import org.jooq.Result; +import org.jooq.Row; +import org.jooq.Select; +import org.jooq.impl.AbstractRecord.TransferRecordState; + +/** + * A wrapper for anonymous multiset data types. + * + * @author Lukas Eder + */ +final class MultisetDataType extends DefaultDataType> { + + final AbstractRow row; + final Class recordType; + + @SuppressWarnings("unchecked") + public MultisetDataType(AbstractRow row, Class recordType) { + // [#11829] TODO: Implement this correctly for ArrayRecord + super(null, (Class) Result.class, "multiset", "multiset"); + + this.row = row; + this.recordType = recordType; + } + + /** + * [#3225] Performant constructor for creating derived types. + */ + MultisetDataType( + DefaultDataType> t, + AbstractRow row, + Class recordType, + Integer precision, + Integer scale, + Integer length, + Nullability nullability, + Collation collation, + CharacterSet characterSet, + boolean identity, + Field> defaultValue + ) { + super(t, precision, scale, length, nullability, collation, characterSet, identity, defaultValue); + + this.row = row; + this.recordType = recordType; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + DefaultDataType> construct( + Integer newPrecision, + Integer newScale, + Integer newLength, + Nullability + newNullability, + Collation newCollation, + CharacterSet newCharacterSet, + boolean newIdentity, + Field> newDefaultValue + ) { + return new MultisetDataType<>( + this, + row, + recordType, + newPrecision, + newScale, + newLength, + newNullability, + newCollation, + newCharacterSet, + newIdentity, + (Field) newDefaultValue + ); + } + + @Override + public final Row getRow() { + return row; + } + + @Override + public Result convert(Object object) { + + // [#3884] TODO: Move this logic into JSONReader to make it more generally useful + if (object instanceof List) { + ResultImpl result = new ResultImpl<>(CTX.configuration(), row); + + for (Object record : (List) object) + result.add(newRecord(true, recordType, row, CTX.configuration()) + .operate(r -> { + + // [#12014] TODO: Fix this and remove workaround + if (record instanceof Record) + ((AbstractRecord) r).from((Record) record); + else + r.from(record); + + return r; + })); + + return result; + } + else if (object == null) + return new ResultImpl<>(CTX.configuration(), row); + else + return super.convert(object); + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java index 55d57b8bf1..7fa4b8c5ee 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java @@ -1017,6 +1017,13 @@ final class DefaultParseContext extends AbstractScope implements ParseContext { + + + + + + + diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectImpl.java index 51500d8efa..3add0f700d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectImpl.java @@ -1947,6 +1947,36 @@ implements + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java index 800687d9ff..616517db1a 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java @@ -1524,6 +1524,13 @@ final class SelectQueryImpl extends AbstractResultQuery imp + + + + + + + @@ -3575,6 +3582,21 @@ final class SelectQueryImpl extends AbstractResultQuery imp + + + + + + + + + + + + + + +