From ad66b4ec3b67cf59478d2bdec5a254e33b8e8661 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Thu, 15 Dec 2022 16:03:31 +0100 Subject: [PATCH] [jOOQ/jOOQ#5799] Add support for the SQL Standard WITH ORDINALITY clause This includes: - [jOOQ/jOOQ#14406] The AutoAlias feature isn't applied from within the JOIN tree, only from the TableList - Update AutoAlias to allow for auto-aliasing other tables than this - Add NoAutoAlias to prevent aliasing in derived column list emulations - Removed TableWithOrdinalityStep again, all Table types are supported - Implement emulations - Added an AbstractAutoAliasTable base implementation for AliasTable - [jOOQ/jOOQ#14409] Refactor Values to implement AutoAlias - [jOOQ/jOOQ#13971] Use DataType::array internally - [jOOQ/jOOQ#14388] Fix data type of ArrayConcat expression --- jOOQ/src/main/java/org/jooq/SQLDialect.java | 6 +- jOOQ/src/main/java/org/jooq/Table.java | 9 + .../org/jooq/impl/AbstractAutoAliasTable.java | 119 ++++++++ .../org/jooq/impl/AbstractRowAsField.java | 8 +- .../java/org/jooq/impl/AbstractTable.java | 6 + jOOQ/src/main/java/org/jooq/impl/Alias.java | 5 +- .../main/java/org/jooq/impl/ArrayAppend.java | 4 +- .../main/java/org/jooq/impl/ArrayConcat.java | 6 +- .../src/main/java/org/jooq/impl/ArrayGet.java | 2 +- .../java/org/jooq/impl/ArrayOfValues.java | 68 ++--- .../main/java/org/jooq/impl/ArrayOverlap.java | 4 +- .../main/java/org/jooq/impl/ArrayPrepend.java | 4 +- .../main/java/org/jooq/impl/ArrayRemove.java | 4 +- .../main/java/org/jooq/impl/ArrayTable.java | 117 ++++---- .../org/jooq/impl/ArrayTableEmulation.java | 91 ++---- .../main/java/org/jooq/impl/AutoAlias.java | 4 +- .../main/java/org/jooq/impl/Cardinality.java | 2 +- jOOQ/src/main/java/org/jooq/impl/Coerce.java | 6 +- jOOQ/src/main/java/org/jooq/impl/DSL.java | 44 +-- .../org/jooq/impl/DataChangeDeltaTable.java | 4 +- .../java/org/jooq/impl/FunctionTable.java | 29 +- .../java/org/jooq/impl/GenerateSeries.java | 6 +- .../main/java/org/jooq/impl/JoinTable.java | 3 +- jOOQ/src/main/java/org/jooq/impl/Names.java | 1 + .../main/java/org/jooq/impl/NoAutoAlias.java | 76 +++++ .../java/org/jooq/impl/OrdinalityTable.java | 266 ++++++++++++++++++ jOOQ/src/main/java/org/jooq/impl/QOM.java | 15 + .../java/org/jooq/impl/SelectFieldList.java | 11 +- .../main/java/org/jooq/impl/TableList.java | 12 +- jOOQ/src/main/java/org/jooq/impl/Tools.java | 65 ++++- jOOQ/src/main/java/org/jooq/impl/Values.java | 110 +++++--- 31 files changed, 807 insertions(+), 300 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/impl/AbstractAutoAliasTable.java create mode 100644 jOOQ/src/main/java/org/jooq/impl/NoAutoAlias.java create mode 100644 jOOQ/src/main/java/org/jooq/impl/OrdinalityTable.java diff --git a/jOOQ/src/main/java/org/jooq/SQLDialect.java b/jOOQ/src/main/java/org/jooq/SQLDialect.java index d94bd9c6e8..b4e9a75c20 100644 --- a/jOOQ/src/main/java/org/jooq/SQLDialect.java +++ b/jOOQ/src/main/java/org/jooq/SQLDialect.java @@ -985,7 +985,7 @@ public enum SQLDialect { for (SQLDialect dialect : dialects) result.addAll(dialect.predecessors()); - return Collections.unmodifiableSet(result); + return result; } /** @@ -1026,7 +1026,7 @@ public enum SQLDialect { public static final Set supportedBy(SQLDialect dialect) { EnumSet result = EnumSet.noneOf(SQLDialect.class); addSupportedBy(dialect, result); - return Collections.unmodifiableSet(result); + return result; } /** @@ -1044,7 +1044,7 @@ public enum SQLDialect { for (SQLDialect dialect : dialects) addSupportedBy(dialect, result); - return Collections.unmodifiableSet(result); + return result; } private static final void addSupportedBy(SQLDialect dialect, EnumSet supported) { diff --git a/jOOQ/src/main/java/org/jooq/Table.java b/jOOQ/src/main/java/org/jooq/Table.java index ae483af983..d643551d69 100644 --- a/jOOQ/src/main/java/org/jooq/Table.java +++ b/jOOQ/src/main/java/org/jooq/Table.java @@ -50,6 +50,7 @@ import static org.jooq.SQLDialect.DERBY; // ... import static org.jooq.SQLDialect.FIREBIRD; // ... +// ... import static org.jooq.SQLDialect.H2; // ... import static org.jooq.SQLDialect.HSQLDB; @@ -63,6 +64,7 @@ import static org.jooq.SQLDialect.MYSQL; // ... // ... // ... +// ... import static org.jooq.SQLDialect.POSTGRES; // ... // ... @@ -3021,6 +3023,13 @@ extends + + + + + + + diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractAutoAliasTable.java b/jOOQ/src/main/java/org/jooq/impl/AbstractAutoAliasTable.java new file mode 100644 index 0000000000..a07379349e --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractAutoAliasTable.java @@ -0,0 +1,119 @@ +/* + * 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 + * + * https://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: https://www.jooq.org/legal/licensing + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +import org.jooq.Context; +import org.jooq.Name; +import org.jooq.Record; +import org.jooq.Table; +import org.jooq.TableOptions; + +/** + * A base implementation for {@link AutoAlias} and {@link Table}. + * + * @author Lukas Eder + */ +abstract class AbstractAutoAliasTable +extends + AbstractTable +implements + AutoAlias> +{ + + final Name alias; + final Name[] fieldAliases; + + AbstractAutoAliasTable(Name alias) { + this(alias, null); + } + + AbstractAutoAliasTable(Name alias, Name[] fieldAliases) { + super(TableOptions.expression(), alias != null ? alias : DSL.name("t")); + + this.alias = alias; + this.fieldAliases = fieldAliases; + } + + abstract AbstractAutoAliasTable construct(Name newAlias, Name[] newFieldAliases); + + // ------------------------------------------------------------------------- + // XXX: Table API + // ------------------------------------------------------------------------- + + @Override + public final boolean declaresTables() { + + // Always true, because unnested tables are always aliased + return true; + } + + @Override + public final Table autoAlias(Context ctx, Table t) { + + // TODO [#5799] Possibly, add dialect specific behaviour? + return t.as(alias, fieldAliases); + } + + // ------------------------------------------------------------------------- + // XXX: DSL API + // ------------------------------------------------------------------------- + + @Override + public final Table as(Name as) { + return new TableAlias<>(construct(as, null), as, fieldAliases); + } + + @Override + public final Table as(Name as, Name... fields) { + return new TableAlias<>(construct(as, fields), as, fields); + } + + // ------------------------------------------------------------------------- + // XXX: Query Object Model + // ------------------------------------------------------------------------- + + @Override + public final Table $aliased() { + return construct(alias, null); + } + + @Override + public final Name $alias() { + return alias; + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractRowAsField.java b/jOOQ/src/main/java/org/jooq/impl/AbstractRowAsField.java index 2ea7007e74..64337fec87 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractRowAsField.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractRowAsField.java @@ -229,19 +229,19 @@ implements } @Override - public final SelectField autoAlias(Context ctx) { + public final SelectField autoAlias(Context ctx, SelectField s) { // [#13843] Re-aliasing only applies if at least ROW() projection is supported natively if (RowAsField.NO_NATIVE_SUPPORT.contains(ctx.dialect())) - return this; + return s; // [#13843] Within MULTISET(), re-aliasing isn't required, while it leads to new edge cases else if (forceMultisetContent(ctx, () -> getDataType().getRow().size() > 1)) - return this; + return s; // [#13843] With native support, re-alias the table as field projection else - return new FieldAlias<>(this, getUnqualifiedName()); + return new FieldAlias<>(DSL.field(s), getUnqualifiedName()); } private static final Field alias(Context ctx, Name alias, Field field) { diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractTable.java b/jOOQ/src/main/java/org/jooq/impl/AbstractTable.java index d799472670..71350013c7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractTable.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractTable.java @@ -1112,6 +1112,12 @@ implements return as(otherTable.getUnqualifiedName(), (f, i) -> aliasFunction.apply(f, i).getUnqualifiedName()); } + @SuppressWarnings("unchecked") + @Override + public final Table withOrdinality() { + return new OrdinalityTable<>(this); + } + diff --git a/jOOQ/src/main/java/org/jooq/impl/Alias.java b/jOOQ/src/main/java/org/jooq/impl/Alias.java index 9c1d62eb22..5412da1e73 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Alias.java +++ b/jOOQ/src/main/java/org/jooq/impl/Alias.java @@ -80,6 +80,7 @@ import static org.jooq.impl.DSL.falseCondition; import static org.jooq.impl.DSL.field; import static org.jooq.impl.DSL.select; import static org.jooq.impl.Keywords.K_AS; +import static org.jooq.impl.NoAutoAlias.noAutoAlias; import static org.jooq.impl.QueryPartListView.wrap; import static org.jooq.impl.SubqueryCharacteristics.DERIVED_TABLE; import static org.jooq.impl.Tools.EMPTY_NAME; @@ -230,7 +231,7 @@ final class Alias extends AbstractQueryPart implements UEmp && (SUPPORT_DERIVED_COLUMN_NAMES_SPECIAL1.contains(dialect)) && (wrapped instanceof TableImpl || wrapped instanceof CommonTableExpressionImpl)) { - visitSubquery(context, select(asterisk()).from(((Table) wrapped).as(alias)), DERIVED_TABLE); + visitSubquery(context, select(asterisk()).from(noAutoAlias((Table) wrapped).as(alias)), DERIVED_TABLE); } // [#1801] Some databases do not support "derived column names". @@ -258,7 +259,7 @@ final class Alias extends AbstractQueryPart implements UEmp ? s : wrapped instanceof DerivedTable d ? d.query() - : select(asterisk()).from(((Table) wrapped).as(alias)); + : select(asterisk()).from(noAutoAlias((Table) wrapped).as(alias)); List> select = wrappedAsSelect.getSelect(); diff --git a/jOOQ/src/main/java/org/jooq/impl/ArrayAppend.java b/jOOQ/src/main/java/org/jooq/impl/ArrayAppend.java index 3ba35c064c..1ff1a7fe53 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ArrayAppend.java +++ b/jOOQ/src/main/java/org/jooq/impl/ArrayAppend.java @@ -81,10 +81,10 @@ implements ) { super( N_ARRAY_APPEND, - allNotNull(((DataType) OTHER).getArrayDataType(), arg1, arg2) + allNotNull((DataType) dataType(((DataType) OTHER).array(), arg1, false), arg1, arg2) ); - this.arg1 = nullSafeNotNull(arg1, ((DataType) OTHER).getArrayDataType()); + this.arg1 = nullSafeNotNull(arg1, ((DataType) OTHER).array()); this.arg2 = nullSafeNotNull(arg2, (DataType) OTHER); } diff --git a/jOOQ/src/main/java/org/jooq/impl/ArrayConcat.java b/jOOQ/src/main/java/org/jooq/impl/ArrayConcat.java index c93803a2e7..30db196f51 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ArrayConcat.java +++ b/jOOQ/src/main/java/org/jooq/impl/ArrayConcat.java @@ -81,11 +81,11 @@ implements ) { super( N_ARRAY_CONCAT, - allNotNull(((DataType) OTHER).getArrayDataType(), arg1, arg2) + allNotNull((DataType) dataType(((DataType) OTHER).array(), arg1, false), arg1, arg2) ); - this.arg1 = nullSafeNotNull(arg1, ((DataType) OTHER).getArrayDataType()); - this.arg2 = nullSafeNotNull(arg2, ((DataType) OTHER).getArrayDataType()); + this.arg1 = nullSafeNotNull(arg1, ((DataType) OTHER).array()); + this.arg2 = nullSafeNotNull(arg2, ((DataType) OTHER).array()); } // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/ArrayGet.java b/jOOQ/src/main/java/org/jooq/impl/ArrayGet.java index 80f32e5b86..995322e639 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ArrayGet.java +++ b/jOOQ/src/main/java/org/jooq/impl/ArrayGet.java @@ -84,7 +84,7 @@ implements allNotNull((DataType) StringUtils.defaultIfNull(array.getDataType().getArrayComponentDataType(), OTHER), array, index) ); - this.array = nullSafeNotNull(array, ((DataType) OTHER).getArrayDataType()); + this.array = nullSafeNotNull(array, ((DataType) OTHER).array()); this.index = nullSafeNotNull(index, INTEGER); } diff --git a/jOOQ/src/main/java/org/jooq/impl/ArrayOfValues.java b/jOOQ/src/main/java/org/jooq/impl/ArrayOfValues.java index 9312b9c8a9..e22e2cc280 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ArrayOfValues.java +++ b/jOOQ/src/main/java/org/jooq/impl/ArrayOfValues.java @@ -46,9 +46,7 @@ import static org.jooq.impl.Tools.isEmpty; import org.jooq.Context; import org.jooq.Field; import org.jooq.Name; -import org.jooq.Param; import org.jooq.Record; -import org.jooq.Table; import org.jooq.TableOptions; import org.jooq.impl.QOM.UNotYetImplemented; import org.jooq.impl.QOM.UTransient; @@ -58,12 +56,15 @@ import org.jooq.impl.QOM.UTransient; * * @author Lukas Eder */ -final class ArrayOfValues extends AbstractTable implements UNotYetImplemented { +final class ArrayOfValues +extends + AbstractAutoAliasTable +implements + UNotYetImplemented +{ private final Field[] array; private final FieldsImpl field; - private final Name alias; - private final Name[] fieldAliases; ArrayOfValues(Field[] array) { this(array, N_ARRAY_TABLE); @@ -74,37 +75,37 @@ final class ArrayOfValues extends AbstractTable implements UNotYetImplem } ArrayOfValues(Field[] array, Name alias, Name[] fieldAliases) { - super(TableOptions.expression(), alias); + super(alias, ArrayTable.fieldAliases(fieldAliases)); Class arrayType = !isEmpty(array) ? array[0].getType() : Object.class; this.array = array; - this.alias = alias; - this.fieldAliases = isEmpty(fieldAliases) ? new Name[] { N_COLUMN_VALUE } : fieldAliases; this.field = ArrayTable.init(arrayType, this.alias, this.fieldAliases[0]); } + @Override + final ArrayOfValues construct(Name newAlias, Name[] newFieldAliases) { + return new ArrayOfValues(array, newAlias, newFieldAliases); + } + + // ------------------------------------------------------------------------- + // XXX: Table API + // ------------------------------------------------------------------------- + @Override public final Class getRecordType() { + // TODO: [#4695] Calculate the correct Record[B] type return RecordImplN.class; } @Override - public final Table as(Name as) { - return new ArrayOfValues(array, as); + final FieldsImpl fields0() { + return field; } - @Override - public final Table as(Name as, Name... fields) { - return new ArrayOfValues(array, as, fields); - } - - @Override - public final boolean declaresTables() { - - // Always true, because unnested tables are always aliased - return true; - } + // ------------------------------------------------------------------------- + // XXX: QueryPart API + // ------------------------------------------------------------------------- @Override public final void accept(Context ctx) { @@ -133,16 +134,16 @@ final class ArrayOfValues extends AbstractTable implements UNotYetImplem case MARIADB: case MYSQL: case SQLITE: - ctx.visit(new ArrayTableEmulation(array).as(alias, fieldAliases)); + ctx.visit(new ArrayTableEmulation(array, fieldAliases)); break; default: - ctx.visit(new PostgresHSQLDBTable().as(alias, fieldAliases)); + ctx.visit(new StandardUnnest()); break; } } - private class PostgresHSQLDBTable extends DialectArrayTable { + private class StandardUnnest extends DialectArrayTable { @Override public final void accept(Context ctx) { @@ -166,23 +167,4 @@ final class ArrayOfValues extends AbstractTable implements UNotYetImplem return ArrayOfValues.this.fields0(); } } - - @Override - final FieldsImpl fields0() { - return field; - } - - // ------------------------------------------------------------------------- - // XXX: Query Object Model - // ------------------------------------------------------------------------- - - @Override - public final Table $aliased() { - return new ArrayOfValues(array); - } - - @Override - public final Name $alias() { - return alias; - } } diff --git a/jOOQ/src/main/java/org/jooq/impl/ArrayOverlap.java b/jOOQ/src/main/java/org/jooq/impl/ArrayOverlap.java index eecef1da8f..f6325ef90a 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ArrayOverlap.java +++ b/jOOQ/src/main/java/org/jooq/impl/ArrayOverlap.java @@ -80,8 +80,8 @@ implements Field arg2 ) { - this.arg1 = nullSafeNotNull(arg1, ((DataType) OTHER).getArrayDataType()); - this.arg2 = nullSafeNotNull(arg2, ((DataType) OTHER).getArrayDataType()); + this.arg1 = nullSafeNotNull(arg1, ((DataType) OTHER).array()); + this.arg2 = nullSafeNotNull(arg2, ((DataType) OTHER).array()); } // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/ArrayPrepend.java b/jOOQ/src/main/java/org/jooq/impl/ArrayPrepend.java index 9660e68538..d48f38ab7d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ArrayPrepend.java +++ b/jOOQ/src/main/java/org/jooq/impl/ArrayPrepend.java @@ -81,11 +81,11 @@ implements ) { super( N_ARRAY_PREPEND, - allNotNull(((DataType) OTHER).getArrayDataType(), arg1, arg2) + allNotNull((DataType) dataType(((DataType) OTHER).array(), arg2, false), arg1, arg2) ); this.arg1 = nullSafeNotNull(arg1, (DataType) OTHER); - this.arg2 = nullSafeNotNull(arg2, ((DataType) OTHER).getArrayDataType()); + this.arg2 = nullSafeNotNull(arg2, ((DataType) OTHER).array()); } // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/ArrayRemove.java b/jOOQ/src/main/java/org/jooq/impl/ArrayRemove.java index 7ef4c25493..4d67d0d60f 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ArrayRemove.java +++ b/jOOQ/src/main/java/org/jooq/impl/ArrayRemove.java @@ -81,10 +81,10 @@ implements ) { super( N_ARRAY_REMOVE, - allNotNull(((DataType) OTHER).getArrayDataType(), arg1, arg2) + allNotNull((DataType) dataType(((DataType) OTHER).array(), arg1, false), arg1, arg2) ); - this.arg1 = nullSafeNotNull(arg1, ((DataType) OTHER).getArrayDataType()); + this.arg1 = nullSafeNotNull(arg1, ((DataType) OTHER).array()); this.arg2 = nullSafeNotNull(arg2, (DataType) OTHER); } diff --git a/jOOQ/src/main/java/org/jooq/impl/ArrayTable.java b/jOOQ/src/main/java/org/jooq/impl/ArrayTable.java index bd78b71557..927c5e4bc4 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ArrayTable.java +++ b/jOOQ/src/main/java/org/jooq/impl/ArrayTable.java @@ -48,30 +48,32 @@ import static org.jooq.impl.Tools.map; // ... import org.jooq.Configuration; import org.jooq.Context; -import org.jooq.DataType; import org.jooq.Field; import org.jooq.Name; import org.jooq.Param; // ... +import org.jooq.QueryPart; import org.jooq.Record; import org.jooq.Table; import org.jooq.TableOptions; import org.jooq.exception.DataTypeException; import org.jooq.impl.QOM.UNotYetImplemented; import org.jooq.impl.QOM.UTransient; -import org.jooq.util.h2.H2DataType; /** * An unnested array * * @author Lukas Eder */ -final class ArrayTable extends AbstractTable implements UNotYetImplemented { +final class ArrayTable +extends + AbstractAutoAliasTable +implements + UNotYetImplemented +{ private final Field array; private final FieldsImpl field; - private final Name alias; - private final Name[] fieldAliases; ArrayTable(Field array) { this(array, N_ARRAY_TABLE); @@ -85,7 +87,7 @@ final class ArrayTable extends AbstractTable implements UNotYetImplement @SuppressWarnings({ "unchecked" }) ArrayTable(Field array, Name alias, Name[] fieldAliases) { - super(TableOptions.expression(), alias); + super(alias, fieldAliases(fieldAliases)); Class arrayType; @@ -111,11 +113,13 @@ final class ArrayTable extends AbstractTable implements UNotYetImplement arrayType = Object.class; this.array = array; - this.alias = alias; - this.fieldAliases = Tools.isEmpty(fieldAliases) ? new Name[] { N_COLUMN_VALUE } : fieldAliases; this.field = init(arrayType, this.alias, this.fieldAliases[0]); } + static Name[] fieldAliases(Name[] fieldAliases) { + return isEmpty(fieldAliases) ? new Name[] { N_COLUMN_VALUE } : fieldAliases; + } + static final FieldsImpl init(Class arrayType, Name alias, Name fieldAlias) { // [#1114] [#7863] VARRAY/TABLE of OBJECT have more than one field @@ -136,34 +140,36 @@ final class ArrayTable extends AbstractTable implements UNotYetImplement return new FieldsImpl<>(DSL.field(alias.unqualifiedName().append(fieldAlias.unqualifiedName()), DSL.getDataType(arrayType))); } + @Override + final ArrayTable construct(Name newAlias, Name[] newFieldAliases) { + return new ArrayTable(array, newAlias, newFieldAliases); + } + + // ------------------------------------------------------------------------- + // XXX: Table API + // ------------------------------------------------------------------------- + @Override public final Class getRecordType() { + // TODO: [#4695] Calculate the correct Record[B] type return RecordImplN.class; } @Override - public final Table as(Name as) { - return new ArrayTable(array, as); + final FieldsImpl fields0() { + return field; } - @Override - public final Table as(Name as, Name... fields) { - return new ArrayTable(array, as, fields); - } - - @Override - public final boolean declaresTables() { - - // Always true, because unnested tables are always aliased - return true; - } + // ------------------------------------------------------------------------- + // XXX: QueryPart API + // ------------------------------------------------------------------------- @Override public final void accept(Context ctx) { ctx.visit(table(ctx.configuration())); } - private final Table table(Configuration configuration) { + private final QueryPart table(Configuration configuration) { switch (configuration.family()) { @@ -173,8 +179,6 @@ final class ArrayTable extends AbstractTable implements UNotYetImplement - case H2: - return new H2ArrayTable().as(alias); // Most dialects can simulate unnested arrays using UNION ALL @@ -215,11 +219,11 @@ final class ArrayTable extends AbstractTable implements UNotYetImplement // [#756] The standard SQL behaviour default: - return new PostgresHSQLDBTable().as(alias, fieldAliases); + return new StandardUnnest(); } } - private class PostgresHSQLDBTable extends DialectArrayTable { + private class StandardUnnest extends DialectArrayTable { @Override public final void accept(Context ctx) { @@ -227,25 +231,6 @@ final class ArrayTable extends AbstractTable implements UNotYetImplement } } - private class H2ArrayTable extends DialectArrayTable { - - @Override - public final void accept(Context ctx) { - ctx.visit(K_TABLE) - .sql('(') - .visit(fieldAliases == null || fieldAliases.length == 0 ? N_COLUMN_VALUE : fieldAliases[0]) - .sql(' '); - - // If the array type is unknown (e.g. because it's returned from - // a stored function), then a reasonable choice for arbitrary types is varchar - if (array.getDataType().getType() == Object[].class) - ctx.sql(H2DataType.VARCHAR.getTypeName()); - else - ctx.sql(array.getDataType().getArrayComponentDataType().getTypeName(ctx.configuration())); - - ctx.sql(" = ").visit(array).sql(')'); - } - } @@ -257,13 +242,23 @@ final class ArrayTable extends AbstractTable implements UNotYetImplement - - private abstract class DialectArrayTable extends AbstractTable implements UTransient { + private abstract class DialectArrayTable + extends + AbstractTable + implements + AutoAlias>, + UTransient + { DialectArrayTable() { super(TableOptions.expression(), alias); } + @Override + public final boolean declaresTables() { + return true; + } + @Override public final Class getRecordType() { return RecordImplN.class; @@ -273,29 +268,15 @@ final class ArrayTable extends AbstractTable implements UNotYetImplement final FieldsImpl fields0() { return ArrayTable.this.fields0(); } + + @Override + public final Table autoAlias(Context ctx, Table t) { + return t.as(alias, fieldAliases); + } } @SuppressWarnings("unchecked") - private final Table emulate() { - return new ArrayTableEmulation(((Param) array).getValue()).as(alias, fieldAliases); - } - - @Override - final FieldsImpl fields0() { - return field; - } - - // ------------------------------------------------------------------------- - // XXX: Query Object Model - // ------------------------------------------------------------------------- - - @Override - public final Table $aliased() { - return new ArrayTable(array); - } - - @Override - public final Name $alias() { - return alias; + private final QueryPart emulate() { + return new ArrayTableEmulation(((Param) array).getValue(), fieldAliases); } } diff --git a/jOOQ/src/main/java/org/jooq/impl/ArrayTableEmulation.java b/jOOQ/src/main/java/org/jooq/impl/ArrayTableEmulation.java index c7613b5e5c..1c24f38cea 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ArrayTableEmulation.java +++ b/jOOQ/src/main/java/org/jooq/impl/ArrayTableEmulation.java @@ -40,19 +40,16 @@ package org.jooq.impl; import static org.jooq.impl.DSL.falseCondition; import static org.jooq.impl.DSL.name; import static org.jooq.impl.DSL.one; -import static org.jooq.impl.DSL.using; -import static org.jooq.impl.Names.N_ARRAY_TABLE; import static org.jooq.impl.Names.N_COLUMN_VALUE; import static org.jooq.impl.Tools.componentDataType; +import static org.jooq.impl.Tools.visitSubquery; -import org.jooq.Configuration; import org.jooq.Context; +import org.jooq.DataType; import org.jooq.Field; import org.jooq.Name; import org.jooq.Record; import org.jooq.Select; -import org.jooq.Table; -import org.jooq.TableOptions; import org.jooq.impl.QOM.UTransient; /** @@ -61,79 +58,49 @@ import org.jooq.impl.QOM.UTransient; * * @author Lukas Eder */ -final class ArrayTableEmulation extends AbstractTable implements UTransient { +final class ArrayTableEmulation +extends + AbstractQueryPart +implements + UTransient +{ - private final Object[] array; - private final FieldsImpl field; - private final Name alias; - private final Name fieldAlias; + private final Object[] array; + private final DataType type; + private final Name fieldAlias; - private transient Table table; + private transient Select table; - ArrayTableEmulation(Object[] array) { - this(array, N_ARRAY_TABLE, null); - } - - ArrayTableEmulation(Object[] array, Name alias) { - this(array, alias, null); - } - - ArrayTableEmulation(Object[] array, Name alias, Name fieldAlias) { - super(TableOptions.expression(), alias); + ArrayTableEmulation(Object[] array, Name[] fieldAliases) { + if (Tools.isEmpty(fieldAliases)) + this.fieldAlias = N_COLUMN_VALUE; + else if (fieldAliases.length == 1) + this.fieldAlias = fieldAliases[0]; + else + throw new IllegalArgumentException("Array table simulations can only have a single field alias"); this.array = array; - this.alias = alias; - this.fieldAlias = fieldAlias == null ? N_COLUMN_VALUE : fieldAlias; - this.field = new FieldsImpl<>(DSL.field(name(alias.last(), this.fieldAlias.last()), componentDataType(array))); + this.type = componentDataType(array); } - @Override - public final Class getRecordType() { - return RecordImplN.class; - } - - @Override - public final Table as(Name as) { - return new ArrayTableEmulation(array, as); - } - - @Override - public final Table as(Name as, Name... fieldAliases) { - if (fieldAliases == null) - return new ArrayTableEmulation(array, as); - else if (fieldAliases.length == 1) - return new ArrayTableEmulation(array, as, fieldAliases[0]); - - throw new IllegalArgumentException("Array table simulations can only have a single field alias"); - } - - @Override - public final boolean declaresTables() { - - // [#1055] Always true, because unnested tables are always aliased. - // This is particularly important for simulated unnested arrays - return true; - } + // ------------------------------------------------------------------------- + // XXX: QueryPart API + // ------------------------------------------------------------------------- @Override public final void accept(Context ctx) { - ctx.visit(table(ctx.configuration())); + visitSubquery(ctx, table(), SubqueryCharacteristics.DERIVED_TABLE, true); } - @Override - final FieldsImpl fields0() { - return field; - } - - private final Table table(Configuration configuration) { + private final Select table() { if (table == null) { Select select = null; for (Object element : array) { // [#1081] Be sure to get the correct cast type also for null - Field val = DSL.val(element, field.fields[0].getDataType()); - Select subselect = using(configuration).select(val.as(fieldAlias)).select(); + Field val = DSL.val(element, type); + Select subselect = DSL.select(val.as(fieldAlias)).select(); if (select == null) select = subselect; @@ -143,9 +110,9 @@ final class ArrayTableEmulation extends AbstractTable implements UTransi // Empty arrays should result in empty tables if (select == null) - select = using(configuration).select(one().as(fieldAlias)).select().where(falseCondition()); + select = DSL.select(one().as(fieldAlias)).select().where(falseCondition()); - table = select.asTable(alias); + table = select; } return table; diff --git a/jOOQ/src/main/java/org/jooq/impl/AutoAlias.java b/jOOQ/src/main/java/org/jooq/impl/AutoAlias.java index af21e85bf7..de2a06f891 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AutoAlias.java +++ b/jOOQ/src/main/java/org/jooq/impl/AutoAlias.java @@ -40,8 +40,6 @@ package org.jooq.impl; import org.jooq.Context; import org.jooq.QueryPart; import org.jooq.QueryPartInternal; -import org.jooq.Record; -import org.jooq.Table; /** * [#11564] A table that produces auto table and column aliases, if no explicit alias is @@ -55,5 +53,5 @@ interface AutoAlias extends QueryPartInternal { * Create the aliased table expression or null if no auto-alias * is required. */ - Q autoAlias(Context ctx); + Q autoAlias(Context ctx, Q unaliased); } diff --git a/jOOQ/src/main/java/org/jooq/impl/Cardinality.java b/jOOQ/src/main/java/org/jooq/impl/Cardinality.java index 5975ef4d16..6748bc192c 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Cardinality.java +++ b/jOOQ/src/main/java/org/jooq/impl/Cardinality.java @@ -82,7 +82,7 @@ implements allNotNull(INTEGER, array) ); - this.array = nullSafeNotNull(array, OTHER.getArrayDataType()); + this.array = nullSafeNotNull(array, OTHER.array()); } // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/Coerce.java b/jOOQ/src/main/java/org/jooq/impl/Coerce.java index 3adb13d9b6..0da86fda4c 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Coerce.java +++ b/jOOQ/src/main/java/org/jooq/impl/Coerce.java @@ -129,11 +129,11 @@ implements @SuppressWarnings("unchecked") @Override - public final Field autoAlias(Context ctx) { + public final Field autoAlias(Context ctx, Field f) { if (field instanceof AutoAlias) - return ((AutoAlias>) field).autoAlias(ctx).coerce(getDataType()); + return ((AutoAlias>) field).autoAlias(ctx, (Field) field).coerce(getDataType()); else - return this; + return f; } // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/DSL.java b/jOOQ/src/main/java/org/jooq/impl/DSL.java index 392085c2eb..24cf29e468 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DSL.java +++ b/jOOQ/src/main/java/org/jooq/impl/DSL.java @@ -32798,7 +32798,7 @@ public class DSL { @NotNull @Support public static Table> values(Row1... rows) { - return new Values>(rows).as("v", "c1"); + return new Values>(rows); } /** @@ -32833,7 +32833,7 @@ public class DSL { @NotNull @Support public static Table> values(Row2... rows) { - return new Values>(rows).as("v", "c1", "c2"); + return new Values>(rows); } /** @@ -32868,7 +32868,7 @@ public class DSL { @NotNull @Support public static Table> values(Row3... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3"); + return new Values>(rows); } /** @@ -32903,7 +32903,7 @@ public class DSL { @NotNull @Support public static Table> values(Row4... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4"); + return new Values>(rows); } /** @@ -32938,7 +32938,7 @@ public class DSL { @NotNull @Support public static Table> values(Row5... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5"); + return new Values>(rows); } /** @@ -32973,7 +32973,7 @@ public class DSL { @NotNull @Support public static Table> values(Row6... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6"); + return new Values>(rows); } /** @@ -33008,7 +33008,7 @@ public class DSL { @NotNull @Support public static Table> values(Row7... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6", "c7"); + return new Values>(rows); } /** @@ -33043,7 +33043,7 @@ public class DSL { @NotNull @Support public static Table> values(Row8... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8"); + return new Values>(rows); } /** @@ -33078,7 +33078,7 @@ public class DSL { @NotNull @Support public static Table> values(Row9... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9"); + return new Values>(rows); } /** @@ -33113,7 +33113,7 @@ public class DSL { @NotNull @Support public static Table> values(Row10... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10"); + return new Values>(rows); } /** @@ -33148,7 +33148,7 @@ public class DSL { @NotNull @Support public static Table> values(Row11... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11"); + return new Values>(rows); } /** @@ -33183,7 +33183,7 @@ public class DSL { @NotNull @Support public static Table> values(Row12... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11", "c12"); + return new Values>(rows); } /** @@ -33218,7 +33218,7 @@ public class DSL { @NotNull @Support public static Table> values(Row13... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11", "c12", "c13"); + return new Values>(rows); } /** @@ -33253,7 +33253,7 @@ public class DSL { @NotNull @Support public static Table> values(Row14... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11", "c12", "c13", "c14"); + return new Values>(rows); } /** @@ -33288,7 +33288,7 @@ public class DSL { @NotNull @Support public static Table> values(Row15... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11", "c12", "c13", "c14", "c15"); + return new Values>(rows); } /** @@ -33323,7 +33323,7 @@ public class DSL { @NotNull @Support public static Table> values(Row16... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11", "c12", "c13", "c14", "c15", "c16"); + return new Values>(rows); } /** @@ -33358,7 +33358,7 @@ public class DSL { @NotNull @Support public static Table> values(Row17... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11", "c12", "c13", "c14", "c15", "c16", "c17"); + return new Values>(rows); } /** @@ -33393,7 +33393,7 @@ public class DSL { @NotNull @Support public static Table> values(Row18... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11", "c12", "c13", "c14", "c15", "c16", "c17", "c18"); + return new Values>(rows); } /** @@ -33428,7 +33428,7 @@ public class DSL { @NotNull @Support public static Table> values(Row19... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11", "c12", "c13", "c14", "c15", "c16", "c17", "c18", "c19"); + return new Values>(rows); } /** @@ -33463,7 +33463,7 @@ public class DSL { @NotNull @Support public static Table> values(Row20... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11", "c12", "c13", "c14", "c15", "c16", "c17", "c18", "c19", "c20"); + return new Values>(rows); } /** @@ -33498,7 +33498,7 @@ public class DSL { @NotNull @Support public static Table> values(Row21... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11", "c12", "c13", "c14", "c15", "c16", "c17", "c18", "c19", "c20", "c21"); + return new Values>(rows); } /** @@ -33533,7 +33533,7 @@ public class DSL { @NotNull @Support public static Table> values(Row22... rows) { - return new Values>(rows).as("v", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11", "c12", "c13", "c14", "c15", "c16", "c17", "c18", "c19", "c20", "c21", "c22"); + return new Values>(rows); } diff --git a/jOOQ/src/main/java/org/jooq/impl/DataChangeDeltaTable.java b/jOOQ/src/main/java/org/jooq/impl/DataChangeDeltaTable.java index 1f9114250b..d0cff285ad 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DataChangeDeltaTable.java +++ b/jOOQ/src/main/java/org/jooq/impl/DataChangeDeltaTable.java @@ -195,8 +195,8 @@ implements } @Override - public final Table autoAlias(Context ctx) { - return as(alias); + public final Table autoAlias(Context ctx, Table t) { + return t.as(alias); } @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/FunctionTable.java b/jOOQ/src/main/java/org/jooq/impl/FunctionTable.java index 756d00d699..b24901cf51 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FunctionTable.java +++ b/jOOQ/src/main/java/org/jooq/impl/FunctionTable.java @@ -52,7 +52,12 @@ import org.jooq.impl.QOM.UNotYetImplemented; /** * @author Lukas Eder */ -final class FunctionTable extends AbstractTable implements UNotYetImplemented { +final class FunctionTable +extends + AbstractTable +implements + UNotYetImplemented +{ private final Field function; @@ -62,6 +67,10 @@ final class FunctionTable extends AbstractTable implements this.function = function; } + // ------------------------------------------------------------------------- + // XXX: Table API + // ------------------------------------------------------------------------- + @SuppressWarnings("unchecked") @Override public final Class getRecordType() { @@ -69,6 +78,15 @@ final class FunctionTable extends AbstractTable implements return (Class) RecordImplN.class; } + @Override + final FieldsImpl fields0() { + return new FieldsImpl<>(); + } + + // ------------------------------------------------------------------------- + // XXX: DSL API + // ------------------------------------------------------------------------- + @Override public final Table as(Name as) { return new TableAlias<>(new FunctionTable<>(function), as); @@ -79,6 +97,10 @@ final class FunctionTable extends AbstractTable implements return new TableAlias<>(new FunctionTable<>(function), as, fieldAliases); } + // ------------------------------------------------------------------------- + // XXX: QueryPart API + // ------------------------------------------------------------------------- + @Override public final void accept(Context ctx) { switch (ctx.family()) { @@ -101,9 +123,4 @@ final class FunctionTable extends AbstractTable implements throw new SQLDialectNotSupportedException("FUNCTION TABLE is not supported for " + ctx.dialect()); } } - - @Override - final FieldsImpl fields0() { - return new FieldsImpl<>(); - } } diff --git a/jOOQ/src/main/java/org/jooq/impl/GenerateSeries.java b/jOOQ/src/main/java/org/jooq/impl/GenerateSeries.java index 6b61a0ccb1..bd1b46696c 100644 --- a/jOOQ/src/main/java/org/jooq/impl/GenerateSeries.java +++ b/jOOQ/src/main/java/org/jooq/impl/GenerateSeries.java @@ -247,11 +247,11 @@ implements } @Override - public final Table> autoAlias(Context ctx) { + public final Table> autoAlias(Context ctx, Table> t) { if (EMULATE_WITH_RECURSIVE.contains(ctx.dialect())) - return as(name); + return t.as(name); else if (EMULATE_SYSTEM_RANGE.contains(ctx.dialect())) - return as(name, name); + return t.as(name, name); diff --git a/jOOQ/src/main/java/org/jooq/impl/JoinTable.java b/jOOQ/src/main/java/org/jooq/impl/JoinTable.java index 4e03c4e076..3f8912fe7a 100755 --- a/jOOQ/src/main/java/org/jooq/impl/JoinTable.java +++ b/jOOQ/src/main/java/org/jooq/impl/JoinTable.java @@ -109,6 +109,7 @@ import static org.jooq.impl.Names.N_JOIN; import static org.jooq.impl.QueryPartListView.wrap; import static org.jooq.impl.Tools.containsUnaliasedTable; import static org.jooq.impl.Tools.map; +import static org.jooq.impl.Tools.visitAutoAliased; import static org.jooq.impl.Tools.BooleanDataKey.DATA_COLLECT_SEMI_ANTI_JOIN; import static org.jooq.impl.Tools.SimpleDataKey.DATA_COLLECTED_SEMI_ANTI_JOIN; @@ -467,7 +468,7 @@ implements if (wrap) ctx.sqlIndentStart('('); - ctx.visit(table); + visitAutoAliased(ctx, table, Context::declareTables, (c, t) -> c.visit(t)); if (wrap) ctx.sqlIndentEnd(')'); diff --git a/jOOQ/src/main/java/org/jooq/impl/Names.java b/jOOQ/src/main/java/org/jooq/impl/Names.java index 6f7e17e586..66cf93c901 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Names.java +++ b/jOOQ/src/main/java/org/jooq/impl/Names.java @@ -231,6 +231,7 @@ final class Names { static final Name N_NTILE = systemName("ntile"); static final Name N_NULL = systemName("null"); static final Name N_NVL2 = systemName("nvl2"); + static final Name N_OFFSET = systemName("offset"); static final Name N_OPENJSON = systemName("openjson"); static final Name N_OPENXML = systemName("openxml"); static final Name N_ORDINAL = systemName("ordinal"); diff --git a/jOOQ/src/main/java/org/jooq/impl/NoAutoAlias.java b/jOOQ/src/main/java/org/jooq/impl/NoAutoAlias.java new file mode 100644 index 0000000000..5abb98d5af --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/NoAutoAlias.java @@ -0,0 +1,76 @@ +/* + * 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 + * + * https://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: https://www.jooq.org/legal/licensing + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +import org.jooq.Context; +import org.jooq.Record; +import org.jooq.Table; +import org.jooq.impl.QOM.UTransient; + +/** + * A delegating table that un-{@link AutoAlias}-es a wrapped table. + *

+ * When emulating the derived column list feature, the auto-aliasing feature + * would re-alias the table again and again, leading to a + * {@link StackOverflowError}. This helps prevent it. + */ +final class NoAutoAlias +extends + AbstractDelegatingTable +implements + UTransient +{ + + NoAutoAlias(AbstractTable delegate) { + super(delegate); + } + + static final Table noAutoAlias(Table table) { + return table instanceof AutoAlias ? new NoAutoAlias<>((AbstractTable) table) : table; + } + + @Override + final NoAutoAlias construct(AbstractTable newDelegate) { + return new NoAutoAlias<>(newDelegate); + } + + @Override + public final void accept(Context ctx) { + ctx.visit(delegate); + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/OrdinalityTable.java b/jOOQ/src/main/java/org/jooq/impl/OrdinalityTable.java new file mode 100644 index 0000000000..6a6bb27ec1 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/OrdinalityTable.java @@ -0,0 +1,266 @@ +/* + * 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 + * + * https://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: https://www.jooq.org/legal/licensing + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +// ... +// ... +// ... +// ... +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.MARIADB; +// ... +import static org.jooq.SQLDialect.MYSQL; +// ... +import static org.jooq.SQLDialect.POSTGRES; +// ... +// ... +import static org.jooq.SQLDialect.SQLITE; +// ... +// ... +// ... +// ... +import static org.jooq.SQLDialect.YUGABYTEDB; +import static org.jooq.impl.DSL.one; +import static org.jooq.impl.DSL.rowNumber; +// ... +import static org.jooq.impl.DSL.select; +import static org.jooq.impl.DSL.table; +import static org.jooq.impl.Keywords.K_ORDINALITY; +import static org.jooq.impl.Keywords.K_WITH; +import static org.jooq.impl.Names.N_OFFSET; +import static org.jooq.impl.Names.N_ORDINAL; +import static org.jooq.impl.SQLDataType.BIGINT; +import static org.jooq.impl.SubqueryCharacteristics.DERIVED_TABLE; +import static org.jooq.impl.Tools.visitSubquery; + +import java.util.List; +import java.util.Set; + +import org.jooq.Context; +import org.jooq.Field; +import org.jooq.ForeignKey; +import org.jooq.Name; +// ... +import org.jooq.QueryPart; +import org.jooq.Record; +// ... +import org.jooq.SQLDialect; +import org.jooq.Select; +import org.jooq.Table; +// ... +import org.jooq.impl.QOM.Aliasable; + +/** + * @author Lukas Eder + */ +final class OrdinalityTable +extends + AbstractTable +implements + AutoAlias>, + QOM.OrdinalityTable +{ + + static final Set NO_SUPPORT_STANDARD = SQLDialect.supportedBy(DERBY, FIREBIRD, MARIADB, MYSQL, SQLITE); + static final Set NO_SUPPORT_TVF = SQLDialect.supportedBy(H2, HSQLDB); + static final Set NO_SUPPORT_TABLE_EXPRESSIONS = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB); + + static { + NO_SUPPORT_TVF.addAll(NO_SUPPORT_STANDARD); + NO_SUPPORT_TABLE_EXPRESSIONS.addAll(NO_SUPPORT_TVF); + } + + final AbstractTable delegate; + + OrdinalityTable(AbstractTable delegate) { + super(delegate.getOptions(), delegate.getQualifiedName(), delegate.getSchema()); + + this.delegate = delegate; + } + + // ------------------------------------------------------------------------- + // XXX: Table API + // ------------------------------------------------------------------------- + + // [#5799] TODO: Maybe share some logic with AbstractDelegatingTable + + @Override + public final boolean declaresTables() { + return true; + } + + @SuppressWarnings("unchecked") + @Override + public final Class getRecordType() { + // TODO: [#4695] Calculate the correct Record[B] type + return (Class) RecordImplN.class; + } + + @Override + public final List> getReferences() { + return (List) delegate.getReferences(); + } + + @Override + final FieldsImpl fields0() { + FieldsImpl r = new FieldsImpl<>(delegate.fields0().fields); + r.add(DSL.field(N_ORDINAL, BIGINT)); + return r; + } + + @SuppressWarnings("unchecked") + @Override + public final Table autoAlias(Context ctx, Table t) { + if (t != this && t instanceof AutoAlias a) { + return ((AutoAlias>) a).autoAlias(ctx, t); + } + else if (t instanceof Aliasable a) { + Name alias = a.$alias(); + if (alias == null) + alias = ((Table) a.$aliased()).getUnqualifiedName(); + + Field[] fields = t.fields(); + if (Tools.isEmpty(fields)) + return t.as(alias); + else + return t.as(table(alias), fields); + } + else + return null; + } + + // ------------------------------------------------------------------------ + // XXX: QueryPart API + // ------------------------------------------------------------------------ + + @Override + public final void accept(Context ctx) { + if ((delegate instanceof ArrayTable || delegate instanceof ArrayOfValues) && NO_SUPPORT_STANDARD.contains(ctx.dialect())) + acceptEmulation(ctx); + else if (delegate instanceof FunctionTable && NO_SUPPORT_TVF.contains(ctx.dialect())) + acceptEmulation(ctx); + else if (delegate instanceof TableImpl && ((TableImpl) delegate).parameters != null && NO_SUPPORT_TVF.contains(ctx.dialect())) + acceptEmulation(ctx); + else if (NO_SUPPORT_TABLE_EXPRESSIONS.contains(ctx.dialect())) + acceptEmulation(ctx); + + + + + else + ctx.visit(delegate).sql(' ').visit(K_WITH).sql(' ').visit(K_ORDINALITY); + } + + private final void acceptEmulation(Context ctx) { + Select s; + + switch (ctx.family()) { + + + + + + + default: + s = select(delegate.fields()).select(rowNumber().over().as(N_ORDINAL)).from(delegate); + break; + } + + visitSubquery(ctx, s, DERIVED_TABLE, true); + } + + + + + + + + + + + + + // ------------------------------------------------------------------------- + // XXX: Query Object Model + // ------------------------------------------------------------------------- + + @Override + public final Table $table() { + return delegate; + } + + @SuppressWarnings("unchecked") + @Override + public final OrdinalityTable $table(Table newTable) { + return new OrdinalityTable<>((AbstractTable) newTable); + } + + + + + + + + + + + + + + + + + + + + @Override + public final Table $aliased() { + return new OrdinalityTable<>((AbstractTable) ((Aliasable>) delegate).$aliased()); + } + + @Override + public final Name $alias() { + return ((Aliasable>) delegate).$alias(); + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/QOM.java b/jOOQ/src/main/java/org/jooq/impl/QOM.java index e2144840f4..b183dc8506 100644 --- a/jOOQ/src/main/java/org/jooq/impl/QOM.java +++ b/jOOQ/src/main/java/org/jooq/impl/QOM.java @@ -721,6 +721,21 @@ public final class QOM { @NotNull HintedTable $table(Table newTable); } + /** + * A collection derived table or table valued function with a + * WITH ORDINALITY clause. + */ + public sealed interface OrdinalityTable + extends + Table + permits + org.jooq.impl.OrdinalityTable + { + @NotNull Table $table(); + @CheckReturnValue + @NotNull OrdinalityTable $table(Table newTable); + } + public interface PrimaryKey extends Constraint { @NotNull UnmodifiableList> $fields(); } diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectFieldList.java b/jOOQ/src/main/java/org/jooq/impl/SelectFieldList.java index 406a65f57b..7372ccc65d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectFieldList.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectFieldList.java @@ -38,7 +38,8 @@ package org.jooq.impl; -import org.jooq.Condition; +import static org.jooq.impl.Tools.visitAutoAliased; + import org.jooq.Context; import org.jooq.SelectFieldOrAsterisk; @@ -88,13 +89,7 @@ final class SelectFieldList extends QueryPartLi acceptElement0(ctx, part); } - @SuppressWarnings("unchecked") private void acceptElement0(Context ctx, F part) { - F alternative; - - if (ctx.declareFields() && part instanceof AutoAlias && (alternative = ((AutoAlias) part).autoAlias(ctx)) != null) - super.acceptElement(ctx, alternative); - else - super.acceptElement(ctx, part); + visitAutoAliased(ctx, part, Context::declareFields, (c, t) -> super.acceptElement(c, t)); } } diff --git a/jOOQ/src/main/java/org/jooq/impl/TableList.java b/jOOQ/src/main/java/org/jooq/impl/TableList.java index 1f68c01191..3cc9154546 100644 --- a/jOOQ/src/main/java/org/jooq/impl/TableList.java +++ b/jOOQ/src/main/java/org/jooq/impl/TableList.java @@ -46,11 +46,11 @@ import static org.jooq.SQLDialect.H2; // ... import static org.jooq.SQLDialect.HSQLDB; import static org.jooq.impl.Tools.flatMap; +import static org.jooq.impl.Tools.visitAutoAliased; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import org.jooq.Context; import org.jooq.Field; @@ -82,15 +82,9 @@ final class TableList extends QueryPartList> { return true; } - @SuppressWarnings("unchecked") @Override protected void acceptElement(Context ctx, Table part) { - Table alternative; - - if (ctx.declareTables() && part instanceof AutoAlias && (alternative = ((AutoAlias>) part).autoAlias(ctx)) != null) - super.acceptElement(ctx, alternative); - else - super.acceptElement(ctx, part); + visitAutoAliased(ctx, part, Context::declareTables, (c, t) -> super.acceptElement(c, t)); } @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index c21a6d07d5..322e054bc9 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -240,6 +240,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool.ManagedBlocker; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -1614,12 +1615,16 @@ final class Tools { // ------------------------------------------------------------------------ private static final int FIELD_NAME_CACHE_SIZE = 128; - private static final String[] FIELD_NAME_STRINGS; - private static final Name[] FIELD_NAMES; + private static final String[] V_FIELD_NAME_STRINGS; + private static final String[] C_FIELD_NAME_STRINGS; + private static final Name[] V_FIELD_NAMES; + private static final Name[] C_FIELD_NAMES; static { - FIELD_NAME_STRINGS = IntStream.range(0, FIELD_NAME_CACHE_SIZE).mapToObj(Tools::fieldNameString0).toArray(String[]::new); - FIELD_NAMES = IntStream.range(0, FIELD_NAME_CACHE_SIZE).mapToObj(i -> name(FIELD_NAME_STRINGS[i])).toArray(Name[]::new); + V_FIELD_NAME_STRINGS = IntStream.range(0, FIELD_NAME_CACHE_SIZE).mapToObj(Tools::fieldNameStringV0).toArray(String[]::new); + C_FIELD_NAME_STRINGS = IntStream.range(0, FIELD_NAME_CACHE_SIZE).mapToObj(Tools::fieldNameStringC0).toArray(String[]::new); + V_FIELD_NAMES = IntStream.range(0, FIELD_NAME_CACHE_SIZE).mapToObj(i -> name(V_FIELD_NAME_STRINGS[i])).toArray(Name[]::new); + C_FIELD_NAMES = IntStream.range(0, FIELD_NAME_CACHE_SIZE).mapToObj(i -> name(C_FIELD_NAME_STRINGS[i])).toArray(Name[]::new); } static final SortField sortField(OrderField field) { @@ -1642,16 +1647,29 @@ final class Tools { return Tools.map(fields, (OrderField o) -> sortField(o)); } - private static final String fieldNameString0(int index) { + // TODO: Check if these field names are ever really needed, or if we can just use the C field names + private static final String fieldNameStringV0(int index) { return "v" + index; } + private static final String fieldNameStringC0(int index) { + return "c" + (index + 1); + } + static final String fieldNameString(int index) { - return index < FIELD_NAME_CACHE_SIZE ? FIELD_NAME_STRINGS[index] : fieldNameString0(index); + return index < FIELD_NAME_CACHE_SIZE ? V_FIELD_NAME_STRINGS[index] : fieldNameStringV0(index); + } + + static final String fieldNameStringC(int index) { + return index < FIELD_NAME_CACHE_SIZE ? C_FIELD_NAME_STRINGS[index] : fieldNameStringC0(index); } static final Name fieldName(int index) { - return index < FIELD_NAME_CACHE_SIZE ? FIELD_NAMES[index] : name(fieldNameString0(index)); + return index < FIELD_NAME_CACHE_SIZE ? V_FIELD_NAMES[index] : name(fieldNameStringV0(index)); + } + + static final Name fieldNameC(int index) { + return index < FIELD_NAME_CACHE_SIZE ? C_FIELD_NAMES[index] : name(fieldNameStringC0(index)); } static final Name[] fieldNames(int length) { @@ -1672,6 +1690,24 @@ final class Tools { return result; } + static final Name[] fieldNamesC(int length) { + Name[] result = new Name[length]; + + for (int i = 0; i < length; i++) + result[i] = fieldNameC(i); + + return result; + } + + static final String[] fieldNameStringsC(int length) { + String[] result = new String[length]; + + for (int i = 0; i < length; i++) + result[i] = fieldNameStringC(i); + + return result; + } + static final Field[] fields(int length) { return fields(length, SQLDataType.OTHER); } @@ -2752,6 +2788,21 @@ final class Tools { return e; } + @SuppressWarnings("unchecked") + static final void visitAutoAliased( + Context ctx, + Q q, + Predicate> declaring, + BiConsumer, ? super Q> visit + ) { + Q alternative; + + if (declaring.test(ctx) && q instanceof AutoAlias && (alternative = ((AutoAlias) q).autoAlias(ctx, q)) != null) + visit.accept(ctx, alternative); + else + visit.accept(ctx, q); + } + static final void visitSubquery( Context ctx, QueryPart query diff --git a/jOOQ/src/main/java/org/jooq/impl/Values.java b/jOOQ/src/main/java/org/jooq/impl/Values.java index de5e0b7ff4..aadeef3502 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Values.java +++ b/jOOQ/src/main/java/org/jooq/impl/Values.java @@ -59,6 +59,7 @@ import static org.jooq.SQLDialect.MYSQL; // ... // ... import static org.jooq.conf.ParamType.INLINED; +import static org.jooq.impl.DSL.name; import static org.jooq.impl.Keywords.K_MULTISET; import static org.jooq.impl.Keywords.K_ROW; import static org.jooq.impl.Keywords.K_STRUCT; @@ -69,6 +70,9 @@ import static org.jooq.impl.Names.N_VALUES; import static org.jooq.impl.QueryPartListView.wrap; import static org.jooq.impl.SubqueryCharacteristics.DERIVED_TABLE; import static org.jooq.impl.Tools.EMPTY_ROW; +import static org.jooq.impl.Tools.fieldNamesC; +import static org.jooq.impl.Tools.isEmpty; +import static org.jooq.impl.Tools.map; import static org.jooq.impl.Tools.visitSubquery; import java.util.Set; @@ -93,39 +97,77 @@ import org.jooq.impl.QOM.UnmodifiableList; * * @author Lukas Eder */ -final class Values extends AbstractTable implements QOM.Values { +final class Values +extends + AbstractAutoAliasTable +implements + QOM.Values +{ - static final Set NO_SUPPORT_VALUES = SQLDialect.supportedUntil(FIREBIRD, MARIADB); - static final Set REQUIRE_ROWTYPE_CAST = SQLDialect.supportedBy(FIREBIRD); - static final Set NO_SUPPORT_PARENTHESES = SQLDialect.supportedBy(); + static final Set NO_SUPPORT_VALUES = SQLDialect.supportedUntil(FIREBIRD, MARIADB); + static final Set REQUIRE_ROWTYPE_CAST = SQLDialect.supportedBy(FIREBIRD); + static final Set NO_SUPPORT_PARENTHESES = SQLDialect.supportedBy(); - private final QueryPartListView rows; - private transient DataType[] types; + private final Row[] rows; + private transient DataType[] types; Values(Row[] rows) { - super(TableOptions.expression(), N_VALUES); + this(rows, name("v"), fieldNamesC(degree(rows))); + } + + Values(Row[] rows, Name alias, Name[] fieldAliases) { + super(alias, fieldAliases); this.rows = assertNotEmpty(rows); } - static final QueryPartListView assertNotEmpty(Row[] rows) { - if (rows == null || rows.length == 0) + private static final int degree(Row[] rows) { + return isEmpty(rows) ? 0 : rows[0].size(); + } + + static final Row[] assertNotEmpty(Row[] rows) { + if (isEmpty(rows)) throw new IllegalArgumentException("Cannot create a VALUES() constructor with an empty set of rows"); - return QueryPartListView.wrap(rows); + return rows; } + @Override + final Values construct(Name newAlias, Name[] newFieldAliases) { + return new Values(rows, newAlias, newFieldAliases); + } + + // ------------------------------------------------------------------------- + // XXX: Table API + // ------------------------------------------------------------------------- + + @SuppressWarnings("unchecked") + @Override + public final Class getRecordType() { + // TODO: [#4695] Calculate the correct Record[B] type + return (Class) RecordImplN.class; + } + + @Override + final FieldsImpl fields0() { + return new FieldsImpl<>(map(fieldAliases, (n, i) -> DSL.field(n, rows[0].dataType(i)))); + } + + // ------------------------------------------------------------------------- + // XXX: QueryPart API + // ------------------------------------------------------------------------- + private final DataType[] rowType() { if (types == null) { - types = new DataType[rows.get(0).size()]; + types = new DataType[rows[0].size()]; typeLoop: for (int i = 0; i < types.length; i++) { - types[i] = rows.get(0).dataType(i); + types[i] = rows[0].dataType(i); if (types[i].getType() == Object.class) { - for (int j = 1; j < rows.size(); j++) { - DataType type = rows.get(j).dataType(i); + for (int j = 1; j < rows.length; j++) { + DataType type = rows[j].dataType(i); if (type.getType() != Object.class) { types[i] = type; @@ -150,25 +192,9 @@ final class Values extends AbstractTable implements QOM.Val return result; } - @SuppressWarnings("unchecked") - @Override - public final Class getRecordType() { - // TODO: [#4695] Calculate the correct Record[B] type - return (Class) RecordImplN.class; - } - - @Override - public final Table as(Name alias) { - return new TableAlias<>(this, alias, c -> !NO_SUPPORT_PARENTHESES.contains(c.dialect())); - } - - @Override - public final Table as(Name alias, Name... fieldAliases) { - return new TableAlias<>(this, alias, fieldAliases, c -> !NO_SUPPORT_PARENTHESES.contains(c.dialect())); - } - @Override public final void accept(Context ctx) { + // [#915] Emulate VALUES(..) with SELECT .. UNION ALL SELECT .. // for those dialects that do not support a VALUES() constructor if (NO_SUPPORT_VALUES.contains(ctx.dialect())) { @@ -184,7 +210,7 @@ final class Values extends AbstractTable implements QOM.Val selects = selects.unionAll(select); } - visitSubquery(ctx, selects, DERIVED_TABLE, false); + visitSubquery(ctx, selects, DERIVED_TABLE, true); } @@ -197,6 +223,9 @@ final class Values extends AbstractTable implements QOM.Val else { ctx.start(TABLE_VALUES); + if (!NO_SUPPORT_PARENTHESES.contains(ctx.dialect())) + ctx.sqlIndentStart('('); + @@ -213,13 +242,13 @@ final class Values extends AbstractTable implements QOM.Val ctx.visit(K_VALUES); - if (rows.size() > 1) + if (rows.length > 1) ctx.formatIndentStart() .formatSeparator(); else ctx.sql(' '); - for (int i = 0; i < rows.size(); i++) { + for (int i = 0; i < rows.length; i++) { if (i > 0) ctx.sql(',') .formatSeparator(); @@ -231,10 +260,10 @@ final class Values extends AbstractTable implements QOM.Val - ctx.visit(rows.get(i)); + ctx.visit(rows[i]); } - if (rows.size() > 1) + if (rows.length > 1) ctx.formatIndentEnd() .formatNewLine(); @@ -247,15 +276,14 @@ final class Values extends AbstractTable implements QOM.Val + + if (!NO_SUPPORT_PARENTHESES.contains(ctx.dialect())) + ctx.sqlIndentEnd(')'); + ctx.end(TABLE_VALUES); } } - @Override - final FieldsImpl fields0() { - return new FieldsImpl<>(rows.get(0).fields()); - } - // ------------------------------------------------------------------------- // XXX: Query Object Model // -------------------------------------------------------------------------