[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
This commit is contained in:
Lukas Eder 2021-06-18 09:54:38 +02:00
parent 2648781a47
commit ed6d56b3c9
13 changed files with 380 additions and 28 deletions

View File

@ -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 <code>2</code>.
* The new global indentation size applied on all levels, defaulting to <code>0</code>.
*/
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 <code>2</code>.
*/
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,

View File

@ -121,6 +121,14 @@ import static org.jooq.SQLDialect.POSTGRES;

View File

@ -101,6 +101,22 @@ import org.jetbrains.annotations.*;

View File

@ -106,6 +106,22 @@ import static org.jooq.SQLDialect.POSTGRES;

View File

@ -1305,6 +1305,27 @@ public interface SelectQuery<R extends Record> extends Select<R>, ConditionProvi

View File

@ -461,6 +461,7 @@ abstract class AbstractResult<R extends Record> 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<R extends Record> 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<R extends Record> 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<R extends Record> 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<R extends Record> 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<R extends Record> 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<R extends Record> 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<R extends Record> extends AbstractFormattable impl
if (format.header())
writer.append(newline).append(format.indentString(1)).append("</records>");
writer.append(newline).append("</result>");
writer.append(newline).append(format.indentString(0)).append("</result>");
writer.flush();
}
catch (java.io.IOException e) {
@ -818,7 +842,7 @@ abstract class AbstractResult<R extends Record> 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())

View File

@ -93,6 +93,19 @@ package org.jooq.impl;

View File

@ -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");

View File

@ -77,7 +77,6 @@ final class Multiset<R extends Record> extends AbstractField<Result<R>> {
this.select = select;
}
@SuppressWarnings("unchecked")
@Override
public final void accept(Context<?> ctx) {
switch (emulateMultiset(ctx.configuration())) {

View File

@ -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<R extends Record> extends DefaultDataType<Result<R>> {
final AbstractRow<R> row;
final Class<? extends R> recordType;
@SuppressWarnings("unchecked")
public MultisetDataType(AbstractRow<R> row, Class<? extends R> 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<Result<R>> t,
AbstractRow<R> row,
Class<? extends R> recordType,
Integer precision,
Integer scale,
Integer length,
Nullability nullability,
Collation collation,
CharacterSet characterSet,
boolean identity,
Field<Result<R>> defaultValue
) {
super(t, precision, scale, length, nullability, collation, characterSet, identity, defaultValue);
this.row = row;
this.recordType = recordType;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
DefaultDataType<Result<R>> construct(
Integer newPrecision,
Integer newScale,
Integer newLength,
Nullability
newNullability,
Collation newCollation,
CharacterSet newCharacterSet,
boolean newIdentity,
Field<Result<R>> 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<R> convert(Object object) {
// [#3884] TODO: Move this logic into JSONReader to make it more generally useful
if (object instanceof List) {
ResultImpl<R> 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);
}
}

View File

@ -1017,6 +1017,13 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {

View File

@ -1947,6 +1947,36 @@ implements

View File

@ -1524,6 +1524,13 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp
@ -3575,6 +3582,21 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp