Skip to content

Commit

Permalink
Case insensitive like operator
Browse files Browse the repository at this point in the history
  • Loading branch information
Shredder121 committed Nov 14, 2015
1 parent 983aab4 commit 68a2783
Show file tree
Hide file tree
Showing 21 changed files with 199 additions and 54 deletions.
Expand Up @@ -17,6 +17,7 @@
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.*;
import java.util.regex.Pattern;

import com.google.common.base.Objects;
import com.mysema.query.types.Expression;
Expand Down Expand Up @@ -252,6 +253,33 @@ public static boolean like(String str, String like, char escape) {
return like(str, like);
}

public static boolean likeIgnoreCase(String str, String like) {
final StringBuilder pattern = new StringBuilder(like.length() + 4);
for (int i = 0; i < like.length(); i++) {
final char ch = like.charAt(i);
if (ch == '%') {
pattern.append(".*");
continue;
} else if (ch == '_') {
pattern.append('.');
continue;
} else if (ch == '.' || ch == '$' || ch == '^') {
pattern.append('\\');
}
pattern.append(ch);
}
if (pattern.toString().equals(like)) {
return str.equalsIgnoreCase(like);
} else {
return Pattern.compile(pattern.toString(), Pattern.CASE_INSENSITIVE)
.matcher(str).matches();
}
}

public static boolean likeIgnoreCase(String str, String like, char escape) {
return likeIgnoreCase(str, like);
}

public static <T> T get(Object parent, String f) {
try {
Field field = ReflectionUtils.getFieldOrNull(parent.getClass(), f);
Expand Down
Expand Up @@ -73,7 +73,9 @@ protected CollQueryTemplates() {

// String
add(Ops.LIKE, functions + ".like({0},{1})");
add(Ops.LIKE_IC, functions + ".likeIgnoreCase({0},{1})");
add(Ops.LIKE_ESCAPE, functions + ".like({0},{1},{2})");
add(Ops.LIKE_ESCAPE_IC, functions + ".likeIgnoreCase({0},{1},{2})");

// Path types
for (PathType type : new PathType[] {
Expand Down
Expand Up @@ -757,6 +757,23 @@ public static Expression<?> toExpression(Object o) {
}
}

/**
* Converts the given expression to lower(expression)
*
* <p>Constants are lower()ed at creation time</p>
*
* @param stringExpression the string to lower()
* @return lower(stringExpression)
*/
public static Expression<String> toLower(Expression<String> stringExpression) {
if (stringExpression instanceof Constant) {
Constant<String> constantExpression = (Constant<String>) stringExpression;
return ConstantImpl.create(constantExpression.getConstant().toLowerCase(Locale.ENGLISH));
} else {
return operation(String.class, Ops.LOWER, stringExpression);
}
}

/**
* Create an expression out of the given order specifiers
*
Expand Down
Expand Up @@ -61,7 +61,7 @@ public JavaTemplates() {
add(Ops.TRIM, "{0}.trim()");
add(Ops.UPPER, "{0}.toUpperCase()");
add(Ops.MATCHES, "{0}.matches({1})");
add(Ops.MATCHES_IC, "{0}.matches({1})");
add(Ops.MATCHES_IC, "{0l}.matches({1l})");
add(Ops.STRING_LENGTH, "{0}.length()");
add(Ops.STRING_IS_EMPTY, "{0}.isEmpty()");
add(Ops.STRING_CONTAINS, "{0}.contains({1})");
Expand Down
4 changes: 4 additions & 0 deletions querydsl-core/src/main/java/com/mysema/query/types/Ops.java
Expand Up @@ -154,8 +154,12 @@ public final class Ops {

public static final Operator<Boolean> LIKE = new OperatorImpl<Boolean>(NS, "LIKE");

public static final Operator<Boolean> LIKE_IC = new OperatorImpl<Boolean>(NS, "LIKE_IC");

public static final Operator<Boolean> LIKE_ESCAPE = new OperatorImpl<Boolean>(NS, "LIKE_ESCAPE");

public static final Operator<Boolean> LIKE_ESCAPE_IC = new OperatorImpl<Boolean>(NS, "LIKE_ESCAPE_IC");

// case
public static final Operator<Object> CASE = new OperatorImpl<Object>(NS, "CASE");

Expand Down
Expand Up @@ -13,6 +13,7 @@
*/
package com.mysema.query.types;

import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.Map;

Expand Down Expand Up @@ -142,7 +143,9 @@ public String escapeForLike(String str) {
add(Ops.INDEX_OF_2ARGS, "indexOf({0},{1},{2})");
add(Ops.STRING_IS_EMPTY, "empty({0})");
add(Ops.LIKE, "{0} like {1}", Precedence.COMPARISON);
add(Ops.LIKE_IC, "{0l} like {1l}", Precedence.COMPARISON);
add(Ops.LIKE_ESCAPE, "{0} like {1} escape '{2s}'", Precedence.COMPARISON);
add(Ops.LIKE_ESCAPE_IC, "{0l} like {1l} escape '{2s}'", Precedence.COMPARISON);

add(Ops.StringOps.LEFT, "left({0},{1})");
add(Ops.StringOps.RIGHT, "right({0},{1})");
Expand Down Expand Up @@ -329,8 +332,12 @@ public final int getPrecedence(Operator<?> op) {
return precedence.get(op).intValue();
}

protected void setPrecedence(int p, Operator... ops) {
for (Operator op : ops) {
protected void setPrecedence(int p, Operator<?>... ops) {
setPrecedence(p, Arrays.asList(ops));
}

protected void setPrecedence(int p, Iterable<? extends Operator<?>> ops) {
for (Operator<?> op : ops) {
precedence.put(op, p);
}
}
Expand Down
Expand Up @@ -319,7 +319,27 @@ public BooleanExpression like(String str) {
public BooleanExpression like(Expression<String> str) {
return BooleanOperation.create(Ops.LIKE, mixin, str);
}


/**
* Expr: {@code this like str} ignoring case.
*
* @param str string
* @return this like string
*/
public BooleanExpression likeIgnoreCase(String str) {
return BooleanOperation.create(Ops.LIKE_IC, mixin, ConstantImpl.create(str));
}

/**
* Expr: {@code this like str} ignoring case.
*
* @param str string
* @return this like string
*/
public BooleanExpression likeIgnoreCase(Expression<String> str) {
return BooleanOperation.create(Ops.LIKE_IC, mixin, str);
}

/**
* Expr: {@code this like str}
*
Expand All @@ -340,6 +360,28 @@ public BooleanExpression like(Expression<String> str, char escape) {
return BooleanOperation.create(Ops.LIKE_ESCAPE, mixin, str, ConstantImpl.create(escape));
}

/**
* Expr: {@code this like str} ignoring case
*
* @param str string
* @param escape escape character
* @return this like string
*/
public BooleanExpression likeIgnoreCase(String str, char escape) {
return BooleanOperation.create(Ops.LIKE_ESCAPE_IC, mixin, ConstantImpl.create(str), ConstantImpl.create(escape));
}

/**
* Expr: {@code this like str} ignoring case
*
* @param str string
* @param escape escape character
* @return this like string
*/
public BooleanExpression likeIgnoreCase(Expression<String> str, char escape) {
return BooleanOperation.create(Ops.LIKE_ESCAPE_IC, mixin, str, ConstantImpl.create(escape));
}

/**
* Get the position of the given String in this String, the first position is 1
*
Expand Down
Expand Up @@ -318,6 +318,14 @@ public Collection<Predicate> string(StringExpression expr, StringExpression othe
rv.add(expr.like("%"+knownValue.substring(1), '!'));
rv.add(expr.like("%"+knownValue.substring(1,2)+"%", '!'));

rv.add(expr.likeIgnoreCase(knownValue.substring(0, 1) + "%"));
rv.add(expr.likeIgnoreCase("%" + knownValue.substring(1)));
rv.add(expr.likeIgnoreCase("%" + knownValue.substring(1, 2) + "%"));

rv.add(expr.likeIgnoreCase(knownValue.substring(0, 1) + "%", '!'));
rv.add(expr.likeIgnoreCase("%" + knownValue.substring(1), '!'));
rv.add(expr.likeIgnoreCase("%" + knownValue.substring(1, 2) + "%", '!'));

rv.add(expr.notLike(knownValue.substring(0,1)+"%"));
rv.add(expr.notLike("%"+knownValue.substring(1)));
rv.add(expr.notLike("%"+knownValue.substring(1,2)+"%"));
Expand Down
Expand Up @@ -279,11 +279,19 @@ protected void visitOperation(Class<?> type, Operator<?> operator, List<? extend
handle(args.get(0)).append(" instanceof ");
append(((Constant<Class<?>>) args.get(1)).getConstant().getName());

} else if (operator == Ops.LIKE || operator == Ops.LIKE_ESCAPE) {
super.visitOperation(type, Ops.MATCHES,
ImmutableList.of(args.get(0), ExpressionUtils.likeToRegex((Expression<String>) args.get(1), false)));

// exists
} else if (operator == Ops.LIKE || operator == Ops.LIKE_ESCAPE || operator == Ops.LIKE_IC || operator == Ops.LIKE_ESCAPE_IC) {
@SuppressWarnings("unchecked") //This is the expected type for like
Expression<String> string = (Expression<String>) args.get(0);
@SuppressWarnings("unchecked") //This is the expected type for like
Expression<String> regex = ExpressionUtils.likeToRegex((Expression<String>) args.get(1), false);
if (operator == Ops.LIKE_IC || operator == Ops.LIKE_ESCAPE_IC) {
string = ExpressionUtils.toLower(string);
regex = ExpressionUtils.toLower(regex);
}
super.visitOperation(type, Ops.MATCHES,
ImmutableList.of(string, regex));

// exists
} else if (operator == Ops.EXISTS && args.get(0) instanceof SubQueryExpression) {
final SubQueryExpression subQuery = (SubQueryExpression) args.get(0);
append("(");
Expand Down
Expand Up @@ -34,7 +34,9 @@ protected JDOQLTemplates() {
add(Ops.EQ_IGNORE_CASE, "{0l}.equals({1l})");
add(Ops.STRING_IS_EMPTY, "{0} == \"\"", Precedence.EQUALITY);
add(Ops.LIKE, "{0}.like({1})");
add(Ops.LIKE_IC, "{0l}.like({1l})");
add(Ops.LIKE_ESCAPE, "{0}.like({1})");
add(Ops.LIKE_ESCAPE_IC, "{0l}.like({1l})");

add(Ops.STRING_CAST, "(String){0}");

Expand Down
19 changes: 13 additions & 6 deletions querydsl-jdo/src/test/java/com/mysema/query/jdo/BasicsTest.java
Expand Up @@ -13,9 +13,17 @@
*/
package com.mysema.query.jdo;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;

import java.io.IOException;

import javax.jdo.PersistenceManager;
import javax.jdo.Transaction;
import java.io.IOException;

import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

import com.google.common.collect.ImmutableList;
import com.mysema.query.BooleanBuilder;
Expand All @@ -26,11 +34,6 @@
import com.mysema.query.jdo.test.domain.QProduct;
import com.mysema.query.types.Expression;
import com.mysema.query.types.Projections;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;

public class BasicsTest extends AbstractJDOTest {

Expand Down Expand Up @@ -222,6 +225,10 @@ public void Starts_With() {
@Test
public void Matches() {
assertEquals("matches", 1, query(product,product.name.matches("Sony.*")).size());
assertEquals(
query(product, product.name.matches("Sony.*")).size(),
query(product, product.name.likeIgnoreCase("sony%")).size()
);
}

@Test
Expand Down
Expand Up @@ -63,6 +63,7 @@ private <A,K,V> List<BooleanExpression> getFilters(
str.substring(1).eq(knownString),
str.substring(1,2).eq(knownString),
str.lower().eq(knownString),
str.likeIgnoreCase(knownString),
str.upper().eq(knownString),
str.matches(".*"),
// java.util.Collection
Expand Down
15 changes: 11 additions & 4 deletions querydsl-jpa/src/main/java/com/mysema/query/jpa/JPQLTemplates.java
Expand Up @@ -13,8 +13,11 @@
*/
package com.mysema.query.jpa;

import java.util.Set;

import javax.annotation.Nullable;

import com.google.common.collect.ImmutableSet;
import com.mysema.query.types.Operator;
import com.mysema.query.types.Ops;
import com.mysema.query.types.PathType;
Expand All @@ -32,6 +35,13 @@ public class JPQLTemplates extends Templates {

public static final char DEFAULT_ESCAPE = '!';

protected static final Set<? extends Operator<?>> OTHER_LIKE_CASES
= ImmutableSet.<Operator<?>>of(Ops.MATCHES, Ops.MATCHES_IC,
Ops.ENDS_WITH, Ops.ENDS_WITH_IC,
Ops.LIKE_IC, Ops.LIKE_ESCAPE_IC,
Ops.STARTS_WITH, Ops.STARTS_WITH_IC,
Ops.STRING_CONTAINS, Ops.STRING_CONTAINS_IC);

public static final JPQLTemplates DEFAULT = new JPQLTemplates();

private final QueryHandler queryHandler;
Expand All @@ -52,10 +62,7 @@ protected JPQLTemplates(char escape, QueryHandler queryHandler) {
Ops.BETWEEN, Ops.COL_IS_EMPTY);

// other like cases
setPrecedence(Precedence.COMPARISON, Ops.MATCHES, Ops.MATCHES_IC,
Ops.ENDS_WITH, Ops.ENDS_WITH_IC,
Ops.STARTS_WITH, Ops.STARTS_WITH_IC,
Ops.STRING_CONTAINS, Ops.STRING_CONTAINS_IC);
setPrecedence(Precedence.COMPARISON, OTHER_LIKE_CASES);

add(Ops.CASE, "case {0} end");
add(Ops.CASE_WHEN, "when {0} then {1} {2}", 0);
Expand Down
Expand Up @@ -147,10 +147,7 @@ public DB2Templates(char escape, boolean quote) {
setPrecedence(Precedence.COMPARISON, Ops.IS_NULL, Ops.IS_NOT_NULL, Ops.LIKE, Ops.LIKE_ESCAPE, Ops.BETWEEN,
Ops.IN, Ops.NOT_IN, Ops.EXISTS);

// other like cases
setPrecedence(Precedence.COMPARISON, Ops.ENDS_WITH, Ops.ENDS_WITH_IC,
Ops.STARTS_WITH, Ops.STARTS_WITH_IC,
Ops.STRING_CONTAINS, Ops.STRING_CONTAINS_IC);
setPrecedence(Precedence.COMPARISON, OTHER_LIKE_CASES);

add(SQLOps.NEXTVAL, "next value for {0s}");

Expand Down
Expand Up @@ -58,10 +58,7 @@ public HSQLDBTemplates(char escape, boolean quote) {
setPrecedence(Precedence.COMPARISON + 1, Ops.IS_NULL, Ops.IS_NOT_NULL, Ops.LIKE, Ops.LIKE_ESCAPE, Ops.BETWEEN,
Ops.IN, Ops.NOT_IN, Ops.EXISTS);

// other like cases
setPrecedence(Precedence.COMPARISON + 1, Ops.ENDS_WITH, Ops.ENDS_WITH_IC,
Ops.STARTS_WITH, Ops.STARTS_WITH_IC,
Ops.STRING_CONTAINS, Ops.STRING_CONTAINS_IC);
setPrecedence(Precedence.COMPARISON + 1, OTHER_LIKE_CASES);

add(Ops.TRIM, "trim(both from {0})");
add(Ops.NEGATE, "{0} * -1", Precedence.ARITH_HIGH);
Expand Down
Expand Up @@ -77,10 +77,7 @@ public OracleTemplates(char escape, boolean quote) {
setPrecedence(Precedence.COMPARISON + 1, Ops.IS_NULL, Ops.IS_NOT_NULL, Ops.LIKE, Ops.LIKE_ESCAPE, Ops.BETWEEN,
Ops.IN, Ops.NOT_IN, Ops.EXISTS);

// other like cases
setPrecedence(Precedence.COMPARISON + 1, Ops.ENDS_WITH, Ops.ENDS_WITH_IC,
Ops.STARTS_WITH, Ops.STARTS_WITH_IC,
Ops.STRING_CONTAINS, Ops.STRING_CONTAINS_IC);
setPrecedence(Precedence.COMPARISON + 1, OTHER_LIKE_CASES);

add(Ops.ALIAS, "{0} {1}");
add(SQLOps.NEXTVAL, "{0s}.nextval");
Expand Down

0 comments on commit 68a2783

Please sign in to comment.