[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
This commit is contained in:
Lukas Eder 2021-04-27 10:04:50 +02:00
parent e20bc6a063
commit d069da0cc1
6 changed files with 146 additions and 53 deletions

View File

@ -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.
* <p>
* 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'"
* <p>
* 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.
* <p>
* 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'"
* <p>
* 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.
* <p>
@ -1013,6 +992,50 @@ public class Settings
this.transformAnsiJoinToTableLists = value;
}
/**
* Transform a subquery from an IN condition with LIMIT to an equivalent derived table.
* <p>
* 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'"
* <p>
* 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.
* <p>
* 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'"
* <p>
* This feature is available in the commercial distribution only.
*
*/
public void setTransformInConditionSubqueryWithLimitToDerivedTable(Transformation value) {
this.transformInConditionSubqueryWithLimitToDerivedTable = value;
}
/**
* Transform the <code>QUALIFY</code> clause to an equivalent derived table to filter on window functions.
* <p>
* This feature is available in the commercial distribution only.
*
*/
public Transformation getTransformQualify() {
return transformQualify;
}
/**
* Transform the <code>QUALIFY</code> clause to an equivalent derived table to filter on window functions.
* <p>
* 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.
* <p>
@ -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.
* <p>
@ -2992,8 +3020,14 @@ public class Settings
return this;
}
public Settings withTransformAnsiJoinToTableLists(Boolean value) {
setTransformAnsiJoinToTableLists(value);
/**
* Transform the <code>QUALIFY</code> clause to an equivalent derived table to filter on window functions.
* <p>
* 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()));

View File

@ -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<SQLDialect> SUPPORT_NATIVE = SQLDialect.supportedBy(H2, HSQLDB, IGNITE, MARIADB, MYSQL, POSTGRES, SQLITE);
private static final Set<SQLDialect> 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<SQLDialect> SUPPORT_NATIVE = SQLDialect.supportedBy(H2, HSQLDB, IGNITE, MARIADB, MYSQL, POSTGRES, SQLITE);
private static final Set<SQLDialect> NO_SUPPORT_QUANTIFIED = SQLDialect.supportedBy(DERBY, FIREBIRD, SQLITE);
// See https://bugs.mysql.com/bug.php?id=103494
private static final Set<SQLDialect> 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:

View File

@ -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<R extends Record> extends AbstractResultQuery<R> imp
private static final Set<SQLDialect> REQUIRES_DERIVED_TABLE_DML = SQLDialect.supportedBy(MARIADB, MYSQL);
private static final Set<SQLDialect> EMULATE_EMPTY_GROUP_BY_CONSTANT = SQLDialect.supportedUntil(DERBY, HSQLDB, IGNITE);
private static final Set<SQLDialect> EMULATE_EMPTY_GROUP_BY_OTHER = SQLDialect.supportedUntil(FIREBIRD, MARIADB, MYSQL, SQLITE);
private static final Set<SQLDialect> EMULATE_QUALIFY = SQLDialect.supportedBy(CUBRID, FIREBIRD, MARIADB, MYSQL, POSTGRES, SQLITE);
@ -1523,7 +1524,7 @@ final class SelectQueryImpl<R extends Record> extends AbstractResultQuery<R> 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)) {

View File

@ -3225,6 +3225,8 @@ final class Tools {
return ((AbstractDelegatingQuery<R, SelectQueryImpl<R>>) 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;
}

View File

@ -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<SQLDialect> NO_SUPPORT_IN_LIMIT = SQLDialect.supportedBy(MARIADB, MYSQL);
private static final Set<SQLDialect> 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.
*/

View File

@ -226,14 +226,6 @@ included in the <code>OUTPUT</code> clause.
For details, see <a href="https://github.com/jOOQ/jOOQ/issues/4498">https://github.com/jOOQ/jOOQ/issues/4498</a>.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="transformInConditionSubqueryWithLimitToDerivedTable" type="jooq-runtime:Transformation" minOccurs="0" maxOccurs="1" default="WHEN_NEEDED">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Transform a subquery from an IN condition with LIMIT to an equivalent derived table.
<p>
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'"
<p>
This feature is available in the commercial distribution only.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="transformAnsiJoinToTableLists" type="boolean" minOccurs="0" maxOccurs="1" default="false">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Transform ANSI join to table lists if possible.
<p>
@ -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).
<p>
This feature is available in the commercial distribution only.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="transformInConditionSubqueryWithLimitToDerivedTable" type="jooq-runtime:Transformation" minOccurs="0" maxOccurs="1" default="WHEN_NEEDED">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Transform a subquery from an IN condition with LIMIT to an equivalent derived table.
<p>
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'"
<p>
This feature is available in the commercial distribution only.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>
<element name="transformQualify" type="jooq-runtime:Transformation" minOccurs="0" maxOccurs="1" default="WHEN_NEEDED">
<annotation><appinfo><jxb:property><jxb:javadoc><![CDATA[Transform the <code>QUALIFY</code> clause to an equivalent derived table to filter on window functions.
<p>
This feature is available in the commercial distribution only.]]></jxb:javadoc></jxb:property></appinfo></annotation>
</element>