From adf3848278fb2551a2e4471e649611bf8ed9b3ba Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Sat, 14 Jul 2012 13:34:20 +0200 Subject: [PATCH] [#620] Add support for the SQL:2008 standard LIKE_REGEX operator --- .../jooq/test/_/testcases/PredicateTests.java | 25 +++ .../src/org/jooq/test/jOOQAbstractTest.java | 5 + jOOQ/src/main/java/org/jooq/Field.java | 156 ++++++++++++++++++ .../java/org/jooq/impl/AbstractField.java | 20 +++ .../main/java/org/jooq/impl/RegexpLike.java | 130 +++++++++++++++ 5 files changed, 336 insertions(+) create mode 100644 jOOQ/src/main/java/org/jooq/impl/RegexpLike.java diff --git a/jOOQ-test/src/org/jooq/test/_/testcases/PredicateTests.java b/jOOQ-test/src/org/jooq/test/_/testcases/PredicateTests.java index 0d9374c949..9d52dd8268 100644 --- a/jOOQ-test/src/org/jooq/test/_/testcases/PredicateTests.java +++ b/jOOQ-test/src/org/jooq/test/_/testcases/PredicateTests.java @@ -285,6 +285,31 @@ extends BaseTest result = + create().selectFrom(TBook()) + .where(TBook_CONTENT_TEXT().likeRegex(".*conscious.*")) + .or(TBook_TITLE().notLikeRegex(".*m.*")) + .orderBy(TBook_ID()) + .fetch(); + + assertEquals(2, result.size()); + assertEquals(1, (int) result.get(0).getValue(TBook_ID())); + assertEquals(4, (int) result.get(1).getValue(TBook_ID())); + } + @Test public void testLargeINCondition() throws Exception { Field count = count(); diff --git a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java index b30abc9ddf..0647f1949c 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java @@ -1399,6 +1399,11 @@ public abstract class jOOQAbstractTest< new PredicateTests(this).testLike(); } + @Test + public void testLikeRegex() throws Exception { + new PredicateTests(this).testLikeRegex(); + } + @Test public void testDual() throws Exception { new GeneralTests(this).testDual(); diff --git a/jOOQ/src/main/java/org/jooq/Field.java b/jOOQ/src/main/java/org/jooq/Field.java index 00993b7705..088e3726c3 100644 --- a/jOOQ/src/main/java/org/jooq/Field.java +++ b/jOOQ/src/main/java/org/jooq/Field.java @@ -573,6 +573,142 @@ public interface Field extends NamedTypeProviderQueryPart, AliasProvider + * The SQL:2008 standard specifies a <regex like predicate> + * of the following form:
+     * <regex like predicate> ::=
+     *   <row value predicand> <regex like predicate part 2>
+     *
+     * <regex like predicate part 2> ::=
+     *  [ NOT ] LIKE_REGEX <XQuery pattern> [ FLAG <XQuery option flag> ]
+     * 
+ *

+ * This particular LIKE_REGEX operator comes in several + * flavours for various databases. jOOQ supports regular expressions as + * follows: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
SQL dialectSQL syntaxPattern syntaxDocumentation
{@link SQLDialect#ASE}---
{@link SQLDialect#CUBRID}[search] REGEXP [pattern]POSIXhttp://www.cubrid.org/manual/841/en/REGEXP Conditional Expression
{@link SQLDialect#DB2}---
{@link SQLDialect#DERBY}---
{@link SQLDialect#H2}[search] REGEXP [pattern]Javahttp + * ://www.h2database.com/html/grammar.html#condition_right_hand_side
{@link SQLDialect#HSQLDB}REGEXP_MATCHES([search], [pattern])Javahttp://hsqldb.org/doc/guide/builtinfunctions-chapt.html#N13577
{@link SQLDialect#INGRES}---
{@link SQLDialect#MYSQL}[search] REGEXP [pattern]POSIXhttp://dev.mysql.com/doc/refman/5.6/en/regexp.html
{@link SQLDialect#ORACLE}REGEXP_LIKE([search], [pattern])POSIXhttp://docs.oracle.com/cd/E14072_01/server.112/e10592/conditions007.htm# + * sthref1994
{@link SQLDialect#POSTGRES}[search] ~ [pattern]POSIXhttp://www.postgresql.org/docs/9.1/static/functions-matching.html# + * FUNCTIONS-POSIX-REGEXP
{@link SQLDialect#SQLITE}[search] REGEXP [pattern]? This module has to be loaded explicitlyhttp://www.sqlite.org/ + * lang_expr.html
{@link SQLDialect#SQLSERVER}---
{@link SQLDialect#SYBASE}[search] REGEXP [pattern]Perlhttp://infocenter.sybase.com/help/topic/com.sybase.help.sqlanywhere.12.0 + * .1/dbreference/like-regexp-similarto.html
+ * + * @see #likeRegex(String) + */ + @Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, POSTGRES, SQLITE, SYBASE }) + Condition likeRegex(String pattern); + + /** + * Create a condition to regex-pattern-check this field against a pattern + *

+ * See {@link #likeRegex(String)} for more details + * + * @see #likeRegex(String) + */ + @Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, POSTGRES, SQLITE, SYBASE }) + Condition likeRegex(Field pattern); + /** * Create a condition to pattern-check this field against a value *

@@ -649,6 +785,26 @@ public interface Field extends NamedTypeProviderQueryPart, AliasProvider + * See {@link #likeRegex(String)} for more details + * + * @see #likeRegex(String) + */ + @Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, POSTGRES, SQLITE, SYBASE }) + Condition notLikeRegex(String pattern); + + /** + * Create a condition to regex-pattern-check this field against a pattern + *

+ * See {@link #likeRegex(String)} for more details + * + * @see #likeRegex(Field) + */ + @Support({ CUBRID, H2, HSQLDB, MYSQL, ORACLE, POSTGRES, SQLITE, SYBASE }) + Condition notLikeRegex(Field pattern); + /** * Convenience method for {@link #like(String, char)} including proper * adding of wildcards and escaping diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractField.java b/jOOQ/src/main/java/org/jooq/impl/AbstractField.java index 478c0c588e..456017b816 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractField.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractField.java @@ -361,6 +361,16 @@ abstract class AbstractField extends AbstractNamedTypeProviderQueryPart im return new CompareCondition(this, nullSafe(value), Comparator.LIKE_IGNORE_CASE, escape); } + @Override + public final Condition likeRegex(String pattern) { + return likeRegex(val(pattern, String.class)); + } + + @Override + public final Condition likeRegex(Field pattern) { + return new RegexpLike(this, nullSafe(pattern)); + } + @Override public final Condition notLike(String value) { return notLike(val(value, String.class)); @@ -401,6 +411,16 @@ abstract class AbstractField extends AbstractNamedTypeProviderQueryPart im return new CompareCondition(this, nullSafe(value), Comparator.NOT_LIKE_IGNORE_CASE, escape); } + @Override + public final Condition notLikeRegex(String pattern) { + return likeRegex(pattern).not(); + } + + @Override + public final Condition notLikeRegex(Field pattern) { + return likeRegex(pattern).not(); + } + @Override public final Condition contains(T value) { return new Contains(this, value); diff --git a/jOOQ/src/main/java/org/jooq/impl/RegexpLike.java b/jOOQ/src/main/java/org/jooq/impl/RegexpLike.java new file mode 100644 index 0000000000..924d6762ed --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/RegexpLike.java @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq.impl; + +import java.util.List; + +import org.jooq.Attachable; +import org.jooq.BindContext; +import org.jooq.Field; +import org.jooq.RenderContext; + +/** + * @author Lukas Eder + */ +class RegexpLike extends AbstractCondition { + + /** + * Generated UID + */ + private static final long serialVersionUID = 3162855665213654276L; + + private final Field search; + private final Field pattern; + + RegexpLike(Field search, Field pattern) { + this.search = search; + this.pattern = pattern; + } + + @Override + public final void toSQL(RenderContext context) { + switch (context.getDialect()) { + + // [#620] These databases are compatible with the MySQL syntax + case CUBRID: + case H2: + case MYSQL: + case SQLITE: + case SYBASE: { + context.sql(search) + .keyword(" regexp ") + .sql(pattern); + + break; + } + + // [#620] HSQLDB has its own syntax + case HSQLDB: { + + // [#1570] TODO: Replace this by Factory.condition(String, QueryPart...) + context.sql(Factory.field("{regexp_matches}({0}, {1})", Boolean.class, search, pattern)); + break; + } + + // [#620] Postgres has its own syntax + case POSTGRES: { + + // [#1570] TODO: Replace this by Factory.condition(String, QueryPart...) + context.sql(Factory.field("{0} ~ {1}", Boolean.class, search, pattern)); + break; + } + + // [#620] Oracle has its own syntax + case ORACLE: { + + // [#1570] TODO: Replace this by Factory.condition(String, QueryPart...) + context.sql(Factory.field("{regexp_like}({0}, {1})", Boolean.class, search, pattern)); + break; + } + + // Render the SQL standard for those databases that do not support + // regular expressions + case ASE: + case DB2: + case DERBY: + case INGRES: + case SQLSERVER: + default: { + context.sql(search) + .keyword(" like_regex ") + .sql(pattern); + + break; + } + } + } + + @Override + public final void bind(BindContext context) { + context.bind(search).bind(pattern); + } + + @Override + public final List getAttachables() { + return getAttachables(search, pattern); + } +}