You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2017/02/07 08:56:30 UTC

cayenne git commit: CAY-2221 Apply in-memory filtering to Collections (include CAY-1356 case)

Repository: cayenne
Updated Branches:
  refs/heads/master 323771a2e -> c2d815dd0


CAY-2221 Apply in-memory filtering to Collections (include CAY-1356 case)


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

Branch: refs/heads/master
Commit: c2d815dd0f9b894e4b2b0b676c108260dcde0475
Parents: 323771a
Author: Nikita Timofeev <st...@gmail.com>
Authored: Tue Feb 7 11:53:54 2017 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Tue Feb 7 11:53:54 2017 +0300

----------------------------------------------------------------------
 .../apache/cayenne/exp/parser/ASTBetween.java   |  21 +-
 .../apache/cayenne/exp/parser/ASTDbPath.java    |   2 +-
 .../org/apache/cayenne/exp/parser/ASTEqual.java |  35 +--
 .../org/apache/cayenne/exp/parser/ASTFalse.java |  12 +-
 .../apache/cayenne/exp/parser/ASTGreater.java   |  16 +-
 .../cayenne/exp/parser/ASTGreaterOrEqual.java   |  16 +-
 .../org/apache/cayenne/exp/parser/ASTIn.java    |  48 +---
 .../org/apache/cayenne/exp/parser/ASTLess.java  |  16 +-
 .../cayenne/exp/parser/ASTLessOrEqual.java      |  16 +-
 .../org/apache/cayenne/exp/parser/ASTLike.java  |  12 +-
 .../cayenne/exp/parser/ASTLikeIgnoreCase.java   |  12 +-
 .../org/apache/cayenne/exp/parser/ASTNot.java   |   3 +-
 .../cayenne/exp/parser/ASTNotBetween.java       |  23 +-
 .../apache/cayenne/exp/parser/ASTNotEqual.java  |  15 +-
 .../org/apache/cayenne/exp/parser/ASTNotIn.java |  30 +-
 .../apache/cayenne/exp/parser/ASTNotLike.java   |  14 +-
 .../exp/parser/ASTNotLikeIgnoreCase.java        |  12 +-
 .../apache/cayenne/exp/parser/ASTObjPath.java   |   2 +-
 .../org/apache/cayenne/exp/parser/ASTTrue.java  |  12 +-
 .../cayenne/exp/parser/ConditionNode.java       |  41 +++
 .../apache/cayenne/exp/parser/SimpleNode.java   |  39 +--
 .../apache/cayenne/exp/ExpressionFactoryIT.java |   9 +-
 .../exp/parser/ASTLikeIgnoreCaseTest.java       |  36 +++
 .../exp/parser/ExpressionEvaluationIT.java      | 274 +++++++++++++++++++
 24 files changed, 508 insertions(+), 208 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBetween.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBetween.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBetween.java
index 38d2cba..0d65c84 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBetween.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBetween.java
@@ -48,23 +48,22 @@ public class ASTBetween extends ConditionNode {
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len != 3) {
-			return Boolean.FALSE;
-		}
+	protected int getRequiredChildrenCount() {
+		return 3;
+	}
 
-		Object o1 = evaluateChild(0, o);
-		Object o2 = evaluateChild(1, o);
-		Object o3 = evaluateChild(2, o);
-		Evaluator e = Evaluator.evaluator(o1);
+	@Override
+	protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+		Object o1 = evaluatedChildren[1];
+		Object o2 = evaluatedChildren[2];
+		Evaluator e = Evaluator.evaluator(o);
 
-		Integer c1 = e.compare(o1, o2);
+		Integer c1 = e.compare(o, o1);
 		if (c1 == null) {
 			return Boolean.FALSE;
 		}
 
-		Integer c2 = e.compare(o1, o3);
+		Integer c2 = e.compare(o, o2);
 		if (c2 == null) {
 			return Boolean.FALSE;
 		}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDbPath.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDbPath.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDbPath.java
index 4e1d3c9..f3f2f7b 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDbPath.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDbPath.java
@@ -103,7 +103,7 @@ public class ASTDbPath extends ASTPath {
 	private Map<?, ?> toMap_AttchedObject_MultiStepPath(ObjectContext context, Persistent persistent) {
 		Iterator<CayenneMapEntry> pathComponents = Cayenne.getObjEntity(persistent).getDbEntity()
 				.resolvePathComponents(this);
-		LinkedList<DbRelationship> reversedPathComponents = new LinkedList<DbRelationship>();
+		LinkedList<DbRelationship> reversedPathComponents = new LinkedList<>();
 
 		while (pathComponents.hasNext()) {
 			CayenneMapEntry component = pathComponents.next();

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEqual.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEqual.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEqual.java
index e2194d0..3f313e5 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEqual.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEqual.java
@@ -19,7 +19,7 @@
 
 package org.apache.cayenne.exp.parser;
 
-import java.util.List;
+import java.util.Collection;
 
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ValueInjector;
@@ -59,16 +59,14 @@ public class ASTEqual extends ConditionNode implements ValueInjector {
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len != 2) {
-			return Boolean.FALSE;
-		}
-
-		Object o1 = evaluateChild(0, o);
-		Object o2 = evaluateChild(1, o);
+	protected int getRequiredChildrenCount() {
+		return 2;
+	}
 
-		return evaluateImpl(o1, o2);
+	@Override
+	protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+		Object o2 = evaluatedChildren[1];
+		return evaluateImpl(o, o2);
 	}
 
 	/**
@@ -83,20 +81,9 @@ public class ASTEqual extends ConditionNode implements ValueInjector {
 		if (o1 == null && o2 == null) {
 			return true;
 		} else if (o1 != null) {
-
-			// Per CAY-419 we perform 'in' comparison if one object is a list,
-			// and other is not
-
-			if (o1 instanceof List && !(o2 instanceof List)) {
-				for (Object element : ((List<?>) o1)) {
-					if (element != null && Evaluator.evaluator(element).eq(element, o2)) {
-						return true;
-					}
-				}
-				return false;
-			}
-			if (o2 instanceof List && !(o1 instanceof List)) {
-				for (Object element : ((List<?>) o2)) {
+			// Per CAY-419 we perform 'in' comparison if one object is a list, and other is not
+			if (o2 instanceof Collection) {
+				for (Object element : ((Collection<?>) o2)) {
 					if (element != null && Evaluator.evaluator(element).eq(element, o1)) {
 						return true;
 					}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFalse.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFalse.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFalse.java
index a117cba..22145d0 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFalse.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFalse.java
@@ -51,14 +51,18 @@ public class ASTFalse extends ConditionNode {
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		return Boolean.FALSE;
+	protected int getRequiredChildrenCount() {
+		return 0;
+	}
+
+	@Override
+	protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+		return false;
 	}
 
 	@Override
 	protected String getExpressionOperator(int index) {
-		throw new UnsupportedOperationException("No operator for '" + ExpressionParserTreeConstants.jjtNodeName[id]
-				+ "'");
+		throw new UnsupportedOperationException("No operator for '" + ExpressionParserTreeConstants.jjtNodeName[id] + "'");
 	}
 
 	@Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreater.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreater.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreater.java
index c7eb1f1..25f1af9 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreater.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreater.java
@@ -47,16 +47,14 @@ public class ASTGreater extends ConditionNode {
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len != 2) {
-			return Boolean.FALSE;
-		}
-
-		Object o1 = evaluateChild(0, o);
-		Object o2 = evaluateChild(1, o);
-		Integer c = Evaluator.evaluator(o1).compare(o1, o2);
+	protected int getRequiredChildrenCount() {
+		return 2;
+	}
 
+	@Override
+	protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+		Object o2 = evaluatedChildren[1];
+		Integer c = Evaluator.evaluator(o).compare(o, o2);
 		return c != null && c > 0 ? Boolean.TRUE : Boolean.FALSE;
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreaterOrEqual.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreaterOrEqual.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreaterOrEqual.java
index a970c12..fb93fa8 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreaterOrEqual.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreaterOrEqual.java
@@ -49,16 +49,14 @@ public class ASTGreaterOrEqual extends ConditionNode {
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len != 2) {
-			return Boolean.FALSE;
-		}
-
-		Object o1 = evaluateChild(0, o);
-		Object o2 = evaluateChild(1, o);
-		Integer c = Evaluator.evaluator(o1).compare(o1, o2);
+	protected int getRequiredChildrenCount() {
+		return 2;
+	}
 
+	@Override
+	protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+		Object o2 = evaluatedChildren[1];
+		Integer c = Evaluator.evaluator(o).compare(o, o2);
 		return c != null && c >= 0 ? Boolean.TRUE : Boolean.FALSE;
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
index 44ced07..31a4ac36 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
@@ -19,8 +19,6 @@
 
 package org.apache.cayenne.exp.parser;
 
-import java.util.Collection;
-
 import org.apache.cayenne.exp.Expression;
 import org.apache.commons.collections.Transformer;
 
@@ -50,45 +48,27 @@ public class ASTIn extends ConditionNode {
 		connectChildren();
 	}
 
-	@SuppressWarnings("rawtypes")
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len != 2) {
-			return Boolean.FALSE;
-		}
-
-		Object o1 = evaluateChild(0, o);
-		// TODO: what if there's a NULL inside IN list?
-		// e.g. ASTEqual evals as "NULL == NULL"
-		if (o1 == null) {
-			return Boolean.FALSE;
-		}
+	protected int getRequiredChildrenCount() {
+		return 2;
+	}
 
-		Object[] objects = (Object[]) evaluateChild(1, o);
-		if (objects == null) {
-			return Boolean.FALSE;
+	@Override
+	protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        // TODO: what if there's a NULL inside IN list?
+        // e.g. ASTEqual evals as "NULL == NULL"
+		if (o == null || evaluatedChildren[1] == null) {
+			return false;
 		}
 
-		int size = objects.length;
-		for (int i = 0; i < size; i++) {
-			if (objects[i] != null) {
-				if (o1 instanceof Collection) {
-					// handle the case where we have a collection of objects
-					for (Object obj : (Collection) o1) {
-						if (Evaluator.evaluator(obj).eq(obj, objects[i])) {
-							return Boolean.TRUE;
-						}
-					}
-				} else {
-					if (Evaluator.evaluator(o1).eq(o1, objects[i])) {
-						return Boolean.TRUE;
-					}
-				}
+		Object[] objects = (Object[]) evaluatedChildren[1];
+		for (Object object : objects) {
+			if (object != null && Evaluator.evaluator(o).eq(o, object)) {
+				return true;
 			}
 		}
 
-		return Boolean.FALSE;
+		return false;
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLess.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLess.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLess.java
index 5880f93..d8c75cc 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLess.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLess.java
@@ -49,16 +49,14 @@ public class ASTLess extends ConditionNode {
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len != 2) {
-			return Boolean.FALSE;
-		}
-
-		Object o1 = evaluateChild(0, o);
-		Object o2 = evaluateChild(1, o);
-		Integer c = Evaluator.evaluator(o1).compare(o1, o2);
+	protected int getRequiredChildrenCount() {
+		return 2;
+	}
 
+	@Override
+	protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+		Object o2 = evaluatedChildren[1];
+		Integer c = Evaluator.evaluator(o).compare(o, o2);
 		return c != null && c < 0 ? Boolean.TRUE : Boolean.FALSE;
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLessOrEqual.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLessOrEqual.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLessOrEqual.java
index d686797..09d10cb 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLessOrEqual.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLessOrEqual.java
@@ -49,16 +49,14 @@ public class ASTLessOrEqual extends ConditionNode {
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len != 2) {
-			return Boolean.FALSE;
-		}
-
-		Object o1 = evaluateChild(0, o);
-		Object o2 = evaluateChild(1, o);
-		Integer c = Evaluator.evaluator(o1).compare(o1, o2);
+	protected int getRequiredChildrenCount() {
+		return 2;
+	}
 
+	@Override
+	protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+		Object o2 = evaluatedChildren[1];
+		Integer c = Evaluator.evaluator(o).compare(o, o2);
 		return c != null && c <= 0 ? Boolean.TRUE : Boolean.FALSE;
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLike.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLike.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLike.java
index 31afa92..9d1eda6 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLike.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLike.java
@@ -56,13 +56,13 @@ public class ASTLike extends PatternMatchNode {
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len != 2) {
-			return Boolean.FALSE;
-		}
+	protected int getRequiredChildrenCount() {
+		return 2;
+	}
 
-		String s1 = ConversionUtil.toString(evaluateChild(0, o));
+	@Override
+	protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+		String s1 = ConversionUtil.toString(o);
 		if (s1 == null) {
 			return Boolean.FALSE;
 		}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCase.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCase.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCase.java
index c8be6a0..b0bd207 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCase.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCase.java
@@ -57,13 +57,13 @@ public class ASTLikeIgnoreCase extends IgnoreCaseNode {
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len != 2) {
-			return Boolean.FALSE;
-		}
+	protected int getRequiredChildrenCount() {
+		return 2;
+	}
 
-		String s1 = ConversionUtil.toString(evaluateChild(0, o));
+	@Override
+	protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+		String s1 = ConversionUtil.toString(o);
 		if (s1 == null) {
 			return Boolean.FALSE;
 		}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNot.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNot.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNot.java
index 37d14b4..0c493e4 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNot.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNot.java
@@ -91,7 +91,6 @@ public class ASTNot extends AggregateConditionNode {
 
 	@Override
 	protected String getExpressionOperator(int index) {
-		throw new UnsupportedOperationException("No operator for '" + ExpressionParserTreeConstants.jjtNodeName[id]
-				+ "'");
+		throw new UnsupportedOperationException("No operator for '" + ExpressionParserTreeConstants.jjtNodeName[id] + "'");
 	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotBetween.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotBetween.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotBetween.java
index 000e5ee..62743e3 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotBetween.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotBetween.java
@@ -43,23 +43,22 @@ public class ASTNotBetween extends ConditionNode {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
-        int len = jjtGetNumChildren();
-        if (len != 3) {
-            return Boolean.FALSE;
-        }
+    protected int getRequiredChildrenCount() {
+        return 3;
+    }
+
+    @Override
+    protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        Object o1 = evaluatedChildren[1];
+        Object o2 = evaluatedChildren[2];
+        Evaluator e = Evaluator.evaluator(o);
 
-        Object o1 = evaluateChild(0, o);
-        Object o2 = evaluateChild(1, o);
-        Object o3 = evaluateChild(2, o);
-        Evaluator e = Evaluator.evaluator(o1);
-        
-        Integer c1 = e.compare(o1, o2);
+        Integer c1 = e.compare(o, o1);
         if (c1 == null) {
             return Boolean.FALSE;
         }
 
-        Integer c2 = e.compare(o1, o3);
+        Integer c2 = e.compare(o, o2);
         if (c2 == null) {
             return Boolean.FALSE;
         }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotEqual.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotEqual.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotEqual.java
index 1ec687b..211b83e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotEqual.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotEqual.java
@@ -46,15 +46,14 @@ public class ASTNotEqual extends ConditionNode {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
-        int len = jjtGetNumChildren();
-        if (len != 2) {
-            return Boolean.FALSE;
-        }
+    protected int getRequiredChildrenCount() {
+        return 2;
+    }
 
-        Object o1 = evaluateChild(0, o);
-        Object o2 = evaluateChild(1, o);
-        return !ASTEqual.evaluateImpl(o1, o2);
+    @Override
+    protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        Object o2 = evaluatedChildren[1];
+        return !ASTEqual.evaluateImpl(o, o2);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
index ea82c38..09cda00 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
@@ -44,30 +44,24 @@ public class ASTNotIn extends ConditionNode {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
-        int len = jjtGetNumChildren();
-        if (len != 2) {
-            return Boolean.FALSE;
-        }
-
-        Object o1 = evaluateChild(0, o);
-        if (o1 == null) {
-            return Boolean.FALSE;
-        }
+    protected int getRequiredChildrenCount() {
+        return 2;
+    }
 
-        Object[] objects = (Object[]) evaluateChild(1, o);
-        if (objects == null) {
-            return Boolean.FALSE;
+    @Override
+    protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        if (o == null || evaluatedChildren[1] == null) {
+            return false;
         }
 
-        int size = objects.length;
-        for (int i = 0; i < size; i++) {
-            if (objects[i] != null && Evaluator.evaluator(o1).eq(o1, objects[i])) {
-                return Boolean.FALSE;
+        Object[] objects = (Object[]) evaluatedChildren[1];
+        for (Object object : objects) {
+            if (object != null && Evaluator.evaluator(o).eq(o, object)) {
+                return false;
             }
         }
 
-        return Boolean.TRUE;
+        return true;
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLike.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLike.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLike.java
index da0fd76..ab90bc6 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLike.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLike.java
@@ -50,15 +50,15 @@ public class ASTNotLike extends PatternMatchNode {
         jjtAddChild(new ASTScalar(value), 1);
         connectChildren();
     }
-    
+
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
-        int len = jjtGetNumChildren();
-        if (len != 2) {
-            return Boolean.FALSE;
-        }
+    protected int getRequiredChildrenCount() {
+        return 2;
+    }
 
-        String s1 = ConversionUtil.toString(evaluateChild(0, o));
+    @Override
+    protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        String s1 = ConversionUtil.toString(o);
         if (s1 == null) {
             return Boolean.FALSE;
         }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLikeIgnoreCase.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLikeIgnoreCase.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLikeIgnoreCase.java
index f6f6d11..b962fc3 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLikeIgnoreCase.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLikeIgnoreCase.java
@@ -53,13 +53,13 @@ public class ASTNotLikeIgnoreCase extends IgnoreCaseNode {
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len != 2) {
-			return Boolean.FALSE;
-		}
+	protected int getRequiredChildrenCount() {
+		return 2;
+	}
 
-		String s1 = ConversionUtil.toString(evaluateChild(0, o));
+	@Override
+	protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+		String s1 = ConversionUtil.toString(o);
 		if (s1 == null) {
 			return Boolean.FALSE;
 		}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTObjPath.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTObjPath.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTObjPath.java
index 3628098..fa75195 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTObjPath.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTObjPath.java
@@ -95,7 +95,7 @@ public class ASTObjPath extends ASTPath {
 	}
 
 	void injectValue(Object source, Object value) {
-		if (getPath().indexOf(ObjEntity.PATH_SEPARATOR) == -1) {
+		if (!getPath().contains(ObjEntity.PATH_SEPARATOR)) {
 			try {
 				if (source instanceof DataObject) {
 					((DataObject) source).writeProperty(getPath(), value);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTTrue.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTTrue.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTTrue.java
index e41cf87..ae9de66 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTTrue.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTTrue.java
@@ -47,14 +47,18 @@ public class ASTTrue extends ConditionNode {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
-        return Boolean.TRUE;
+    protected int getRequiredChildrenCount() {
+        return 0;
+    }
+
+    @Override
+    protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        return true;
     }
 
     @Override
     protected String getExpressionOperator(int index) {
-        throw new UnsupportedOperationException("No operator for '" + ExpressionParserTreeConstants.jjtNodeName[id]
-                + "'");
+        throw new UnsupportedOperationException("No operator for '" + ExpressionParserTreeConstants.jjtNodeName[id] + "'");
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ConditionNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ConditionNode.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ConditionNode.java
index 7b5543f..7a05940 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ConditionNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ConditionNode.java
@@ -20,6 +20,9 @@
 
 package org.apache.cayenne.exp.parser;
 
+import java.util.Collection;
+import java.util.Map;
+
 import org.apache.cayenne.exp.ExpressionException;
 
 /**
@@ -46,4 +49,42 @@ public abstract class ConditionNode extends SimpleNode {
 
         super.jjtSetParent(n);
     }
+
+    @Override
+    protected Object evaluateNode(Object o) throws Exception {
+        int len = jjtGetNumChildren();
+        int requiredLen = getRequiredChildrenCount();
+        if (len != requiredLen) {
+            return Boolean.FALSE;
+        }
+
+        if(requiredLen == 0) {
+            return evaluateSubNode(null, null);
+        }
+
+        Object[] evaluatedChildren = new Object[requiredLen];
+        for(int i=0; i<requiredLen; i++) {
+            evaluatedChildren[i] = evaluateChild(i, o);
+        }
+
+        Object firstChild = evaluatedChildren[0];
+        // don't care here for keys
+        if(firstChild instanceof Map) {
+            firstChild = ((Map) firstChild).values();
+        }
+        if (firstChild instanceof Collection) {
+            for(Object c : (Collection)firstChild) {
+                if(evaluateSubNode(c, evaluatedChildren)) {
+                    return Boolean.TRUE;
+                }
+            }
+            return Boolean.FALSE;
+        } else {
+            return evaluateSubNode(firstChild, evaluatedChildren);
+        }
+    }
+
+    abstract protected int getRequiredChildrenCount();
+
+    abstract protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception;
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/SimpleNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/SimpleNode.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/SimpleNode.java
index cbe9e16..117fd12 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/SimpleNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/SimpleNode.java
@@ -35,11 +35,10 @@ import org.apache.cayenne.exp.ExpressionException;
 import org.apache.cayenne.util.Util;
 
 /**
- * Superclass of AST* expressions that implements Node interface defined by
- * JavaCC framework.
+ * Superclass of AST* expressions that implements Node interface defined by JavaCC framework.
  * <p>
- * Some parts of the parser are based on OGNL parser, copyright (c) 2002, Drew
- * Davidson and Luke Blanshard.
+ * Some parts of the parser are based on OGNL parser,
+ * copyright (c) 2002, Drew Davidson and Luke Blanshard.
  * </p>
  * 
  * @since 1.1
@@ -84,9 +83,7 @@ public abstract class SimpleNode extends Expression implements Node {
 		if (null != parameterAccumulator) {
 			parameterAccumulator.add(scalar);
 			out.append('?');
-			out.append(Integer.toString(parameterAccumulator.size())); // parameters
-																		// start
-																		// at 1
+			out.append(Integer.toString(parameterAccumulator.size())); // parameters start at 1
 			return;
 		}
 
@@ -105,7 +102,7 @@ public abstract class SimpleNode extends Expression implements Node {
 		if (scalar instanceof Enum<?>) {
 			Enum<?> e = (Enum<?>) scalar;
 			out.append("enum:");
-			out.append(e.getClass().getName() + "." + e.name());
+			out.append(e.getClass().getName()).append(".").append(e.name());
 			return;
 		}
 
@@ -131,8 +128,7 @@ public abstract class SimpleNode extends Expression implements Node {
 			out.append(quoteChar);
 		}
 
-		// encode only ObjectId for Persistent, ensure that the order of keys is
-		// predictable....
+		// encode only ObjectId for Persistent, ensure that the order of keys is predictable....
 
 		// TODO: should we use UUID here?
 		if (scalar instanceof Persistent) {
@@ -142,7 +138,7 @@ public abstract class SimpleNode extends Expression implements Node {
 		} else if (scalar instanceof Enum<?>) {
 			Enum<?> e = (Enum<?>) scalar;
 			out.append("enum:");
-			out.append(e.getClass().getName() + "." + e.name());
+			out.append(e.getClass().getName()).append(".").append(e.name());
 		} else {
 			appendAsEscapedString(out, String.valueOf(scalar));
 		}
@@ -153,8 +149,7 @@ public abstract class SimpleNode extends Expression implements Node {
 	}
 
 	/**
-	 * Utility method that prints a string to the provided Appendable, escaping
-	 * special characters.
+	 * Utility method that prints a string to the provided Appendable, escaping special characters.
 	 */
 	protected static void appendAsEscapedString(Appendable out, String source) throws IOException {
 		int len = source.length();
@@ -209,8 +204,7 @@ public abstract class SimpleNode extends Expression implements Node {
 	protected abstract String getExpressionOperator(int index);
 
 	/**
-	 * Returns operator for ebjql statements, which can differ for Cayenne
-	 * expression operator
+	 * Returns operator for EJBQL statements, which can differ for Cayenne expression operator
 	 */
 	protected String getEJBQLExpressionOperator(int index) {
 		return getExpressionOperator(index);
@@ -317,10 +311,8 @@ public abstract class SimpleNode extends Expression implements Node {
 	public Object getOperand(int index) {
 		Node child = jjtGetChild(index);
 
-		// unwrap ASTScalar nodes - this is likely a temporary thing to keep it
-		// compatible
-		// with QualifierTranslator. In the future we might want to keep scalar
-		// nodes
+		// unwrap ASTScalar nodes - this is likely a temporary thing to keep it compatible
+		// with QualifierTranslator. In the future we might want to keep scalar nodes
 		// for the purpose of expression evaluation.
 		return unwrapChild(child);
 	}
@@ -399,9 +391,8 @@ public abstract class SimpleNode extends Expression implements Node {
 	protected void connectChildren() {
 		if (children != null) {
 			for (Node child : children) {
-				// although nulls are expected to be wrapped in scalar, still
-				// doing a
-				// check here to make it more robust
+				// although nulls are expected to be wrapped in scalar,
+				// still doing a check here to make it more robust
 				if (child != null) {
 					child.jjtSetParent(this);
 				}
@@ -426,8 +417,8 @@ public abstract class SimpleNode extends Expression implements Node {
 			return evaluateNode(o);
 		} catch (Throwable th) {
 			String string = this.toString();
-			throw new ExpressionException("Error evaluating expression '" + string + "'", string,
-					Util.unwindException(th));
+			throw new ExpressionException("Error evaluating expression '%s'",
+					string, Util.unwindException(th), string);
 		}
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/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 7fb78a9..c1b2ae2 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
@@ -56,8 +56,9 @@ public class ExpressionFactoryIT extends ServerCase {
 	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);
+		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");
@@ -68,12 +69,12 @@ public class ExpressionFactoryIT extends ServerCase {
 
 		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", p1).match(artist)); // changed to align with SQL
 		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", "p1").match(artist)); // changed to align with SQL
 		assertTrue(ExpressionFactory.noMatchExp("paintingArray.paintingTitle", "p3").match(artist));
 
 		assertTrue(ExpressionFactory.inExp("paintingTitle", "p1").match(p1));

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCaseTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCaseTest.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCaseTest.java
index 9453a2f..55946cf 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCaseTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCaseTest.java
@@ -18,12 +18,15 @@
  ****************************************************************/
 package org.apache.cayenne.exp.parser;
 
+import java.util.Arrays;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.testdo.testmap.Painting;
 import org.junit.Test;
 
 public class ASTLikeIgnoreCaseTest {
@@ -54,4 +57,37 @@ public class ASTLikeIgnoreCaseTest {
 		assertTrue("Failed: " + like, like.match(match2));
 		assertFalse("Failed: " + notLike, notLike.match(match2));
 	}
+
+	@Test
+	public void testEvaluateWithCollection() {
+		Expression like = new ASTLikeIgnoreCase(new ASTObjPath("paintingArray.paintingTitle"), "aBcD");
+		Expression notLike = new ASTNotLikeIgnoreCase(new ASTObjPath("paintingArray.paintingTitle"), "aBcD");
+
+		Artist noMatch1 = new Artist();
+		noMatch1.writePropertyDirectly("paintingArray",
+				Arrays.asList(createPainting("xyz"), createPainting("abc")));
+
+		assertFalse("Failed: " + like, like.match(noMatch1));
+		assertTrue("Failed: " + like, notLike.match(noMatch1));
+
+		Artist match1 = new Artist();
+		match1.writePropertyDirectly("paintingArray",
+				Arrays.asList(createPainting("AbCd"), createPainting("abcd")));
+
+		assertTrue("Failed: " + like, like.match(match1));
+		assertFalse("Failed: " + like, notLike.match(match1));
+
+		Artist match2 = new Artist();
+		match2.writePropertyDirectly("paintingArray",
+				Arrays.asList(createPainting("Xzy"), createPainting("abcd")));
+
+		assertTrue("Failed: " + like, like.match(match2));
+		assertTrue("Failed: " + like, notLike.match(match2));
+	}
+
+	private Painting createPainting(String name) {
+		Painting p = new Painting();
+		p.setPaintingTitle(name);
+		return p;
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c2d815dd/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluationIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluationIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluationIT.java
new file mode 100644
index 0000000..e33dba3
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluationIT.java
@@ -0,0 +1,274 @@
+/*****************************************************************
+ *   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.parser;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.query.Ordering;
+import org.apache.cayenne.test.jdbc.DBHelper;
+import org.apache.cayenne.test.jdbc.TableHelper;
+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.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Here we compare Expression evaluation in-memory vs execution in database.
+ * Results should be same for both cases.
+ * Here is primary collection of complex expressions:
+ *  - To-Many relationships
+ *  - Nulls
+ *
+ * @since 4.0
+ */
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class ExpressionEvaluationIT extends ServerCase {
+
+    @Inject
+    private DataContext context;
+
+    @Inject
+    private DBHelper dbHelper;
+
+    private TableHelper tArtist, tGallery, tPaintings;
+
+    @Before
+    public void createArtistsDataSet() throws Exception {
+        tArtist = new TableHelper(dbHelper, "ARTIST");
+        tArtist.setColumns("ARTIST_ID", "ARTIST_NAME", "DATE_OF_BIRTH");
+
+        long dateBase = System.currentTimeMillis();
+        for (int i = 1; i <= 6; i++) {
+            tArtist.insert(i, "artist" + i, new java.sql.Date(dateBase + 10000 * i));
+        }
+
+        tGallery = new TableHelper(dbHelper, "GALLERY");
+        tGallery.setColumns("GALLERY_ID", "GALLERY_NAME");
+        tGallery.insert(1, "tate modern");
+
+        tPaintings = new TableHelper(dbHelper, "PAINTING");
+        tPaintings.setColumns("PAINTING_ID", "PAINTING_TITLE", "ARTIST_ID", "GALLERY_ID", "ESTIMATED_PRICE");
+        for (int i = 1; i <= 10; i++) {
+            tPaintings.insert(i, "painting" + i, i % 5 + 1, 1, i * 100);
+        }
+
+        tPaintings.insert(11, "painting11", null, 1, 10000);
+    }
+
+    @Test
+    public void testSimpleLike() {
+        Expression exp = Artist.ARTIST_NAME
+                .like("artist%");
+
+        compareSqlAndEval(exp, 6);
+    }
+
+    @Test
+    public void testSimpleNotLike() {
+        Expression exp = Artist.ARTIST_NAME
+                .nlike("artist%");
+
+        compareSqlAndEval(exp, 0);
+    }
+
+    @Test
+    public void testSimpleEqual() {
+        Expression exp = Artist.ARTIST_NAME
+                .eq("artist2");
+
+        compareSqlAndEval(exp, 1);
+    }
+
+    @Test
+    public void testSimpleNotEqual() {
+        Expression exp = Artist.ARTIST_NAME
+                .ne("artist2");
+
+        compareSqlAndEval(exp, 5);
+    }
+
+    @Test
+    public void testLikeIgnoreCase() {
+        Expression exp = Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE)
+                .likeIgnoreCase("painting%");
+
+        compareSqlAndEval(exp, 5);
+    }
+
+    @Test
+    public void testNotLikeIgnoreCase() {
+        Expression exp = Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE)
+                .likeIgnoreCase("PaInTing%");
+
+        compareSqlAndEval(exp, 5);
+    }
+
+    @Test
+    public void testLike() {
+        Expression exp = Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE)
+                .like("painting%");
+
+        compareSqlAndEval(exp, 5);
+    }
+
+    @Test
+    public void testNotLike() {
+        Expression exp = Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE)
+                .like("painting%");
+
+        compareSqlAndEval(exp, 5);
+    }
+
+    @Test
+    public void testEqual() {
+        Expression exp = Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE)
+                .eq("painting1");
+
+        compareSqlAndEval(exp, 1);
+    }
+
+    @Test
+    public void testNotEqual1() {
+        Expression exp = Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE)
+                .ne("painting1");
+
+        compareSqlAndEval(exp, 5);
+    }
+
+    @Test
+    public void testNotEqual2() {
+        Expression exp = Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE)
+                .ne("painting11");
+
+        compareSqlAndEval(exp, 5);
+    }
+
+    @Test
+    public void testNotEqual3() {
+        Expression exp = Artist.PAINTING_ARRAY.dot(Painting.PAINTING_TITLE)
+                .ne("zyz");
+
+        compareSqlAndEval(exp, 5);
+    }
+
+    @Test
+    public void testBetween() {
+        Expression exp = Artist.PAINTING_ARRAY.dot(Painting.ESTIMATED_PRICE)
+                .between(new BigDecimal(300), new BigDecimal(600));
+
+        compareSqlAndEval(exp, 4);
+    }
+
+    @Test
+    public void testNotBetween() {
+        Expression exp = ExpressionFactory.notBetweenExp(
+                "paintingArray.estimatedPrice",
+                new BigDecimal(300), new BigDecimal(600));
+
+        compareSqlAndEval(exp, 5);
+    }
+
+    @Test
+    public void testGreater() {
+        Expression exp = Artist.PAINTING_ARRAY.dot(Painting.ESTIMATED_PRICE)
+                .gt(new BigDecimal(799));
+
+        compareSqlAndEval(exp, 3);
+    }
+
+    @Test
+    public void testGreaterEqual() {
+        Expression exp = Artist.PAINTING_ARRAY.dot(Painting.ESTIMATED_PRICE)
+                .gte(new BigDecimal(800));
+
+        compareSqlAndEval(exp, 3);
+    }
+
+    @Test
+    public void testIn() {
+        Expression exp = Artist.PAINTING_ARRAY.dot(Painting.ESTIMATED_PRICE)
+                .in(new BigDecimal(800), new BigDecimal(300), new BigDecimal(700));
+
+        compareSqlAndEval(exp, 2);
+    }
+
+    @Test
+    public void testNotIn() {
+        Expression exp = Artist.PAINTING_ARRAY.dot(Painting.ESTIMATED_PRICE)
+                .nin(new BigDecimal(800), new BigDecimal(200), new BigDecimal(300), new BigDecimal(400), new BigDecimal(700));
+
+        compareSqlAndEval(exp, 3);
+    }
+
+    @Test
+    public void testLess() {
+        Expression exp = Artist.PAINTING_ARRAY.dot(Painting.ESTIMATED_PRICE)
+                .lt(new BigDecimal(801));
+
+        compareSqlAndEval(exp, 5);
+    }
+
+    @Test
+    public void testLessOrEqual() {
+        Expression exp = Artist.PAINTING_ARRAY.dot(Painting.ESTIMATED_PRICE)
+                .lte(new BigDecimal(800));
+
+        compareSqlAndEval(exp, 5);
+    }
+
+    @Test
+    @Ignore("Null comparisons are partially broken for now")
+    public void testNull() throws Exception {
+        tArtist.deleteAll();
+        tArtist.insert(7, "artist7", null);
+
+        Expression expression = Artist.DATE_OF_BIRTH.gt(new java.sql.Date(System.currentTimeMillis()));
+        compareSqlAndEval(expression, 0);
+
+        Expression expression1 = expression.notExp();
+        compareSqlAndEval(expression1, 0); // this one will fail, in-memory filter will match one Artist
+    }
+
+    private void compareSqlAndEval(Expression exp, int expectedCount) {
+        // apply exp in SQL
+        Ordering ordering = new Ordering("db:ARTIST_ID");
+        List<Artist> filteredInSQL = ObjectSelect.query(Artist.class, exp).orderBy(ordering).select(context);
+        // apply exp to in-memory collection
+        List<Artist> filteredInMemory = exp.filterObjects(
+                ObjectSelect.query(Artist.class).prefetch(Artist.PAINTING_ARRAY.disjoint()).select(context)
+        );
+        ordering.orderList(filteredInMemory);
+
+        assertEquals(expectedCount, filteredInMemory.size());
+        assertEquals(filteredInSQL, filteredInMemory);
+    }
+}