You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2014/11/27 08:12:01 UTC

[18/39] cayenne git commit: CAY-1971 Variants of Property.like(..) : contains(..), startsWith(..), endsWith(..)

CAY-1971 Variants of Property.like(..) : contains(..), startsWith(..), endsWith(..)


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/9587d047
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/9587d047
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/9587d047

Branch: refs/heads/CAY-1946_1
Commit: 9587d0475b3c3f44150fcf8fc50ea772e9ea40ea
Parents: f11fc5c
Author: aadamchik <aa...@apache.org>
Authored: Sat Nov 22 14:46:31 2014 +0300
Committer: aadamchik <aa...@apache.org>
Committed: Sat Nov 22 16:25:50 2014 +0300

----------------------------------------------------------------------
 .../apache/cayenne/exp/ExpressionFactory.java   | 179 ++++++++++++-------
 .../cayenne/exp/LikeExpressionHelper.java       |  73 ++++++++
 .../java/org/apache/cayenne/exp/Property.java   |  99 +++++++++-
 .../org/apache/cayenne/exp/PropertyTest.java    |  21 +++
 4 files changed, 298 insertions(+), 74 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/9587d047/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
index dc1d815..cbef42d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
@@ -93,20 +93,14 @@ public class ExpressionFactory {
 
 		// make sure all types are small integers, then we can use
 		// them as indexes in lookup array
-		int[] allTypes = new int[] { Expression.AND, Expression.OR,
-				Expression.NOT, Expression.EQUAL_TO, Expression.NOT_EQUAL_TO,
-				Expression.LESS_THAN, Expression.GREATER_THAN,
-				Expression.LESS_THAN_EQUAL_TO,
-				Expression.GREATER_THAN_EQUAL_TO, Expression.BETWEEN,
-				Expression.IN, Expression.LIKE, Expression.LIKE_IGNORE_CASE,
-				Expression.ADD, Expression.SUBTRACT, Expression.MULTIPLY,
-				Expression.DIVIDE, Expression.NEGATIVE, Expression.OBJ_PATH,
-				Expression.DB_PATH, Expression.LIST, Expression.NOT_BETWEEN,
-				Expression.NOT_IN, Expression.NOT_LIKE,
-				Expression.NOT_LIKE_IGNORE_CASE, Expression.TRUE,
-				Expression.FALSE, Expression.BITWISE_NOT,
-				Expression.BITWISE_AND, Expression.BITWISE_OR,
-				Expression.BITWISE_XOR, Expression.BITWISE_LEFT_SHIFT,
+		int[] allTypes = new int[] { Expression.AND, Expression.OR, Expression.NOT, Expression.EQUAL_TO,
+				Expression.NOT_EQUAL_TO, Expression.LESS_THAN, Expression.GREATER_THAN, Expression.LESS_THAN_EQUAL_TO,
+				Expression.GREATER_THAN_EQUAL_TO, Expression.BETWEEN, Expression.IN, Expression.LIKE,
+				Expression.LIKE_IGNORE_CASE, Expression.ADD, Expression.SUBTRACT, Expression.MULTIPLY,
+				Expression.DIVIDE, Expression.NEGATIVE, Expression.OBJ_PATH, Expression.DB_PATH, Expression.LIST,
+				Expression.NOT_BETWEEN, Expression.NOT_IN, Expression.NOT_LIKE, Expression.NOT_LIKE_IGNORE_CASE,
+				Expression.TRUE, Expression.FALSE, Expression.BITWISE_NOT, Expression.BITWISE_AND,
+				Expression.BITWISE_OR, Expression.BITWISE_XOR, Expression.BITWISE_LEFT_SHIFT,
 				Expression.BITWISE_RIGHT_SHIFT };
 
 		int max = 0;
@@ -322,19 +316,15 @@ public class ExpressionFactory {
 
 			int splitEnd = path.indexOf(Entity.PATH_SEPARATOR, split + 1);
 
-			String beforeSplit = split > 0 ? path.substring(0, split) + "."
-					: "";
-			String afterSplit = splitEnd > 0 ? "."
-					+ path.substring(splitEnd + 1) : "";
+			String beforeSplit = split > 0 ? path.substring(0, split) + "." : "";
+			String afterSplit = splitEnd > 0 ? "." + path.substring(splitEnd + 1) : "";
 			String aliasBase = "split" + autoAliasId++ + "_";
-			String splitChunk = splitEnd > 0 ? path.substring(split + 1,
-					splitEnd) : path.substring(split + 1);
+			String splitChunk = splitEnd > 0 ? path.substring(split + 1, splitEnd) : path.substring(split + 1);
 
 			// fix the path - replace split with dot if it's in the middle, or
 			// strip it if
 			// it's in the beginning
-			path = split == 0 ? path.substring(1) : path.replace(
-					SPLIT_SEPARATOR, '.');
+			path = split == 0 ? path.substring(1) : path.replace(SPLIT_SEPARATOR, '.');
 
 			int i = 0;
 			for (Object value : values) {
@@ -344,8 +334,7 @@ public class ExpressionFactory {
 				i++;
 
 				ASTPath pathExp = new ASTObjPath(aliasedPath);
-				pathExp.setPathAliases(Collections.singletonMap(alias,
-						splitChunk));
+				pathExp.setPathAliases(Collections.singletonMap(alias, splitChunk));
 				matches.add(new ASTEqual(pathExp, value));
 			}
 		} else {
@@ -572,8 +561,7 @@ public class ExpressionFactory {
 	/**
 	 * A convenience shortcut for building BETWEEN expressions.
 	 */
-	public static Expression betweenExp(String pathSpec, Object value1,
-			Object value2) {
+	public static Expression betweenExp(String pathSpec, Object value1, Object value2) {
 		return new ASTBetween(new ASTObjPath(pathSpec), value1, value2);
 	}
 
@@ -582,16 +570,14 @@ public class ExpressionFactory {
 	 * 
 	 * @since 3.0
 	 */
-	public static Expression betweenDbExp(String pathSpec, Object value1,
-			Object value2) {
+	public static Expression betweenDbExp(String pathSpec, Object value1, Object value2) {
 		return new ASTBetween(new ASTDbPath(pathSpec), value1, value2);
 	}
 
 	/**
 	 * A convenience shortcut for building NOT_BETWEEN expressions.
 	 */
-	public static Expression notBetweenExp(String pathSpec, Object value1,
-			Object value2) {
+	public static Expression notBetweenExp(String pathSpec, Object value1, Object value2) {
 		return new ASTNotBetween(new ASTObjPath(pathSpec), value1, value2);
 	}
 
@@ -600,8 +586,7 @@ public class ExpressionFactory {
 	 * 
 	 * @since 3.0
 	 */
-	public static Expression notBetweenDbExp(String pathSpec, Object value1,
-			Object value2) {
+	public static Expression notBetweenDbExp(String pathSpec, Object value1, Object value2) {
 		return new ASTNotBetween(new ASTDbPath(pathSpec), value1, value2);
 	}
 
@@ -609,7 +594,7 @@ public class ExpressionFactory {
 	 * A convenience shortcut for building LIKE expression.
 	 */
 	public static Expression likeExp(String pathSpec, Object value) {
-		return new ASTLike(new ASTObjPath(pathSpec), value);
+		return likeExpInternal(pathSpec, value, (char) 0);
 	}
 
 	/**
@@ -624,8 +609,11 @@ public class ExpressionFactory {
 	 * 
 	 * @since 3.0.1
 	 */
-	public static Expression likeExp(String pathSpec, Object value,
-			char escapeChar) {
+	public static Expression likeExp(String pathSpec, Object value, char escapeChar) {
+		return likeExpInternal(pathSpec, value, escapeChar);
+	}
+
+	static ASTLike likeExpInternal(String pathSpec, Object value, char escapeChar) {
 		return new ASTLike(new ASTObjPath(pathSpec), value, escapeChar);
 	}
 
@@ -650,8 +638,7 @@ public class ExpressionFactory {
 	 * 
 	 * @since 3.0.1
 	 */
-	public static Expression likeDbExp(String pathSpec, Object value,
-			char escapeChar) {
+	public static Expression likeDbExp(String pathSpec, Object value, char escapeChar) {
 		return new ASTLike(new ASTDbPath(pathSpec), value, escapeChar);
 	}
 
@@ -674,8 +661,7 @@ public class ExpressionFactory {
 	 * 
 	 * @since 3.0.1
 	 */
-	public static Expression notLikeExp(String pathSpec, Object value,
-			char escapeChar) {
+	public static Expression notLikeExp(String pathSpec, Object value, char escapeChar) {
 		return new ASTNotLike(new ASTObjPath(pathSpec), value, escapeChar);
 	}
 
@@ -700,8 +686,7 @@ public class ExpressionFactory {
 	 * 
 	 * @since 3.0.1
 	 */
-	public static Expression notLikeDbExp(String pathSpec, Object value,
-			char escapeChar) {
+	public static Expression notLikeDbExp(String pathSpec, Object value, char escapeChar) {
 		return new ASTNotLike(new ASTDbPath(pathSpec), value, escapeChar);
 	}
 
@@ -709,7 +694,7 @@ public class ExpressionFactory {
 	 * A convenience shortcut for building LIKE_IGNORE_CASE expression.
 	 */
 	public static Expression likeIgnoreCaseExp(String pathSpec, Object value) {
-		return new ASTLikeIgnoreCase(new ASTObjPath(pathSpec), value);
+		return likeIgnoreCaseExpInternal(pathSpec, value, (char) 0);
 	}
 
 	/**
@@ -724,10 +709,12 @@ public class ExpressionFactory {
 	 * 
 	 * @since 3.0.1
 	 */
-	public static Expression likeIgnoreCaseExp(String pathSpec, Object value,
-			char escapeChar) {
-		return new ASTLikeIgnoreCase(new ASTObjPath(pathSpec), value,
-				escapeChar);
+	public static Expression likeIgnoreCaseExp(String pathSpec, Object value, char escapeChar) {
+		return likeIgnoreCaseExpInternal(pathSpec, value, escapeChar);
+	}
+
+	static ASTLikeIgnoreCase likeIgnoreCaseExpInternal(String pathSpec, Object value, char escapeChar) {
+		return new ASTLikeIgnoreCase(new ASTObjPath(pathSpec), value, escapeChar);
 	}
 
 	/**
@@ -751,8 +738,7 @@ public class ExpressionFactory {
 	 * 
 	 * @since 3.0.1
 	 */
-	public static Expression likeIgnoreCaseDbExp(String pathSpec, Object value,
-			char escapeChar) {
+	public static Expression likeIgnoreCaseDbExp(String pathSpec, Object value, char escapeChar) {
 		return new ASTLikeIgnoreCase(new ASTDbPath(pathSpec), value, escapeChar);
 	}
 
@@ -775,10 +761,8 @@ public class ExpressionFactory {
 	 * 
 	 * @since 3.0.1
 	 */
-	public static Expression notLikeIgnoreCaseExp(String pathSpec,
-			Object value, char escapeChar) {
-		return new ASTNotLikeIgnoreCase(new ASTObjPath(pathSpec), value,
-				escapeChar);
+	public static Expression notLikeIgnoreCaseExp(String pathSpec, Object value, char escapeChar) {
+		return new ASTNotLikeIgnoreCase(new ASTObjPath(pathSpec), value, escapeChar);
 	}
 
 	/**
@@ -786,8 +770,7 @@ public class ExpressionFactory {
 	 * 
 	 * @since 3.0
 	 */
-	public static Expression notLikeIgnoreCaseDbExp(String pathSpec,
-			Object value) {
+	public static Expression notLikeIgnoreCaseDbExp(String pathSpec, Object value) {
 		return new ASTNotLikeIgnoreCase(new ASTDbPath(pathSpec), value);
 	}
 
@@ -803,10 +786,77 @@ public class ExpressionFactory {
 	 * 
 	 * @since 3.0.1
 	 */
-	public static Expression notLikeIgnoreCaseDbExp(String pathSpec,
-			Object value, char escapeChar) {
-		return new ASTNotLikeIgnoreCase(new ASTDbPath(pathSpec), value,
-				escapeChar);
+	public static Expression notLikeIgnoreCaseDbExp(String pathSpec, Object value, char escapeChar) {
+		return new ASTNotLikeIgnoreCase(new ASTDbPath(pathSpec), value, escapeChar);
+	}
+
+	/**
+	 * @return An expression for a database "LIKE" query with the value
+	 *         converted to a pattern matching anywhere in the String.
+	 * @since 4.0
+	 */
+	public static Expression containsExp(String pathSpec, String value) {
+		ASTLike like = likeExpInternal(pathSpec, value, (char) 0);
+		LikeExpressionHelper.toContains(like);
+		return like;
+	}
+
+	/**
+	 * @return An expression for a database "LIKE" query with the value
+	 *         converted to a pattern matching the beginning of the String.
+	 * @since 4.0
+	 */
+	public static Expression startsWithExp(String pathSpec, String value) {
+		ASTLike like = likeExpInternal(pathSpec, value, (char) 0);
+		LikeExpressionHelper.toStartsWith(like);
+		return like;
+	}
+
+	/**
+	 * @return An expression for a database "LIKE" query with the value
+	 *         converted to a pattern matching the beginning of the String.
+	 * @since 4.0
+	 */
+	public static Expression endsWithExp(String pathSpec, String value) {
+		ASTLike like = likeExpInternal(pathSpec, value, (char) 0);
+		LikeExpressionHelper.toEndsWith(like);
+		return like;
+	}
+
+	/**
+	 * Same as {@link #containsExp(String, String)} only using case-insensitive
+	 * comparison.
+	 * 
+	 * @since 4.0
+	 */
+	public static Expression containsIgnoreCaseExp(String pathSpec, String value) {
+		ASTLikeIgnoreCase like = likeIgnoreCaseExpInternal(pathSpec, value, (char) 0);
+		LikeExpressionHelper.toContains(like);
+		return like;
+	}
+
+	/**
+	 * Same as {@link #startsWithExp(String, String)} only using
+	 * case-insensitive comparison.
+	 * 
+	 * @since 4.0
+	 */
+	public static Expression startsWithIgnoreCaseExp(String pathSpec, String value) {
+		ASTLikeIgnoreCase like = likeIgnoreCaseExpInternal(pathSpec, value, (char) 0);
+		LikeExpressionHelper.toStartsWith(like);
+		return like;
+	}
+
+	/**
+	 * Same as {@link #endsWithExp(String, String)} only using case-insensitive
+	 * comparison.
+	 * 
+	 * @since 4.0
+	 */
+	public static Expression endsWithIgnoreCaseExp(String pathSpec, String value) {
+		ASTLikeIgnoreCase like = likeIgnoreCaseExpInternal(pathSpec, value, (char) 0);
+		LikeExpressionHelper.toEndsWith(like);
+		return like;
 	}
 
 	/**
@@ -837,8 +887,7 @@ public class ExpressionFactory {
 	 * expression would match any of the expressions.
 	 * </p>
 	 */
-	public static Expression joinExp(int type,
-			Collection<Expression> expressions) {
+	public static Expression joinExp(int type, Collection<Expression> expressions) {
 		int len = expressions.size();
 		if (len == 0) {
 			return null;
@@ -872,8 +921,7 @@ public class ExpressionFactory {
 	 * <code>object</code>.
 	 */
 	public static Expression matchExp(Persistent object) {
-		return matchAllDbExp(object.getObjectId().getIdSnapshot(),
-				Expression.EQUAL_TO);
+		return matchAllDbExp(object.getObjectId().getIdSnapshot(), Expression.EQUAL_TO);
 	}
 
 	/**
@@ -971,12 +1019,11 @@ public class ExpressionFactory {
 		// optimizing parser buffers per CAY-1667...
 		// adding 1 extra char to the buffer size above the String length, as
 		// otherwise resizing still occurs at the end of the stream
-		int bufferSize = expressionString.length() > PARSE_BUFFER_MAX_SIZE ? PARSE_BUFFER_MAX_SIZE
-				: expressionString.length() + 1;
+		int bufferSize = expressionString.length() > PARSE_BUFFER_MAX_SIZE ? PARSE_BUFFER_MAX_SIZE : expressionString
+				.length() + 1;
 		Reader reader = new StringReader(expressionString);
 		JavaCharStream stream = new JavaCharStream(reader, 1, 1, bufferSize);
-		ExpressionParserTokenManager tm = new ExpressionParserTokenManager(
-				stream);
+		ExpressionParserTokenManager tm = new ExpressionParserTokenManager(stream);
 		ExpressionParser parser = new ExpressionParser(tm);
 
 		try {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9587d047/cayenne-server/src/main/java/org/apache/cayenne/exp/LikeExpressionHelper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/LikeExpressionHelper.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/LikeExpressionHelper.java
new file mode 100644
index 0000000..7725dc9
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/LikeExpressionHelper.java
@@ -0,0 +1,73 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.
+ ****************************************************************/
+package org.apache.cayenne.exp;
+
+import org.apache.cayenne.exp.parser.PatternMatchNode;
+
+/**
+ * @since 4.0
+ */
+class LikeExpressionHelper {
+
+	// presumably only "?" can't be an escape char
+	private static final char[] ESCAPE_ALPHABET = new char[] { '\\', '|', '/', ' ' };
+
+	private static final String WILDCARD_SEQUENCE = "%";
+	private static final String WILDCARD_ONE = "_";
+
+	static void toContains(PatternMatchNode exp) {
+		escape(exp);
+		wrap(exp, true, true);
+	}
+
+	static void toStartsWith(PatternMatchNode exp) {
+		escape(exp);
+		wrap(exp, false, true);
+	}
+
+	static void toEndsWith(PatternMatchNode exp) {
+		escape(exp);
+		wrap(exp, true, false);
+	}
+
+	static void escape(PatternMatchNode exp) {
+
+	}
+
+	static void wrap(PatternMatchNode exp, boolean start, boolean end) {
+
+		Object pattern = exp.getOperand(1);
+		if (pattern instanceof String) {
+
+			StringBuilder buffer = new StringBuilder();
+			if (start) {
+				buffer.append(WILDCARD_SEQUENCE);
+			}
+
+			buffer.append(pattern);
+
+			if (end) {
+				buffer.append(WILDCARD_SEQUENCE);
+			}
+
+			exp.setOperand(1, buffer.toString());
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9587d047/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
index b42e890..1676315 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
@@ -165,34 +165,117 @@ public class Property<E> {
 	}
 
 	/**
-	 * @return An expression for a Database "Like" query.
+	 * @param pattern
+	 *            a pattern matching property value. Pattern may include "_" and
+	 *            "%" wildcard symbols to match any single character or a
+	 *            sequence of characters. To prevent "_" and "%" from being
+	 *            treated as wildcards, they need to be escaped and escape char
+	 *            passed with {@link #like(String, char)} method.
+	 * @return An expression for a Database "LIKE" query.
 	 */
-	public Expression like(E value) {
-		return ExpressionFactory.likeExp(getName(), value);
+	public Expression like(String pattern) {
+		return ExpressionFactory.likeExp(getName(), pattern);
 	}
 
 	/**
-	 * @return An expression for a case insensitive "Like" query.
+	 * @param pattern
+	 *            a properly escaped pattern matching property value. Pattern
+	 *            may include "_" and "%" wildcard symbols to match any single
+	 *            character or a sequence of characters.
+	 * @param escapeChar
+	 *            an escape character used in the pattern to escape "%" and "_".
+	 * 
+	 * @return An expression for a Database "LIKE" query.
+	 */
+	public Expression like(String pattern, char escapeChar) {
+		return ExpressionFactory.likeExp(getName(), pattern, escapeChar);
+	}
+
+	/**
+	 * @return An expression for a case insensitive "LIKE" query.
 	 */
-	public Expression likeInsensitive(E value) {
-		return ExpressionFactory.likeIgnoreCaseExp(getName(), value);
+	public Expression likeInsensitive(String pattern) {
+		return ExpressionFactory.likeIgnoreCaseExp(getName(), pattern);
 	}
 
 	/**
 	 * @return An expression for a Database "NOT LIKE" query.
 	 */
-	public Expression nlike(E value) {
+	public Expression nlike(String value) {
 		return ExpressionFactory.notLikeExp(getName(), value);
 	}
 
 	/**
 	 * @return An expression for a case insensitive "NOT LIKE" query.
 	 */
-	public Expression nlikeInsensitive(E value) {
+	public Expression nlikeInsensitive(String value) {
 		return ExpressionFactory.notLikeIgnoreCaseExp(getName(), value);
 	}
 
 	/**
+	 * Creates an expression for a database "LIKE" query with the value
+	 * converted to a pattern matching anywhere in the String.
+	 * 
+	 * @param substring
+	 *            a String to match against property value. "_" and "%" symbols
+	 *            are NOT treated as wildcards and are escaped when converted to
+	 *            a LIKE expression.
+	 */
+	public Expression contains(String substring) {
+		return ExpressionFactory.containsExp(getName(), substring);
+	}
+
+	/**
+	 * Creates an expression for a database "LIKE" query with the value
+	 * converted to a pattern matching the beginning of a String.
+	 * 
+	 * @param substring
+	 *            a String to match against property value. "_" and "%" symbols
+	 *            are NOT treated as wildcards and are escaped when converted to
+	 *            a LIKE expression.
+	 */
+	public Expression startsWith(String value) {
+		return ExpressionFactory.startsWithExp(getName(), value);
+	}
+
+	/**
+	 * Creates an expression for a database "LIKE" query with the value
+	 * converted to a pattern matching the tail of a String.
+	 * 
+	 * @param substring
+	 *            a String to match against property value. "_" and "%" symbols
+	 *            are NOT treated as wildcards and are escaped when converted to
+	 *            a LIKE expression.
+	 */
+	public Expression endsWith(String value) {
+		return ExpressionFactory.endsWithExp(getName(), value);
+	}
+
+	/**
+	 * Same as {@link #contains(String)}, only using case-insensitive
+	 * comparison.
+	 */
+	public Expression icontains(String value) {
+		return ExpressionFactory.containsIgnoreCaseExp(getName(), value);
+	}
+
+	/**
+	 * Same as {@link #startsWith(String)}, only using case-insensitive
+	 * comparison.
+	 */
+	public Expression istartsWith(String value) {
+		return ExpressionFactory.startsWithIgnoreCaseExp(getName(), value);
+	}
+
+	/**
+	 * Same as {@link #endsWith(String)}, only using case-insensitive
+	 * comparison.
+	 */
+	public Expression iendsWith(String value) {
+		return ExpressionFactory.endsWithIgnoreCaseExp(getName(), value);
+	}
+
+	/**
 	 * @return An expression checking for objects between a lower and upper
 	 *         bound inclusive
 	 * 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/9587d047/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java
index 1144449..70fd3ce 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/PropertyTest.java
@@ -192,4 +192,25 @@ public class PropertyTest {
     	Expression e = p.like("ab%c");
     	assertEquals("prop like \"ab%c\"", e.toString());
     }
+    
+    @Test
+    public void testContains() {
+    	Property<String> p = new Property<String>("prop");
+    	Expression e = p.contains("abc");
+    	assertEquals("prop like \"%abc%\"", e.toString());
+    }
+    
+    @Test
+    public void testStartsWith() {
+    	Property<String> p = new Property<String>("prop");
+    	Expression e = p.startsWith("abc");
+    	assertEquals("prop like \"abc%\"", e.toString());
+    }
+    
+    @Test
+    public void testEndsWith() {
+    	Property<String> p = new Property<String>("prop");
+    	Expression e = p.endsWith("abc");
+    	assertEquals("prop like \"%abc\"", e.toString());
+    }
 }