From aa20f07f4ebc062a4132759982017906f1d55e42 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 31 Mar 2020 14:50:05 +0200 Subject: [PATCH] [jOOQ/jOOQ#10016] Improve H2's derived column list support --- jOOQ/src/main/java/org/jooq/impl/Alias.java | 84 ++++++++++++--------- jOOQ/src/main/java/org/jooq/impl/Tools.java | 12 +++ 2 files changed, 62 insertions(+), 34 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/impl/Alias.java b/jOOQ/src/main/java/org/jooq/impl/Alias.java index 0a1dcc853c..417d93c013 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Alias.java +++ b/jOOQ/src/main/java/org/jooq/impl/Alias.java @@ -75,9 +75,12 @@ 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.QueryPartListView.wrap; +import static org.jooq.impl.Tools.fieldNames; import static org.jooq.impl.Tools.BooleanDataKey.DATA_AS_REQUIRED; import static org.jooq.impl.Tools.BooleanDataKey.DATA_UNALIAS_ALIASED_EXPRESSIONS; +import java.util.HashSet; +import java.util.List; import java.util.Set; import org.jooq.Clause; @@ -103,7 +106,8 @@ final class Alias extends AbstractQueryPart { private static final Clause[] CLAUSES_FIELD_ALIAS = { FIELD, FIELD_ALIAS }; private static final Set SUPPORT_AS_REQUIRED = SQLDialect.supportedBy(DERBY, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLITE); private static final Set SUPPORT_DERIVED_COLUMN_NAMES_SPECIAL1 = SQLDialect.supportedBy(CUBRID, FIREBIRD, MYSQL); - private static final Set SUPPORT_DERIVED_COLUMN_NAMES_SPECIAL2 = SQLDialect.supportedBy(H2, MARIADB, MYSQL, SQLITE); + private static final Set SUPPORT_DERIVED_COLUMN_NAMES_SPECIAL2 = SQLDialect.supportedBy(MARIADB, MYSQL, SQLITE); + private static final Set SUPPORT_DERIVED_COLUMN_NAMES_SPECIAL3 = SQLDialect.supportedBy(H2); final Q wrapped; final Q wrapping; @@ -179,49 +183,61 @@ final class Alias extends AbstractQueryPart { // [#1801] Some databases do not support "derived column names". // They can be emulated by concatenating a dummy SELECT with no // results using UNION ALL - else if (fieldAliases != null && SUPPORT_DERIVED_COLUMN_NAMES_SPECIAL2.contains(family)) { + else if (fieldAliases != null && ( + SUPPORT_DERIVED_COLUMN_NAMES_SPECIAL2.contains(family) + || SUPPORT_DERIVED_COLUMN_NAMES_SPECIAL3.contains(family))) { + emulatedDerivedColumnList = true; - SelectFieldList> fields = new SelectFieldList<>(); - for (Name fieldAlias : fieldAliases) { - switch (family) { + // [#3156] Do not SELECT * from derived tables to prevent ambiguously defined columns + // in those derived tables + Select wrappedAsSelect = + wrapped instanceof Select + ? (Select) wrapped + : wrapped instanceof DerivedTable + ? ((DerivedTable) wrapped).query() + : select(asterisk()).from(((Table) wrapped).as(alias)); + // [#9486] H2 cannot handle duplicate column names in derived tables, despite derived column lists + // See: https://github.com/h2database/h2database/issues/2532 + if (SUPPORT_DERIVED_COLUMN_NAMES_SPECIAL3.contains(family)) { + List names = fieldNames(wrappedAsSelect.getSelect()); - - - - - - - - - default: { - fields.add(field("null").as(fieldAlias)); - break; - } + if (names.size() > 0 && names.size() == new HashSet<>(names).size()) { + toSQLWrapped(context); + emulatedDerivedColumnList = false; } } - Select select = select(fields).where(falseCondition()) - .unionAll( + if (emulatedDerivedColumnList) { + SelectFieldList> fields = new SelectFieldList<>(); + for (Name fieldAlias : fieldAliases) { + switch (family) { - // [#3156] Do not SELECT * from derived tables to prevent ambiguously defined columns - // in those derived tables - wrapped instanceof Select - ? (Select) wrapped - : wrapped instanceof DerivedTable - ? ((DerivedTable) wrapped).query() - : select(asterisk()).from(((Table) wrapped).as(alias)) - ); - context.sql('(') - .formatIndentStart().formatNewLine() - .subquery(true) - .visit(select) - .subquery(false) - .formatIndentEnd().formatNewLine() - .sql(')'); + + + + + + + + default: { + fields.add(field("null").as(fieldAlias)); + break; + } + } + } + + context.sql('(') + .formatIndentStart().formatNewLine() + .subquery(true) + .visit(select(fields).where(falseCondition()).unionAll(wrappedAsSelect)) + .subquery(false) + .formatIndentEnd().formatNewLine() + .sql(')'); + } } // The default behaviour diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 79afb17299..b24b6d0471 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -1273,6 +1273,18 @@ final class Tools { return result; } + static final List fieldNames(Collection> fields) { + if (fields == null) + return null; + + List result = new ArrayList<>(fields.size()); + + for (Field field : fields) + result.add(field.getUnqualifiedName()); + + return result; + } + static final String[] fieldNameStrings(Field[] fields) { if (fields == null) return null;