From 04f00df329f17caca86e13de646104a5b2787e18 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Wed, 15 Sep 2021 16:11:58 +0200 Subject: [PATCH] [jOOQ/jOOQ#12425] Move IS [ NOT ] DISTINCT FROM to API generator This includes: - [jOOQ/jOOQ#12432] Extract CompareCondition and CombinedCondition into their own classes --- jOOQ/src/main/java/org/jooq/Field.java | 248 +++++------------- .../java/org/jooq/impl/AbstractField.java | 76 +++--- .../java/org/jooq/impl/IsDistinctFrom.java | 153 ++++++----- .../java/org/jooq/impl/IsNotDistinctFrom.java | 146 +++++++++++ jOOQ/src/main/java/org/jooq/impl/Tools.java | 4 + 5 files changed, 332 insertions(+), 295 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/impl/IsNotDistinctFrom.java diff --git a/jOOQ/src/main/java/org/jooq/Field.java b/jOOQ/src/main/java/org/jooq/Field.java index e8820cc595..5d697597a8 100644 --- a/jOOQ/src/main/java/org/jooq/Field.java +++ b/jOOQ/src/main/java/org/jooq/Field.java @@ -676,6 +676,70 @@ extends + // ------------------------------------------------------------------------- + // Boolean functions + // ------------------------------------------------------------------------- + + /** + * The IS_DISTINCT_FROM operator. + *

+ * The DISTINCT predicate allows for creating NULL safe comparisons where the two operands + * are tested for non-equality + */ + @NotNull + @Support + Condition isDistinctFrom(T arg2); + + /** + * The IS_DISTINCT_FROM operator. + *

+ * The DISTINCT predicate allows for creating NULL safe comparisons where the two operands + * are tested for non-equality + */ + @NotNull + @Support + Condition isDistinctFrom(Select> arg2); + + /** + * The IS_DISTINCT_FROM operator. + *

+ * The DISTINCT predicate allows for creating NULL safe comparisons where the two operands + * are tested for non-equality + */ + @NotNull + @Support + Condition isDistinctFrom(Field arg2); + + /** + * The IS_NOT_DISTINCT_FROM operator. + *

+ * The NOT DISTINCT predicate allows for creating NULL safe comparisons where the two + * operands are tested for equality + */ + @NotNull + @Support + Condition isNotDistinctFrom(T arg2); + + /** + * The IS_NOT_DISTINCT_FROM operator. + *

+ * The NOT DISTINCT predicate allows for creating NULL safe comparisons where the two + * operands are tested for equality + */ + @NotNull + @Support + Condition isNotDistinctFrom(Select> arg2); + + /** + * The IS_NOT_DISTINCT_FROM operator. + *

+ * The NOT DISTINCT predicate allows for creating NULL safe comparisons where the two + * operands are tested for equality + */ + @NotNull + @Support + Condition isNotDistinctFrom(Field arg2); + /** * The BIT_AND operator. * @@ -1259,190 +1323,6 @@ extends @Support Condition isNotNull(); - // ------------------------------------------------------------------------ - // DISTINCT predicates - // ------------------------------------------------------------------------ - - /** - * Create a condition to check if this field is DISTINCT from - * another value. - *

- * In {@link SQLDialect#MYSQL} and {@link SQLDialect#MARIADB}, this can be - * emulated through

not([this] <=> [value])
- *

- * In {@link SQLDialect#SQLITE}, this can be emulated through - *

[this] IS NOT [value]
- *

- * In databases that support INTERSECT (see - * {@link Select#intersect(Select)}, this predicate can be emulated as - * follows:

-     * NOT EXISTS (SELECT [this] INTERSECT SELECT [value])
-     * 
- *

- * If this is not supported by the underlying database, jOOQ will render - * this instead:

-     * CASE WHEN [this] IS     NULL AND [value] IS     NULL THEN FALSE
-     *      WHEN [this] IS     NULL AND [value] IS NOT NULL THEN TRUE
-     *      WHEN [this] IS NOT NULL AND [value] IS     NULL THEN TRUE
-     *      WHEN [this] =               [value]             THEN FALSE
-     *      ELSE                                                 TRUE
-     * END
-     * 
SQL: this is distinct from value - */ - @NotNull - @Support - Condition isDistinctFrom(T value); - - /** - * Create a condition to check if this field is DISTINCT from - * another field. - *

- * In {@link SQLDialect#MYSQL} and {@link SQLDialect#MARIADB}, this can be - * emulated through

not([this] <=> [value])
- *

- * In {@link SQLDialect#SQLITE}, this can be emulated through - *

[this] IS NOT [value]
- *

- * In databases that support INTERSECT (see - * {@link Select#intersect(Select)}, this predicate can be emulated as - * follows:

-     * NOT EXISTS (SELECT [this] INTERSECT SELECT [value])
-     * 
- *

- * If this is not supported by the underlying database, jOOQ will render - * this instead:

-     * CASE WHEN [this] IS     NULL AND [field] IS     NULL THEN FALSE
-     *      WHEN [this] IS     NULL AND [field] IS NOT NULL THEN TRUE
-     *      WHEN [this] IS NOT NULL AND [field] IS     NULL THEN TRUE
-     *      WHEN [this] =               [field]             THEN FALSE
-     *      ELSE                                                 TRUE
-     * END
-     * 
SQL: this is distinct from field - */ - @NotNull - @Support - Condition isDistinctFrom(Field field); - - /** - * Create a condition to check if this field is DISTINCT from - * another field. - *

- * In {@link SQLDialect#MYSQL} and {@link SQLDialect#MARIADB}, this can be - * emulated through

not([this] <=> [value])
- *

- * In {@link SQLDialect#SQLITE}, this can be emulated through - *

[this] IS NOT [value]
- *

- * In databases that support INTERSECT (see - * {@link Select#intersect(Select)}, this predicate can be emulated as - * follows:

-     * NOT EXISTS (SELECT [this] INTERSECT SELECT [value])
-     * 
- *

- * If this is not supported by the underlying database, jOOQ will render - * this instead:

-     * CASE WHEN [this] IS     NULL AND [field] IS     NULL THEN FALSE
-     *      WHEN [this] IS     NULL AND [field] IS NOT NULL THEN TRUE
-     *      WHEN [this] IS NOT NULL AND [field] IS     NULL THEN TRUE
-     *      WHEN [this] =               [field]             THEN FALSE
-     *      ELSE                                                 TRUE
-     * END
-     * 
SQL: this is distinct from field - */ - @NotNull - @Support - Condition isDistinctFrom(Select> select); - - /** - * Create a condition to check if this field is NOT DISTINCT - * from another value. - *

- * In {@link SQLDialect#MYSQL} and {@link SQLDialect#MARIADB}, this can be - * emulated through

[this] <=> [value]
- *

- * In {@link SQLDialect#SQLITE}, this can be emulated through - *

[this] IS [value]
- *

- * In databases that support INTERSECT (see - * {@link Select#intersect(Select)}, this predicate can be emulated as - * follows:

-     * EXISTS (SELECT [this] INTERSECT SELECT [value])
-     * 
- *

- * If this is not supported by the underlying database, jOOQ will render - * this instead:

-     * CASE WHEN [this] IS     NULL AND [value] IS     NULL THEN TRUE
-     *      WHEN [this] IS     NULL AND [value] IS NOT NULL THEN FALSE
-     *      WHEN [this] IS NOT NULL AND [value] IS     NULL THEN FALSE
-     *      WHEN [this] =               [value]             THEN TRUE
-     *      ELSE                                                 FALSE
-     * END
-     * 
SQL: this is not distinct from value - */ - @NotNull - @Support - Condition isNotDistinctFrom(T value); - - /** - * Create a condition to check if this field is NOT DISTINCT - * from another field. - *

- * In {@link SQLDialect#MYSQL} and {@link SQLDialect#MARIADB}, this can be - * emulated through

[this] <=> [value]
- *

- * In {@link SQLDialect#SQLITE}, this can be emulated through - *

[this] IS [value]
- *

- * In databases that support INTERSECT (see - * {@link Select#intersect(Select)}, this predicate can be emulated as - * follows:

-     * EXISTS (SELECT [this] INTERSECT SELECT [value])
-     * 
- *

- * If this is not supported by the underlying database, jOOQ will render - * this instead:

-     * CASE WHEN [this] IS     NULL AND [field] IS     NULL THEN TRUE
-     *      WHEN [this] IS     NULL AND [field] IS NOT NULL THEN FALSE
-     *      WHEN [this] IS NOT NULL AND [field] IS     NULL THEN FALSE
-     *      WHEN [this] =               [value]             THEN TRUE
-     *      ELSE                                                 FALSE
-     * END
-     * 
SQL: this is not distinct from field - */ - @NotNull - @Support - Condition isNotDistinctFrom(Field field); - - /** - * Create a condition to check if this field is NOT DISTINCT - * from another field. - *

- * In {@link SQLDialect#MYSQL} and {@link SQLDialect#MARIADB}, this can be - * emulated through

[this] <=> [value]
- *

- * In {@link SQLDialect#SQLITE}, this can be emulated through - *

[this] IS [value]
- *

- * In databases that support INTERSECT (see - * {@link Select#intersect(Select)}, this predicate can be emulated as - * follows:

-     * EXISTS (SELECT [this] INTERSECT SELECT [value])
-     * 
- *

- * If this is not supported by the underlying database, jOOQ will render - * this instead:

-     * CASE WHEN [this] IS     NULL AND [field] IS     NULL THEN TRUE
-     *      WHEN [this] IS     NULL AND [field] IS NOT NULL THEN FALSE
-     *      WHEN [this] IS NOT NULL AND [field] IS     NULL THEN FALSE
-     *      WHEN [this] =               [value]             THEN TRUE
-     *      ELSE                                                 FALSE
-     * END
-     * 
SQL: this is not distinct from field - */ - @NotNull - @Support - Condition isNotDistinctFrom(Select> select); - // ------------------------------------------------------------------------ // LIKE_REGEX predicates // ------------------------------------------------------------------------ diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractField.java b/jOOQ/src/main/java/org/jooq/impl/AbstractField.java index 6cb7f40d62..9db9732757 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractField.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractField.java @@ -42,8 +42,6 @@ import static org.jooq.Comparator.EQUALS; import static org.jooq.Comparator.GREATER; import static org.jooq.Comparator.GREATER_OR_EQUAL; import static org.jooq.Comparator.IN; -import static org.jooq.Comparator.IS_DISTINCT_FROM; -import static org.jooq.Comparator.IS_NOT_DISTINCT_FROM; import static org.jooq.Comparator.LESS; import static org.jooq.Comparator.LESS_OR_EQUAL; import static org.jooq.Comparator.LIKE; @@ -337,6 +335,46 @@ abstract class AbstractField extends AbstractTypedNamed implements Field> arg2) { + return new IsDistinctFrom(this, DSL.field(arg2)); + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public final Condition isDistinctFrom(Field arg2) { + return new IsDistinctFrom(this, arg2); + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public final Condition isNotDistinctFrom(T arg2) { + return new IsNotDistinctFrom(this, Tools.field(arg2, this)); + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public final Condition isNotDistinctFrom(Select> arg2) { + return new IsNotDistinctFrom(this, DSL.field(arg2)); + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public final Condition isNotDistinctFrom(Field arg2) { + return new IsNotDistinctFrom(this, arg2); + } + @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public final Field bitAnd(T arg2) { @@ -652,36 +690,6 @@ abstract class AbstractField extends AbstractTypedNamed implements Field field) { - return compare(IS_DISTINCT_FROM, field); - } - - @Override - public final Condition isDistinctFrom(Select> select) { - return isDistinctFrom(DSL.field(select)); - } - - @Override - public final Condition isNotDistinctFrom(T value) { - return isNotDistinctFrom(Tools.field(value, this)); - } - - @Override - public final Condition isNotDistinctFrom(Field field) { - return compare(IS_NOT_DISTINCT_FROM, field); - } - - @Override - public final Condition isNotDistinctFrom(Select> select) { - return isNotDistinctFrom(DSL.field(select)); - } - /** * [#11200] Nest these constants to prevent initialisation deadlocks. */ @@ -1381,9 +1389,9 @@ abstract class AbstractField extends AbstractTypedNamed implements Field field) { switch (comparator) { case IS_DISTINCT_FROM: + return new IsDistinctFrom<>(this, nullSafe(field, getDataType())); case IS_NOT_DISTINCT_FROM: - return new IsDistinctFrom<>(this, nullSafe(field, getDataType()), comparator); - + return new IsNotDistinctFrom<>(this, nullSafe(field, getDataType())); default: return new CompareCondition(this, nullSafe(field, getDataType()), comparator); } diff --git a/jOOQ/src/main/java/org/jooq/impl/IsDistinctFrom.java b/jOOQ/src/main/java/org/jooq/impl/IsDistinctFrom.java index f7dd9fed92..40b94b75cf 100644 --- a/jOOQ/src/main/java/org/jooq/impl/IsDistinctFrom.java +++ b/jOOQ/src/main/java/org/jooq/impl/IsDistinctFrom.java @@ -37,70 +37,60 @@ */ package org.jooq.impl; -import static org.jooq.Comparator.IS_DISTINCT_FROM; -import static org.jooq.Comparator.IS_NOT_DISTINCT_FROM; -// ... -// ... -// ... -import static org.jooq.SQLDialect.CUBRID; -// ... -import static org.jooq.SQLDialect.DERBY; -// ... -// ... -// ... -// ... -// ... -import static org.jooq.SQLDialect.MARIADB; -// ... -import static org.jooq.SQLDialect.MYSQL; -// ... -// ... -// ... -import static org.jooq.SQLDialect.SQLITE; -// ... -// ... -// ... -// ... -import static org.jooq.impl.DSL.condition; -import static org.jooq.impl.DSL.decode; -import static org.jooq.impl.DSL.exists; -import static org.jooq.impl.DSL.inline; -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.Tools.embeddedFields; +import static org.jooq.impl.DSL.*; +import static org.jooq.impl.Internal.*; +import static org.jooq.impl.Keywords.*; +import static org.jooq.impl.Names.*; +import static org.jooq.impl.SQLDataType.*; +import static org.jooq.impl.Tools.*; +import static org.jooq.impl.Tools.BooleanDataKey.*; +import static org.jooq.impl.Tools.DataExtendedKey.*; +import static org.jooq.impl.Tools.DataKey.*; +import static org.jooq.SQLDialect.*; -import java.util.Set; +import org.jooq.*; +import org.jooq.Record; +import org.jooq.conf.*; +import org.jooq.impl.*; +import org.jooq.tools.*; + +import java.util.*; -import org.jooq.Comparator; -import org.jooq.Context; -import org.jooq.Field; -// ... -import org.jooq.QueryPartInternal; -import org.jooq.SQLDialect; /** - * @author Lukas Eder + * The IS DISTINCT FROM statement. */ -final class IsDistinctFrom extends AbstractCondition { +@SuppressWarnings({ "rawtypes", "unchecked", "unused" }) +final class IsDistinctFrom +extends + AbstractCondition +{ - private static final Set EMULATE_DISTINCT_PREDICATE = SQLDialect.supportedUntil(CUBRID, DERBY); - private static final Set SUPPORT_DISTINCT_WITH_ARROW = SQLDialect.supportedBy(MARIADB, MYSQL); + private final Field arg1; + private final Field arg2; + IsDistinctFrom( + Field arg1, + Field arg2 + ) { - - - - final Field lhs; - final Field rhs; - final Comparator comparator; - - IsDistinctFrom(Field lhs, Field rhs, Comparator comparator) { - this.lhs = lhs; - this.rhs = rhs; - this.comparator = comparator; + this.arg1 = nullSafeNotNull(arg1, (DataType) OTHER); + this.arg2 = nullSafeNotNull(arg2, (DataType) OTHER); } + // ------------------------------------------------------------------------- + // XXX: QueryPart API + // ------------------------------------------------------------------------- + + + + static final Set EMULATE_DISTINCT_PREDICATE = SQLDialect.supportedUntil(CUBRID, DERBY); + static final Set SUPPORT_DISTINCT_WITH_ARROW = SQLDialect.supportedBy(MARIADB, MYSQL); + + + + + @Override final boolean isNullable() { return false; @@ -108,10 +98,8 @@ final class IsDistinctFrom extends AbstractCondition { @Override public final void accept(Context ctx) { - if (lhs.getDataType().isEmbeddable() && rhs.getDataType().isEmbeddable()) - ctx.visit(row(embeddedFields(lhs)).compare(comparator, row(embeddedFields(rhs)))); - - + if (arg1.getDataType().isEmbeddable() && arg2.getDataType().isEmbeddable()) + ctx.visit(row(embeddedFields(arg1)).isDistinctFrom(row(embeddedFields(arg2)))); @@ -122,32 +110,43 @@ final class IsDistinctFrom extends AbstractCondition { // optimally using INTERSECT... // [#7222] [#7224] Make sure the columns are aliased else if (EMULATE_DISTINCT_PREDICATE.contains(ctx.dialect())) - ctx.visit(comparator == IS_DISTINCT_FROM - ? notExists(select(lhs.as("x")).intersect(select(rhs.as("x")))) - : exists(select(lhs.as("x")).intersect(select(rhs.as("x"))))); + ctx.visit(notExists(select(arg1.as("x")).intersect(select(arg2.as("x"))))); // MySQL knows the <=> operator else if (SUPPORT_DISTINCT_WITH_ARROW.contains(ctx.dialect())) - ctx.visit(comparator == IS_DISTINCT_FROM - ? condition("{not}({0} <=> {1})", lhs, rhs) - : condition("{0} <=> {1}", lhs, rhs)); + ctx.visit(condition("{not}({0} <=> {1})", arg1, arg2)); // SQLite knows the IS / IS NOT predicate else if (SQLITE == ctx.family()) - ctx.visit(comparator == IS_DISTINCT_FROM - ? condition("{0} {is not} {1}", lhs, rhs) - : condition("{0} {is} {1}", lhs, rhs)); - - - - - - - - - + ctx.visit(condition("{0} {is not} {1}", arg1, arg2)); else - ctx.visit(new CompareCondition(lhs, rhs, comparator)); + ctx.visit(arg1).sql(' ').visit(K_IS).sql(' ').visit(K_DISTINCT).sql(' ').visit(K_FROM).sql(' ').visit(arg2); + } + + final Field $arg1() { + return arg1; + } + + final Field $arg2() { + return arg2; + } + + + + // ------------------------------------------------------------------------- + // The Object API + // ------------------------------------------------------------------------- + + @Override + public boolean equals(Object that) { + if (that instanceof IsDistinctFrom) { + return + StringUtils.equals(arg1, ((IsDistinctFrom) that).arg1) && + StringUtils.equals(arg2, ((IsDistinctFrom) that).arg2) + ; + } + else + return super.equals(that); } } diff --git a/jOOQ/src/main/java/org/jooq/impl/IsNotDistinctFrom.java b/jOOQ/src/main/java/org/jooq/impl/IsNotDistinctFrom.java new file mode 100644 index 0000000000..a28e6ab7fc --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/IsNotDistinctFrom.java @@ -0,0 +1,146 @@ +/* + * 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 + * + * http://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: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +import static org.jooq.impl.DSL.*; +import static org.jooq.impl.Internal.*; +import static org.jooq.impl.Keywords.*; +import static org.jooq.impl.Names.*; +import static org.jooq.impl.SQLDataType.*; +import static org.jooq.impl.Tools.*; +import static org.jooq.impl.Tools.BooleanDataKey.*; +import static org.jooq.impl.Tools.DataExtendedKey.*; +import static org.jooq.impl.Tools.DataKey.*; +import static org.jooq.SQLDialect.*; + +import org.jooq.*; +import org.jooq.Record; +import org.jooq.conf.*; +import org.jooq.impl.*; +import org.jooq.tools.*; + +import java.util.*; + + +/** + * The IS NOT DISTINCT FROM statement. + */ +@SuppressWarnings({ "rawtypes", "unchecked", "unused" }) +final class IsNotDistinctFrom +extends + AbstractCondition +{ + + private final Field arg1; + private final Field arg2; + + IsNotDistinctFrom( + Field arg1, + Field arg2 + ) { + + this.arg1 = nullSafeNotNull(arg1, (DataType) OTHER); + this.arg2 = nullSafeNotNull(arg2, (DataType) OTHER); + } + + // ------------------------------------------------------------------------- + // XXX: QueryPart API + // ------------------------------------------------------------------------- + + + + @Override + public final void accept(Context ctx) { + if (arg1.getDataType().isEmbeddable() && arg2.getDataType().isEmbeddable()) + ctx.visit(row(embeddedFields(arg1)).isNotDistinctFrom(row(embeddedFields(arg2)))); + + + + + + + // [#3511] These dialects need to emulate the IS DISTINCT FROM predicate, + // optimally using INTERSECT... + // [#7222] [#7224] Make sure the columns are aliased + else if (IsDistinctFrom.EMULATE_DISTINCT_PREDICATE.contains(ctx.dialect())) + ctx.visit(exists(select(arg1.as("x")).intersect(select(arg2.as("x"))))); + + // MySQL knows the <=> operator + else if (IsDistinctFrom.SUPPORT_DISTINCT_WITH_ARROW.contains(ctx.dialect())) + ctx.visit(condition("{0} <=> {1}", arg1, arg2)); + + // SQLite knows the IS / IS NOT predicate + else if (SQLITE == ctx.family()) + ctx.visit(condition("{0} {is} {1}", arg1, arg2)); + + + + + + + + else + ctx.visit(arg1).sql(' ').visit(K_IS).sql(' ').visit(K_NOT).sql(' ').visit(K_DISTINCT).sql(' ').visit(K_FROM).sql(' ').visit(arg2); + } + + final Field $arg1() { + return arg1; + } + + final Field $arg2() { + return arg2; + } + + + + // ------------------------------------------------------------------------- + // The Object API + // ------------------------------------------------------------------------- + + @Override + public boolean equals(Object that) { + if (that instanceof IsNotDistinctFrom) { + return + StringUtils.equals(arg1, ((IsNotDistinctFrom) that).arg1) && + StringUtils.equals(arg2, ((IsNotDistinctFrom) that).arg2) + ; + } + else + return super.equals(that); + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 4d28cf56d5..2e6ca1975e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -6438,6 +6438,10 @@ final class Tools { + + + +