[jOOQ/jOOQ#11812] Support projecting ROW() in RETURNING
This commit is contained in:
parent
b69eea96ab
commit
7352791108
@ -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
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user