Support overlapping embeddables
This commit is contained in:
Lukas Eder 2020-08-14 17:13:35 +02:00
parent e90a9abd76
commit 061e425340
7 changed files with 113 additions and 43 deletions

View File

@ -1057,8 +1057,6 @@ public class JavaGenerator extends AbstractGenerator {

View File

@ -88,12 +88,4 @@ public interface ColumnDefinition extends TypedElementDefinition<TableDefinition
}

View File

@ -41,7 +41,6 @@ package org.jooq.meta;
import static java.util.Collections.singletonList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jooq.tools.JooqLogger;
@ -58,8 +57,7 @@ public class DefaultColumnDefinition
private static final JooqLogger log = JooqLogger.getLogger(DefaultColumnDefinition.class);
private final int position;
private final boolean isIdentity;
private transient List<EmbeddableDefinition> containedInEmbeddables;
private transient EmbeddableDefinition replacedByEmbeddable;
private transient List<EmbeddableDefinition> replacedByEmbeddables;
public DefaultColumnDefinition(TableDefinition table, String name, int position, DataTypeDefinition type,
boolean isIdentity, String comment) {
@ -124,20 +122,6 @@ public class DefaultColumnDefinition

View File

@ -101,7 +101,7 @@ final class FieldMapForUpdate extends AbstractQueryPartMap<Field<?>, Field<?>> {
if (!CASTS_NEEDED.contains(ctx.dialect()))
ctx.castMode(CastMode.NEVER);
for (Entry<Field<?>, Field<?>> entry : flattenEntrySet(entrySet())) {
for (Entry<Field<?>, Field<?>> entry : flattenEntrySet(entrySet(), true)) {
if (!"".equals(separator))
ctx.sql(separator)
.formatSeparator();

View File

@ -46,6 +46,11 @@ import static org.jooq.SQLDialect.POSTGRES;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.Keywords.K_DEFAULT_VALUES;
import static org.jooq.impl.Keywords.K_VALUES;
import static org.jooq.impl.Tools.collect;
import static org.jooq.impl.Tools.flatten;
import static org.jooq.impl.Tools.flattenCollection;
import static org.jooq.impl.Tools.isEmbeddable;
import static org.jooq.impl.Tools.lazy;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_EMULATE_BULK_INSERT_RETURNING;
import java.util.AbstractList;
@ -86,6 +91,9 @@ final class FieldMapsForInsert extends AbstractQueryPart {
final Table<?> table;
final Map<Field<?>, Field<?>> empty;
// Depending on whether embeddable types are allowed, this data structure
// needs to be flattened with duplicates removed, prior to consumption
// [#2530] [#6124] [#10481] TODO: Refactor and optimise these flattening algorithms
final Map<Field<?>, List<Field<?>>> values;
int rows;
int nextRow = -1;
@ -225,10 +233,11 @@ final class FieldMapsForInsert extends AbstractQueryPart {
final Select<Record> insertSelect() {
Select<Record> select = null;
Map<Field<?>, List<Field<?>>> v = valuesFlattened();
for (int row = 0; row < rows; row++) {
List<Field<?>> fields = new ArrayList<>(values.size());
List<Field<?>> fields = new ArrayList<>(v.size());
for (List<Field<?>> list : values.values())
for (List<Field<?>> list : v.values())
fields.add(list.get(row));
Select<Record> iteration = DSL.select(fields);
@ -269,7 +278,7 @@ final class FieldMapsForInsert extends AbstractQueryPart {
String separator = "";
int i = 0;
for (List<Field<?>> list : values.values()) {
for (List<Field<?>> list : valuesFlattened().values()) {
ctx.sql(separator);
if (indent)
@ -289,6 +298,8 @@ final class FieldMapsForInsert extends AbstractQueryPart {
ctx.visit(list.get(row));
separator = ", ";
}
@ -502,7 +513,7 @@ final class FieldMapsForInsert extends AbstractQueryPart {
return;
// [#2995] Do not generate empty column lists.
if (values.size() == 0)
if (values.isEmpty())
return;
// [#4629] Do not generate column lists for unknown columns
@ -519,9 +530,45 @@ final class FieldMapsForInsert extends AbstractQueryPart {
// [#989] Avoid qualifying fields in INSERT field declaration
boolean qualify = ctx.qualify();
ctx.qualify(false)
.visit(new QueryPartCollectionView<>(values.keySet()))
.visit(new QueryPartCollectionView<>(collect(flattenCollection(values.keySet(), true))))
.qualify(qualify);
ctx.sql(')');
}
final Map<Field<?>, List<Field<?>>> valuesFlattened() {
Map<Field<?>, List<Field<?>>> result = new LinkedHashMap<>();
// [#2530] [#6124] [#10481] TODO: Shortcut for performance, when there are no embeddables
// [#2530] [#6124] [#10481] TODO: Refactor and optimise these flattening algorithms
Set<Field<?>> overlapping = null;
for (Entry<Field<?>, List<Field<?>>> entry : values.entrySet()) {
if (isEmbeddable(entry.getKey())) {
List<Iterator<? extends Field<?>>> value = new ArrayList<>(entry.getValue().size());
for (Field<?> f : entry.getValue())
value.add(flatten(f).iterator());
for (Field<?> key : flatten(entry.getKey())) {
if ((overlapping = lazy(overlapping)).add(key)) {
List<Field<?>> list = new ArrayList<>(entry.getValue().size());
for (Iterator<? extends Field<?>> v : value)
if (v.hasNext())
list.add(v.next());
result.put(key, list);
}
else
for (Iterator<? extends Field<?>> v : value)
if (v.hasNext())
v.next();
}
}
else
result.put(entry.getKey(), entry.getValue());
}
return result;
}
}

View File

@ -668,6 +668,7 @@ final class InsertQueryImpl<R extends Record> extends AbstractStoreQuery<R> impl
// [#8353] TODO: Support overlapping embeddables
ctx.formatSeparator()
.start(INSERT_SELECT)
.visit(select)

View File

@ -5358,10 +5358,23 @@ final class Tools {
}
}
static final <E> List<E> collect(Iterable<E> iterable) {
if (iterable instanceof List)
return (List<E>) iterable;
List<E> result = new ArrayList<>();
for (E e : iterable)
result.add(e);
return result;
}
/**
* Flatten out an {@link EmbeddableTableField}.
*/
@SuppressWarnings("unchecked")
static final <E extends Field<?>> Iterable<E> flatten(final E field) {
// [#2530] [#6124] [#10481] TODO: Refactor and optimise these flattening algorithms
return new Iterable<E>() {
@Override
public Iterator<E> iterator() {
@ -5369,12 +5382,18 @@ final class Tools {
if (field instanceof EmbeddableTableField)
return new FlatteningIterator<E>(it) {
@SuppressWarnings("unchecked")
@Override
List<E> flatten(E e) {
return (List<E>) Arrays.asList(((EmbeddableTableField<?, ?>) e).fields);
}
};
else if (field instanceof Val && ((Val<?>) field).getValue() instanceof EmbeddableRecord)
return new FlatteningIterator<E>(it) {
@Override
List<E> flatten(E e) {
return (List<E>) Arrays.asList(embeddedFields(field));
}
};
else
return it;
}
@ -5385,16 +5404,33 @@ 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) {
static final <E extends Field<?>> Iterable<E> flattenCollection(
final Iterable<E> iterable,
final boolean removeDuplicates
) {
// [#2530] [#6124] [#10481] TODO: Refactor and optimise these flattening algorithms
return new Iterable<E>() {
@Override
public Iterator<E> iterator() {
return new FlatteningIterator<E>(iterable.iterator()) {
Set<Field<?>> overlapping = null;
@SuppressWarnings("unchecked")
@Override
List<E> flatten(E e) {
if (e instanceof EmbeddableTableField)
return (List<E>) Arrays.asList(((EmbeddableTableField<?, ?>) e).fields);
if (e instanceof EmbeddableTableField) {
if (removeDuplicates) {
List<E> result = new ArrayList<>();
for (Field<?> field : ((EmbeddableTableField<?, ?>) e).fields)
if ((overlapping = lazy(overlapping)).add(field))
result.add((E) field);
return result;
}
else
return (List<E>) Arrays.asList(((EmbeddableTableField<?, ?>) e).fields);
}
return null;
}
@ -5404,14 +5440,21 @@ final class Tools {
}
/**
* Flatten out {@link EmbeddableTableField} elements contained in an
* entry set iterable.
* Flatten out {@link EmbeddableTableField} elements contained in an entry
* 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 <E extends Entry<Field<?>, Field<?>>> Iterable<E> flattenEntrySet(
final Iterable<E> iterable,
final boolean removeDuplicates
) {
// [#2530] [#6124] [#10481] TODO: Refactor and optimise these flattening algorithms
return new Iterable<E>() {
@Override
public Iterator<E> iterator() {
return new FlatteningIterator<E>(iterable.iterator()) {
Set<Field<?>> overlapping = null;
@SuppressWarnings("unchecked")
@Override
List<E> flatten(E e) {
@ -5421,9 +5464,10 @@ final class Tools {
Field<?>[] values = embeddedFields(e.getValue());
for (int i = 0; i < keys.length; i++)
result.add((E) new SimpleImmutableEntry<Field<?>, Field<?>>(
keys[i], values[i]
));
if (!removeDuplicates || (overlapping = lazy(overlapping)).add(keys[i]))
result.add((E) new SimpleImmutableEntry<Field<?>, Field<?>>(
keys[i], values[i]
));
return result;
}
@ -5435,6 +5479,10 @@ final class Tools {
};
}
static final <T> Set<T> lazy(Set<T> set) {
return set == null ? new HashSet<>() : set;
}
/**
* A base implementation for {@link EmbeddableTableField} flattening
* iterators with a default implementation for {@link Iterator#remove()} for