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/22 15:43:06 UTC

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

Repository: cayenne
Updated Branches:
  refs/heads/master 98bd4974e -> 5fe569112


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

unit tests for existing methods


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

Branch: refs/heads/master
Commit: f11fc5ca384e891bdc248c3a040c5fea9b6be95a
Parents: 98bd497
Author: aadamchik <aa...@apache.org>
Authored: Sat Nov 22 14:46:31 2014 +0300
Committer: aadamchik <aa...@apache.org>
Committed: Sat Nov 22 14:46:31 2014 +0300

----------------------------------------------------------------------
 .../org/apache/cayenne/exp/PropertyTest.java    | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/f11fc5ca/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 7e262f9..1144449 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
@@ -172,4 +172,24 @@ public class PropertyTest {
         assertEquals("xyz+", outer.outer().getName());
     }
     
+    @Test
+    public void testLike() {
+    	Property<String> p = new Property<String>("prop");
+    	Expression e = p.like("abc");
+    	assertEquals("prop like \"abc\"", e.toString());
+    }
+    
+    @Test
+    public void testLikeIgnoreCase() {
+    	Property<String> p = new Property<String>("prop");
+    	Expression e = p.likeInsensitive("abc");
+    	assertEquals("prop likeIgnoreCase \"abc\"", e.toString());
+    }
+    
+    @Test
+    public void testLike_NoEscape() {
+    	Property<String> p = new Property<String>("prop");
+    	Expression e = p.like("ab%c");
+    	assertEquals("prop like \"ab%c\"", e.toString());
+    }
 }


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

Posted by aa...@apache.org.
CAY-1971 Variants of Property.like(..) : contains(..), startsWith(..), endsWith(..)

* integration tests


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

Branch: refs/heads/master
Commit: 5fe569112a0fb54b98a4da4c8559efd3d2c85776
Parents: a0d54ab
Author: aadamchik <aa...@apache.org>
Authored: Sat Nov 22 14:46:31 2014 +0300
Committer: aadamchik <aa...@apache.org>
Committed: Sat Nov 22 17:39:19 2014 +0300

----------------------------------------------------------------------
 .../apache/cayenne/exp/ExpressionFactoryIT.java  | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/5fe56911/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryIT.java
index c03b998..33e728d 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryIT.java
@@ -103,4 +103,23 @@ public class ExpressionFactoryIT extends ServerCase {
 		artists = context.select(q2);
 		assertEquals(1, artists.size());
 	}
+	
+	@Test
+	public void testContains_Escape() {
+		Artist a1 = context.newObject(Artist.class);
+		a1.setArtistName("MA_1X");
+		Artist a2 = context.newObject(Artist.class);
+		a2.setArtistName("CA%2Y");
+		context.commitChanges();
+
+		Expression ex1 = ExpressionFactory.containsExp(Artist.ARTIST_NAME.getName(), "A_1");
+		SelectQuery<Artist> q1 = new SelectQuery<Artist>(Artist.class, ex1);
+		List<Artist> artists = context.select(q1);
+		assertEquals(1, artists.size());
+
+		Expression ex2 = ExpressionFactory.containsExp(Artist.ARTIST_NAME.getName(), "A%2");
+		SelectQuery<Artist> q2 = new SelectQuery<Artist>(Artist.class, ex2);
+		artists = context.select(q2);
+		assertEquals(1, artists.size());
+	}
 }


[4/5] cayenne git commit: re-categorizing Test vs IT

Posted by aa...@apache.org.
re-categorizing Test vs IT

* some test cases were misplaced
* some no longer require "_InContext" qualifier in the name


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

Branch: refs/heads/master
Commit: a0d54aba704c3dee919fb2980db6f7fd0666ccd1
Parents: 7aeba71
Author: aadamchik <aa...@apache.org>
Authored: Sat Nov 22 17:35:06 2014 +0300
Committer: aadamchik <aa...@apache.org>
Committed: Sat Nov 22 17:36:05 2014 +0300

----------------------------------------------------------------------
 .../apache/cayenne/exp/ExpressionFactoryIT.java | 106 ++++++
 .../cayenne/exp/ExpressionFactoryTest.java      | 250 ++++++++++++-
 .../exp/ExpressionFactory_InContextIT.java      | 354 -------------------
 .../org/apache/cayenne/exp/ExpressionIT.java    | 128 +++++++
 .../cayenne/exp/Expression_InContextIT.java     | 128 -------
 5 files changed, 470 insertions(+), 496 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/a0d54aba/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryIT.java
new file mode 100644
index 0000000..c03b998
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryIT.java
@@ -0,0 +1,106 @@
+/*****************************************************************
+ *   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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.testdo.testmap.Painting;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Test;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class ExpressionFactoryIT extends ServerCase {
+
+	@Inject
+	private ObjectContext context;
+
+	// CAY-416
+	@Test
+	public void testCollectionMatch() {
+		Artist artist = context.newObject(Artist.class);
+		artist.setArtistName("artist");
+		Painting p1 = context.newObject(Painting.class), p2 = context.newObject(Painting.class), p3 = context
+				.newObject(Painting.class);
+		p1.setPaintingTitle("p1");
+		p2.setPaintingTitle("p2");
+		p3.setPaintingTitle("p3");
+		artist.addToPaintingArray(p1);
+		artist.addToPaintingArray(p2);
+
+		context.commitChanges();
+
+		assertTrue(ExpressionFactory.matchExp("paintingArray", p1).match(artist));
+		assertFalse(ExpressionFactory.matchExp("paintingArray", p3).match(artist));
+		assertFalse(ExpressionFactory.noMatchExp("paintingArray", p1).match(artist));
+		assertTrue(ExpressionFactory.noMatchExp("paintingArray", p3).match(artist));
+
+		assertTrue(ExpressionFactory.matchExp("paintingArray.paintingTitle", "p1").match(artist));
+		assertFalse(ExpressionFactory.matchExp("paintingArray.paintingTitle", "p3").match(artist));
+		assertFalse(ExpressionFactory.noMatchExp("paintingArray.paintingTitle", "p1").match(artist));
+		assertTrue(ExpressionFactory.noMatchExp("paintingArray.paintingTitle", "p3").match(artist));
+
+		assertTrue(ExpressionFactory.inExp("paintingTitle", "p1").match(p1));
+		assertFalse(ExpressionFactory.notInExp("paintingTitle", "p3").match(p3));
+	}
+
+	@Test
+	public void testIn() {
+		Artist a1 = context.newObject(Artist.class);
+		a1.setArtistName("a1");
+		Painting p1 = context.newObject(Painting.class);
+		p1.setPaintingTitle("p1");
+		Painting p2 = context.newObject(Painting.class);
+		p2.setPaintingTitle("p2");
+		a1.addToPaintingArray(p1);
+		a1.addToPaintingArray(p2);
+
+		Expression in = ExpressionFactory.inExp("paintingArray", p1);
+		assertTrue(in.match(a1));
+	}
+
+	@Test
+	public void testEscapeCharacter() {
+		Artist a1 = context.newObject(Artist.class);
+		a1.setArtistName("A_1");
+		Artist a2 = context.newObject(Artist.class);
+		a2.setArtistName("A_2");
+		context.commitChanges();
+
+		Expression ex1 = ExpressionFactory.likeIgnoreCaseDbExp("ARTIST_NAME", "A*_1", '*');
+		SelectQuery<Artist> q1 = new SelectQuery<Artist>(Artist.class, ex1);
+		List<Artist> artists = context.select(q1);
+		assertEquals(1, artists.size());
+
+		Expression ex2 = ExpressionFactory.likeExp("artistName", "A*_2", '*');
+		SelectQuery<Artist> q2 = new SelectQuery<Artist>(Artist.class, ex2);
+		artists = context.select(q2);
+		assertEquals(1, artists.size());
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/a0d54aba/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryTest.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryTest.java
index d52548c..5c41ff1 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactoryTest.java
@@ -28,8 +28,11 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
+import org.apache.cayenne.exp.parser.ASTLike;
+import org.apache.cayenne.exp.parser.ASTLikeIgnoreCase;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -42,6 +45,232 @@ public class ExpressionFactoryTest {
 		handler = new TstTraversalHandler();
 	}
 
+	@Test(expected = ExpressionException.class)
+	public void testExpressionOfBadType() throws Exception {
+		// non existing type
+		int badType = -50;
+		ExpressionFactory.expressionOfType(badType);
+	}
+
+	@Test
+	public void testBetweenExp() throws Exception {
+		Object v1 = new Object();
+		Object v2 = new Object();
+		Expression exp = ExpressionFactory.betweenExp("abc", v1, v2);
+		assertEquals(Expression.BETWEEN, exp.getType());
+
+		Expression path = (Expression) exp.getOperand(0);
+		assertEquals(Expression.OBJ_PATH, path.getType());
+	}
+
+	@Test
+	public void testBetweenDbExp() throws Exception {
+		Object v1 = new Object();
+		Object v2 = new Object();
+		Expression exp = ExpressionFactory.betweenDbExp("abc", v1, v2);
+		assertEquals(Expression.BETWEEN, exp.getType());
+
+		Expression path = (Expression) exp.getOperand(0);
+		assertEquals(Expression.DB_PATH, path.getType());
+	}
+
+	@Test
+	public void testNotBetweenExp() throws Exception {
+		Object v1 = new Object();
+		Object v2 = new Object();
+		Expression exp = ExpressionFactory.notBetweenExp("abc", v1, v2);
+		assertEquals(Expression.NOT_BETWEEN, exp.getType());
+
+		Expression path = (Expression) exp.getOperand(0);
+		assertEquals(Expression.OBJ_PATH, path.getType());
+	}
+
+	@Test
+	public void testNotBetweenDbExp() throws Exception {
+		Object v1 = new Object();
+		Object v2 = new Object();
+		Expression exp = ExpressionFactory.notBetweenDbExp("abc", v1, v2);
+		assertEquals(Expression.NOT_BETWEEN, exp.getType());
+
+		Expression path = (Expression) exp.getOperand(0);
+		assertEquals(Expression.DB_PATH, path.getType());
+	}
+
+	@Test
+	public void testGreaterExp() throws Exception {
+		Object v = new Object();
+		Expression exp = ExpressionFactory.greaterExp("abc", v);
+		assertEquals(Expression.GREATER_THAN, exp.getType());
+	}
+
+	@Test
+	public void testGreaterDbExp() throws Exception {
+		Object v = new Object();
+		Expression exp = ExpressionFactory.greaterDbExp("abc", v);
+		assertEquals(Expression.GREATER_THAN, exp.getType());
+
+		Expression path = (Expression) exp.getOperand(0);
+		assertEquals(Expression.DB_PATH, path.getType());
+	}
+
+	@Test
+	public void testGreaterOrEqualExp() throws Exception {
+		Object v = new Object();
+		Expression exp = ExpressionFactory.greaterOrEqualExp("abc", v);
+		assertEquals(Expression.GREATER_THAN_EQUAL_TO, exp.getType());
+	}
+
+	@Test
+	public void testGreaterOrEqualDbExp() throws Exception {
+		Object v = new Object();
+		Expression exp = ExpressionFactory.greaterOrEqualDbExp("abc", v);
+		assertEquals(Expression.GREATER_THAN_EQUAL_TO, exp.getType());
+
+		Expression path = (Expression) exp.getOperand(0);
+		assertEquals(Expression.DB_PATH, path.getType());
+	}
+
+	@Test
+	public void testLessExp() throws Exception {
+		Object v = new Object();
+		Expression exp = ExpressionFactory.lessExp("abc", v);
+		assertEquals(Expression.LESS_THAN, exp.getType());
+	}
+
+	@Test
+	public void testLessDbExp() throws Exception {
+		Object v = new Object();
+		Expression exp = ExpressionFactory.lessDbExp("abc", v);
+		assertEquals(Expression.LESS_THAN, exp.getType());
+
+		Expression path = (Expression) exp.getOperand(0);
+		assertEquals(Expression.DB_PATH, path.getType());
+	}
+
+	@Test
+	public void testLessOrEqualExp() throws Exception {
+		Object v = new Object();
+		Expression exp = ExpressionFactory.lessOrEqualExp("abc", v);
+		assertEquals(Expression.LESS_THAN_EQUAL_TO, exp.getType());
+
+		Expression path = (Expression) exp.getOperand(0);
+		assertEquals(Expression.OBJ_PATH, path.getType());
+	}
+
+	@Test
+	public void testLessOrEqualDbExp() throws Exception {
+		Object v = new Object();
+		Expression exp = ExpressionFactory.lessOrEqualDbExp("abc", v);
+		assertEquals(Expression.LESS_THAN_EQUAL_TO, exp.getType());
+
+		Expression path = (Expression) exp.getOperand(0);
+		assertEquals(Expression.DB_PATH, path.getType());
+	}
+
+	@Test
+	public void testInExp1() throws Exception {
+		Expression exp = ExpressionFactory.inExp("abc", "a", "b");
+		assertEquals(Expression.IN, exp.getType());
+	}
+
+	@Test
+	public void testInExp2() throws Exception {
+		List<Object> v = new ArrayList<Object>();
+		v.add("a");
+		v.add("b");
+		Expression exp = ExpressionFactory.inExp("abc", v);
+		assertEquals(Expression.IN, exp.getType());
+	}
+
+	@Test
+	public void testInExp3() throws Exception {
+		List<Object> v = new ArrayList<Object>();
+		Expression exp = ExpressionFactory.inExp("abc", v);
+		assertEquals(Expression.FALSE, exp.getType());
+	}
+
+	@Test
+	public void testLikeExp() throws Exception {
+		String v = "abc";
+		Expression exp = ExpressionFactory.likeExp("abc", v);
+		assertEquals(Expression.LIKE, exp.getType());
+
+		Expression path = (Expression) exp.getOperand(0);
+		assertEquals(Expression.OBJ_PATH, path.getType());
+	}
+
+	@Test
+	public void testLikeDbExp() throws Exception {
+		String v = "abc";
+		Expression exp = ExpressionFactory.likeDbExp("abc", v);
+		assertEquals(Expression.LIKE, exp.getType());
+
+		Expression path = (Expression) exp.getOperand(0);
+		assertEquals(Expression.DB_PATH, path.getType());
+	}
+
+	@Test
+	public void testLikeExpEscape() throws Exception {
+		String v = "abc";
+		Expression exp = ExpressionFactory.likeExp("=abc", v, '=');
+		assertEquals(Expression.LIKE, exp.getType());
+
+		assertEquals('=', ((ASTLike) exp).getEscapeChar());
+
+		Expression path = (Expression) exp.getOperand(0);
+		assertEquals(Expression.OBJ_PATH, path.getType());
+	}
+
+	@Test
+	public void testLikeIgnoreCaseExp() throws Exception {
+		String v = "abc";
+		Expression exp = ExpressionFactory.likeIgnoreCaseExp("abc", v);
+		assertEquals(Expression.LIKE_IGNORE_CASE, exp.getType());
+		assertEquals(0, ((ASTLikeIgnoreCase) exp).getEscapeChar());
+
+		Expression path = (Expression) exp.getOperand(0);
+		assertEquals(Expression.OBJ_PATH, path.getType());
+	}
+
+	@Test
+	public void testLikeIgnoreCaseExpEscape() throws Exception {
+		String v = "abc";
+		Expression exp = ExpressionFactory.likeIgnoreCaseExp("=abc", v, '=');
+		assertEquals(Expression.LIKE_IGNORE_CASE, exp.getType());
+		assertEquals('=', ((ASTLikeIgnoreCase) exp).getEscapeChar());
+
+		Expression path = (Expression) exp.getOperand(0);
+		assertEquals(Expression.OBJ_PATH, path.getType());
+	}
+
+	@Test
+	public void testLikeIgnoreCaseDbExp() throws Exception {
+		String v = "abc";
+		Expression exp = ExpressionFactory.likeIgnoreCaseDbExp("abc", v);
+		assertEquals(Expression.LIKE_IGNORE_CASE, exp.getType());
+
+		Expression path = (Expression) exp.getOperand(0);
+		assertEquals(Expression.DB_PATH, path.getType());
+	}
+
+	@Test
+	public void testNotLikeIgnoreCaseExp() throws Exception {
+		String v = "abc";
+		Expression exp = ExpressionFactory.notLikeIgnoreCaseExp("abc", v);
+		assertEquals(Expression.NOT_LIKE_IGNORE_CASE, exp.getType());
+	}
+
+	// testing CAY-941 bug
+	@Test
+	public void testLikeExpNull() throws Exception {
+		Expression exp = ExpressionFactory.likeExp("abc", null);
+		assertEquals(Expression.LIKE, exp.getType());
+
+		Expression path = (Expression) exp.getOperand(0);
+		assertEquals(Expression.OBJ_PATH, path.getType());
+		assertNull(exp.getOperand(1));
+	}
+
 	@Test
 	public void testMatchAllExp() throws Exception {
 		// create expressions and check the counts,
@@ -59,16 +288,14 @@ public class ExpressionFactoryTest {
 				map.put("k" + i, "v" + i);
 			}
 
-			Expression exp = ExpressionFactory.matchAllExp(map,
-					Expression.LESS_THAN);
+			Expression exp = ExpressionFactory.matchAllExp(map, Expression.LESS_THAN);
 			assertNotNull(exp);
 			handler.traverseExpression(exp);
 
 			// assert statistics
 			handler.assertConsistency();
 			assertEquals("Failed: " + exp, 2 * n, handler.getLeafs());
-			assertEquals("Failed: " + exp, n < 2 ? 2 * n : 2 * n + 1,
-					handler.getNodeCount());
+			assertEquals("Failed: " + exp, n < 2 ? 2 * n : 2 * n + 1, handler.getNodeCount());
 		}
 	}
 
@@ -95,8 +322,7 @@ public class ExpressionFactoryTest {
 			// assert statistics
 			handler.assertConsistency();
 			assertEquals("Failed: " + exp, 2 * n, handler.getLeafs());
-			assertEquals("Failed: " + exp, n > 1 ? 2 * n + 1 : 2 * n,
-					handler.getNodeCount());
+			assertEquals("Failed: " + exp, n > 1 ? 2 * n + 1 : 2 * n, handler.getNodeCount());
 		}
 	}
 
@@ -125,8 +351,7 @@ public class ExpressionFactoryTest {
 	@Test
 	public void testAnd_Collection_Empty() {
 
-		Expression e = ExpressionFactory.and(Collections
-				.<Expression> emptyList());
+		Expression e = ExpressionFactory.and(Collections.<Expression> emptyList());
 
 		// hmm... is this really a valid return value?
 		assertNull(e);
@@ -211,16 +436,13 @@ public class ExpressionFactoryTest {
 
 	@Test
 	public void testExp_Enum() {
-		Expression e1 = ExpressionFactory
-				.exp("a = enum:org.apache.cayenne.exp.ExpEnum1.ONE");
+		Expression e1 = ExpressionFactory.exp("a = enum:org.apache.cayenne.exp.ExpEnum1.ONE");
 		assertEquals(ExpEnum1.ONE, e1.getOperand(1));
 
-		Expression e2 = ExpressionFactory
-				.exp("a = enum:org.apache.cayenne.exp.ExpEnum1.TWO");
+		Expression e2 = ExpressionFactory.exp("a = enum:org.apache.cayenne.exp.ExpEnum1.TWO");
 		assertEquals(ExpEnum1.TWO, e2.getOperand(1));
 
-		Expression e3 = ExpressionFactory
-				.exp("a = enum:org.apache.cayenne.exp.ExpEnum1.THREE");
+		Expression e3 = ExpressionFactory.exp("a = enum:org.apache.cayenne.exp.ExpEnum1.THREE");
 		assertEquals(ExpEnum1.THREE, e3.getOperand(1));
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/a0d54aba/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactory_InContextIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactory_InContextIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactory_InContextIT.java
deleted file mode 100644
index fa39d6d..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionFactory_InContextIT.java
+++ /dev/null
@@ -1,354 +0,0 @@
-/*****************************************************************
- *   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.ObjectContext;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.exp.parser.ASTLike;
-import org.apache.cayenne.exp.parser.ASTLikeIgnoreCase;
-import org.apache.cayenne.query.SelectQuery;
-import org.apache.cayenne.testdo.testmap.Artist;
-import org.apache.cayenne.testdo.testmap.Painting;
-import org.apache.cayenne.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.ServerCase;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class ExpressionFactory_InContextIT extends ServerCase {
-
-	@Inject
-	private ObjectContext context;
-
-    @Test
-	public void testExpressionOfBadType() throws Exception {
-
-		// non existing type
-		int badType = -50;
-
-		try {
-			ExpressionFactory.expressionOfType(badType);
-			fail();
-		} catch (ExpressionException ex) {
-			// exception expected
-		}
-	}
-
-    @Test
-	public void testBetweenExp() throws Exception {
-		Object v1 = new Object();
-		Object v2 = new Object();
-		Expression exp = ExpressionFactory.betweenExp("abc", v1, v2);
-		assertEquals(Expression.BETWEEN, exp.getType());
-
-		Expression path = (Expression) exp.getOperand(0);
-		assertEquals(Expression.OBJ_PATH, path.getType());
-	}
-
-    @Test
-	public void testBetweenDbExp() throws Exception {
-		Object v1 = new Object();
-		Object v2 = new Object();
-		Expression exp = ExpressionFactory.betweenDbExp("abc", v1, v2);
-		assertEquals(Expression.BETWEEN, exp.getType());
-
-		Expression path = (Expression) exp.getOperand(0);
-		assertEquals(Expression.DB_PATH, path.getType());
-	}
-
-    @Test
-	public void testNotBetweenExp() throws Exception {
-		Object v1 = new Object();
-		Object v2 = new Object();
-		Expression exp = ExpressionFactory.notBetweenExp("abc", v1, v2);
-		assertEquals(Expression.NOT_BETWEEN, exp.getType());
-
-		Expression path = (Expression) exp.getOperand(0);
-		assertEquals(Expression.OBJ_PATH, path.getType());
-	}
-
-    @Test
-	public void testNotBetweenDbExp() throws Exception {
-		Object v1 = new Object();
-		Object v2 = new Object();
-		Expression exp = ExpressionFactory.notBetweenDbExp("abc", v1, v2);
-		assertEquals(Expression.NOT_BETWEEN, exp.getType());
-
-		Expression path = (Expression) exp.getOperand(0);
-		assertEquals(Expression.DB_PATH, path.getType());
-	}
-
-    @Test
-	public void testGreaterExp() throws Exception {
-		Object v = new Object();
-		Expression exp = ExpressionFactory.greaterExp("abc", v);
-		assertEquals(Expression.GREATER_THAN, exp.getType());
-	}
-
-    @Test
-	public void testGreaterDbExp() throws Exception {
-		Object v = new Object();
-		Expression exp = ExpressionFactory.greaterDbExp("abc", v);
-		assertEquals(Expression.GREATER_THAN, exp.getType());
-
-		Expression path = (Expression) exp.getOperand(0);
-		assertEquals(Expression.DB_PATH, path.getType());
-	}
-
-    @Test
-	public void testGreaterOrEqualExp() throws Exception {
-		Object v = new Object();
-		Expression exp = ExpressionFactory.greaterOrEqualExp("abc", v);
-		assertEquals(Expression.GREATER_THAN_EQUAL_TO, exp.getType());
-	}
-
-    @Test
-	public void testGreaterOrEqualDbExp() throws Exception {
-		Object v = new Object();
-		Expression exp = ExpressionFactory.greaterOrEqualDbExp("abc", v);
-		assertEquals(Expression.GREATER_THAN_EQUAL_TO, exp.getType());
-
-		Expression path = (Expression) exp.getOperand(0);
-		assertEquals(Expression.DB_PATH, path.getType());
-	}
-
-    @Test
-	public void testLessExp() throws Exception {
-		Object v = new Object();
-		Expression exp = ExpressionFactory.lessExp("abc", v);
-		assertEquals(Expression.LESS_THAN, exp.getType());
-	}
-
-    @Test
-	public void testLessDbExp() throws Exception {
-		Object v = new Object();
-		Expression exp = ExpressionFactory.lessDbExp("abc", v);
-		assertEquals(Expression.LESS_THAN, exp.getType());
-
-		Expression path = (Expression) exp.getOperand(0);
-		assertEquals(Expression.DB_PATH, path.getType());
-	}
-
-    @Test
-	public void testLessOrEqualExp() throws Exception {
-		Object v = new Object();
-		Expression exp = ExpressionFactory.lessOrEqualExp("abc", v);
-		assertEquals(Expression.LESS_THAN_EQUAL_TO, exp.getType());
-
-		Expression path = (Expression) exp.getOperand(0);
-		assertEquals(Expression.OBJ_PATH, path.getType());
-	}
-
-    @Test
-	public void testLessOrEqualDbExp() throws Exception {
-		Object v = new Object();
-		Expression exp = ExpressionFactory.lessOrEqualDbExp("abc", v);
-		assertEquals(Expression.LESS_THAN_EQUAL_TO, exp.getType());
-
-		Expression path = (Expression) exp.getOperand(0);
-		assertEquals(Expression.DB_PATH, path.getType());
-	}
-
-    @Test
-	public void testInExp1() throws Exception {
-		Expression exp = ExpressionFactory.inExp("abc", "a", "b");
-		assertEquals(Expression.IN, exp.getType());
-	}
-
-    @Test
-	public void testInExp2() throws Exception {
-		List<Object> v = new ArrayList<Object>();
-		v.add("a");
-		v.add("b");
-		Expression exp = ExpressionFactory.inExp("abc", v);
-		assertEquals(Expression.IN, exp.getType());
-	}
-
-    @Test
-	public void testInExp3() throws Exception {
-		List<Object> v = new ArrayList<Object>();
-		Expression exp = ExpressionFactory.inExp("abc", v);
-		assertEquals(Expression.FALSE, exp.getType());
-	}
-
-    @Test
-	public void testLikeExp() throws Exception {
-		String v = "abc";
-		Expression exp = ExpressionFactory.likeExp("abc", v);
-		assertEquals(Expression.LIKE, exp.getType());
-
-		Expression path = (Expression) exp.getOperand(0);
-		assertEquals(Expression.OBJ_PATH, path.getType());
-	}
-
-    @Test
-	public void testLikeDbExp() throws Exception {
-		String v = "abc";
-		Expression exp = ExpressionFactory.likeDbExp("abc", v);
-		assertEquals(Expression.LIKE, exp.getType());
-
-		Expression path = (Expression) exp.getOperand(0);
-		assertEquals(Expression.DB_PATH, path.getType());
-	}
-
-    @Test
-	public void testLikeExpEscape() throws Exception {
-		String v = "abc";
-		Expression exp = ExpressionFactory.likeExp("=abc", v, '=');
-		assertEquals(Expression.LIKE, exp.getType());
-
-		assertEquals('=', ((ASTLike) exp).getEscapeChar());
-
-		Expression path = (Expression) exp.getOperand(0);
-		assertEquals(Expression.OBJ_PATH, path.getType());
-	}
-
-    @Test
-	public void testLikeIgnoreCaseExp() throws Exception {
-		String v = "abc";
-		Expression exp = ExpressionFactory.likeIgnoreCaseExp("abc", v);
-		assertEquals(Expression.LIKE_IGNORE_CASE, exp.getType());
-		assertEquals(0, ((ASTLikeIgnoreCase) exp).getEscapeChar());
-
-		Expression path = (Expression) exp.getOperand(0);
-		assertEquals(Expression.OBJ_PATH, path.getType());
-	}
-
-    @Test
-	public void testLikeIgnoreCaseExpEscape() throws Exception {
-		String v = "abc";
-		Expression exp = ExpressionFactory.likeIgnoreCaseExp("=abc", v, '=');
-		assertEquals(Expression.LIKE_IGNORE_CASE, exp.getType());
-		assertEquals('=', ((ASTLikeIgnoreCase) exp).getEscapeChar());
-
-		Expression path = (Expression) exp.getOperand(0);
-		assertEquals(Expression.OBJ_PATH, path.getType());
-	}
-
-    @Test
-	public void testLikeIgnoreCaseDbExp() throws Exception {
-		String v = "abc";
-		Expression exp = ExpressionFactory.likeIgnoreCaseDbExp("abc", v);
-		assertEquals(Expression.LIKE_IGNORE_CASE, exp.getType());
-
-		Expression path = (Expression) exp.getOperand(0);
-		assertEquals(Expression.DB_PATH, path.getType());
-	}
-
-    @Test
-	public void testNotLikeIgnoreCaseExp() throws Exception {
-		String v = "abc";
-		Expression exp = ExpressionFactory.notLikeIgnoreCaseExp("abc", v);
-		assertEquals(Expression.NOT_LIKE_IGNORE_CASE, exp.getType());
-	}
-
-	// testing CAY-941 bug
-    @Test
-	public void testLikeExpNull() throws Exception {
-		Expression exp = ExpressionFactory.likeExp("abc", null);
-		assertEquals(Expression.LIKE, exp.getType());
-
-		Expression path = (Expression) exp.getOperand(0);
-		assertEquals(Expression.OBJ_PATH, path.getType());
-		assertNull(exp.getOperand(1));
-	}
-
-	// CAY-416
-    @Test
-	public void testCollectionMatch() {
-		Artist artist = context.newObject(Artist.class);
-		artist.setArtistName("artist");
-		Painting p1 = context.newObject(Painting.class), p2 = context
-				.newObject(Painting.class), p3 = context
-				.newObject(Painting.class);
-		p1.setPaintingTitle("p1");
-		p2.setPaintingTitle("p2");
-		p3.setPaintingTitle("p3");
-		artist.addToPaintingArray(p1);
-		artist.addToPaintingArray(p2);
-
-		context.commitChanges();
-
-		assertTrue(ExpressionFactory.matchExp("paintingArray", p1)
-				.match(artist));
-		assertFalse(ExpressionFactory.matchExp("paintingArray", p3).match(
-				artist));
-		assertFalse(ExpressionFactory.noMatchExp("paintingArray", p1).match(
-				artist));
-		assertTrue(ExpressionFactory.noMatchExp("paintingArray", p3).match(
-				artist));
-
-		assertTrue(ExpressionFactory.matchExp("paintingArray.paintingTitle",
-				"p1").match(artist));
-		assertFalse(ExpressionFactory.matchExp("paintingArray.paintingTitle",
-				"p3").match(artist));
-		assertFalse(ExpressionFactory.noMatchExp("paintingArray.paintingTitle",
-				"p1").match(artist));
-		assertTrue(ExpressionFactory.noMatchExp("paintingArray.paintingTitle",
-				"p3").match(artist));
-
-		assertTrue(ExpressionFactory.inExp("paintingTitle", "p1").match(p1));
-		assertFalse(ExpressionFactory.notInExp("paintingTitle", "p3").match(p3));
-	}
-
-    @Test
-	public void testIn() {
-		Artist a1 = context.newObject(Artist.class);
-		a1.setArtistName("a1");
-		Painting p1 = context.newObject(Painting.class);
-		p1.setPaintingTitle("p1");
-		Painting p2 = context.newObject(Painting.class);
-		p2.setPaintingTitle("p2");
-		a1.addToPaintingArray(p1);
-		a1.addToPaintingArray(p2);
-
-		Expression in = ExpressionFactory.inExp("paintingArray", p1);
-		assertTrue(in.match(a1));
-	}
-
-    @Test
-	public void testEscapeCharacter() {
-		Artist a1 = context.newObject(Artist.class);
-		a1.setArtistName("A_1");
-		Artist a2 = context.newObject(Artist.class);
-		a2.setArtistName("A_2");
-		context.commitChanges();
-
-		Expression ex1 = ExpressionFactory.likeIgnoreCaseDbExp("ARTIST_NAME",
-				"A*_1", '*');
-		SelectQuery<Artist> q1 = new SelectQuery<Artist>(Artist.class, ex1);
-		List<Artist> artists = context.select(q1);
-		assertEquals(1, artists.size());
-
-		Expression ex2 = ExpressionFactory.likeExp("artistName", "A*_2", '*');
-		SelectQuery<Artist> q2 = new SelectQuery<Artist>(Artist.class, ex2);
-		artists = context.select(q2);
-		assertEquals(1, artists.size());
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/a0d54aba/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionIT.java
new file mode 100644
index 0000000..b2d8749
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/ExpressionIT.java
@@ -0,0 +1,128 @@
+/*****************************************************************
+ *   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.ObjectContext;
+import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.testdo.testmap.Painting;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class ExpressionIT extends ServerCase {
+
+	@Inject
+	private ObjectContext context;
+
+	@Inject
+	private ServerRuntime runtime;
+
+    @Test
+	public void testMatch() {
+
+		assertTrue(context instanceof DataContext);
+
+		DataContext context2 = (DataContext) runtime.newContext();
+
+		Artist a1 = context.newObject(Artist.class);
+		a1.setArtistName("Equals");
+		Painting p1 = context.newObject(Painting.class);
+		p1.setToArtist(a1);
+		p1.setPaintingTitle("painting1");
+
+		context.commitChanges();
+
+		SelectQuery<Painting> query = new SelectQuery<Painting>(Painting.class);
+		Expression e = Painting.TO_ARTIST.eq(a1);
+		query.setQualifier(e);
+
+		assertNotSame(context2, context);
+
+		List<Painting> objects = context2.select(query);
+		assertEquals(1, objects.size());
+
+		// 2 same objects in different contexts
+		assertTrue(e.match(objects.get(0)));
+
+		// we change one object - so the objects are different now
+		// (PersistenceState different)
+		a1.setArtistName("newName");
+
+		SelectQuery<Painting> q2 = new SelectQuery<Painting>(Painting.class);
+		Expression ex2 = Painting.TO_ARTIST.eq(a1);
+		q2.setQualifier(ex2);
+
+		assertTrue(ex2.match(objects.get(0)));
+
+		Artist a2 = context.newObject(Artist.class);
+		a2.setArtistName("Equals");
+
+		context.commitChanges();
+
+		SelectQuery<Painting> q = new SelectQuery<Painting>(Painting.class);
+		Expression ex = Painting.TO_ARTIST.eq(a2);
+		q.setQualifier(ex);
+
+		// 2 different objects in different contexts
+		assertFalse(ex.match(objects.get(0)));
+	}
+
+    @Test
+	public void testFirst() {
+		List<Painting> paintingList = new ArrayList<Painting>();
+		Painting p1 = context.newObject(Painting.class);
+		p1.setPaintingTitle("x1");
+		paintingList.add(p1);
+
+		Painting p2 = context.newObject(Painting.class);
+		p2.setPaintingTitle("x2");
+		paintingList.add(p2);
+
+		Painting p3 = context.newObject(Painting.class);
+		p3.setPaintingTitle("x3");
+		paintingList.add(p3);
+
+		Expression e1 = ExpressionFactory.likeExp("paintingTitle", "x%");
+		assertSame(p1, e1.first(paintingList));
+
+		Expression e3 = ExpressionFactory.matchExp("paintingTitle", "x3");
+		assertSame(p3, e3.first(paintingList));
+
+		Expression e4 = ExpressionFactory.matchExp("paintingTitle", "x4");
+		assertNull(e4.first(paintingList));
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/a0d54aba/cayenne-server/src/test/java/org/apache/cayenne/exp/Expression_InContextIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/Expression_InContextIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/Expression_InContextIT.java
deleted file mode 100644
index fc7e0bf..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/Expression_InContextIT.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*****************************************************************
- *   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.ObjectContext;
-import org.apache.cayenne.access.DataContext;
-import org.apache.cayenne.configuration.server.ServerRuntime;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.query.SelectQuery;
-import org.apache.cayenne.testdo.testmap.Artist;
-import org.apache.cayenne.testdo.testmap.Painting;
-import org.apache.cayenne.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.ServerCase;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class Expression_InContextIT extends ServerCase {
-
-	@Inject
-	private ObjectContext context;
-
-	@Inject
-	private ServerRuntime runtime;
-
-    @Test
-	public void testMatch() {
-
-		assertTrue(context instanceof DataContext);
-
-		DataContext context2 = (DataContext) runtime.newContext();
-
-		Artist a1 = context.newObject(Artist.class);
-		a1.setArtistName("Equals");
-		Painting p1 = context.newObject(Painting.class);
-		p1.setToArtist(a1);
-		p1.setPaintingTitle("painting1");
-
-		context.commitChanges();
-
-		SelectQuery<Painting> query = new SelectQuery<Painting>(Painting.class);
-		Expression e = Painting.TO_ARTIST.eq(a1);
-		query.setQualifier(e);
-
-		assertNotSame(context2, context);
-
-		List<Painting> objects = context2.select(query);
-		assertEquals(1, objects.size());
-
-		// 2 same objects in different contexts
-		assertTrue(e.match(objects.get(0)));
-
-		// we change one object - so the objects are different now
-		// (PersistenceState different)
-		a1.setArtistName("newName");
-
-		SelectQuery<Painting> q2 = new SelectQuery<Painting>(Painting.class);
-		Expression ex2 = Painting.TO_ARTIST.eq(a1);
-		q2.setQualifier(ex2);
-
-		assertTrue(ex2.match(objects.get(0)));
-
-		Artist a2 = context.newObject(Artist.class);
-		a2.setArtistName("Equals");
-
-		context.commitChanges();
-
-		SelectQuery<Painting> q = new SelectQuery<Painting>(Painting.class);
-		Expression ex = Painting.TO_ARTIST.eq(a2);
-		q.setQualifier(ex);
-
-		// 2 different objects in different contexts
-		assertFalse(ex.match(objects.get(0)));
-	}
-
-    @Test
-	public void testFirst() {
-		List<Painting> paintingList = new ArrayList<Painting>();
-		Painting p1 = context.newObject(Painting.class);
-		p1.setPaintingTitle("x1");
-		paintingList.add(p1);
-
-		Painting p2 = context.newObject(Painting.class);
-		p2.setPaintingTitle("x2");
-		paintingList.add(p2);
-
-		Painting p3 = context.newObject(Painting.class);
-		p3.setPaintingTitle("x3");
-		paintingList.add(p3);
-
-		Expression e1 = ExpressionFactory.likeExp("paintingTitle", "x%");
-		assertSame(p1, e1.first(paintingList));
-
-		Expression e3 = ExpressionFactory.matchExp("paintingTitle", "x3");
-		assertSame(p3, e3.first(paintingList));
-
-		Expression e4 = ExpressionFactory.matchExp("paintingTitle", "x4");
-		assertNull(e4.first(paintingList));
-	}
-
-}


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

Posted by aa...@apache.org.
CAY-1971 Variants of Property.like(..) : contains(..), startsWith(..), endsWith(..)

* implementing escaping


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

Branch: refs/heads/master
Commit: 7aeba71b31bc513fda6b3678d81a4b9393aa772c
Parents: 9587d04
Author: aadamchik <aa...@apache.org>
Authored: Sat Nov 22 14:46:31 2014 +0300
Committer: aadamchik <aa...@apache.org>
Committed: Sat Nov 22 17:31:22 2014 +0300

----------------------------------------------------------------------
 .../cayenne/exp/LikeExpressionHelper.java       |  88 ++++-
 .../org/apache/cayenne/exp/PropertyTest.java    | 383 ++++++++++---------
 2 files changed, 285 insertions(+), 186 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/7aeba71b/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
index 7725dc9..31ec1f2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/LikeExpressionHelper.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/LikeExpressionHelper.java
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.exp;
 
+import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.exp.parser.PatternMatchNode;
 
 /**
@@ -25,11 +26,22 @@ import org.apache.cayenne.exp.parser.PatternMatchNode;
  */
 class LikeExpressionHelper {
 
-	// presumably only "?" can't be an escape char
-	private static final char[] ESCAPE_ALPHABET = new char[] { '\\', '|', '/', ' ' };
+	private static final char WILDCARD_SEQUENCE = '%';
+	private static final char WILDCARD_ONE = '_';
+	private static final boolean[] ESCAPE_ALPHABET;
+	private static final int ESCAPE_ALPHABET_START = '!';
 
-	private static final String WILDCARD_SEQUENCE = "%";
-	private static final String WILDCARD_ONE = "_";
+	static {
+
+		ESCAPE_ALPHABET = new boolean[Byte.MAX_VALUE];
+		// exclude certain chars, such as unprintable ones, and ?
+		for (int i = ESCAPE_ALPHABET_START; i < Byte.MAX_VALUE; i++) {
+
+			if (i != '?' && i != '\"' && i != '\'' && i != WILDCARD_SEQUENCE && i != WILDCARD_ONE) {
+				ESCAPE_ALPHABET[i] = true;
+			}
+		}
+	}
 
 	static void toContains(PatternMatchNode exp) {
 		escape(exp);
@@ -47,10 +59,76 @@ class LikeExpressionHelper {
 	}
 
 	static void escape(PatternMatchNode exp) {
+		Object pattern = exp.getOperand(1);
+		if (pattern instanceof String) {
+			// find _ or % and then attempt to escape...
+
+			String pString = pattern.toString();
+
+			int len = pString.length();
+			for (int i = 0; i < len; i++) {
+
+				char c = pString.charAt(i);
+				if (c == WILDCARD_SEQUENCE || c == WILDCARD_ONE) {
+					exp.setOperand(1, escapeFrom(exp, pString, i, len));
+					break;
+				}
+			}
+		}
+	}
+
+	private static String escapeFrom(PatternMatchNode exp, String pattern, int firstWildcard, int len) {
+
+		boolean[] mutableEscapeAlphabet = new boolean[Byte.MAX_VALUE];
+		System.arraycopy(ESCAPE_ALPHABET, ESCAPE_ALPHABET_START, mutableEscapeAlphabet, ESCAPE_ALPHABET_START,
+				Byte.MAX_VALUE - ESCAPE_ALPHABET_START);
+
+		// can't use chars already in the pattern, so exclude the ones already
+		// taken
+		for (int i = 0; i < len; i++) {
+			char c = pattern.charAt(i);
+			if (c < Byte.MAX_VALUE) {
+				mutableEscapeAlphabet[c] = false;
+			}
+		}
+
+		// find the first available char
+		char escapeChar = 0;
+		for (int i = ESCAPE_ALPHABET_START; i < Byte.MAX_VALUE; i++) {
+			if (mutableEscapeAlphabet[i]) {
+				escapeChar = (char) i;
+				break;
+			}
+		}
+
+		if (escapeChar == 0) {
+			// if we start seeing this this error in the wild, I guess we'll
+			// need to extend escape char set beyond ASCII
+			throw new CayenneRuntimeException("Could not properly escape pattern: " + pattern);
+		}
+		
+		exp.setEscapeChar(escapeChar);
+
+		// build escaped pattern
+		StringBuilder buffer = new StringBuilder(len + 1);
+		buffer.append(pattern.substring(0, firstWildcard));
+		buffer.append(escapeChar).append(pattern.charAt(firstWildcard));
+
+		for (int i = firstWildcard + 1; i < len; i++) {
+
+			char c = pattern.charAt(i);
+			if (c == WILDCARD_SEQUENCE || c == WILDCARD_ONE) {
+				buffer.append(escapeChar);
+				break;
+			}
+
+			buffer.append(c);
+		}
 
+		return buffer.toString();
 	}
 
-	static void wrap(PatternMatchNode exp, boolean start, boolean end) {
+	private static void wrap(PatternMatchNode exp, boolean start, boolean end) {
 
 		Object pattern = exp.getOperand(1);
 		if (pattern instanceof String) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/7aeba71b/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 70fd3ce..538fc0b 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
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.exp;
 
+import org.apache.cayenne.exp.parser.PatternMatchNode;
 import org.apache.cayenne.reflect.TstJavaBean;
 import org.apache.cayenne.reflect.UnresolvablePathException;
 import org.apache.cayenne.util.Util;
@@ -32,185 +33,205 @@ import static org.junit.Assert.fail;
 
 public class PropertyTest {
 
-    @Test
-    public void testIn() {
-        Property<String> p = new Property<String>("x.y");
-
-        Expression e1 = p.in("a");
-        assertEquals("x.y in (\"a\")", e1.toString());
-
-        Expression e2 = p.in("a", "b");
-        assertEquals("x.y in (\"a\", \"b\")", e2.toString());
-
-        Expression e3 = p.in(Arrays.asList("a", "b"));
-        assertEquals("x.y in (\"a\", \"b\")", e3.toString());
-    }
-
-    @Test
-    public void testGetFrom() {
-    	TstJavaBean bean = new TstJavaBean();
-    	bean.setIntField(7);
-    	final Property<Integer> INT_FIELD = new Property<Integer>("intField");
-    	assertEquals(Integer.valueOf(7), INT_FIELD.getFrom(bean));
-    }
-
-    @Test
-    public void testGetFromNestedProperty() {
-    	TstJavaBean bean = new TstJavaBean();
-    	TstJavaBean nestedBean = new TstJavaBean();
-    	nestedBean.setIntField(7);
-    	bean.setObjectField(nestedBean);
-    	final Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<Integer>("objectField.intField");
-    	assertEquals(Integer.valueOf(7), OBJECT_FIELD_INT_FIELD.getFrom(bean));
-    }
-
-    @Test
-    public void testGetFromNestedNull() {
-    	TstJavaBean bean = new TstJavaBean();
-    	bean.setObjectField(null);
-    	final Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<Integer>("objectField.intField");
-    	try {
-    		OBJECT_FIELD_INT_FIELD.getFrom(bean);
-    		fail();
-    	} catch (Exception e) {
-    		Throwable rootException = Util.unwindException(e);
-    		if (!(rootException instanceof UnresolvablePathException)) {
-    			fail();
-    		}
-    	}
-    }
-
-    @Test
-    public void testGetFromAll() {
-    	TstJavaBean bean = new TstJavaBean();
-    	bean.setIntField(7);
-    	
-    	TstJavaBean bean2 = new TstJavaBean();
-    	bean2.setIntField(8);
-    	
-    	List<TstJavaBean> beans = Arrays.asList(bean, bean2);
-
-    	final Property<Integer> INT_FIELD = new Property<Integer>("intField");
-    	assertEquals(Arrays.asList(7, 8), INT_FIELD.getFromAll(beans));
-    }
-
-    @Test
-    public void testSetIn() {
-    	TstJavaBean bean = new TstJavaBean();
-    	final Property<Integer> INT_FIELD = new Property<Integer>("intField");
-    	INT_FIELD.setIn(bean, 7);
-    	assertEquals(7, bean.getIntField());
-    }
-
-    @Test
-    public void testSetInNestedProperty() {
-    	TstJavaBean bean = new TstJavaBean();
-    	bean.setObjectField(new TstJavaBean());
-    	
-    	final Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<Integer>("objectField.intField");
-
-    	OBJECT_FIELD_INT_FIELD.setIn(bean, 7);
-    	assertEquals(7, ((TstJavaBean)bean.getObjectField()).getIntField());
-    }
-
-    @Test
-    public void testSetInNestedNull() {
-    	TstJavaBean bean = new TstJavaBean();
-    	bean.setObjectField(null);
-    	final Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<Integer>("objectField.intField");
-    	try {
-    		OBJECT_FIELD_INT_FIELD.setIn(bean, 7);
-    		fail();
-    	} catch (Exception e) {
-    		Throwable rootException = Util.unwindException(e);
-    		if (!(rootException instanceof UnresolvablePathException)) {
-    			fail();
-    		}
-    	}
-    }
-
-    @Test
-    public void testSetInAll() {
-    	TstJavaBean bean = new TstJavaBean();
-    	TstJavaBean bean2 = new TstJavaBean();
-    	List<TstJavaBean> beans = Arrays.asList(bean, bean2);
-
-    	final Property<Integer> INT_FIELD = new Property<Integer>("intField");
-    	INT_FIELD.setInAll(beans, 7);
-    	assertEquals(7, bean.getIntField());
-    	assertEquals(7, bean2.getIntField());
-    }
-
-    @Test
-    public void testEquals() {
-    	final Property<Integer> INT_FIELD = new Property<Integer>("intField");
-    	final Property<Integer> INT_FIELD2 = new Property<Integer>("intField");
-
-    	assertTrue(INT_FIELD != INT_FIELD2);
-    	assertTrue(INT_FIELD.equals(INT_FIELD2));
-    }
-
-    @Test
-    public void testHashCode() {
-    	final Property<Integer> INT_FIELD  = new Property<Integer>("intField");
-    	final Property<Integer> INT_FIELD2 = new Property<Integer>("intField");
-    	final Property<Long> LONG_FIELD = new Property<Long>("longField");
-
-    	assertTrue(INT_FIELD.hashCode() == INT_FIELD2.hashCode());
-    	assertTrue(INT_FIELD.hashCode() != LONG_FIELD.hashCode());
-    }
-
-    @Test
-    public void testOuter() {
-        Property<String> inner = new Property<String>("xyz");
-        assertEquals("xyz+", inner.outer().getName());
-        
-        Property<String> inner1 = new Property<String>("xyz.xxx");
-        assertEquals("xyz.xxx+", inner1.outer().getName());
-        
-        Property<String> outer = new Property<String>("xyz+");
-        assertEquals("xyz+", outer.outer().getName());
-    }
-    
-    @Test
-    public void testLike() {
-    	Property<String> p = new Property<String>("prop");
-    	Expression e = p.like("abc");
-    	assertEquals("prop like \"abc\"", e.toString());
-    }
-    
-    @Test
-    public void testLikeIgnoreCase() {
-    	Property<String> p = new Property<String>("prop");
-    	Expression e = p.likeInsensitive("abc");
-    	assertEquals("prop likeIgnoreCase \"abc\"", e.toString());
-    }
-    
-    @Test
-    public void testLike_NoEscape() {
-    	Property<String> p = new Property<String>("prop");
-    	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());
-    }
+	@Test
+	public void testIn() {
+		Property<String> p = new Property<String>("x.y");
+
+		Expression e1 = p.in("a");
+		assertEquals("x.y in (\"a\")", e1.toString());
+
+		Expression e2 = p.in("a", "b");
+		assertEquals("x.y in (\"a\", \"b\")", e2.toString());
+
+		Expression e3 = p.in(Arrays.asList("a", "b"));
+		assertEquals("x.y in (\"a\", \"b\")", e3.toString());
+	}
+
+	@Test
+	public void testGetFrom() {
+		TstJavaBean bean = new TstJavaBean();
+		bean.setIntField(7);
+		final Property<Integer> INT_FIELD = new Property<Integer>("intField");
+		assertEquals(Integer.valueOf(7), INT_FIELD.getFrom(bean));
+	}
+
+	@Test
+	public void testGetFromNestedProperty() {
+		TstJavaBean bean = new TstJavaBean();
+		TstJavaBean nestedBean = new TstJavaBean();
+		nestedBean.setIntField(7);
+		bean.setObjectField(nestedBean);
+		final Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<Integer>("objectField.intField");
+		assertEquals(Integer.valueOf(7), OBJECT_FIELD_INT_FIELD.getFrom(bean));
+	}
+
+	@Test
+	public void testGetFromNestedNull() {
+		TstJavaBean bean = new TstJavaBean();
+		bean.setObjectField(null);
+		final Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<Integer>("objectField.intField");
+		try {
+			OBJECT_FIELD_INT_FIELD.getFrom(bean);
+			fail();
+		} catch (Exception e) {
+			Throwable rootException = Util.unwindException(e);
+			if (!(rootException instanceof UnresolvablePathException)) {
+				fail();
+			}
+		}
+	}
+
+	@Test
+	public void testGetFromAll() {
+		TstJavaBean bean = new TstJavaBean();
+		bean.setIntField(7);
+
+		TstJavaBean bean2 = new TstJavaBean();
+		bean2.setIntField(8);
+
+		List<TstJavaBean> beans = Arrays.asList(bean, bean2);
+
+		final Property<Integer> INT_FIELD = new Property<Integer>("intField");
+		assertEquals(Arrays.asList(7, 8), INT_FIELD.getFromAll(beans));
+	}
+
+	@Test
+	public void testSetIn() {
+		TstJavaBean bean = new TstJavaBean();
+		final Property<Integer> INT_FIELD = new Property<Integer>("intField");
+		INT_FIELD.setIn(bean, 7);
+		assertEquals(7, bean.getIntField());
+	}
+
+	@Test
+	public void testSetInNestedProperty() {
+		TstJavaBean bean = new TstJavaBean();
+		bean.setObjectField(new TstJavaBean());
+
+		final Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<Integer>("objectField.intField");
+
+		OBJECT_FIELD_INT_FIELD.setIn(bean, 7);
+		assertEquals(7, ((TstJavaBean) bean.getObjectField()).getIntField());
+	}
+
+	@Test
+	public void testSetInNestedNull() {
+		TstJavaBean bean = new TstJavaBean();
+		bean.setObjectField(null);
+		final Property<Integer> OBJECT_FIELD_INT_FIELD = new Property<Integer>("objectField.intField");
+		try {
+			OBJECT_FIELD_INT_FIELD.setIn(bean, 7);
+			fail();
+		} catch (Exception e) {
+			Throwable rootException = Util.unwindException(e);
+			if (!(rootException instanceof UnresolvablePathException)) {
+				fail();
+			}
+		}
+	}
+
+	@Test
+	public void testSetInAll() {
+		TstJavaBean bean = new TstJavaBean();
+		TstJavaBean bean2 = new TstJavaBean();
+		List<TstJavaBean> beans = Arrays.asList(bean, bean2);
+
+		final Property<Integer> INT_FIELD = new Property<Integer>("intField");
+		INT_FIELD.setInAll(beans, 7);
+		assertEquals(7, bean.getIntField());
+		assertEquals(7, bean2.getIntField());
+	}
+
+	@Test
+	public void testEquals() {
+		final Property<Integer> INT_FIELD = new Property<Integer>("intField");
+		final Property<Integer> INT_FIELD2 = new Property<Integer>("intField");
+
+		assertTrue(INT_FIELD != INT_FIELD2);
+		assertTrue(INT_FIELD.equals(INT_FIELD2));
+	}
+
+	@Test
+	public void testHashCode() {
+		final Property<Integer> INT_FIELD = new Property<Integer>("intField");
+		final Property<Integer> INT_FIELD2 = new Property<Integer>("intField");
+		final Property<Long> LONG_FIELD = new Property<Long>("longField");
+
+		assertTrue(INT_FIELD.hashCode() == INT_FIELD2.hashCode());
+		assertTrue(INT_FIELD.hashCode() != LONG_FIELD.hashCode());
+	}
+
+	@Test
+	public void testOuter() {
+		Property<String> inner = new Property<String>("xyz");
+		assertEquals("xyz+", inner.outer().getName());
+
+		Property<String> inner1 = new Property<String>("xyz.xxx");
+		assertEquals("xyz.xxx+", inner1.outer().getName());
+
+		Property<String> outer = new Property<String>("xyz+");
+		assertEquals("xyz+", outer.outer().getName());
+	}
+
+	@Test
+	public void testLike() {
+		Property<String> p = new Property<String>("prop");
+		Expression e = p.like("abc");
+		assertEquals("prop like \"abc\"", e.toString());
+	}
+
+	@Test
+	public void testLikeIgnoreCase() {
+		Property<String> p = new Property<String>("prop");
+		Expression e = p.likeInsensitive("abc");
+		assertEquals("prop likeIgnoreCase \"abc\"", e.toString());
+	}
+
+	@Test
+	public void testLike_NoEscape() {
+		Property<String> p = new Property<String>("prop");
+		Expression e = p.like("ab%c");
+		assertEquals("prop like \"ab%c\"", e.toString());
+		assertEquals(0, ((PatternMatchNode) e).getEscapeChar());
+	}
+
+	@Test
+	public void testContains() {
+		Property<String> p = new Property<String>("prop");
+		Expression e = p.contains("abc");
+		assertEquals("prop like \"%abc%\"", e.toString());
+		assertEquals(0, ((PatternMatchNode) e).getEscapeChar());
+	}
+
+	@Test
+	public void testStartsWith() {
+		Property<String> p = new Property<String>("prop");
+		Expression e = p.startsWith("abc");
+		assertEquals("prop like \"abc%\"", e.toString());
+		assertEquals(0, ((PatternMatchNode) e).getEscapeChar());
+	}
+
+	@Test
+	public void testEndsWith() {
+		Property<String> p = new Property<String>("prop");
+		Expression e = p.endsWith("abc");
+		assertEquals("prop like \"%abc\"", e.toString());
+		assertEquals(0, ((PatternMatchNode) e).getEscapeChar());
+	}
+	
+	@Test
+	public void testContains_Escape1() {
+		Property<String> p = new Property<String>("prop");
+		Expression e = p.contains("a%bc");
+		assertEquals("prop like \"%a!%bc%\"", e.toString());
+		assertEquals('!', ((PatternMatchNode) e).getEscapeChar());
+	}
+	
+	@Test
+	public void testContains_Escape2() {
+		Property<String> p = new Property<String>("prop");
+		Expression e = p.contains("a_!bc");
+		assertEquals("prop like \"%a#_!bc%\"", e.toString());
+		assertEquals('#', ((PatternMatchNode) e).getEscapeChar());
+	}
 }


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

Posted by aa...@apache.org.
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/master
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());
+    }
 }