From 061e4253400a8470e42b9bfdd04e6a939cac1b77 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Fri, 14 Aug 2020 17:13:35 +0200 Subject: [PATCH] [jOOQ/jOOQ#2530] [jOOQ/jOOQ#6124] [jOOQ/jOOQ#10481] Support overlapping embeddables --- .../java/org/jooq/codegen/JavaGenerator.java | 2 - .../java/org/jooq/meta/ColumnDefinition.java | 8 --- .../jooq/meta/DefaultColumnDefinition.java | 18 +---- .../java/org/jooq/impl/FieldMapForUpdate.java | 2 +- .../org/jooq/impl/FieldMapsForInsert.java | 57 ++++++++++++++-- .../java/org/jooq/impl/InsertQueryImpl.java | 1 + jOOQ/src/main/java/org/jooq/impl/Tools.java | 68 ++++++++++++++++--- 7 files changed, 113 insertions(+), 43 deletions(-) diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java index 28f62d5f72..a069eb0161 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java @@ -1057,8 +1057,6 @@ public class JavaGenerator extends AbstractGenerator { - - diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/ColumnDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/ColumnDefinition.java index 4d9eaaa2bb..aa57ea1285 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/ColumnDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/ColumnDefinition.java @@ -88,12 +88,4 @@ public interface ColumnDefinition extends TypedElementDefinition containedInEmbeddables; - private transient EmbeddableDefinition replacedByEmbeddable; + private transient List replacedByEmbeddables; public DefaultColumnDefinition(TableDefinition table, String name, int position, DataTypeDefinition type, boolean isIdentity, String comment) { @@ -124,20 +122,6 @@ public class DefaultColumnDefinition - - - - - - - - - - - - - - diff --git a/jOOQ/src/main/java/org/jooq/impl/FieldMapForUpdate.java b/jOOQ/src/main/java/org/jooq/impl/FieldMapForUpdate.java index b241f22746..44091ba2a7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FieldMapForUpdate.java +++ b/jOOQ/src/main/java/org/jooq/impl/FieldMapForUpdate.java @@ -101,7 +101,7 @@ final class FieldMapForUpdate extends AbstractQueryPartMap, Field> { if (!CASTS_NEEDED.contains(ctx.dialect())) ctx.castMode(CastMode.NEVER); - for (Entry, Field> entry : flattenEntrySet(entrySet())) { + for (Entry, Field> entry : flattenEntrySet(entrySet(), true)) { if (!"".equals(separator)) ctx.sql(separator) .formatSeparator(); diff --git a/jOOQ/src/main/java/org/jooq/impl/FieldMapsForInsert.java b/jOOQ/src/main/java/org/jooq/impl/FieldMapsForInsert.java index ff9e596960..4a95416471 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FieldMapsForInsert.java +++ b/jOOQ/src/main/java/org/jooq/impl/FieldMapsForInsert.java @@ -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> 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, List>> values; int rows; int nextRow = -1; @@ -225,10 +233,11 @@ final class FieldMapsForInsert extends AbstractQueryPart { final Select insertSelect() { Select select = null; + Map, List>> v = valuesFlattened(); for (int row = 0; row < rows; row++) { - List> fields = new ArrayList<>(values.size()); + List> fields = new ArrayList<>(v.size()); - for (List> list : values.values()) + for (List> list : v.values()) fields.add(list.get(row)); Select iteration = DSL.select(fields); @@ -269,7 +278,7 @@ final class FieldMapsForInsert extends AbstractQueryPart { String separator = ""; int i = 0; - for (List> list : values.values()) { + for (List> 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, List>> valuesFlattened() { + Map, List>> 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> overlapping = null; + for (Entry, List>> entry : values.entrySet()) { + if (isEmbeddable(entry.getKey())) { + List>> 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> list = new ArrayList<>(entry.getValue().size()); + + for (Iterator> v : value) + if (v.hasNext()) + list.add(v.next()); + + result.put(key, list); + } + else + for (Iterator> v : value) + if (v.hasNext()) + v.next(); + } + } + else + result.put(entry.getKey(), entry.getValue()); + } + + return result; + } } diff --git a/jOOQ/src/main/java/org/jooq/impl/InsertQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/InsertQueryImpl.java index a88c2722fe..a09b898d8d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/InsertQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/InsertQueryImpl.java @@ -668,6 +668,7 @@ final class InsertQueryImpl extends AbstractStoreQuery impl + // [#8353] TODO: Support overlapping embeddables ctx.formatSeparator() .start(INSERT_SELECT) .visit(select) diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 44b6c1e009..530962f7f9 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -5358,10 +5358,23 @@ final class Tools { } } + static final List collect(Iterable iterable) { + if (iterable instanceof List) + return (List) iterable; + + List result = new ArrayList<>(); + for (E e : iterable) + result.add(e); + + return result; + } + /** * Flatten out an {@link EmbeddableTableField}. */ + @SuppressWarnings("unchecked") static final > Iterable flatten(final E field) { + // [#2530] [#6124] [#10481] TODO: Refactor and optimise these flattening algorithms return new Iterable() { @Override public Iterator iterator() { @@ -5369,12 +5382,18 @@ final class Tools { if (field instanceof EmbeddableTableField) return new FlatteningIterator(it) { - @SuppressWarnings("unchecked") @Override List flatten(E e) { return (List) Arrays.asList(((EmbeddableTableField) e).fields); } }; + else if (field instanceof Val && ((Val) field).getValue() instanceof EmbeddableRecord) + return new FlatteningIterator(it) { + @Override + List flatten(E e) { + return (List) 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 > Iterable flattenCollection(final Iterable iterable) { + static final > Iterable flattenCollection( + final Iterable iterable, + final boolean removeDuplicates + ) { + // [#2530] [#6124] [#10481] TODO: Refactor and optimise these flattening algorithms return new Iterable() { @Override public Iterator iterator() { return new FlatteningIterator(iterable.iterator()) { + Set> overlapping = null; + @SuppressWarnings("unchecked") @Override List flatten(E e) { - if (e instanceof EmbeddableTableField) - return (List) Arrays.asList(((EmbeddableTableField) e).fields); + if (e instanceof EmbeddableTableField) { + if (removeDuplicates) { + List result = new ArrayList<>(); + + for (Field field : ((EmbeddableTableField) e).fields) + if ((overlapping = lazy(overlapping)).add(field)) + result.add((E) field); + + return result; + } + else + return (List) 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 , Field>> Iterable flattenEntrySet(final Iterable iterable) { + static final , Field>> Iterable flattenEntrySet( + final Iterable iterable, + final boolean removeDuplicates + ) { + // [#2530] [#6124] [#10481] TODO: Refactor and optimise these flattening algorithms return new Iterable() { @Override public Iterator iterator() { return new FlatteningIterator(iterable.iterator()) { + Set> overlapping = null; + @SuppressWarnings("unchecked") @Override List 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>( - keys[i], values[i] - )); + if (!removeDuplicates || (overlapping = lazy(overlapping)).add(keys[i])) + result.add((E) new SimpleImmutableEntry, Field>( + keys[i], values[i] + )); return result; } @@ -5435,6 +5479,10 @@ final class Tools { }; } + static final Set lazy(Set set) { + return set == null ? new HashSet<>() : set; + } + /** * A base implementation for {@link EmbeddableTableField} flattening * iterators with a default implementation for {@link Iterator#remove()} for