[jOOQ/jOOQ#11812] Support projecting ROW() in RETURNING

This commit is contained in:
Lukas Eder 2021-04-30 11:28:29 +02:00
parent b69eea96ab
commit 7352791108
6 changed files with 116 additions and 84 deletions

View File

@ -141,6 +141,7 @@ import org.jooq.Param;
import org.jooq.QualifiedAsterisk;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.Row;
import org.jooq.SQLDialect;
import org.jooq.Scope;
import org.jooq.Select;
@ -236,8 +237,10 @@ abstract class AbstractDMLQuery<R extends Record> extends AbstractRowCountQuery
returningResolvedAsterisks.addAll(Arrays.asList(((QualifiedAsterisk) f).qualifier().fields()));
else if (f instanceof Asterisk)
returningResolvedAsterisks.addAll(Arrays.asList(table.fields()));
else if (f instanceof Row)
returningResolvedAsterisks.add(new RowField<>((Row) f));
else
throw new AssertionError("Type not supported: " + f);
throw new UnsupportedOperationException("Type not supported: " + f);
}
// @Override
@ -914,7 +917,7 @@ abstract class AbstractDMLQuery<R extends Record> extends AbstractRowCountQuery
// PostgreSQL generated case-insensitive Fields (default to lower case)
// and wants to query HSQLDB (default to upper case), they may choose
// to overwrite casing using RenderNameCase.
ctx.statement(connection.prepareStatement(ctx.sql(), map(Tools.flattenCollection(returningResolvedAsterisks, false),
ctx.statement(connection.prepareStatement(ctx.sql(), map(flattenCollection(returningResolvedAsterisks, false, true),
style == RenderNameCase.UPPER
? f -> f.getName().toUpperCase(renderLocale(configuration().settings()))
: style == RenderNameCase.LOWER

View File

@ -38,6 +38,7 @@
package org.jooq.impl;
// ...
import static org.jooq.impl.RowField.NO_NATIVE_SUPPORT;
import static org.jooq.impl.Tools.embeddedFields;
import static org.jooq.impl.Tools.embeddedRecordType;
import static org.jooq.impl.Tools.recordFactory;
@ -1517,10 +1518,9 @@ final class CursorImpl<R extends Record> extends AbstractCursor<R> {
AbstractRow<?> nested = null;
Class<? extends AbstractRecord> recordType = null;
if (field instanceof RowField) {
if (field instanceof RowField && NO_NATIVE_SUPPORT.contains(ctx.dialect())) {
nested = ((RowField<?, ?>) field).emulatedFields();
// TODO: [#4695] Calculate the correct Record[B] type
recordType = RecordImplN.class;
recordType = Tools.recordType(nested.size());
}
else if (field.getDataType().isEmbeddable()) {
nested = Tools.row0(embeddedFields(field));

View File

@ -518,7 +518,7 @@ final class FieldMapsForInsert extends AbstractQueryPart {
}
// [#989] Avoid qualifying fields in INSERT field declaration
ctx.sql(" (").visit(new QueryPartCollectionView<>(collect(flattenCollection(values.keySet(), true))).qualify(false)).sql(')');
ctx.sql(" (").visit(new QueryPartCollectionView<>(collect(flattenCollection(values.keySet(), true, true))).qualify(false)).sql(')');
}
final Map<Field<?>, List<Field<?>>> valuesFlattened() {

View File

@ -37,19 +37,51 @@
*/
package org.jooq.impl;
import static org.jooq.Converter.fromNullable;
// ...
// ...
// ...
// ...
import static org.jooq.SQLDialect.CUBRID;
// ...
import static org.jooq.SQLDialect.DERBY;
// ...
import static org.jooq.SQLDialect.FIREBIRD;
import static org.jooq.SQLDialect.H2;
// ...
import static org.jooq.SQLDialect.HSQLDB;
import static org.jooq.SQLDialect.IGNITE;
// ...
// ...
import static org.jooq.SQLDialect.MARIADB;
// ...
import static org.jooq.SQLDialect.MYSQL;
// ...
// ...
// ...
// ...
import static org.jooq.SQLDialect.SQLITE;
// ...
// ...
// ...
// ...
import static org.jooq.impl.DefaultBinding.binding;
import static org.jooq.impl.DefaultBinding.DefaultRecordBinding.pgNewRecord;
import static org.jooq.impl.Keywords.K_ROW;
import static org.jooq.impl.Names.N_ROW;
import static org.jooq.impl.Tools.map;
import static org.jooq.impl.Tools.row0;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_LIST_ALREADY_INDENTED;
import java.util.Set;
import org.jooq.Context;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.Name;
import org.jooq.Record;
import org.jooq.Row;
import org.jooq.SQLDialect;
/**
* @author Lukas Eder
@ -59,7 +91,9 @@ final class RowField<ROW extends Row, REC extends Record> extends AbstractField<
/**
* Generated UID
*/
private static final long serialVersionUID = -2065258332642911588L;
private static final long serialVersionUID = -2065258332642911588L;
static final Set<SQLDialect> NO_NATIVE_SUPPORT = SQLDialect.supportedBy(CUBRID, DERBY, FIREBIRD, H2, HSQLDB, IGNITE, MARIADB, MYSQL, SQLITE);
private final ROW row;
private final AbstractRow<REC> emulatedFields;
@ -68,22 +102,12 @@ final class RowField<ROW extends Row, REC extends Record> extends AbstractField<
this(row, N_ROW);
}
@SuppressWarnings({ "serial", "unchecked", "rawtypes" })
@SuppressWarnings({ "unchecked", "rawtypes" })
RowField(final ROW row, Name as) {
super(as, (DataType) SQLDataType.RECORD, CommentImpl.NO_COMMENT, binding(new AbstractConverter<Object, REC>(
Object.class, (Class<REC>) Tools.recordType(row.size())
) {
@Override
public REC from(final Object t) {
// So far, this is only supported for PostgreSQL
return (REC) (t == null ? null : pgNewRecord(Record.class, (AbstractRow) row, t));
}
@Override
public Object to(REC u) {
throw new UnsupportedOperationException("Converting from nested records to bind values is not yet supported");
}
}));
super(as, (DataType) SQLDataType.RECORD, CommentImpl.NO_COMMENT, binding(fromNullable(
// So far, this is only supported for PostgreSQL
Object.class, (Class<REC>) Tools.recordType(row.size()), t -> (REC) pgNewRecord(Record.class, (AbstractRow) row, t)
)));
this.row = row;
this.emulatedFields = (AbstractRow<REC>) row0(map(row.fields(), x -> x.as(as + "." + x.getName()), Field[]::new));
@ -93,10 +117,25 @@ final class RowField<ROW extends Row, REC extends Record> extends AbstractField<
return emulatedFields;
}
ROW row() {
return row;
}
@Override
public final void accept(Context<?> ctx) {
if (ctx.declareFields())
ctx.data(DATA_LIST_ALREADY_INDENTED, true, c -> c.visit(new SelectFieldList<>(emulatedFields.fields.fields)));
if (ctx.declareFields()) {
if (NO_NATIVE_SUPPORT.contains(ctx.dialect()))
ctx.data(DATA_LIST_ALREADY_INDENTED, true, c -> c.visit(new SelectFieldList<>(emulatedFields.fields.fields)));
// [#11812] RowField is mainly used for projections, in case of which an
// explicit ROW keyword helps disambiguate (1) from ROW(1)
else
ctx.visit(K_ROW).sql(' ').visit(row);
}
else
ctx.visit(row);
}

View File

@ -3660,8 +3660,6 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp
for (SelectFieldOrAsterisk f : list)
if (f instanceof Field<?>)
result.add(getResolveProjection(c, (Field<?>) f));
else if (f instanceof Row)
result.add(getResolveProjection(c, new RowField<Row, Record>((Row) f)));
else if (f instanceof QualifiedAsterisk)
if (((QualifiedAsteriskImpl) f).fields.isEmpty())
if (resolveSupported)
@ -3682,6 +3680,8 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> imp
result.addAll(resolveAsterisk(new QueryPartList<>(), ((AsteriskImpl) f).fields));
else
result.add(f);
else if (f instanceof Row)
result.add(getResolveProjection(c, new RowField<Row, Record>((Row) f)));
else
throw new AssertionError("Type not supported: " + f);

View File

@ -5162,13 +5162,13 @@ final class Tools {
static final SelectFieldOrAsterisk qualify(Table<?> table, SelectFieldOrAsterisk field) {
if (field instanceof Field)
return qualify(table, (Field<?>) field);
// [#11812] TODO: handle field instanceof Row
else if (field instanceof Asterisk)
return table.asterisk();
else if (field instanceof QualifiedAsterisk)
return table.asterisk();
// [#11812] TODO: handle field instanceof Row
else
throw new IllegalArgumentException("Unsupported field : " + field);
throw new UnsupportedOperationException("Unsupported field : " + field);
}
static final <T> Field<T> qualify(Table<?> table, Field<T> field) {
@ -5387,7 +5387,7 @@ final class Tools {
static final Row embeddedFieldsRow(Row row) {
if (hasEmbeddedFields(row.fields()))
return row(map(flattenCollection(Arrays.asList(row.fields()), false), f -> f));
return row(map(flattenCollection(Arrays.asList(row.fields()), false, false), f -> f));
else
return row;
}
@ -5400,7 +5400,7 @@ final class Tools {
// [#8353] [#10522] [#10523] TODO: Factor out some of this logic and
// reuse it for the emulation of UPDATE .. SET row = (SELECT ..)
List<Field<?>> select = field.query.getSelect();
List<Field<?>> result = collect(flattenCollection(select, false));
List<Field<?>> result = collect(flattenCollection(select, false, false));
Name tableName = name("t");
Name[] fieldNames = fieldNames(result.size());
Table<?> t = new AliasedSelect<>(field.query, true, fieldNames).as("t");
@ -5449,12 +5449,7 @@ final class Tools {
// [#11729] Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=572873
Iterator<E> it2 = field.getDataType().isEmbeddable()
? new FlatteningIterator<E>(it1) {
@Override
List<E> flatten(E e) {
return (List<E>) Arrays.asList(embeddedFields(field));
}
}
? new FlatteningIterator<E>(it1, (e, duplicates) -> (List<E>) Arrays.asList(embeddedFields(field)))
: it1;
return () -> it2;
@ -5464,20 +5459,13 @@ final class Tools {
* Flatten out {@link EmbeddableTableField} elements contained in an
* ordinary iterable.
*/
static final <E extends Field<?>> Iterable<E> flattenCollection(
final Iterable<E> iterable,
final boolean removeDuplicates
static final Iterable<Field<?>> flattenCollection(
final Iterable<? extends Field<?>> iterable,
final boolean removeDuplicates,
final boolean flattenRowFields
) {
// [#2530] [#6124] [#10481] TODO: Refactor and optimise these flattening algorithms
// [#11729] Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=572873
Iterator<E> it = new FlatteningIterator<E>(iterable.iterator()) {
@SuppressWarnings("unchecked")
@Override
Iterable<E> flatten(E e) {
return () -> new FlatteningIterator<>(iterable.iterator(), (e, duplicates) -> {
@ -5490,11 +5478,20 @@ final class Tools {
return Tools.flatten(e);
// TODO [#10525] Should embedded records be emulated as RowField?
if (flattenRowFields && e instanceof RowField) {
List<Field<?>> result = new ArrayList<>();
for (Field<?> field : ((RowField<?, ?>) e).row().fields())
if (duplicates.test(field))
result.add(field);
return result;
}
};
return () -> it;
return Tools.flatten(e);
});
}
/**
@ -5502,41 +5499,31 @@ final class Tools {
* set iterable, making sure no duplicate keys resulting from overlapping
* embeddables will be produced.
*/
static final <E extends Entry<Field<?>, Field<?>>> Iterable<E> flattenEntrySet(
final Iterable<E> iterable,
static final Iterable<Entry<Field<?>, Field<?>>> flattenEntrySet(
final Iterable<Entry<Field<?>, Field<?>>> iterable,
final boolean removeDuplicates
) {
// [#2530] [#6124] [#10481] TODO: Refactor and optimise these flattening algorithms
// [#11729] Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=572873
Iterator<E> it = new FlatteningIterator<E>(iterable.iterator()) {
return () -> new FlatteningIterator<>(iterable.iterator(), (e, duplicates) -> {
if (e.getKey() instanceof EmbeddableTableField) {
List<Entry<Field<?>, Field<?>>> result = new ArrayList<>();
Field<?>[] keys = embeddedFields(e.getKey());
Field<?>[] values = embeddedFields(e.getValue());
for (int i = 0; i < keys.length; i++)
result.add(new SimpleImmutableEntry<Field<?>, Field<?>>(
keys[i], values[i]
));
@SuppressWarnings("unchecked")
@Override
List<E> flatten(E e) {
if (e.getKey() instanceof EmbeddableTableField) {
List<E> result = new ArrayList<>();
Field<?>[] keys = embeddedFields(e.getKey());
Field<?>[] values = embeddedFields(e.getValue());
for (int i = 0; i < keys.length; i++)
result.add((E) new SimpleImmutableEntry<Field<?>, Field<?>>(
keys[i], values[i]
));
return result;
}
return null;
return result;
}
};
return () -> it;
return null;
});
}
static final <T> Set<T> lazy(Set<T> set) {
@ -5548,17 +5535,20 @@ final class Tools {
* iterators with a default implementation for {@link Iterator#remove()} for
* convenience in the Java 6 build.
*/
static abstract class FlatteningIterator<E> implements Iterator<E> {
private final Iterator<E> delegate;
private Iterator<E> flatten;
private E next;
static final class FlatteningIterator<E> implements Iterator<E> {
private final Iterator<? extends E> delegate;
private final BiFunction<? super E, Predicate<Object>, ? extends Iterable<E>> flattener;
private final Predicate<Object> checkDuplicates;
private Iterator<E> flatten;
private E next;
private Set<Object> duplicates;
FlatteningIterator(Iterator<E> delegate) {
FlatteningIterator(Iterator<? extends E> delegate, BiFunction<? super E, Predicate<Object>, ? extends Iterable<E>> flattener) {
this.delegate = delegate;
this.flattener = flattener;
this.checkDuplicates = e -> (duplicates = lazy(duplicates)).add(e);
}
abstract Iterable<E> flatten(E e);
private final void move() {
if (next == null) {
if (flatten != null) {
@ -5574,7 +5564,7 @@ final class Tools {
if (delegate.hasNext()) {
next = delegate.next();
Iterable<E> flattened = flatten(next);
Iterable<E> flattened = flattener.apply(next, checkDuplicates);
if (flattened == null)
return;