From d069da0cc182efe90c9abe3f9351a9f31ddfbd0e Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 27 Apr 2021 10:04:50 +0200 Subject: [PATCH] [jOOQ/jOOQ#5810] [jOOQ/jOOQ#11802] Add Settings.transformQualify This includes: [jOOQ/jOOQ#11803] Support = ANY and != ALL quantified comparison predicates of degree > 1 for MySQL --- .../src/main/java/org/jooq/conf/Settings.java | 111 ++++++++++++------ .../org/jooq/impl/RowSubqueryCondition.java | 32 +++-- .../java/org/jooq/impl/SelectQueryImpl.java | 5 +- jOOQ/src/main/java/org/jooq/impl/Tools.java | 2 + .../java/org/jooq/impl/Transformations.java | 27 +++++ .../resources/xsd/jooq-runtime-3.15.0.xsd | 22 ++-- 6 files changed, 146 insertions(+), 53 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/conf/Settings.java b/jOOQ/src/main/java/org/jooq/conf/Settings.java index 02de18327b..cb7f606eaf 100644 --- a/jOOQ/src/main/java/org/jooq/conf/Settings.java +++ b/jOOQ/src/main/java/org/jooq/conf/Settings.java @@ -101,11 +101,14 @@ public class Settings protected Boolean bindOffsetTimeType = false; @XmlElement(defaultValue = "true") protected Boolean fetchTriggerValuesAfterSQLServerOutput = true; + @XmlElement(defaultValue = "false") + protected Boolean transformAnsiJoinToTableLists = false; @XmlElement(defaultValue = "WHEN_NEEDED") @XmlSchemaType(name = "string") protected Transformation transformInConditionSubqueryWithLimitToDerivedTable = Transformation.WHEN_NEEDED; - @XmlElement(defaultValue = "false") - protected Boolean transformAnsiJoinToTableLists = false; + @XmlElement(defaultValue = "WHEN_NEEDED") + @XmlSchemaType(name = "string") + protected Transformation transformQualify = Transformation.WHEN_NEEDED; @XmlElement(defaultValue = "false") protected Boolean transformTableListsToAnsiJoin = false; @XmlElement(defaultValue = "false") @@ -954,30 +957,6 @@ public class Settings this.fetchTriggerValuesAfterSQLServerOutput = value; } - /** - * Transform a subquery from an IN condition with LIMIT to an equivalent derived table. - *

- * This transformation works around a known MySQL limitation "ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'" - *

- * This feature is available in the commercial distribution only. - * - */ - public Transformation getTransformInConditionSubqueryWithLimitToDerivedTable() { - return transformInConditionSubqueryWithLimitToDerivedTable; - } - - /** - * Transform a subquery from an IN condition with LIMIT to an equivalent derived table. - *

- * This transformation works around a known MySQL limitation "ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'" - *

- * This feature is available in the commercial distribution only. - * - */ - public void setTransformInConditionSubqueryWithLimitToDerivedTable(Transformation value) { - this.transformInConditionSubqueryWithLimitToDerivedTable = value; - } - /** * Transform ANSI join to table lists if possible. *

@@ -1013,6 +992,50 @@ public class Settings this.transformAnsiJoinToTableLists = value; } + /** + * Transform a subquery from an IN condition with LIMIT to an equivalent derived table. + *

+ * This transformation works around a known MySQL limitation "ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'" + *

+ * This feature is available in the commercial distribution only. + * + */ + public Transformation getTransformInConditionSubqueryWithLimitToDerivedTable() { + return transformInConditionSubqueryWithLimitToDerivedTable; + } + + /** + * Transform a subquery from an IN condition with LIMIT to an equivalent derived table. + *

+ * This transformation works around a known MySQL limitation "ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'" + *

+ * This feature is available in the commercial distribution only. + * + */ + public void setTransformInConditionSubqueryWithLimitToDerivedTable(Transformation value) { + this.transformInConditionSubqueryWithLimitToDerivedTable = value; + } + + /** + * Transform the QUALIFY clause to an equivalent derived table to filter on window functions. + *

+ * This feature is available in the commercial distribution only. + * + */ + public Transformation getTransformQualify() { + return transformQualify; + } + + /** + * Transform the QUALIFY clause to an equivalent derived table to filter on window functions. + *

+ * This feature is available in the commercial distribution only. + * + */ + public void setTransformQualify(Transformation value) { + this.transformQualify = value; + } + /** * Transform table lists to ANSI join if possible. *

@@ -2979,6 +3002,11 @@ public class Settings return this; } + public Settings withTransformAnsiJoinToTableLists(Boolean value) { + setTransformAnsiJoinToTableLists(value); + return this; + } + /** * Transform a subquery from an IN condition with LIMIT to an equivalent derived table. *

@@ -2992,8 +3020,14 @@ public class Settings return this; } - public Settings withTransformAnsiJoinToTableLists(Boolean value) { - setTransformAnsiJoinToTableLists(value); + /** + * Transform the QUALIFY clause to an equivalent derived table to filter on window functions. + *

+ * This feature is available in the commercial distribution only. + * + */ + public Settings withTransformQualify(Transformation value) { + setTransformQualify(value); return this; } @@ -3710,8 +3744,9 @@ public class Settings builder.append("bindOffsetDateTimeType", bindOffsetDateTimeType); builder.append("bindOffsetTimeType", bindOffsetTimeType); builder.append("fetchTriggerValuesAfterSQLServerOutput", fetchTriggerValuesAfterSQLServerOutput); - builder.append("transformInConditionSubqueryWithLimitToDerivedTable", transformInConditionSubqueryWithLimitToDerivedTable); builder.append("transformAnsiJoinToTableLists", transformAnsiJoinToTableLists); + builder.append("transformInConditionSubqueryWithLimitToDerivedTable", transformInConditionSubqueryWithLimitToDerivedTable); + builder.append("transformQualify", transformQualify); builder.append("transformTableListsToAnsiJoin", transformTableListsToAnsiJoin); builder.append("transformRownum", transformRownum); builder.append("transformUnneededArithmeticExpressions", transformUnneededArithmeticExpressions); @@ -4061,6 +4096,15 @@ public class Settings return false; } } + if (transformAnsiJoinToTableLists == null) { + if (other.transformAnsiJoinToTableLists!= null) { + return false; + } + } else { + if (!transformAnsiJoinToTableLists.equals(other.transformAnsiJoinToTableLists)) { + return false; + } + } if (transformInConditionSubqueryWithLimitToDerivedTable == null) { if (other.transformInConditionSubqueryWithLimitToDerivedTable!= null) { return false; @@ -4070,12 +4114,12 @@ public class Settings return false; } } - if (transformAnsiJoinToTableLists == null) { - if (other.transformAnsiJoinToTableLists!= null) { + if (transformQualify == null) { + if (other.transformQualify!= null) { return false; } } else { - if (!transformAnsiJoinToTableLists.equals(other.transformAnsiJoinToTableLists)) { + if (!transformQualify.equals(other.transformQualify)) { return false; } } @@ -4878,8 +4922,9 @@ public class Settings result = ((prime*result)+((bindOffsetDateTimeType == null)? 0 :bindOffsetDateTimeType.hashCode())); result = ((prime*result)+((bindOffsetTimeType == null)? 0 :bindOffsetTimeType.hashCode())); result = ((prime*result)+((fetchTriggerValuesAfterSQLServerOutput == null)? 0 :fetchTriggerValuesAfterSQLServerOutput.hashCode())); - result = ((prime*result)+((transformInConditionSubqueryWithLimitToDerivedTable == null)? 0 :transformInConditionSubqueryWithLimitToDerivedTable.hashCode())); result = ((prime*result)+((transformAnsiJoinToTableLists == null)? 0 :transformAnsiJoinToTableLists.hashCode())); + result = ((prime*result)+((transformInConditionSubqueryWithLimitToDerivedTable == null)? 0 :transformInConditionSubqueryWithLimitToDerivedTable.hashCode())); + result = ((prime*result)+((transformQualify == null)? 0 :transformQualify.hashCode())); result = ((prime*result)+((transformTableListsToAnsiJoin == null)? 0 :transformTableListsToAnsiJoin.hashCode())); result = ((prime*result)+((transformRownum == null)? 0 :transformRownum.hashCode())); result = ((prime*result)+((transformUnneededArithmeticExpressions == null)? 0 :transformUnneededArithmeticExpressions.hashCode())); diff --git a/jOOQ/src/main/java/org/jooq/impl/RowSubqueryCondition.java b/jOOQ/src/main/java/org/jooq/impl/RowSubqueryCondition.java index 9422f03595..d9a32304ff 100644 --- a/jOOQ/src/main/java/org/jooq/impl/RowSubqueryCondition.java +++ b/jOOQ/src/main/java/org/jooq/impl/RowSubqueryCondition.java @@ -76,6 +76,7 @@ import static org.jooq.impl.DSL.notExists; import static org.jooq.impl.DSL.row; import static org.jooq.impl.DSL.select; import static org.jooq.impl.Quantifier.ALL; +import static org.jooq.impl.Quantifier.ANY; import static org.jooq.impl.Tools.embeddedFieldsRow; import static org.jooq.impl.Tools.fieldNames; import static org.jooq.impl.Tools.fieldsByName; @@ -108,10 +109,13 @@ final class RowSubqueryCondition extends AbstractCondition { /** * Generated UID */ - private static final long serialVersionUID = -1806139685201770706L; - private static final Clause[] CLAUSES = { CONDITION, CONDITION_COMPARISON }; - private static final Set SUPPORT_NATIVE = SQLDialect.supportedBy(H2, HSQLDB, IGNITE, MARIADB, MYSQL, POSTGRES, SQLITE); - private static final Set NO_SUPPORT_NATIVE_QUANTIFIED = SQLDialect.supportedBy(DERBY, FIREBIRD, MARIADB, MYSQL, SQLITE); + private static final long serialVersionUID = -1806139685201770706L; + private static final Clause[] CLAUSES = { CONDITION, CONDITION_COMPARISON }; + private static final Set SUPPORT_NATIVE = SQLDialect.supportedBy(H2, HSQLDB, IGNITE, MARIADB, MYSQL, POSTGRES, SQLITE); + private static final Set NO_SUPPORT_QUANTIFIED = SQLDialect.supportedBy(DERBY, FIREBIRD, SQLITE); + // See https://bugs.mysql.com/bug.php?id=103494 + private static final Set NO_SUPPORT_QUANTIFIED_OTHER_THAN_IN_NOT_IN = SQLDialect.supportedBy(MARIADB, MYSQL); + @@ -146,22 +150,30 @@ final class RowSubqueryCondition extends AbstractCondition { return null; } + private static final boolean inOrNotIn(Comparator comparator, Quantifier quantifier) { + return comparator == EQUALS && quantifier == ANY + || comparator == NOT_EQUALS && quantifier == ALL; + } + private final QueryPartInternal delegate(Context ctx) { // [#3505] TODO: Emulate this where it is not supported if (rightQuantified != null) { - if (NO_SUPPORT_NATIVE_QUANTIFIED.contains(ctx.dialect())) { - // TODO: Handle all cases, not just the query one - QuantifiedSelectImpl q = (QuantifiedSelectImpl) rightQuantified; + // TODO: Handle all cases, not just the query one + QuantifiedSelectImpl q = (QuantifiedSelectImpl) rightQuantified; + boolean inOrNotIn = inOrNotIn(comparator, q.quantifier); + + if (NO_SUPPORT_QUANTIFIED.contains(ctx.dialect()) || + NO_SUPPORT_QUANTIFIED_OTHER_THAN_IN_NOT_IN.contains(ctx.dialect()) && !inOrNotIn) { switch (comparator) { case EQUALS: case NOT_EQUALS: { - if (comparator == NOT_EQUALS ^ q.quantifier == ALL) - return emulationUsingExists(ctx, left, q.query, comparator == EQUALS ? NOT_EQUALS : EQUALS, comparator == EQUALS); - else + if (inOrNotIn) return new RowSubqueryCondition(left, q.query, comparator == EQUALS ? IN : NOT_IN); + else + return emulationUsingExists(ctx, left, q.query, comparator == EQUALS ? NOT_EQUALS : EQUALS, comparator == EQUALS); } case GREATER: diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java index a39005aec6..a3bc161b8f 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java @@ -212,6 +212,7 @@ import static org.jooq.impl.Tools.DataKey.DATA_SELECT_ALIASES; import static org.jooq.impl.Tools.DataKey.DATA_SELECT_INTO_TABLE; import static org.jooq.impl.Tools.DataKey.DATA_TOP_LEVEL_CTE; import static org.jooq.impl.Tools.DataKey.DATA_WINDOW_DEFINITIONS; +import static org.jooq.impl.Transformations.applyTransformationForQualify; import java.sql.ResultSetMetaData; import java.util.ArrayDeque; @@ -306,7 +307,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp private static final Set REQUIRES_DERIVED_TABLE_DML = SQLDialect.supportedBy(MARIADB, MYSQL); private static final Set EMULATE_EMPTY_GROUP_BY_CONSTANT = SQLDialect.supportedUntil(DERBY, HSQLDB, IGNITE); private static final Set EMULATE_EMPTY_GROUP_BY_OTHER = SQLDialect.supportedUntil(FIREBIRD, MARIADB, MYSQL, SQLITE); - private static final Set EMULATE_QUALIFY = SQLDialect.supportedBy(CUBRID, FIREBIRD, MARIADB, MYSQL, POSTGRES, SQLITE); + @@ -1523,7 +1524,7 @@ final class SelectQueryImpl extends AbstractResultQuery imp } // [#5810] Emulate the Teradata QUALIFY clause - else if (qualify.hasWhere() && EMULATE_QUALIFY.contains(ctx.dialect()) && ctx.configuration().commercial(() -> "The QUALIFY clause can be emulated in the jOOQ 3.15 Professional Edition and jOOQ Enterprise Edition, see https://github.com/jOOQ/jOOQ/issues/5810")) { + else if (qualify.hasWhere() && applyTransformationForQualify(ctx)) { diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 548b98e359..df540ad08d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -3225,6 +3225,8 @@ final class Tools { return ((AbstractDelegatingQuery>) part).getDelegate(); else if (part instanceof ScalarSubquery) return selectQueryImpl(((ScalarSubquery) part).query); + else if (part instanceof QuantifiedSelectImpl) + return selectQueryImpl(((QuantifiedSelectImpl) part).query); else return null; } diff --git a/jOOQ/src/main/java/org/jooq/impl/Transformations.java b/jOOQ/src/main/java/org/jooq/impl/Transformations.java index de04c19a14..6729f10f4e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Transformations.java +++ b/jOOQ/src/main/java/org/jooq/impl/Transformations.java @@ -37,8 +37,25 @@ */ package org.jooq.impl; +// ... +// ... +// ... +import static org.jooq.SQLDialect.CUBRID; +// ... +import static org.jooq.SQLDialect.FIREBIRD; +// ... +// ... 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.conf.Transformation.WHEN_NEEDED; import static org.jooq.impl.Tools.selectQueryImpl; import static org.jooq.tools.StringUtils.defaultIfNull; @@ -60,6 +77,7 @@ import org.jooq.conf.Transformation; final class Transformations { private static final Set NO_SUPPORT_IN_LIMIT = SQLDialect.supportedBy(MARIADB, MYSQL); + private static final Set EMULATE_QUALIFY = SQLDialect.supportedBy(CUBRID, FIREBIRD, MARIADB, MYSQL, POSTGRES, SQLITE); static final SelectQueryImpl subqueryWithLimit(QueryPart source) { SelectQueryImpl s; @@ -75,6 +93,15 @@ final class Transformations { ); } + static final boolean applyTransformationForQualify(Context ctx) { + return applyTransformation( + ctx, + "Settings.transformQualify", + ctx.settings().getTransformQualify(), + c -> EMULATE_QUALIFY.contains(c.dialect()) + ); + } + /** * Check whether a given SQL transformation needs to be applied. */ diff --git a/jOOQ/src/main/resources/xsd/jooq-runtime-3.15.0.xsd b/jOOQ/src/main/resources/xsd/jooq-runtime-3.15.0.xsd index 9976d56b7c..725039dd23 100644 --- a/jOOQ/src/main/resources/xsd/jooq-runtime-3.15.0.xsd +++ b/jOOQ/src/main/resources/xsd/jooq-runtime-3.15.0.xsd @@ -226,14 +226,6 @@ included in the OUTPUT clause. For details, see https://github.com/jOOQ/jOOQ/issues/4498.]]> - - -This transformation works around a known MySQL limitation "ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'" -

-This feature is available in the commercial distribution only.]]> - - @@ -246,6 +238,20 @@ converted to equivalent table lists in generated SQL using this flag. This flag has a limited implementation that supports inner joins (in most cases) and outer joins (only for simple comparison predicates).

+This feature is available in the commercial distribution only.]]> + + + + +This transformation works around a known MySQL limitation "ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'" +

+This feature is available in the commercial distribution only.]]> + + + + QUALIFY clause to an equivalent derived table to filter on window functions. +

This feature is available in the commercial distribution only.]]>