[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:
parent
23b0c94774
commit
af024cde2d
@ -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.
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user