From e6f32327100fd225bc1fc8f482a6914f4618fab1 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Fri, 24 Sep 2021 15:13:33 +0200 Subject: [PATCH] [jOOQ/jOOQ#12465] [jOOQ/jOOQ#12432] Extract LIKE, NOT LIKE --- jOOQ/src/main/java/org/jooq/Field.java | 68 +++--- .../main/java/org/jooq/LikeEscapeStep.java | 45 +--- .../java/org/jooq/impl/AbstractField.java | 51 +++-- .../main/java/org/jooq/impl/JSONArray.java | 15 ++ .../main/java/org/jooq/impl/JSONObject.java | 29 ++- jOOQ/src/main/java/org/jooq/impl/Like.java | 209 ++++++++++++++++++ jOOQ/src/main/java/org/jooq/impl/NotLike.java | 155 +++++++++++++ jOOQ/src/main/java/org/jooq/impl/Signal.java | 153 +++++++------ 8 files changed, 553 insertions(+), 172 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/impl/Like.java create mode 100644 jOOQ/src/main/java/org/jooq/impl/NotLike.java diff --git a/jOOQ/src/main/java/org/jooq/Field.java b/jOOQ/src/main/java/org/jooq/Field.java index ee0852ddf0..9b5f78c574 100644 --- a/jOOQ/src/main/java/org/jooq/Field.java +++ b/jOOQ/src/main/java/org/jooq/Field.java @@ -943,6 +943,22 @@ extends @Support Condition lessThan(Field arg2); + /** + * The LIKE operator. + * + * @param pattern is wrapped as {@link #val(Object)}. + */ + @NotNull + @Support + LikeEscapeStep like(@Stringly.Param String pattern); + + /** + * The LIKE operator. + */ + @NotNull + @Support + LikeEscapeStep like(Field pattern); + /** * The LT operator. */ @@ -1006,6 +1022,22 @@ extends @Support Condition notEqual(Field arg2); + /** + * The NOT_LIKE operator. + * + * @param pattern is wrapped as {@link #val(Object)}. + */ + @NotNull + @Support + LikeEscapeStep notLike(@Stringly.Param String pattern); + + /** + * The NOT_LIKE operator. + */ + @NotNull + @Support + LikeEscapeStep notLike(Field pattern); + // ------------------------------------------------------------------------- // XML predicates // ------------------------------------------------------------------------- @@ -2079,15 +2111,6 @@ extends // LIKE predicates // ------------------------------------------------------------------------ - /** - * Create a condition to pattern-check this field against a value. - *

- * SQL: this like value - */ - @NotNull - @Support - LikeEscapeStep like(Field value); - /** * Create a condition to pattern-check this field against a value. *

@@ -2099,15 +2122,6 @@ extends @Support Condition like(Field value, char escape); - /** - * Create a condition to pattern-check this field against a value. - *

- * SQL: this like value - */ - @NotNull - @Support - LikeEscapeStep like(String value); - /** * Create a condition to pattern-check this field against a value. *

@@ -2191,15 +2205,6 @@ extends @Support Condition likeIgnoreCase(String value, char escape); - /** - * Create a condition to pattern-check this field against a field. - *

- * SQL: this not like field - */ - @NotNull - @Support - LikeEscapeStep notLike(Field field); - /** * Create a condition to pattern-check this field against a field. *

@@ -2211,15 +2216,6 @@ extends @Support Condition notLike(Field field, char escape); - /** - * Create a condition to pattern-check this field against a value. - *

- * SQL: this not like value - */ - @NotNull - @Support - LikeEscapeStep notLike(String value); - /** * Create a condition to pattern-check this field against a value. *

diff --git a/jOOQ/src/main/java/org/jooq/LikeEscapeStep.java b/jOOQ/src/main/java/org/jooq/LikeEscapeStep.java index f507ddc930..5cf5f5fc28 100644 --- a/jOOQ/src/main/java/org/jooq/LikeEscapeStep.java +++ b/jOOQ/src/main/java/org/jooq/LikeEscapeStep.java @@ -37,37 +37,14 @@ */ package org.jooq; -// ... -// ... -// ... -// ... -import static org.jooq.SQLDialect.CUBRID; -// ... -import static org.jooq.SQLDialect.DERBY; -import static org.jooq.SQLDialect.FIREBIRD; -import static org.jooq.SQLDialect.H2; -// ... -import static org.jooq.SQLDialect.HSQLDB; -import static org.jooq.SQLDialect.IGNITE; -// ... -// ... -import static org.jooq.SQLDialect.MARIADB; -import static org.jooq.SQLDialect.MYSQL; -// ... -import static org.jooq.SQLDialect.POSTGRES; -// ... -// ... -import static org.jooq.SQLDialect.SQLITE; -// ... -// ... -// ... -// ... +import static org.jooq.SQLDialect.*; -import org.jetbrains.annotations.NotNull; +import java.util.*; + +import org.jetbrains.annotations.*; /** - * A step in the creation of a LIKE predicate to which an - * ESCAPE clause can be appended. + * A step in the construction of the NOT LIKE function. *

*

Referencing XYZ*Step types directly from client code

*

@@ -86,22 +63,20 @@ import org.jetbrains.annotations.NotNull; *

  • They're less readable
  • *
  • They might have binary incompatible changes between minor releases
  • * - * - * @author Lukas Eder */ +@SuppressWarnings({ "unused" }) public interface LikeEscapeStep extends Condition { - /** - * Add an ESCAPE clause to the LIKE predicate. + * Add the ESCAPE clause to the NOT LIKE function. *

    * For example: - * + * *

          * some_column LIKE 'A!%%' ESCAPE '!'
          * 
    */ - @NotNull @Support({ CUBRID, DERBY, FIREBIRD, H2, HSQLDB, IGNITE, MARIADB, MYSQL, POSTGRES, SQLITE }) - Condition escape(char c); + @NotNull @CheckReturnValue + Condition escape(char escape); } diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractField.java b/jOOQ/src/main/java/org/jooq/impl/AbstractField.java index 0db58d09e2..888f4a99ee 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractField.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractField.java @@ -532,6 +532,18 @@ abstract class AbstractField extends AbstractTypedNamed implements Field pattern) { + return new Like(this, nullSafe(pattern, getDataType())); + } + @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public final Condition lt(T arg2) { @@ -583,6 +595,18 @@ abstract class AbstractField extends AbstractTypedNamed implements Field pattern) { + return new NotLike(this, nullSafe(pattern, getDataType())); + } + // ------------------------------------------------------------------------- // XML predicates // ------------------------------------------------------------------------- @@ -1067,19 +1091,9 @@ abstract class AbstractField extends AbstractTypedNamed implements Field field) { - return new CompareCondition(this, nullSafe(field, getDataType()), LIKE); + return like(value).escape(escape); } @Override @@ -1122,21 +1136,11 @@ abstract class AbstractField extends AbstractTypedNamed implements Field field) { - return new CompareCondition(this, nullSafe(field, getDataType()), NOT_LIKE); - } - @Override public final Condition notLike(Field field, char escape) { return notLike(field).escape(escape); @@ -1456,6 +1460,11 @@ abstract class AbstractField extends AbstractTypedNamed implements Field(this, nullSafe(field, getDataType())); + case LIKE: + return new Like(this, (Field) nullSafe(field, getDataType())); + case NOT_LIKE: + return new NotLike(this, (Field) nullSafe(field, getDataType())); + case IS_DISTINCT_FROM: return new IsDistinctFrom<>(this, nullSafe(field, getDataType())); case IS_NOT_DISTINCT_FROM: diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONArray.java b/jOOQ/src/main/java/org/jooq/impl/JSONArray.java index 928489fbdd..8b2a7045e9 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JSONArray.java +++ b/jOOQ/src/main/java/org/jooq/impl/JSONArray.java @@ -204,6 +204,21 @@ implements + + + + + + + + + + + + + + + // ------------------------------------------------------------------------- // The Object API // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONObject.java b/jOOQ/src/main/java/org/jooq/impl/JSONObject.java index d4d19ee895..8b27bd31d8 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JSONObject.java +++ b/jOOQ/src/main/java/org/jooq/impl/JSONObject.java @@ -179,6 +179,9 @@ implements + + + @@ -213,17 +216,6 @@ implements || field instanceof ScalarSubquery && isJSONArray(((ScalarSubquery) field).query.getSelect().get(0)); } - - - - - - - - - - - private final void acceptStandard(Context ctx) { JSONNull jsonNull; JSONReturning jsonReturning = new JSONReturning(returning); @@ -244,6 +236,21 @@ implements + + + + + + + + + + + + + + + // ------------------------------------------------------------------------- // The Object API // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/Like.java b/jOOQ/src/main/java/org/jooq/impl/Like.java new file mode 100644 index 0000000000..6b9a6ffe25 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/Like.java @@ -0,0 +1,209 @@ +/* + * 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 LIKE statement. + */ +@SuppressWarnings({ "hiding", "rawtypes", "unchecked", "unused" }) +final class Like +extends + AbstractCondition +implements + LikeEscapeStep +{ + + final Field value; + final Field pattern; + Character escape; + + Like( + Field value, + Field pattern + ) { + this( + value, + pattern, + null + ); + } + + Like( + Field value, + Field pattern, + Character escape + ) { + + this.value = nullSafeNotNull(value, OTHER); + this.pattern = nullSafeNotNull(pattern, VARCHAR); + this.escape = escape; + } + + // ------------------------------------------------------------------------- + // XXX: DSL API + // ------------------------------------------------------------------------- + + @Override + public final Like escape(char escape) { + this.escape = escape; + return this; + } + + // ------------------------------------------------------------------------- + // XXX: QueryPart API + // ------------------------------------------------------------------------- + + + + private static final Set REQUIRES_CAST_ON_LIKE = SQLDialect.supportedBy(DERBY, POSTGRES, YUGABYTE); + + @Override + public final void accept(Context ctx) { + + + + + + + + + accept0(ctx, value, org.jooq.Comparator.LIKE, pattern, escape); + } + + static final boolean castRhs(Context ctx, Field arg2) { + boolean castRhs = false; + + + + + + + + + + return castRhs; + } + + static final ParamType forcedParamType(Context ctx, Character escape) { + ParamType forcedParamType = ctx.paramType(); + + + + + + + + + return forcedParamType; + } + + static final void accept0(Context ctx, Field arg1, org.jooq.Comparator op, Field arg2, Character escape) { + + // [#1159] [#1725] Some dialects cannot auto-convert the LHS operand to a + // VARCHAR when applying a LIKE predicate + if (arg1.getType() != String.class && REQUIRES_CAST_ON_LIKE.contains(ctx.dialect())) + arg1 = castIfNeeded(arg1, String.class); + + boolean castRhs = castRhs(ctx, arg2); + + ctx.visit(arg1).sql(' ').visit(op.toKeyword()).sql(' '); + + if (castRhs) + ctx.visit(K_CAST).sql('('); + + ctx.visit(arg2, forcedParamType(ctx, escape)); + + if (castRhs) + ctx.sql(' ').visit(K_AS).sql(' ').visit(K_VARCHAR).sql("(4000))"); + + if (escape != null) { + ctx.sql(' ').visit(K_ESCAPE).sql(' ') + .visit(inline(escape)); + } + } + + + + + + + + + + + + + // ------------------------------------------------------------------------- + // The Object API + // ------------------------------------------------------------------------- + + @Override + public boolean equals(Object that) { + if (that instanceof Like) { + return + StringUtils.equals(value, ((Like) that).value) && + StringUtils.equals(pattern, ((Like) that).pattern) && + StringUtils.equals(escape, ((Like) that).escape) + ; + } + else + return super.equals(that); + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/NotLike.java b/jOOQ/src/main/java/org/jooq/impl/NotLike.java new file mode 100644 index 0000000000..e814e57fe8 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/NotLike.java @@ -0,0 +1,155 @@ +/* + * 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 NOT LIKE statement. + */ +@SuppressWarnings({ "hiding", "rawtypes", "unchecked", "unused" }) +final class NotLike +extends + AbstractCondition +implements + LikeEscapeStep +{ + + final Field value; + final Field pattern; + Character escape; + + NotLike( + Field value, + Field pattern + ) { + this( + value, + pattern, + null + ); + } + + NotLike( + Field value, + Field pattern, + Character escape + ) { + + this.value = nullSafeNotNull(value, OTHER); + this.pattern = nullSafeNotNull(pattern, VARCHAR); + this.escape = escape; + } + + // ------------------------------------------------------------------------- + // XXX: DSL API + // ------------------------------------------------------------------------- + + @Override + public final NotLike escape(char escape) { + this.escape = escape; + return this; + } + + // ------------------------------------------------------------------------- + // XXX: QueryPart API + // ------------------------------------------------------------------------- + + + + @Override + public final void accept(Context ctx) { + + + + + + + + + Like.accept0(ctx, value, org.jooq.Comparator.NOT_LIKE, pattern, escape); + } + + + + + + + + + + + + + // ------------------------------------------------------------------------- + // The Object API + // ------------------------------------------------------------------------- + + @Override + public boolean equals(Object that) { + if (that instanceof NotLike) { + return + StringUtils.equals(value, ((NotLike) that).value) && + StringUtils.equals(pattern, ((NotLike) that).pattern) && + StringUtils.equals(escape, ((NotLike) that).escape) + ; + } + else + return super.equals(that); + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/Signal.java b/jOOQ/src/main/java/org/jooq/impl/Signal.java index 7a6937b26f..08b52dff5c 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Signal.java +++ b/jOOQ/src/main/java/org/jooq/impl/Signal.java @@ -124,72 +124,87 @@ package org.jooq.impl; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + case POSTGRES: + case YUGABYTE: + acceptPostgres(ctx); + break; + + + case HSQLDB: + case MARIADB: + case MYSQL: + acceptDefaultPullingUpDeclarations(ctx); + break; + + default: + acceptDefault(ctx, value, messageText); + break; + } + } + + private final void acceptPostgres(Context ctx) { + ctx.visit(K_RAISE).sql(' ').visit(K_SQLSTATE).sql(' ').visit(value); + + if (messageText != null) + ctx.sql(' ').visit(K_USING).sql(' ').visit(K_MESSAGE).sql(" = ").visit(messageText); + } + + private final void acceptDefaultPullingUpDeclarations(Context ctx) { + boolean bv = value instanceof ParamOrVariable; + boolean bm = messageText == null || messageText instanceof ParamOrVariable; + + if (bv && bm) { + ctx.paramType(ParamType.INLINED, c -> acceptDefault(c, value, messageText)); + } + else { + List s = new ArrayList<>(); + + Field v = bv ? value : variable("sqlstate", CHAR(5)); + Field m = bm ? messageText : variable("messagetext", VARCHAR); + + if (v != value) + s.add(declare((Variable) v).set(value)); + if (m != messageText) + s.add(declare((Variable) m).set(messageText)); + + s.add(messageText != null ? signalSQLState(v).setMessageText(m) : signalSQLState(v)); + ctx.visit(begin(s)); + } + } + + private static final void acceptDefault(Context ctx, Field value, Field messageText) { + ctx.visit(K_SIGNAL).sql(' ').visit(ctx.family() == HANA ? K_SQL_ERROR_CODE : K_SQLSTATE).sql(' ').visit(value); + + if (messageText != null) + ctx.sql(' ').visit(K_SET).sql(' ').visit(K_MESSAGE_TEXT).sql(" = ").visit(messageText); + } + + + + @Pro + private final void acceptJava(Context ctx) { + ctx.sql("signalSQLState("); + String s = ""; + ctx.sql(s).visit(Val.getJavaValue(value)); s = ", "; + ctx.sql(s).visit(Val.getJavaValue(messageText)); s = ", "; + ctx.sql(')'); + } + + // ------------------------------------------------------------------------- + // The Object API + // ------------------------------------------------------------------------- + + @Override + public boolean equals(Object that) { + if (that instanceof Signal) { + return + StringUtils.equals(value, ((Signal) that).value) && + StringUtils.equals(messageText, ((Signal) that).messageText) + ; + } + else + return super.equals(that); + } +} + +/* [/pro] */