[jOOQ/jOOQ#8853] Support ANY LIKE with subqueries

The implementation of jOOQ/jOOQ#8577 was generalized by building on the
already present quantified predicates support in jOOQ. Thus there are
now the new overloads `Field#like(QuantifiedSelect)` and
`Field#notLike(QuantifiedSelect)` which can be used together with any of
the existing `DSL#any()` and `DSL#all()` overloads. In addition there
are also the new overloads `DSL#any(Field...)` and `DSL#all(Field...)`.

On the implementation side the new predicates are either implemented by
AND- or OR-chaining `LIKE` or `NOT LIKE` predicates (as appropriate) or
for the subquery case by using a query like:

```sql
[TRUE|FALSE] = [ANY|ALL] (
  SELECT <field> {NOT} LIKE "PATTERN"
  FROM <subquery> AS "T"("PATTERN")
)
```
This commit is contained in:
Knut Wannheden 2019-07-03 12:50:41 +02:00
parent 23b0c94774
commit af024cde2d
7 changed files with 358 additions and 237 deletions

View File

@ -1495,6 +1495,25 @@ extends
@Support
Condition like(String value, char escape);
/**
* Create a condition to pattern-check this field against a quantified select.
* <p>
* For example a query like {@code field.like(any("a%", "b%"))} translates into
* the SQL {@code (field like 'a%' or field like 'b%')}.
*
* @see DSL#all(Field)
* @see DSL#all(Field...)
* @see DSL#all(Select)
* @see DSL#all(Object...)
* @see DSL#any(Field)
* @see DSL#any(Field...)
* @see DSL#any(Select)
* @see DSL#any(Object...)
* @see LikeEscapeStep#escape(char)
*/
@Support
LikeEscapeStep like(QuantifiedSelect<Record1<String>> query);
/**
* Create a condition to case-insensitively pattern-check this field against
* a field.
@ -1579,6 +1598,25 @@ extends
@Support
Condition notLike(String value, char escape);
/**
* Create a condition to pattern-check this field against a quantified select.
* <p>
* For example a query like {@code field.notLike(any("a%", "b%"))} translates into
* the SQL {@code (field not like 'a%' or field not like 'b%')}.
*
* @see DSL#all(Field)
* @see DSL#all(Field...)
* @see DSL#all(Select)
* @see DSL#all(Object...)
* @see DSL#any(Field)
* @see DSL#any(Field...)
* @see DSL#any(Select)
* @see DSL#any(Object...)
* @see LikeEscapeStep#escape(char)
*/
@Support
LikeEscapeStep notLike(QuantifiedSelect<Record1<String>> query);
/**
* Create a condition to case-insensitively pattern-check this field against
* a field.
@ -1627,130 +1665,6 @@ extends
@Support
Condition notLikeIgnoreCase(String value, char escape);
/**
* Create a condition to pattern-check this field against any element in an array of values.
* <p>
* SQL: <code>(this like value0 or this like value1 or ...)</code>
*
* @see LikeEscapeStep#escape(char)
*/
@Support
LikeEscapeStep likeAny(String... values);
/**
* Create a condition to pattern-check this field against any element in an array of fields.
* <p>
* SQL: <code>(this like field0 or this like field1 or ...)</code>
*
* @see LikeEscapeStep#escape(char)
*/
@Support
@SuppressWarnings("unchecked")
LikeEscapeStep likeAny(Field<String>... fields);
/**
* Create a condition to pattern-check this field against any element in a collection of fields or values.
* <p>
* SQL: <code>(this like field0 or this like field1 or ...)</code>
*
* @see LikeEscapeStep#escape(char)
*/
@Support
LikeEscapeStep likeAny(Collection<?> values);
/**
* Create a condition to negatively pattern-check this field against any element in an array of values.
* <p>
* SQL: <code>(this not like value0 or this not like value1 or ...)</code>
*
* @see LikeEscapeStep#escape(char)
*/
@Support
LikeEscapeStep notLikeAny(String... values);
/**
* Create a condition to negatively pattern-check this field against any element in an array of values.
* <p>
* SQL: <code>(this not like value0 or this not like value1 or ...)</code>
*
* @see LikeEscapeStep#escape(char)
*/
@Support
@SuppressWarnings("unchecked")
LikeEscapeStep notLikeAny(Field<String>... fields);
/**
* Create a condition to negatively pattern-check this field against any element in an array of values.
* <p>
* SQL: <code>(this not like value0 or this not like value1 or ...)</code>
*
* @see LikeEscapeStep#escape(char)
*/
@Support
LikeEscapeStep notLikeAny(Collection<?> values);
/**
* Create a condition to pattern-check this field against all elements in an array of values.
* <p>
* SQL: <code>(this like value0 and this like value1 and ...)</code>
*
* @see LikeEscapeStep#escape(char)
*/
@Support
LikeEscapeStep likeAll(String... values);
/**
* Create a condition to pattern-check this field against all elements in an array of fields.
* <p>
* SQL: <code>(this like field0 and this like field1 and ...)</code>
*
* @see LikeEscapeStep#escape(char)
*/
@Support
@SuppressWarnings("unchecked")
LikeEscapeStep likeAll(Field<String>... fields);
/**
* Create a condition to pattern-check this field against all elements in a collection of fields or values.
* <p>
* SQL: <code>(this like field0 and this like field1 and ...)</code>
*
* @see LikeEscapeStep#escape(char)
*/
@Support
LikeEscapeStep likeAll(Collection<?> values);
/**
* Create a condition to negatively pattern-check this field against all elements in an array of values.
* <p>
* SQL: <code>(this not like value0 and this not like value1 and ...)</code>
*
* @see LikeEscapeStep#escape(char)
*/
@Support
LikeEscapeStep notLikeAll(String... values);
/**
* Create a condition to negatively pattern-check this field against all elements in an array of fields.
* <p>
* SQL: <code>(this not like field0 and this not like field1 and ...)</code>
*
* @see LikeEscapeStep#escape(char)
*/
@Support
@SuppressWarnings("unchecked")
LikeEscapeStep notLikeAll(Field<String>... fields);
/**
* Create a condition to negatively pattern-check this field against all elements in a collection of fields or values.
* <p>
* SQL: <code>(this not like field0 and this not like field1 and ...)</code>
*
* @see LikeEscapeStep#escape(char)
*/
@Support
LikeEscapeStep notLikeAll(Collection<?> values);
/**
* Convenience method for {@link #like(String, char)} including proper
* adding of wildcards and escaping.

View File

@ -782,6 +782,11 @@ abstract class AbstractField<T> extends AbstractNamed implements Field<T> {
return like(field).escape(escape);
}
@Override
public LikeEscapeStep like(QuantifiedSelect<Record1<String>> query) {
return new QuantifiedComparisonCondition(query, this, LIKE);
}
@Override
public final LikeEscapeStep likeIgnoreCase(String value) {
return likeIgnoreCase(Tools.field(value));
@ -832,6 +837,11 @@ abstract class AbstractField<T> extends AbstractNamed implements Field<T> {
return notLike(field).escape(escape);
}
@Override
public LikeEscapeStep notLike(QuantifiedSelect<Record1<String>> query) {
return new QuantifiedComparisonCondition(query, this, NOT_LIKE);
}
@Override
public final LikeEscapeStep notLikeIgnoreCase(String value) {
return notLikeIgnoreCase(Tools.field(value));
@ -852,70 +862,6 @@ abstract class AbstractField<T> extends AbstractNamed implements Field<T> {
return notLikeIgnoreCase(field).escape(escape);
}
@Override
public final LikeEscapeStep likeAny(String... values) {
return likeAny(Tools.fields(values));
}
@Override
@SuppressWarnings("unchecked")
public final LikeEscapeStep likeAny(Field<String>... fields) {
return likeAny(Arrays.asList(fields));
}
@Override
public final LikeEscapeStep likeAny(Collection<?> values) {
return new CombinedCompareCondition(this, LIKE, Quantifier.ANY, Tools.fields(values, SQLDataType.VARCHAR));
}
@Override
public final LikeEscapeStep notLikeAny(String... values) {
return notLikeAny(Tools.fields(values));
}
@Override
@SuppressWarnings("unchecked")
public final LikeEscapeStep notLikeAny(Field<String>... fields) {
return notLikeAny(Arrays.asList(fields));
}
@Override
public final LikeEscapeStep notLikeAny(Collection<?> values) {
return new CombinedCompareCondition(this, NOT_LIKE, Quantifier.ANY, Tools.fields(values, SQLDataType.VARCHAR));
}
@Override
public final LikeEscapeStep likeAll(String... values) {
return likeAll(Tools.fields(values));
}
@Override
@SuppressWarnings("unchecked")
public final LikeEscapeStep likeAll(Field<String>... fields) {
return likeAll(Arrays.asList(fields));
}
@Override
public final LikeEscapeStep likeAll(Collection<?> values) {
return new CombinedCompareCondition(this, LIKE, Quantifier.ALL, Tools.fields(values, SQLDataType.VARCHAR));
}
@Override
public final LikeEscapeStep notLikeAll(String... values) {
return notLikeAll(Tools.fields(values));
}
@Override
@SuppressWarnings("unchecked")
public final LikeEscapeStep notLikeAll(Field<String>... fields) {
return notLikeAll(Arrays.asList(fields));
}
@Override
public final LikeEscapeStep notLikeAll(Collection<?> values) {
return new CombinedCompareCondition(this, NOT_LIKE, Quantifier.ALL, Tools.fields(values, SQLDataType.VARCHAR));
}
@Override
public final Condition notLikeRegex(String pattern) {
return likeRegex(pattern).not();

View File

@ -8137,6 +8137,7 @@ public class DSL {
* @see Field#greaterOrEqual(QuantifiedSelect)
* @see Field#lessThan(QuantifiedSelect)
* @see Field#lessOrEqual(QuantifiedSelect)
* @see Field#like(QuantifiedSelect)
*/
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
public static <R extends Record> QuantifiedSelect<R> all(Select<R> select) {
@ -8156,6 +8157,7 @@ public class DSL {
* @see Field#greaterOrEqual(QuantifiedSelect)
* @see Field#lessThan(QuantifiedSelect)
* @see Field#lessOrEqual(QuantifiedSelect)
* @see Field#like(QuantifiedSelect)
*/
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
public static <T> QuantifiedSelect<Record1<T>> all(T... array) {
@ -8175,12 +8177,36 @@ public class DSL {
* @see Field#greaterOrEqual(QuantifiedSelect)
* @see Field#lessThan(QuantifiedSelect)
* @see Field#lessOrEqual(QuantifiedSelect)
* @see Field#like(QuantifiedSelect)
*/
@Support({ H2, HSQLDB, POSTGRES })
public static <T> QuantifiedSelect<Record1<T>> all(Field<T[]> array) {
return new QuantifiedSelectImpl<Record1<T>>(Quantifier.ALL, array);
}
/**
* Create an <code>ALL</code> quantified select to be used in quantified
* comparison predicate expressions.
* <p>
* This is natively supported by {@link SQLDialect#POSTGRES}. Other dialects
* will render a subselect unnesting the array.
*
* @see Field#equal(QuantifiedSelect)
* @see Field#notEqual(QuantifiedSelect)
* @see Field#greaterThan(QuantifiedSelect)
* @see Field#greaterOrEqual(QuantifiedSelect)
* @see Field#lessThan(QuantifiedSelect)
* @see Field#lessOrEqual(QuantifiedSelect)
* @see Field#like(QuantifiedSelect)
*/
@Support
@SafeVarargs
public static <T> QuantifiedSelect<Record1<T>> all(Field<T>... fields) {
return new QuantifiedSelectImpl<Record1<T>>(Quantifier.ALL, fields);
}
/**
* Create an <code>ANY</code> quantified select to be used in quantified
* comparison predicate expressions.
@ -8191,6 +8217,7 @@ public class DSL {
* @see Field#greaterOrEqual(QuantifiedSelect)
* @see Field#lessThan(QuantifiedSelect)
* @see Field#lessOrEqual(QuantifiedSelect)
* @see Field#like(QuantifiedSelect)
*/
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
public static <R extends Record> QuantifiedSelect<R> any(Select<R> select) {
@ -8210,6 +8237,7 @@ public class DSL {
* @see Field#greaterOrEqual(QuantifiedSelect)
* @see Field#lessThan(QuantifiedSelect)
* @see Field#lessOrEqual(QuantifiedSelect)
* @see Field#like(QuantifiedSelect)
*/
@Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, MARIADB, MYSQL, POSTGRES })
public static <T> QuantifiedSelect<Record1<T>> any(T... array) {
@ -8229,12 +8257,36 @@ public class DSL {
* @see Field#greaterOrEqual(QuantifiedSelect)
* @see Field#lessThan(QuantifiedSelect)
* @see Field#lessOrEqual(QuantifiedSelect)
* @see Field#like(QuantifiedSelect)
*/
@Support({ H2, HSQLDB, POSTGRES })
public static <T> QuantifiedSelect<Record1<T>> any(Field<T[]> array) {
return new QuantifiedSelectImpl<Record1<T>>(Quantifier.ANY, array);
}
/**
* Create an <code>ANY</code> quantified select to be used in quantified
* comparison predicate expressions.
* <p>
* This is natively supported by {@link SQLDialect#POSTGRES}. Other dialects
* will render a subselect unnesting the array.
*
* @see Field#equal(QuantifiedSelect)
* @see Field#notEqual(QuantifiedSelect)
* @see Field#greaterThan(QuantifiedSelect)
* @see Field#greaterOrEqual(QuantifiedSelect)
* @see Field#lessThan(QuantifiedSelect)
* @see Field#lessOrEqual(QuantifiedSelect)
* @see Field#like(QuantifiedSelect)
*/
@Support
@SafeVarargs
public static <T> QuantifiedSelect<Record1<T>> any(Field<T>... fields) {
return new QuantifiedSelectImpl<Record1<T>>(Quantifier.ANY, fields);
}
// -------------------------------------------------------------------------
// XXX Access control
// -------------------------------------------------------------------------

View File

@ -51,6 +51,8 @@ import static org.jooq.conf.ParseWithMetaLookups.IGNORE_ON_FAILURE;
import static org.jooq.conf.ParseWithMetaLookups.THROW_ON_FAILURE;
import static org.jooq.impl.DSL.abs;
import static org.jooq.impl.DSL.acos;
import static org.jooq.impl.DSL.all;
import static org.jooq.impl.DSL.any;
import static org.jooq.impl.DSL.arrayAgg;
import static org.jooq.impl.DSL.arrayAggDistinct;
import static org.jooq.impl.DSL.ascii;
@ -4400,39 +4402,61 @@ final class ParserImpl implements Parser {
else if (left instanceof Field && parseKeywordIf(ctx, "LIKE")) {
if (parseKeywordIf(ctx, "ANY")) {
parse(ctx, '(');
List<Field<?>> fields = null;
if (parseIf(ctx, ')'))
fields = Collections.<Field<?>> emptyList();
else {
fields = new ArrayList<Field<?>>();
do {
fields.add(toField(ctx, parseConcat(ctx, null)));
}
while (parseIf(ctx, ','));
if (peekKeyword(ctx, "SELECT") || peekKeyword(ctx, "SEL")) {
SelectQueryImpl<Record> select = parseSelect(ctx);
parse(ctx, ')');
boolean escape = parseKeywordIf(ctx, "ESCAPE");
char character = escape ? parseCharacterLiteral(ctx) : ' ';
LikeEscapeStep result = not ? ((Field) left).notLike(any(select)) : ((Field) left).like(any(select));
return escape ? result.escape(character) : result;
}
else {
List<Field<?>> fields = null;
if (parseIf(ctx, ')'))
fields = Collections.<Field<?>> emptyList();
else {
fields = new ArrayList<Field<?>>();
do {
fields.add(toField(ctx, parseConcat(ctx, null)));
}
while (parseIf(ctx, ','));
parse(ctx, ')');
}
boolean escape = parseKeywordIf(ctx, "ESCAPE");
char character = escape ? parseCharacterLiteral(ctx) : ' ';
Field<?>[] fieldArray = fields.toArray(new Field[0]);
LikeEscapeStep result = not ? ((Field) left).notLike(any(fieldArray)) : ((Field) left).like(any(fieldArray));
return escape ? result.escape(character) : result;
}
boolean escape = parseKeywordIf(ctx, "ESCAPE");
char character = escape ? parseCharacterLiteral(ctx) : ' ';
LikeEscapeStep result = not ? ((Field) left).notLikeAny(fields) : ((Field) left).likeAny(fields);
return escape ? result.escape(character) : result;
}
else if (parseKeywordIf(ctx, "ALL")) {
parse(ctx, '(');
List<Field<?>> fields = null;
if (parseIf(ctx, ')'))
fields = Collections.<Field<?>> emptyList();
else {
fields = new ArrayList<Field<?>>();
do {
fields.add(toField(ctx, parseConcat(ctx, null)));
}
while (parseIf(ctx, ','));
if (peekKeyword(ctx, "SELECT") || peekKeyword(ctx, "SEL")) {
SelectQueryImpl<Record> select = parseSelect(ctx);
parse(ctx, ')');
boolean escape = parseKeywordIf(ctx, "ESCAPE");
char character = escape ? parseCharacterLiteral(ctx) : ' ';
LikeEscapeStep result = not ? ((Field) left).notLike(all(select)) : ((Field) left).like(all(select));
return escape ? result.escape(character) : result;
}
else {
List<Field<?>> fields = null;
if (parseIf(ctx, ')'))
fields = Collections.<Field<?>> emptyList();
else {
fields = new ArrayList<Field<?>>();
do {
fields.add(toField(ctx, parseConcat(ctx, null)));
}
while (parseIf(ctx, ','));
parse(ctx, ')');
}
boolean escape = parseKeywordIf(ctx, "ESCAPE");
char character = escape ? parseCharacterLiteral(ctx) : ' ';
Field<?>[] fieldArray = fields.toArray(new Field[0]);
LikeEscapeStep result = not ? ((Field) left).notLike(all(fieldArray)) : ((Field) left).like(all(fieldArray));
return escape ? result.escape(character) : result;
}
boolean escape = parseKeywordIf(ctx, "ESCAPE");
char character = escape ? parseCharacterLiteral(ctx) : ' ';
LikeEscapeStep result = not ? ((Field) left).notLikeAll(fields) : ((Field) left).likeAll(fields);
return escape ? result.escape(character) : result;
}
else {
Field right = toField(ctx, parseConcat(ctx, null));

View File

@ -41,27 +41,45 @@ package org.jooq.impl;
import static org.jooq.Clause.CONDITION;
import static org.jooq.Clause.CONDITION_BETWEEN;
import static org.jooq.impl.DSL.inline;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DSL.row;
import static org.jooq.impl.DSL.select;
import static org.jooq.impl.SQLDataType.VARCHAR;
import static org.jooq.impl.Tools.embeddedFields;
import static org.jooq.impl.Tools.isEmbeddable;
import static org.jooq.tools.Convert.convert;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import org.jooq.Clause;
import org.jooq.Comparator;
import org.jooq.Condition;
import org.jooq.Context;
import org.jooq.Field;
import org.jooq.LikeEscapeStep;
import org.jooq.Operator;
import org.jooq.Param;
import org.jooq.QuantifiedSelect;
import org.jooq.Record1;
import org.jooq.Select;
import org.jooq.Table;
/**
* @author Lukas Eder
*/
final class QuantifiedComparisonCondition extends AbstractCondition {
final class QuantifiedComparisonCondition extends AbstractCondition implements LikeEscapeStep {
private static final long serialVersionUID = -402776705884329740L;
private static final Clause[] CLAUSES = { CONDITION, CONDITION_BETWEEN };
private static final long serialVersionUID = -402776705884329740L;
private static final Clause[] CLAUSES = { CONDITION, CONDITION_BETWEEN };
private static final EnumSet<Comparator> SYNTHETIC_OPERATORS = EnumSet.of(Comparator.LIKE, Comparator.NOT_LIKE, Comparator.LIKE_IGNORE_CASE, Comparator.NOT_LIKE_IGNORE_CASE, Comparator.SIMILAR_TO, Comparator.NOT_SIMILAR_TO);
private final QuantifiedSelect<?> query;
private final Field<?> field;
private final Comparator comparator;
private final QuantifiedSelect<?> query;
private final Field<?> field;
private final Comparator comparator;
private Character escape;
QuantifiedComparisonCondition(QuantifiedSelect<?> query, Field<?> field, Comparator comparator) {
this.query = query;
@ -69,6 +87,12 @@ final class QuantifiedComparisonCondition extends AbstractCondition {
this.comparator = comparator;
}
@Override
public Condition escape(char c) {
this.escape = c;
return this;
}
@Override
public final void accept(Context<?> ctx) {
if (isEmbeddable(field))
@ -77,12 +101,135 @@ final class QuantifiedComparisonCondition extends AbstractCondition {
accept0(ctx);
}
@SuppressWarnings({ "unchecked", "null" })
private final void accept0(Context<?> ctx) {
Field<? extends Object[]> array = ((QuantifiedSelectImpl<?>) query).array;
Field<String>[] values = (Field<String>[]) ((QuantifiedSelectImpl<?>) query).values;
Quantifier quantifier = ((QuantifiedSelectImpl<?>) query).quantifier;
Select<?> subquery = ((QuantifiedSelectImpl<?>) query).query;
if (values != null || array instanceof Param<?>) {
List<Condition> conditions = new ArrayList<Condition>();
if (values != null)
for (Field<String> value : values)
conditions.add(comparisonCondition(comparator, value));
else
for (Object value : ((Param<? extends Object[]>) array).getValue())
conditions.add(value instanceof Field ? comparisonCondition(comparator, (Field<String>) value) : comparisonCondition(comparator, value));
Condition combinedCondition = CombinedCondition.of(quantifier == Quantifier.ALL ? Operator.AND : Operator.OR, conditions);
ctx.visit(combinedCondition);
}
else if ((array != null || subquery != null) && SYNTHETIC_OPERATORS.contains(comparator)) {
Field<String> pattern = DSL.field(name("pattern"), VARCHAR);
Condition cond;
Field<Boolean> lhs;
switch (comparator) {
case NOT_LIKE:
case NOT_SIMILAR_TO:
case NOT_LIKE_IGNORE_CASE:
cond = comparisonCondition(inverse(comparator), pattern);
lhs = inline(false);
break;
case LIKE:
case SIMILAR_TO:
case LIKE_IGNORE_CASE:
cond = comparisonCondition(comparator, pattern);
lhs = inline(true);
break;
default:
throw new IllegalStateException();
}
Table<?> t = (array != null ? new ArrayTable(array) : subquery).asTable("t", "pattern");
Select<Record1<Boolean>> select = select(DSL.field(cond)).from(t);
ctx.visit(lhs.eq(quantifier.apply(select)));
}
else {
accept1(ctx);
}
}
private final void accept1(Context<?> ctx) {
ctx.visit(field)
.sql(' ')
.visit(comparator.toKeyword())
.sql(' ')
.visit(query);
.sql(' ')
.visit(comparator.toKeyword())
.sql(' ')
.visit(query);
}
private Comparator inverse(Comparator operator) {
switch (operator) {
case IN: return Comparator.NOT_IN;
case NOT_IN: return Comparator.IN;
case EQUALS: return Comparator.NOT_EQUALS;
case NOT_EQUALS: return Comparator.EQUALS;
case LESS: return Comparator.GREATER_OR_EQUAL;
case LESS_OR_EQUAL: return Comparator.GREATER;
case GREATER: return Comparator.LESS_OR_EQUAL;
case GREATER_OR_EQUAL: return Comparator.LESS;
case IS_DISTINCT_FROM: return Comparator.IS_NOT_DISTINCT_FROM;
case IS_NOT_DISTINCT_FROM: return Comparator.IS_DISTINCT_FROM;
case LIKE: return Comparator.NOT_LIKE;
case NOT_LIKE: return Comparator.LIKE;
case SIMILAR_TO: return Comparator.NOT_SIMILAR_TO;
case NOT_SIMILAR_TO: return Comparator.SIMILAR_TO;
case LIKE_IGNORE_CASE: return Comparator.NOT_LIKE_IGNORE_CASE;
case NOT_LIKE_IGNORE_CASE: return Comparator.LIKE_IGNORE_CASE;
default: throw new IllegalStateException();
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Condition comparisonCondition(Comparator operator, Field<String> value) {
switch (operator) {
case LIKE:
return escape != null ? field.like(value, escape) : field.like(value);
case NOT_LIKE:
return escape != null ? field.notLike(value, escape) : field.notLike(value);
case SIMILAR_TO:
return escape != null ? field.similarTo(value, escape) : field.similarTo(value);
case NOT_SIMILAR_TO:
return escape != null ? field.notSimilarTo(value, escape) : field.notSimilarTo(value);
case LIKE_IGNORE_CASE:
return escape != null ? field.likeIgnoreCase(value, escape) : field.likeIgnoreCase(value);
case NOT_LIKE_IGNORE_CASE:
return escape != null ? field.notLikeIgnoreCase(value, escape) : field.notLikeIgnoreCase(value);
default:
return ((Field) field).compare(operator, value);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Condition comparisonCondition(Comparator operator, Object value) {
switch (operator) {
case LIKE:
return escape != null ? field.like(convert(value, String.class), escape) : field.like(convert(value, String.class));
case NOT_LIKE:
return escape != null ? field.notLike(convert(value, String.class), escape) : field.notLike(convert(value, String.class));
case SIMILAR_TO:
return escape != null ? field.similarTo(convert(value, String.class), escape) : field.similarTo(convert(value, String.class));
case NOT_SIMILAR_TO:
return escape != null ? field.notSimilarTo(convert(value, String.class), escape) : field.notSimilarTo(convert(value, String.class));
case LIKE_IGNORE_CASE:
return escape != null ? field.likeIgnoreCase(convert(value, String.class), escape) : field.likeIgnoreCase(convert(value, String.class));
case NOT_LIKE_IGNORE_CASE:
return escape != null ? field.notLikeIgnoreCase(convert(value, String.class), escape) : field.notLikeIgnoreCase(convert(value, String.class));
default:
return ((Field) field).compare(operator, value);
}
}
@Override

View File

@ -48,7 +48,7 @@ import org.jooq.Context;
import org.jooq.Field;
import org.jooq.Param;
import org.jooq.QuantifiedSelect;
import org.jooq.QueryPartInternal;
import org.jooq.QueryPart;
import org.jooq.Record;
import org.jooq.Record1;
import org.jooq.Select;
@ -62,22 +62,32 @@ final class QuantifiedSelectImpl<R extends Record> extends AbstractQueryPart imp
/**
* Generated UID
*/
private static final long serialVersionUID = -1224570388944748450L;
private static final long serialVersionUID = -1224570388944748450L;
private final Quantifier quantifier;
private final Select<R> query;
private final Field<? extends Object[]> array;
final Quantifier quantifier;
final Select<R> query;
final Field<? extends Object[]> array;
final Field<?>[] values;
QuantifiedSelectImpl(Quantifier quantifier, Select<R> query) {
this.quantifier = quantifier;
this.query = query;
this.array = null;
this.values = null;
}
QuantifiedSelectImpl(Quantifier quantifier, Field<? extends Object[]> array) {
this.quantifier = quantifier;
this.query = null;
this.array = array;
this.values = null;
}
QuantifiedSelectImpl(Quantifier quantifier, Field<?>... values) {
this.quantifier = quantifier;
this.query = null;
this.array = null;
this.values = values;
}
@Override
@ -106,9 +116,20 @@ final class QuantifiedSelectImpl<R extends Record> extends AbstractQueryPart imp
}
private final QueryPartInternal delegate(Configuration ctx) {
@SuppressWarnings({ "rawtypes", "unchecked" })
private final QueryPart delegate(Configuration ctx) {
if (query != null) {
return (QueryPartInternal) query;
return query;
}
else if (values != null) {
Select<Record1<?>> select = null;
for (Field value : values)
if (select == null)
select = select(value);
else
select = select.unionAll(select(value));
return select;
}
else {
switch (ctx.family()) {
@ -118,14 +139,14 @@ final class QuantifiedSelectImpl<R extends Record> extends AbstractQueryPart imp
case POSTGRES: {
return (QueryPartInternal) array;
return array;
}
// [#869] H2 and HSQLDB can emulate this syntax by unnesting
// the array in a subselect
case H2:
case HSQLDB:
return (QueryPartInternal) create(ctx).select().from(table(array));
return create(ctx).select().from(table(array));
// [#1048] All other dialects emulate unnesting of arrays using
// UNION ALL-connected subselects
@ -134,19 +155,19 @@ final class QuantifiedSelectImpl<R extends Record> extends AbstractQueryPart imp
// The Informix database has an interesting bug when quantified comparison predicates
// use nested derived tables with UNION ALL
if (array instanceof Param) {
Object[] values = ((Param<? extends Object[]>) array).getValue();
Object[] values0 = ((Param<? extends Object[]>) array).getValue();
Select<Record1<Object>> select = null;
for (Object value : values)
for (Object value : values0)
if (select == null)
select = select(val(value));
else
select = select.unionAll(select(val(value)));
return (QueryPartInternal) select;
return select;
}
else {
return (QueryPartInternal) select().from(table(array));
return select().from(table(array));
}
}
}

View File

@ -37,7 +37,13 @@
*/
package org.jooq.impl;
import static org.jooq.impl.DSL.all;
import static org.jooq.impl.DSL.any;
import org.jooq.Keyword;
import org.jooq.QuantifiedSelect;
import org.jooq.Record;
import org.jooq.Select;
/**
* A quantifier used for quantified comparison predicates
@ -73,4 +79,15 @@ enum Quantifier {
public final Keyword toKeyword() {
return keyword;
}
public <R extends Record> QuantifiedSelect<R> apply(Select<R> select) {
switch (this) {
case ANY:
return any(select);
case ALL:
return all(select);
default:
throw new IllegalStateException();
}
}
}