[jOOQ/jOOQ#12465] [jOOQ/jOOQ#12432] Extract more CompareConditions

Including:
- IN
- NOT_IN
This commit is contained in:
Lukas Eder 2021-09-24 16:39:04 +02:00
parent 2e061133ea
commit b286fe1abc
16 changed files with 350 additions and 145 deletions

View File

@ -78,6 +78,21 @@ public interface Context<C extends Context<C>> extends Scope {
@NotNull
C visit(QueryPart part) throws DataAccessException;
/**
* Visit a <code>QueryPart</code> as a subquery in the current
* <code>Context</code>.
* <p>
* This method is called by certain <code>QueryPart</code> implementations
* to recursively visit component <code>QueryPart</code>s.
*
* @param part The component <code>QueryPart</code>
* @throws DataAccessException If something went wrong while visiting the
* component <code>QueryPart</code>, e.g. when binding a
* variable
*/
@NotNull
C visitSubquery(QueryPart part) throws DataAccessException;
/**
* TODO [#2667]
*

View File

@ -806,6 +806,17 @@ extends
@Support
Condition gt(Field<T> arg2);
/**
* The <code>IN</code> operator.
* <p>
* The subquery must return exactly one field. This is not checked
* by jOOQ and will result in syntax errors in the database, if not used
* correctly.
*/
@NotNull
@Support
Condition in(Select<? extends Record1<T>> arg2);
/**
* The <code>IS_DISTINCT_FROM</code> operator.
* <p>
@ -1052,6 +1063,21 @@ extends
@Support
Condition notEqual(Field<T> arg2);
/**
* The <code>NOT_IN</code> operator.
* <p>
* The subquery must return exactly one field. This is not checked
* by jOOQ and will result in syntax errors in the database, if not used
* correctly.
* <p>
* If any of the passed values is <code>NULL</code>, then the
* condition will be <code>NULL</code> (or <code>false</code>, depending on
* the dialect) as well. This is standard SQL behaviour.
*/
@NotNull
@Support
Condition notIn(Select<? extends Record1<T>> arg2);
/**
* The <code>NOT_LIKE</code> operator.
*
@ -1428,7 +1454,7 @@ extends
* also to express the "ARRAY contains" operator. For example: <code><pre>
* // Use this expression
* val(new Integer[] { 1, 2, 3 }).contains(new Integer[] { 1, 2 })
*
*
* // ... to render this SQL
* ARRAY[1, 2, 3] @&gt; ARRAY[1, 2]
* </pre></code>
@ -1457,7 +1483,7 @@ extends
* also to express the "ARRAY contains" operator. For example: <code><pre>
* // Use this expression
* val(new Integer[] { 1, 2, 3 }).contains(new Integer[] { 1, 2 })
*
*
* // ... to render this SQL
* ARRAY[1, 2, 3] @&gt; ARRAY[1, 2]
* </pre></code>
@ -2415,19 +2441,6 @@ extends
@Support
Condition in(Field<?>... values);
/**
* Create a condition to check this field against a subquery.
* <p>
* Note that the subquery must return exactly one field. This is not checked
* by jOOQ and will result in syntax errors in the database, if not used
* correctly.
* <p>
* SQL: <code>this in (select...)</code>
*/
@NotNull
@Support
Condition in(Select<? extends Record1<T>> query);
/**
* Create a condition to check this field against several values.
* <p>
@ -2520,23 +2533,6 @@ extends
@Support
Condition notIn(Field<?>... values);
/**
* Create a condition to check this field against a subquery.
* <p>
* Note that the subquery must return exactly one field. This is not checked
* by jOOQ and will result in syntax errors in the database, if not used
* correctly.
* <p>
* Note that if any of the passed values is <code>NULL</code>, then the
* condition will be <code>NULL</code> (or <code>false</code>, depending on
* the dialect) as well. This is standard SQL behaviour.
* <p>
* SQL: <code>this not in (select...)</code>
*/
@NotNull
@Support
Condition notIn(Select<? extends Record1<T>> query);
// ------------------------------------------------------------------------
// BETWEEN predicates
// ------------------------------------------------------------------------

View File

@ -95,7 +95,6 @@ import org.jooq.conf.SettingsTools;
import org.jooq.conf.StatementType;
import org.jooq.tools.StringUtils;
import org.jetbrains.annotations.NotNull;
/**
* @author Lukas Eder
@ -309,6 +308,12 @@ abstract class AbstractContext<C extends Context<C>> extends AbstractScope imple
return (C) this;
}
@Override
public final C visitSubquery(QueryPart part) {
Tools.visitSubquery(this, part);
return (C) this;
}
protected abstract void visit0(QueryPartInternal internal);
private final C toggle(boolean b, BooleanSupplier get, BooleanConsumer set, Consumer<? super C> consumer) {

View File

@ -438,6 +438,12 @@ abstract class AbstractField<T> extends AbstractTypedNamed<T> implements Field<T
return new Gt(this, nullSafe(arg2, getDataType()));
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public final Condition in(Select<? extends Record1<T>> arg2) {
return new In(this, arg2);
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public final Condition isDistinctFrom(T arg2) {
@ -607,6 +613,12 @@ abstract class AbstractField<T> extends AbstractTypedNamed<T> implements Field<T
return ne(arg2);
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public final Condition notIn(Select<? extends Record1<T>> arg2) {
return new NotIn(this, arg2);
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public final LikeEscapeStep notLike(String pattern) {
@ -1247,11 +1259,6 @@ abstract class AbstractField<T> extends AbstractTypedNamed<T> implements Field<T
return in(result.getValues(0, getType()));
}
@Override
public final Condition in(Select<? extends Record1<T>> query) {
return compare(IN, query);
}
@SuppressWarnings("unchecked")
@Override
public final Condition notIn(T... values) {
@ -1282,11 +1289,6 @@ abstract class AbstractField<T> extends AbstractTypedNamed<T> implements Field<T
return notIn(result.getValues(0, getType()));
}
@Override
public final Condition notIn(Select<? extends Record1<T>> query) {
return compare(NOT_IN, query);
}
@Override
public final Condition between(T minValue, T maxValue) {
return between(Tools.field(minValue, this), Tools.field(maxValue, this));
@ -1487,9 +1489,20 @@ abstract class AbstractField<T> extends AbstractTypedNamed<T> implements Field<T
case IS_NOT_DISTINCT_FROM:
return new IsNotDistinctFrom<>(this, nullSafe(field, getDataType()));
default:
return new CompareCondition(this, nullSafe(field, getDataType()), comparator);
case IN:
if (field instanceof ScalarSubquery)
return new In<>(this, (Select<? extends Record1<T>>) ((ScalarSubquery<?>) field).query);
break;
case NOT_IN:
if (field instanceof ScalarSubquery)
return new NotIn<>(this, (Select<? extends Record1<T>>) ((ScalarSubquery<?>) field).query);
break;
}
throw new IllegalArgumentException("Comparator not supported: " + comparator);
}
@Override

View File

@ -73,8 +73,6 @@ final class ArraySelect<T> extends AbstractField<T[]> {
case H2: {
Table<?> t = select.asTable("t", "c");
Field<?> c = t.field("c");
@ -86,8 +84,7 @@ final class ArraySelect<T> extends AbstractField<T[]> {
}
default:
ctx.visit(K_ARRAY);
visitSubquery(ctx, select);
ctx.visit(K_ARRAY).visitSubquery(select);
break;
}

View File

@ -38,81 +38,14 @@
package org.jooq.impl;
import static java.lang.Boolean.TRUE;
import static org.jooq.Clause.CONDITION;
import static org.jooq.Clause.CONDITION_COMPARISON;
import static org.jooq.Comparator.IN;
import static org.jooq.Comparator.NOT_IN;
// ...
import static org.jooq.impl.DSL.asterisk;
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.Tools.nullSafe;
import static org.jooq.impl.Tools.nullableIf;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_MULTISET_CONDITION;
import static org.jooq.impl.Transformations.subqueryWithLimit;
import static org.jooq.impl.Transformations.transformInConditionSubqueryWithLimitToDerivedTable;
import org.jooq.Clause;
import org.jooq.Comparator;
import org.jooq.Context;
import org.jooq.Field;
// ...
import org.jooq.Select;
/**
* @author Lukas Eder
*/
final class CompareCondition extends AbstractCondition {
private static final Clause[] CLAUSES = { CONDITION, CONDITION_COMPARISON };
final Field<?> field1;
final Field<?> field2;
final Comparator comparator;
CompareCondition(Field<?> field1, Field<?> field2, Comparator comparator) {
this.field1 = nullableIf(comparator.supportsNulls(), nullSafe(field1, field2.getDataType()));
this.field2 = nullableIf(comparator.supportsNulls(), nullSafe(field2, field1.getDataType()));
this.comparator = comparator;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public final void accept(Context<?> ctx) {
boolean field1Embeddable = field1.getDataType().isEmbeddable();
SelectQueryImpl<?> s;
if (field1Embeddable && field2 instanceof ScalarSubquery)
ctx.visit(row(embeddedFields(field1)).compare(comparator, ((ScalarSubquery<?>) field2).query));
else if (field1Embeddable && field2.getDataType().isEmbeddable())
ctx.visit(row(embeddedFields(field1)).compare(comparator, embeddedFields(field2)));
else if ((comparator == IN || comparator == NOT_IN)
&& (s = subqueryWithLimit(field2)) != null
&& transformInConditionSubqueryWithLimitToDerivedTable(ctx.configuration())) {
}
else if (field1.getDataType().isMultiset()
&& field2.getDataType().isMultiset()
&& !TRUE.equals(ctx.data(DATA_MULTISET_CONDITION)))
ctx.data(DATA_MULTISET_CONDITION, true, c -> c.visit(this));
else
accept0(ctx);
}
private final void accept0(Context<?> ctx) {
ctx.visit(field1).sql(' ').visit(comparator.toKeyword()).sql(' ').visit(field2);
}
final class CompareCondition {
@ -139,15 +72,4 @@ final class CompareCondition extends AbstractCondition {
@Override
public final Clause[] clauses(Context<?> ctx) {
return CLAUSES;
}
}

View File

@ -97,7 +97,7 @@ extends
Eq.acceptCompareCondition(ctx, this, arg1, arg2, RowN::eq, RowN::eq, c -> c.visit(arg1).sql(" = ").visit(arg2));
Eq.acceptCompareCondition(ctx, this, arg1, org.jooq.Comparator.EQUALS, arg2, RowN::eq, RowN::eq, c -> c.visit(arg1).sql(" = ").visit(arg2));
}
@Override
@ -113,6 +113,7 @@ extends
Context<?> ctx,
AbstractCondition condition,
Field<T> arg1,
org.jooq.Comparator op,
Field<T> arg2,
BiFunction<RowN, Select<?>, Condition> compareRowSubquery,
BiFunction<RowN, RowN, Condition> compareRowRow,
@ -125,6 +126,13 @@ extends
ctx.visit(compareRowSubquery.apply(row(embeddedFields(arg1)), ((ScalarSubquery<?>) arg2).query));
else if (field1Embeddable && arg2.getDataType().isEmbeddable())
ctx.visit(compareRowRow.apply(row(embeddedFields(arg1)), row(embeddedFields(arg2))));
else if ((op == org.jooq.Comparator.IN || op == org.jooq.Comparator.NOT_IN)
&& (s = Transformations.subqueryWithLimit(arg2)) != null
&& Transformations.transformInConditionSubqueryWithLimitToDerivedTable(ctx.configuration())) {
}
else if (arg1.getDataType().isMultiset()
&& arg2.getDataType().isMultiset()
&& !Boolean.TRUE.equals(ctx.data(DATA_MULTISET_CONDITION)))

View File

@ -95,7 +95,7 @@ extends
Eq.acceptCompareCondition(ctx, this, arg1, arg2, RowN::ge, RowN::ge, c -> c.visit(arg1).sql(" >= ").visit(arg2));
Eq.acceptCompareCondition(ctx, this, arg1, org.jooq.Comparator.GREATER_OR_EQUAL, arg2, RowN::ge, RowN::ge, c -> c.visit(arg1).sql(" >= ").visit(arg2));
}
@Override

View File

@ -95,7 +95,7 @@ extends
Eq.acceptCompareCondition(ctx, this, arg1, arg2, RowN::gt, RowN::gt, c -> c.visit(arg1).sql(" > ").visit(arg2));
Eq.acceptCompareCondition(ctx, this, arg1, org.jooq.Comparator.GREATER, arg2, RowN::gt, RowN::gt, c -> c.visit(arg1).sql(" > ").visit(arg2));
}
@Override

View File

@ -0,0 +1,128 @@
/*
* 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.*;
import java.util.function.*;
import java.util.stream.*;
/**
* The <code>IN</code> statement.
*/
@SuppressWarnings({ "rawtypes", "unchecked", "unused" })
final class In<T>
extends
AbstractCondition
{
final Field<T> arg1;
final Select<? extends Record1<T>> arg2;
In(
Field<T> arg1,
Select<? extends Record1<T>> arg2
) {
this.arg1 = nullSafeNotNull(arg1, (DataType) OTHER);
this.arg2 = arg2;
}
// -------------------------------------------------------------------------
// XXX: QueryPart API
// -------------------------------------------------------------------------
@Override
public final void accept(Context<?> ctx) {
ScalarSubquery<T> f = new ScalarSubquery<>(arg2, arg1.getDataType());
Eq.acceptCompareCondition(ctx, this, arg1, org.jooq.Comparator.IN, f, RowN::eq, RowN::eq, c -> c.visit(arg1).sql(' ').visit(K_IN).sql(' ').visit(f));
}
// -------------------------------------------------------------------------
// The Object API
// -------------------------------------------------------------------------
@Override
public boolean equals(Object that) {
if (that instanceof In) {
return
StringUtils.equals(arg1, ((In) that).arg1) &&
StringUtils.equals(arg2, ((In) that).arg2)
;
}
else
return super.equals(that);
}
}

View File

@ -95,7 +95,7 @@ extends
Eq.acceptCompareCondition(ctx, this, arg1, arg2, RowN::le, RowN::le, c -> c.visit(arg1).sql(" <= ").visit(arg2));
Eq.acceptCompareCondition(ctx, this, arg1, org.jooq.Comparator.LESS_OR_EQUAL, arg2, RowN::le, RowN::le, c -> c.visit(arg1).sql(" <= ").visit(arg2));
}
@Override

View File

@ -46,12 +46,6 @@ 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.Comparator.LIKE;
import static org.jooq.Comparator.LIKE_IGNORE_CASE;
import static org.jooq.Comparator.NOT_LIKE;
import static org.jooq.Comparator.NOT_LIKE_IGNORE_CASE;
import static org.jooq.Comparator.NOT_SIMILAR_TO;
import static org.jooq.Comparator.SIMILAR_TO;
import static org.jooq.SQLDialect.*;
import org.jooq.*;
@ -165,7 +159,7 @@ implements
// [#1159] [#1725] Some dialects cannot auto-convert the LHS operand to a
// VARCHAR when applying a LIKE predicate
if ((op == LIKE || op == NOT_LIKE || op == SIMILAR_TO || op == NOT_SIMILAR_TO)
if ((op == org.jooq.Comparator.LIKE || op == org.jooq.Comparator.NOT_LIKE || op == org.jooq.Comparator.SIMILAR_TO || op == org.jooq.Comparator.NOT_SIMILAR_TO)
&& arg1.getType() != String.class
&& REQUIRES_CAST_ON_LIKE.contains(ctx.dialect())) {
arg1 = castIfNeeded(arg1, String.class);
@ -173,10 +167,10 @@ implements
// [#1423] [#9889] PostgreSQL and H2 support ILIKE natively. Other dialects
// need to emulate this as LOWER(lhs) LIKE LOWER(rhs)
else if ((op == LIKE_IGNORE_CASE || op == NOT_LIKE_IGNORE_CASE) && NO_SUPPORT_ILIKE.contains(ctx.dialect())) {
else if ((op == org.jooq.Comparator.LIKE_IGNORE_CASE || op == org.jooq.Comparator.NOT_LIKE_IGNORE_CASE) && NO_SUPPORT_ILIKE.contains(ctx.dialect())) {
arg1 = DSL.lower((Field) arg1);
arg2 = DSL.lower((Field) arg2);
op = (op == LIKE_IGNORE_CASE ? LIKE : NOT_LIKE);
op = (op == org.jooq.Comparator.LIKE_IGNORE_CASE ? org.jooq.Comparator.LIKE : org.jooq.Comparator.NOT_LIKE);
}
boolean castRhs = castRhs(ctx, arg2);

View File

@ -95,7 +95,7 @@ extends
Eq.acceptCompareCondition(ctx, this, arg1, arg2, RowN::lt, RowN::lt, c -> c.visit(arg1).sql(" < ").visit(arg2));
Eq.acceptCompareCondition(ctx, this, arg1, org.jooq.Comparator.LESS, arg2, RowN::lt, RowN::lt, c -> c.visit(arg1).sql(" < ").visit(arg2));
}
@Override

View File

@ -95,7 +95,7 @@ extends
Eq.acceptCompareCondition(ctx, this, arg1, arg2, RowN::ne, RowN::ne, c -> c.visit(arg1).sql(" <> ").visit(arg2));
Eq.acceptCompareCondition(ctx, this, arg1, org.jooq.Comparator.NOT_EQUALS, arg2, RowN::ne, RowN::ne, c -> c.visit(arg1).sql(" <> ").visit(arg2));
}
@Override

View File

@ -0,0 +1,128 @@
/*
* 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.*;
import java.util.function.*;
import java.util.stream.*;
/**
* The <code>NOT IN</code> statement.
*/
@SuppressWarnings({ "rawtypes", "unchecked", "unused" })
final class NotIn<T>
extends
AbstractCondition
{
final Field<T> arg1;
final Select<? extends Record1<T>> arg2;
NotIn(
Field<T> arg1,
Select<? extends Record1<T>> arg2
) {
this.arg1 = nullSafeNotNull(arg1, (DataType) OTHER);
this.arg2 = arg2;
}
// -------------------------------------------------------------------------
// XXX: QueryPart API
// -------------------------------------------------------------------------
@Override
public final void accept(Context<?> ctx) {
ScalarSubquery<T> f = new ScalarSubquery<>(arg2, arg1.getDataType());
Eq.acceptCompareCondition(ctx, this, arg1, org.jooq.Comparator.NOT_IN, f, RowN::eq, RowN::eq, c -> c.visit(arg1).sql(' ').visit(K_NOT_IN).sql(' ').visit(f));
}
// -------------------------------------------------------------------------
// The Object API
// -------------------------------------------------------------------------
@Override
public boolean equals(Object that) {
if (that instanceof NotIn) {
return
StringUtils.equals(arg1, ((NotIn) that).arg1) &&
StringUtils.equals(arg2, ((NotIn) that).arg2)
;
}
else
return super.equals(that);
}
}

View File

@ -50,6 +50,7 @@ import java.util.Set;
import org.jooq.Context;
import org.jooq.DataType;
import org.jooq.Record1;
import org.jooq.SQLDialect;
import org.jooq.Select;
@ -77,8 +78,6 @@ final class ScalarSubquery<T> extends AbstractField<T> {
// HSQLDB allows for using WITH inside of IN, see: https://sourceforge.net/p/hsqldb/bugs/1617/
// We'll still emulate CTE in scalar subqueries with a derived tables in all cases.
if (q != null && q.with != null && NO_SUPPORT_WITH_IN_SCALAR_SUBQUERY.contains(ctx.dialect()))