cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From johnth...@apache.org
Subject [1/6] cayenne git commit: Soem fixes for evaluating Expressions with nulls in memory
Date Tue, 09 Aug 2016 16:30:30 GMT
Repository: cayenne
Updated Branches:
  refs/heads/ics11 [created] 6b73bad78


Soem fixes for evaluating Expressions with nulls in memory

More fixes for evaluating Expression with nulls in memory

null does not short circuit an AND expression
select null and false; -- returns false
select null and true; -- returns null

Add unit tests for in memory evaluation of expression involving nulls, bug fix


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

Branch: refs/heads/ics11
Commit: b30e0fa21620cda5014adb70f690849965b9b49b
Parents: b93a51c
Author: David Feshbach <dfeshbach@icsusa.com>
Authored: Mon Feb 24 11:51:30 2014 -0600
Committer: John Huss <johnthuss@apache.org>
Committed: Wed Jun 24 11:40:12 2015 -0500

----------------------------------------------------------------------
 .../java/org/apache/cayenne/exp/Expression.java | 1177 +++++++++---------
 .../org/apache/cayenne/exp/parser/ASTAnd.java   |  175 +--
 .../org/apache/cayenne/exp/parser/ASTOr.java    |  145 ++-
 .../apache/cayenne/exp/parser/Evaluator.java    |    2 +-
 .../parser/ExpressionEvaluateInMemoryIT.java    |  682 ++++++++++
 5 files changed, 1448 insertions(+), 733 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/b30e0fa2/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
index 105bcdd..0c8812d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
@@ -46,221 +46,221 @@ public abstract class Expression implements Serializable, XMLSerializable {
 
 	private static final long serialVersionUID = 5268695167038124596L;
 
-	/**
-	 * A value that a Transformer might return to indicate that a node has to be
-	 * pruned from the expression during the transformation.
-	 * 
-	 * @since 1.2
-	 */
-	public final static Object PRUNED_NODE = new Object();
-
-	public static final int AND = 0;
-	public static final int OR = 1;
-	public static final int NOT = 2;
-	public static final int EQUAL_TO = 3;
-	public static final int NOT_EQUAL_TO = 4;
-	public static final int LESS_THAN = 5;
-	public static final int GREATER_THAN = 6;
-	public static final int LESS_THAN_EQUAL_TO = 7;
-	public static final int GREATER_THAN_EQUAL_TO = 8;
-	public static final int BETWEEN = 9;
-	public static final int IN = 10;
-	public static final int LIKE = 11;
-	public static final int LIKE_IGNORE_CASE = 12;
-	public static final int ADD = 16;
-	public static final int SUBTRACT = 17;
-	public static final int MULTIPLY = 18;
-	public static final int DIVIDE = 19;
-	public static final int NEGATIVE = 20;
-	public static final int TRUE = 21;
-	public static final int FALSE = 22;
-
-	/**
-	 * Expression describes a path relative to an ObjEntity. OBJ_PATH expression
-	 * is resolved relative to some root ObjEntity. Path expression components
-	 * are separated by "." (dot). Path can point to either one of these:
-	 * <ul>
-	 * <li><i>An attribute of root ObjEntity.</i> For entity Gallery OBJ_PATH
-	 * expression "galleryName" will point to ObjAttribute "galleryName"
-	 * <li><i>Another ObjEntity related to root ObjEntity via a chain of
-	 * relationships.</i> For entity Gallery OBJ_PATH expression
-	 * "paintingArray.toArtist" will point to ObjEntity "Artist"
-	 * <li><i>ObjAttribute of another ObjEntity related to root ObjEntity via a
-	 * chain of relationships.</i> For entity Gallery OBJ_PATH expression
-	 * "paintingArray.toArtist.artistName" will point to ObjAttribute
-	 * "artistName"
-	 * </ul>
-	 */
-	public static final int OBJ_PATH = 26;
-
-	/**
-	 * Expression describes a path relative to a DbEntity. DB_PATH expression is
-	 * resolved relative to some root DbEntity. Path expression components are
-	 * separated by "." (dot). Path can point to either one of these:
-	 * <ul>
-	 * <li><i>An attribute of root DbEntity.</i> For entity GALLERY, DB_PATH
-	 * expression "GALLERY_NAME" will point to a DbAttribute "GALLERY_NAME".</li>
-	 * <li><i>Another DbEntity related to root DbEntity via a chain of
-	 * relationships.</i> For entity GALLERY DB_PATH expression
-	 * "paintingArray.toArtist" will point to DbEntity "ARTIST".</li>
-	 * <li><i>DbAttribute of another ObjEntity related to root DbEntity via a
-	 * chain of relationships.</i> For entity GALLERY DB_PATH expression
-	 * "paintingArray.toArtist.ARTIST_NAME" will point to DbAttribute
-	 * "ARTIST_NAME".</li>
-	 * </ul>
-	 */
-	public static final int DB_PATH = 27;
-
-	/**
-	 * Interpreted as a comma-separated list of literals.
-	 */
-	public static final int LIST = 28;
-
-	public static final int NOT_BETWEEN = 35;
-	public static final int NOT_IN = 36;
-	public static final int NOT_LIKE = 37;
-	public static final int NOT_LIKE_IGNORE_CASE = 38;
-
-	/**
-	 * @since 3.1
-	 */
-	public static final int BITWISE_NOT = 39;
-
-	/**
-	 * @since 3.1
-	 */
-	public static final int BITWISE_AND = 40;
-
-	/**
-	 * @since 3.1
-	 */
-	public static final int BITWISE_OR = 41;
-
-	/**
-	 * @since 3.1
-	 */
-	public static final int BITWISE_XOR = 42;
-
-	/**
+    /**
+     * A value that a Transformer might return to indicate that a node has to be
+     * pruned from the expression during the transformation.
+     * 
+     * @since 1.2
+     */
+    public final static Object PRUNED_NODE = new Object();
+
+    public static final int AND = 0;
+    public static final int OR = 1;
+    public static final int NOT = 2;
+    public static final int EQUAL_TO = 3;
+    public static final int NOT_EQUAL_TO = 4;
+    public static final int LESS_THAN = 5;
+    public static final int GREATER_THAN = 6;
+    public static final int LESS_THAN_EQUAL_TO = 7;
+    public static final int GREATER_THAN_EQUAL_TO = 8;
+    public static final int BETWEEN = 9;
+    public static final int IN = 10;
+    public static final int LIKE = 11;
+    public static final int LIKE_IGNORE_CASE = 12;
+    public static final int ADD = 16;
+    public static final int SUBTRACT = 17;
+    public static final int MULTIPLY = 18;
+    public static final int DIVIDE = 19;
+    public static final int NEGATIVE = 20;
+    public static final int TRUE = 21;
+    public static final int FALSE = 22;
+
+    /**
+     * Expression describes a path relative to an ObjEntity. OBJ_PATH expression
+     * is resolved relative to some root ObjEntity. Path expression components
+     * are separated by "." (dot). Path can point to either one of these:
+     * <ul>
+     * <li><i>An attribute of root ObjEntity.</i> For entity Gallery OBJ_PATH
+     * expression "galleryName" will point to ObjAttribute "galleryName"
+     * <li><i>Another ObjEntity related to root ObjEntity via a chain of
+     * relationships.</i> For entity Gallery OBJ_PATH expression
+     * "paintingArray.toArtist" will point to ObjEntity "Artist"
+     * <li><i>ObjAttribute of another ObjEntity related to root ObjEntity via a
+     * chain of relationships.</i> For entity Gallery OBJ_PATH expression
+     * "paintingArray.toArtist.artistName" will point to ObjAttribute
+     * "artistName"
+     * </ul>
+     */
+    public static final int OBJ_PATH = 26;
+
+    /**
+     * Expression describes a path relative to a DbEntity. DB_PATH expression is
+     * resolved relative to some root DbEntity. Path expression components are
+     * separated by "." (dot). Path can point to either one of these:
+     * <ul>
+     * <li><i>An attribute of root DbEntity.</i> For entity GALLERY, DB_PATH
+     * expression "GALLERY_NAME" will point to a DbAttribute "GALLERY_NAME".</li>
+     * <li><i>Another DbEntity related to root DbEntity via a chain of
+     * relationships.</i> For entity GALLERY DB_PATH expression
+     * "paintingArray.toArtist" will point to DbEntity "ARTIST".</li>
+     * <li><i>DbAttribute of another ObjEntity related to root DbEntity via a
+     * chain of relationships.</i> For entity GALLERY DB_PATH expression
+     * "paintingArray.toArtist.ARTIST_NAME" will point to DbAttribute
+     * "ARTIST_NAME".</li>
+     * </ul>
+     */
+    public static final int DB_PATH = 27;
+
+    /**
+     * Interpreted as a comma-separated list of literals.
+     */
+    public static final int LIST = 28;
+
+    public static final int NOT_BETWEEN = 35;
+    public static final int NOT_IN = 36;
+    public static final int NOT_LIKE = 37;
+    public static final int NOT_LIKE_IGNORE_CASE = 38;
+
+    /**
+     * @since 3.1
+     */
+    public static final int BITWISE_NOT = 39;
+
+    /**
+     * @since 3.1
+     */
+    public static final int BITWISE_AND = 40;
+
+    /**
+     * @since 3.1
+     */
+    public static final int BITWISE_OR = 41;
+
+    /**
+     * @since 3.1
+     */
+    public static final int BITWISE_XOR = 42;
+
+    /**
 	 * @since 4.0
-	 */
-	public static final int BITWISE_LEFT_SHIFT = 43;
+     */
+    public static final int BITWISE_LEFT_SHIFT = 43;
 
-	/**
+    /**
 	 * @since 4.0
-	 */
-	public static final int BITWISE_RIGHT_SHIFT = 44;
+     */
+    public static final int BITWISE_RIGHT_SHIFT = 44;
 
-	protected int type;
+    protected int type;
 
-	/**
-	 * Parses string, converting it to Expression. If string does not represent
-	 * a semantically correct expression, an ExpressionException is thrown.
-	 * 
-	 * @since 1.1
+    /**
+     * Parses string, converting it to Expression. If string does not represent
+     * a semantically correct expression, an ExpressionException is thrown.
+     * 
+     * @since 1.1
 	 * @deprecated since 4.0 use
 	 *             {@link ExpressionFactory#exp(String, Object...)}
-	 */
+     */
 	@Deprecated
-	public static Expression fromString(String expressionString) {
+    public static Expression fromString(String expressionString) {
 		return exp(expressionString);
-	}
-
-	/**
-	 * Returns a map of path aliases for this expression. It returns a non-empty
-	 * map only if this is a path expression and the aliases are known at the
-	 * expression creation time. Otherwise an empty map is returned.
-	 * 
-	 * @since 3.0
-	 */
-	public abstract Map<String, String> getPathAliases();
-
-	/**
-	 * Returns String label for this expression. Used for debugging.
-	 */
-	public String expName() {
-		switch (type) {
-		case AND:
-			return "AND";
-		case OR:
-			return "OR";
-		case NOT:
-			return "NOT";
-		case EQUAL_TO:
-			return "=";
-		case NOT_EQUAL_TO:
-			return "<>";
-		case LESS_THAN:
-			return "<";
-		case LESS_THAN_EQUAL_TO:
-			return "<=";
-		case GREATER_THAN:
-			return ">";
-		case GREATER_THAN_EQUAL_TO:
-			return ">=";
-		case BETWEEN:
-			return "BETWEEN";
-		case IN:
-			return "IN";
-		case LIKE:
-			return "LIKE";
-		case LIKE_IGNORE_CASE:
-			return "LIKE_IGNORE_CASE";
-		case OBJ_PATH:
-			return "OBJ_PATH";
-		case DB_PATH:
-			return "DB_PATH";
-		case LIST:
-			return "LIST";
-		case NOT_BETWEEN:
-			return "NOT BETWEEN";
-		case NOT_IN:
-			return "NOT IN";
-		case NOT_LIKE:
-			return "NOT LIKE";
-		case NOT_LIKE_IGNORE_CASE:
-			return "NOT LIKE IGNORE CASE";
-		default:
-			return "other";
-		}
-	}
-
-	@Override
-	public boolean equals(Object object) {
-		if (!(object instanceof Expression)) {
-			return false;
-		}
-
-		Expression e = (Expression) object;
-
-		if (e.getType() != getType() || e.getOperandCount() != getOperandCount()) {
-			return false;
-		}
-
-		// compare operands
-		int len = e.getOperandCount();
-		for (int i = 0; i < len; i++) {
-			if (!Util.nullSafeEquals(e.getOperand(i), getOperand(i))) {
-				return false;
-			}
-		}
-
-		return true;
-	}
-
-	/**
-	 * Returns a type of expression. Most common types are defined as public
-	 * static fields of this interface.
-	 */
-	public int getType() {
-		return type;
-	}
-
-	public void setType(int type) {
-		this.type = type;
-	}
-
-	/**
+    }
+
+    /**
+     * Returns a map of path aliases for this expression. It returns a non-empty
+     * map only if this is a path expression and the aliases are known at the
+     * expression creation time. Otherwise an empty map is returned.
+     * 
+     * @since 3.0
+     */
+    public abstract Map<String, String> getPathAliases();
+
+    /**
+     * Returns String label for this expression. Used for debugging.
+     */
+    public String expName() {
+        switch (type) {
+        case AND:
+            return "AND";
+        case OR:
+            return "OR";
+        case NOT:
+            return "NOT";
+        case EQUAL_TO:
+            return "=";
+        case NOT_EQUAL_TO:
+            return "<>";
+        case LESS_THAN:
+            return "<";
+        case LESS_THAN_EQUAL_TO:
+            return "<=";
+        case GREATER_THAN:
+            return ">";
+        case GREATER_THAN_EQUAL_TO:
+            return ">=";
+        case BETWEEN:
+            return "BETWEEN";
+        case IN:
+            return "IN";
+        case LIKE:
+            return "LIKE";
+        case LIKE_IGNORE_CASE:
+            return "LIKE_IGNORE_CASE";
+        case OBJ_PATH:
+            return "OBJ_PATH";
+        case DB_PATH:
+            return "DB_PATH";
+        case LIST:
+            return "LIST";
+        case NOT_BETWEEN:
+            return "NOT BETWEEN";
+        case NOT_IN:
+            return "NOT IN";
+        case NOT_LIKE:
+            return "NOT LIKE";
+        case NOT_LIKE_IGNORE_CASE:
+            return "NOT LIKE IGNORE CASE";
+        default:
+            return "other";
+        }
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (!(object instanceof Expression)) {
+            return false;
+        }
+
+        Expression e = (Expression) object;
+
+        if (e.getType() != getType() || e.getOperandCount() != getOperandCount()) {
+            return false;
+        }
+
+        // compare operands
+        int len = e.getOperandCount();
+        for (int i = 0; i < len; i++) {
+            if (!Util.nullSafeEquals(e.getOperand(i), getOperand(i))) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns a type of expression. Most common types are defined as public
+     * static fields of this interface.
+     */
+    public int getType() {
+        return type;
+    }
+
+    public void setType(int type) {
+        this.type = type;
+    }
+
+    /**
 	 * Creates and returns a new Expression instance based on this expression,
 	 * but with parameters substituted with provided values. This is a
 	 * positional style of binding. If a given parameter name is used more than
@@ -322,380 +322,387 @@ public abstract class Expression implements Serializable, XMLSerializable {
 	}
 
 	/**
-	 * A shortcut for <code>expWithParams(params, true)</code>.
+     * A shortcut for <code>expWithParams(params, true)</code>.
 	 * 
 	 * @deprecated since 4.0 use {@link #params(Map)}
-	 */
+     */
 	@Deprecated
-	public Expression expWithParameters(Map<String, ?> parameters) {
-		return expWithParameters(parameters, true);
-	}
-
-	/**
-	 * Creates and returns a new Expression instance using this expression as a
-	 * prototype. All ExpressionParam operands are substituted with the values
-	 * in the <code>params</code> map.
-	 * <p>
-	 * <i>Null values in the <code>params</code> map should be explicitly
-	 * created in the map for the corresponding key. </i>
-	 * </p>
-	 * 
-	 * @param parameters
-	 *            a map of parameters, with each key being a string name of an
-	 *            expression parameter, and value being the value that should be
-	 *            used in the final expression.
-	 * @param pruneMissing
-	 *            If <code>true</code>, subexpressions that rely on missing
-	 *            parameters will be pruned from the resulting tree. If
-	 *            <code>false</code> , any missing values will generate an
-	 *            exception.
-	 * @return Expression resulting from the substitution of parameters with
-	 *         real values, or null if the whole expression was pruned, due to
-	 *         the missing parameters.
+    public Expression expWithParameters(Map<String, ?> parameters) {
+        return expWithParameters(parameters, true);
+    }
+
+    /**
+     * Creates and returns a new Expression instance using this expression as a
+     * prototype. All ExpressionParam operands are substituted with the values
+     * in the <code>params</code> map.
+     * <p>
+     * <i>Null values in the <code>params</code> map should be explicitly
+     * created in the map for the corresponding key. </i>
+     * </p>
+     * 
+     * @param parameters
+     *            a map of parameters, with each key being a string name of an
+     *            expression parameter, and value being the value that should be
+     *            used in the final expression.
+     * @param pruneMissing
+     *            If <code>true</code>, subexpressions that rely on missing
+     *            parameters will be pruned from the resulting tree. If
+     *            <code>false</code> , any missing values will generate an
+     *            exception.
+     * @return Expression resulting from the substitution of parameters with
+     *         real values, or null if the whole expression was pruned, due to
+     *         the missing parameters.
 	 * 
 	 * @deprecated since 4.0 use {@link #params(Map, boolean)} instead.
-	 */
+     */
 	@Deprecated
 	public Expression expWithParameters(Map<String, ?> parameters, boolean pruneMissing) {
 		return params(parameters, pruneMissing);
-	}
-
-	/**
-	 * Creates a new expression that joins this object with another expression,
-	 * using specified join type. It is very useful for incrementally building
-	 * chained expressions, like long AND or OR statements.
-	 */
-	public Expression joinExp(int type, Expression exp) {
-		return joinExp(type, exp, new Expression[0]);
-	}
-
-	/**
-	 * Creates a new expression that joins this object with other expressions,
-	 * using specified join type. It is very useful for incrementally building
-	 * chained expressions, like long AND or OR statements.
-	 * 
+                    }
+
+    /**
+     * Creates a new expression that joins this object with another expression,
+     * using specified join type. It is very useful for incrementally building
+     * chained expressions, like long AND or OR statements.
+     */
+    public Expression joinExp(int type, Expression exp) {
+        return joinExp(type, exp, new Expression[0]);
+    }
+
+    /**
+     * Creates a new expression that joins this object with other expressions,
+     * using specified join type. It is very useful for incrementally building
+     * chained expressions, like long AND or OR statements.
+     * 
 	 * @since 4.0
-	 */
-	public Expression joinExp(int type, Expression exp, Expression... expressions) {
-		Expression join = ExpressionFactory.expressionOfType(type);
-		join.setOperand(0, this);
-		join.setOperand(1, exp);
-		for (int i = 0; i < expressions.length; i++) {
-			Expression expressionInArray = expressions[i];
-			join.setOperand(2 + i, expressionInArray);
-		}
-		join.flattenTree();
-		return join;
-	}
-
-	/**
-	 * Chains this expression with another expression using "and".
-	 */
-	public Expression andExp(Expression exp) {
-		return joinExp(Expression.AND, exp);
-	}
-
-	/**
-	 * Chains this expression with other expressions using "and".
-	 * 
+     */
+    public Expression joinExp(int type, Expression exp, Expression... expressions) {
+        Expression join = ExpressionFactory.expressionOfType(type);
+        join.setOperand(0, this);
+        join.setOperand(1, exp);
+        for (int i = 0; i < expressions.length; i++) {
+            Expression expressionInArray = expressions[i];
+            join.setOperand(2 + i, expressionInArray);
+        }
+        join.flattenTree();
+        return join;
+    }
+
+    /**
+     * Chains this expression with another expression using "and".
+     */
+    public Expression andExp(Expression exp) {
+        return joinExp(Expression.AND, exp);
+    }
+
+    /**
+     * Chains this expression with other expressions using "and".
+     * 
 	 * @since 4.0
-	 */
-	public Expression andExp(Expression exp, Expression... expressions) {
-		return joinExp(Expression.AND, exp, expressions);
-	}
-
-	/**
-	 * Chains this expression with another expression using "or".
-	 */
-	public Expression orExp(Expression exp) {
-		return joinExp(Expression.OR, exp);
-	}
-
-	/**
-	 * Chains this expression with other expressions using "or".
-	 * 
+     */
+    public Expression andExp(Expression exp, Expression... expressions) {
+        return joinExp(Expression.AND, exp, expressions);
+    }
+
+    /**
+     * Chains this expression with another expression using "or".
+     */
+    public Expression orExp(Expression exp) {
+        return joinExp(Expression.OR, exp);
+    }
+
+    /**
+     * Chains this expression with other expressions using "or".
+     * 
 	 * @since 4.0
-	 */
-	public Expression orExp(Expression exp, Expression... expressions) {
-		return joinExp(Expression.OR, exp, expressions);
-	}
-
-	/**
-	 * Returns a logical NOT of current expression.
-	 * 
-	 * @since 1.0.6
-	 */
-	public abstract Expression notExp();
-
-	/**
-	 * Returns a count of operands of this expression. In real life there are
-	 * unary (count == 1), binary (count == 2) and ternary (count == 3)
-	 * expressions.
-	 */
-	public abstract int getOperandCount();
-
-	/**
-	 * Returns a value of operand at <code>index</code>. Operand indexing starts
-	 * at 0.
-	 */
-	public abstract Object getOperand(int index);
-
-	/**
-	 * Sets a value of operand at <code>index</code>. Operand indexing starts at
-	 * 0.
-	 */
-	public abstract void setOperand(int index, Object value);
-
-	/**
-	 * Calculates expression value with object as a context for path
-	 * expressions.
-	 * 
-	 * @since 1.1
-	 */
-	public abstract Object evaluate(Object o);
-
-	/**
-	 * Calculates expression boolean value with object as a context for path
-	 * expressions.
-	 * 
-	 * @since 1.1
-	 */
-	public boolean match(Object o) {
-		return ConversionUtil.toBoolean(evaluate(o));
-	}
-
-	/**
-	 * Returns the first object in the list that matches the expression.
-	 * 
-	 * @since 3.1
-	 */
-	public <T> T first(List<T> objects) {
-		for (T o : objects) {
-			if (match(o)) {
-				return o;
-			}
-		}
-
-		return null;
-	}
-
-	/**
-	 * Returns a list of objects that match the expression.
-	 */
+     */
+    public Expression orExp(Expression exp, Expression... expressions) {
+        return joinExp(Expression.OR, exp, expressions);
+    }
+
+    /**
+     * Returns a logical NOT of current expression.
+     * 
+     * @since 1.0.6
+     */
+    public abstract Expression notExp();
+
+    /**
+     * Returns a count of operands of this expression. In real life there are
+     * unary (count == 1), binary (count == 2) and ternary (count == 3)
+     * expressions.
+     */
+    public abstract int getOperandCount();
+
+    /**
+     * Returns a value of operand at <code>index</code>. Operand indexing starts
+     * at 0.
+     */
+    public abstract Object getOperand(int index);
+
+    /**
+     * Sets a value of operand at <code>index</code>. Operand indexing starts at
+     * 0.
+     */
+    public abstract void setOperand(int index, Object value);
+
+    /**
+     * Calculates expression value with object as a context for path
+     * expressions.
+     * 
+     * @since 1.1
+     */
+    public abstract Object evaluate(Object o);
+
+    /**
+     * Calculates expression boolean value with object as a context for path
+     * expressions.
+     * 
+     * @since 1.1
+     */
+    public boolean match(Object o) {
+        try {
+            return ConversionUtil.toBoolean(evaluate(o));
+        } catch (ExpressionException e) {
+        	if (e.getCause() instanceof UnsupportedOperationException) {
+            return false;
+        }
+            throw e;
+        }
+    }
+
+    /**
+     * Returns the first object in the list that matches the expression.
+     * 
+     * @since 3.1
+     */
+    public <T> T first(List<T> objects) {
+        for (T o : objects) {
+            if (match(o)) {
+                return o;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns a list of objects that match the expression.
+     */
 	@SuppressWarnings("unchecked")
-	public <T> List<T> filterObjects(Collection<T> objects) {
-		if (objects == null || objects.size() == 0) {
+    public <T> List<T> filterObjects(Collection<T> objects) {
+        if (objects == null || objects.size() == 0) {
 			return Collections.emptyList();
-		}
-
-		return (List<T>) filter(objects, new LinkedList<T>());
-	}
-
-	/**
-	 * Adds objects matching this expression from the source collection to the
-	 * target collection.
-	 * 
-	 * @since 1.1
-	 */
-	public <T> Collection<?> filter(Collection<T> source, Collection<T> target) {
-		for (T o : source) {
-			if (match(o)) {
-				target.add(o);
-			}
-		}
-
-		return target;
-	}
-
-	/**
-	 * Clones this expression.
-	 * 
-	 * @since 1.1
-	 */
-	public Expression deepCopy() {
-		return transform(null);
-	}
-
-	/**
-	 * Creates a copy of this expression node, without copying children.
-	 * 
-	 * @since 1.1
-	 */
-	public abstract Expression shallowCopy();
-
-	/**
-	 * Returns true if this node should be pruned from expression tree in the
-	 * event a child is removed.
-	 * 
-	 * @since 1.1
-	 */
-	protected abstract boolean pruneNodeForPrunedChild(Object prunedChild);
-
-	/**
-	 * Restructures expression to make sure that there are no children of the
-	 * same type as this expression.
-	 * 
-	 * @since 1.1
-	 */
-	protected abstract void flattenTree();
-
-	/**
-	 * Traverses itself and child expressions, notifying visitor via callback
-	 * methods as it goes. This is an Expression-specific implementation of the
-	 * "Visitor" design pattern.
-	 * 
-	 * @since 1.1
-	 */
-	public void traverse(TraversalHandler visitor) {
-		if (visitor == null) {
-			throw new NullPointerException("Null Visitor.");
-		}
-
-		traverse(null, visitor);
-	}
-
-	/**
-	 * Traverses itself and child expressions, notifying visitor via callback
-	 * methods as it goes.
-	 * 
-	 * @since 1.1
-	 */
-	protected void traverse(Expression parentExp, TraversalHandler visitor) {
-
-		visitor.startNode(this, parentExp);
-
-		// recursively traverse each child
-		int count = getOperandCount();
-		for (int i = 0; i < count; i++) {
-			Object child = getOperand(i);
-
-			if (child instanceof Expression) {
-				Expression childExp = (Expression) child;
-				childExp.traverse(this, visitor);
-			} else {
-				visitor.objectNode(child, this);
-			}
-
-			visitor.finishedChild(this, i, i < count - 1);
-		}
-
-		visitor.endNode(this, parentExp);
-	}
-
-	/**
-	 * Creates a transformed copy of this expression, applying transformation
-	 * provided by Transformer to all its nodes. Null transformer will result in
-	 * an identical deep copy of this expression.
-	 * <p>
-	 * To force a node and its children to be pruned from the copy, Transformer
-	 * should return Expression.PRUNED_NODE. Otherwise an expectation is that if
-	 * a node is an Expression it must be transformed to null or another
-	 * Expression. Any other object type would result in a ExpressionException.
-	 * 
-	 * @since 1.1
-	 */
-	public Expression transform(Transformer transformer) {
-		Object transformed = transformExpression(transformer);
-
-		if (transformed == PRUNED_NODE || transformed == null) {
-			return null;
-		} else if (transformed instanceof Expression) {
-			return (Expression) transformed;
-		}
-
-		throw new ExpressionException("Invalid transformed expression: " + transformed);
-	}
-
-	/**
-	 * A recursive method called from "transform" to do the actual
-	 * transformation.
-	 * 
-	 * @return null, Expression.PRUNED_NODE or transformed expression.
-	 * @since 1.2
-	 */
-	protected Object transformExpression(Transformer transformer) {
-		Expression copy = shallowCopy();
-		int count = getOperandCount();
-		for (int i = 0, j = 0; i < count; i++) {
-			Object operand = getOperand(i);
-			Object transformedChild;
-
-			if (operand instanceof Expression) {
-				transformedChild = ((Expression) operand).transformExpression(transformer);
-			} else if (transformer != null) {
-				transformedChild = transformer.transform(operand);
-			} else {
-				transformedChild = operand;
-			}
-
-			// prune null children only if there is a transformer and it
-			// indicated so
-			boolean prune = transformer != null && transformedChild == PRUNED_NODE;
-
-			if (!prune) {
-				copy.setOperand(j, transformedChild);
-				j++;
-			}
-
-			if (prune && pruneNodeForPrunedChild(operand)) {
-				// bail out early...
-				return PRUNED_NODE;
-			}
-		}
-
-		// all the children are processed, only now transform this copy
-		return (transformer != null) ? (Expression) transformer.transform(copy) : copy;
-	}
-
-	/**
-	 * Encodes itself, wrapping the string into XML CDATA section.
-	 * 
-	 * @since 1.1
-	 */
-	public void encodeAsXML(XMLEncoder encoder) {
-		encoder.print("<![CDATA[");
-		try {
-			appendAsString(encoder.getPrintWriter());
-		} catch (IOException e) {
-			throw new CayenneRuntimeException("Unexpected IO exception appending to PrintWriter", e);
-		}
-		encoder.print("]]>");
-	}
-
-	/**
-	 * Stores a String representation of Expression using a provided
-	 * PrintWriter.
-	 * 
-	 * @since 1.1
+        }
+
+        return (List<T>) filter(objects, new LinkedList<T>());
+    }
+
+    /**
+     * Adds objects matching this expression from the source collection to the
+     * target collection.
+     * 
+     * @since 1.1
+     */
+    public <T> Collection<?> filter(Collection<T> source, Collection<T> target) {
+        for (T o : source) {
+            if (match(o)) {
+                target.add(o);
+            }
+        }
+
+        return target;
+    }
+
+    /**
+     * Clones this expression.
+     * 
+     * @since 1.1
+     */
+    public Expression deepCopy() {
+        return transform(null);
+    }
+
+    /**
+     * Creates a copy of this expression node, without copying children.
+     * 
+     * @since 1.1
+     */
+    public abstract Expression shallowCopy();
+
+    /**
+     * Returns true if this node should be pruned from expression tree in the
+     * event a child is removed.
+     * 
+     * @since 1.1
+     */
+    protected abstract boolean pruneNodeForPrunedChild(Object prunedChild);
+
+    /**
+     * Restructures expression to make sure that there are no children of the
+     * same type as this expression.
+     * 
+     * @since 1.1
+     */
+    protected abstract void flattenTree();
+
+    /**
+     * Traverses itself and child expressions, notifying visitor via callback
+     * methods as it goes. This is an Expression-specific implementation of the
+     * "Visitor" design pattern.
+     * 
+     * @since 1.1
+     */
+    public void traverse(TraversalHandler visitor) {
+        if (visitor == null) {
+            throw new NullPointerException("Null Visitor.");
+        }
+
+        traverse(null, visitor);
+    }
+
+    /**
+     * Traverses itself and child expressions, notifying visitor via callback
+     * methods as it goes.
+     * 
+     * @since 1.1
+     */
+    protected void traverse(Expression parentExp, TraversalHandler visitor) {
+
+        visitor.startNode(this, parentExp);
+
+        // recursively traverse each child
+        int count = getOperandCount();
+        for (int i = 0; i < count; i++) {
+            Object child = getOperand(i);
+
+            if (child instanceof Expression) {
+                Expression childExp = (Expression) child;
+                childExp.traverse(this, visitor);
+            } else {
+                visitor.objectNode(child, this);
+            }
+
+            visitor.finishedChild(this, i, i < count - 1);
+        }
+
+        visitor.endNode(this, parentExp);
+    }
+
+    /**
+     * Creates a transformed copy of this expression, applying transformation
+     * provided by Transformer to all its nodes. Null transformer will result in
+     * an identical deep copy of this expression.
+     * <p>
+     * To force a node and its children to be pruned from the copy, Transformer
+     * should return Expression.PRUNED_NODE. Otherwise an expectation is that if
+     * a node is an Expression it must be transformed to null or another
+     * Expression. Any other object type would result in a ExpressionException.
+     * 
+     * @since 1.1
+     */
+    public Expression transform(Transformer transformer) {
+        Object transformed = transformExpression(transformer);
+
+        if (transformed == PRUNED_NODE || transformed == null) {
+            return null;
+        } else if (transformed instanceof Expression) {
+            return (Expression) transformed;
+        }
+
+        throw new ExpressionException("Invalid transformed expression: " + transformed);
+    }
+
+    /**
+     * A recursive method called from "transform" to do the actual
+     * transformation.
+     * 
+     * @return null, Expression.PRUNED_NODE or transformed expression.
+     * @since 1.2
+     */
+    protected Object transformExpression(Transformer transformer) {
+        Expression copy = shallowCopy();
+        int count = getOperandCount();
+        for (int i = 0, j = 0; i < count; i++) {
+            Object operand = getOperand(i);
+            Object transformedChild;
+
+            if (operand instanceof Expression) {
+                transformedChild = ((Expression) operand).transformExpression(transformer);
+            } else if (transformer != null) {
+                transformedChild = transformer.transform(operand);
+            } else {
+                transformedChild = operand;
+            }
+
+            // prune null children only if there is a transformer and it
+            // indicated so
+            boolean prune = transformer != null && transformedChild == PRUNED_NODE;
+
+            if (!prune) {
+                copy.setOperand(j, transformedChild);
+                j++;
+            }
+
+            if (prune && pruneNodeForPrunedChild(operand)) {
+                // bail out early...
+                return PRUNED_NODE;
+            }
+        }
+
+        // all the children are processed, only now transform this copy
+        return (transformer != null) ? (Expression) transformer.transform(copy) : copy;
+    }
+
+    /**
+     * Encodes itself, wrapping the string into XML CDATA section.
+     * 
+     * @since 1.1
+     */
+    public void encodeAsXML(XMLEncoder encoder) {
+        encoder.print("<![CDATA[");
+        try {
+            appendAsString(encoder.getPrintWriter());
+        } catch (IOException e) {
+            throw new CayenneRuntimeException("Unexpected IO exception appending to PrintWriter", e);
+        }
+        encoder.print("]]>");
+    }
+
+    /**
+     * Stores a String representation of Expression using a provided
+     * PrintWriter.
+     * 
+     * @since 1.1
 	 * @deprecated since 4.0 use {@link #appendAsString(Appendable)}.
-	 */
-	@Deprecated
-	public abstract void encodeAsString(PrintWriter pw);
+     */
+    @Deprecated
+    public abstract void encodeAsString(PrintWriter pw);
 
-	/**
-	 * Appends own content as a String to the provided Appendable.
-	 * 
+    /**
+     * Appends own content as a String to the provided Appendable.
+     * 
 	 * @since 4.0
-	 * @throws IOException
-	 */
-	public abstract void appendAsString(Appendable out) throws IOException;
-
-	/**
-	 * Stores a String representation of Expression as EJBQL using a provided
-	 * PrintWriter. DB path expressions produce non-standard EJBQL path
-	 * expressions.
-	 * 
-	 * @since 3.0
+     * @throws IOException
+     */
+    public abstract void appendAsString(Appendable out) throws IOException;
+
+    /**
+     * Stores a String representation of Expression as EJBQL using a provided
+     * PrintWriter. DB path expressions produce non-standard EJBQL path
+     * expressions.
+     * 
+     * @since 3.0
 	 * @deprecated since 4.0 use {@link #appendAsEJBQL(Appendable, String)}
-	 */
-	@Deprecated
-	public abstract void encodeAsEJBQL(PrintWriter pw, String rootId);
-
-	/**
-	 * Stores a String representation of Expression as EJBQL using a provided
-	 * Appendable. DB path expressions produce non-standard EJBQL path
-	 * expressions.
-	 * 
+     */
+    @Deprecated
+    public abstract void encodeAsEJBQL(PrintWriter pw, String rootId);
+
+    /**
+     * Stores a String representation of Expression as EJBQL using a provided
+     * Appendable. DB path expressions produce non-standard EJBQL path
+     * expressions.
+     * 
 	 * @since 4.0
 	 * @throws IOException
 	 */
@@ -716,23 +723,23 @@ public abstract class Expression implements Serializable, XMLSerializable {
 	 * EJBQL.
 	 * 
 	 * @since 4.0
-	 * @throws IOException
-	 */
+     * @throws IOException
+     */
 	public abstract void appendAsEJBQL(List<Object> parameterAccumulator, Appendable out, String rootId)
 			throws IOException;
 
-	@Override
-	public String toString() {
-		StringBuilder out = new StringBuilder();
-		try {
-			appendAsString(out);
-		} catch (IOException e) {
-			throw new CayenneRuntimeException("Unexpected IO exception appending to StringBuilder", e);
-		}
-		return out.toString();
-	}
-
-	/**
+    @Override
+    public String toString() {
+        StringBuilder out = new StringBuilder();
+        try {
+            appendAsString(out);
+        } catch (IOException e) {
+            throw new CayenneRuntimeException("Unexpected IO exception appending to StringBuilder", e);
+        }
+        return out.toString();
+    }
+
+    /**
 	 * Produces an EJBQL string that represents this expression. If the
 	 * parameterAccumulator is supplied then, where appropriate, parameters to
 	 * the EJBQL may be written into the parameterAccumulator. If this method
@@ -742,16 +749,16 @@ public abstract class Expression implements Serializable, XMLSerializable {
 	 * Expression as EJBQL.
 	 * 
 	 * @since 3.1
-	 */
+     */
 	public String toEJBQL(List<Object> parameterAccumulator, String rootId) {
-		StringBuilder out = new StringBuilder();
-		try {
+        StringBuilder out = new StringBuilder();
+        try {
 			appendAsEJBQL(parameterAccumulator, out, rootId);
-		} catch (IOException e) {
-			throw new CayenneRuntimeException("Unexpected IO exception appending to StringBuilder", e);
-		}
-		return out.toString();
-	}
+        } catch (IOException e) {
+            throw new CayenneRuntimeException("Unexpected IO exception appending to StringBuilder", e);
+        }
+        return out.toString();
+    }
 
 	/**
 	 * Produces an EJBQL string that represents this expression. If this method

http://git-wip-us.apache.org/repos/asf/cayenne/blob/b30e0fa2/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAnd.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAnd.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAnd.java
index 33fdcb1..1c782ec 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAnd.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAnd.java
@@ -23,6 +23,7 @@ import java.util.Collection;
 import java.util.Iterator;
 
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionException;
 import org.apache.cayenne.exp.ValueInjector;
 import org.apache.cayenne.util.ConversionUtil;
 
@@ -35,86 +36,98 @@ public class ASTAnd extends AggregateConditionNode implements ValueInjector {
 
 	private static final long serialVersionUID = -5936206826390819160L;
 
-	/**
-	 * Constructor used by expression parser. Do not invoke directly.
-	 */
-	ASTAnd(int id) {
-		super(id);
-	}
-
-	public ASTAnd() {
-		super(ExpressionParserTreeConstants.JJTAND);
-	}
-
-	public ASTAnd(Object[] nodes) {
-		super(ExpressionParserTreeConstants.JJTAND);
-		int len = nodes.length;
-		for (int i = 0; i < len; i++) {
-			jjtAddChild((Node) nodes[i], i);
-		}
-
-		connectChildren();
-	}
-
-	public ASTAnd(Collection<? extends Node> nodes) {
-		super(ExpressionParserTreeConstants.JJTAND);
-		int len = nodes.size();
-		Iterator<? extends Node> it = nodes.iterator();
-		for (int i = 0; i < len; i++) {
-			jjtAddChild(it.next(), i);
-		}
-
-		connectChildren();
-	}
-
-	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len == 0) {
-			return Boolean.FALSE;
-		}
-
-		for (int i = 0; i < len; i++) {
-			if (!ConversionUtil.toBoolean(evaluateChild(i, o))) {
-				return Boolean.FALSE;
-			}
-		}
-
-		return Boolean.TRUE;
-	}
-
-	/**
-	 * Creates a copy of this expression node, without copying children.
-	 */
-	@Override
-	public Expression shallowCopy() {
-		return new ASTAnd(id);
-	}
-
-	@Override
-	public int getType() {
-		return Expression.AND;
-	}
-
-	@Override
-	public void jjtClose() {
-		super.jjtClose();
-		flattenTree();
-	}
-
-	@Override
-	protected String getExpressionOperator(int index) {
-		return "and";
-	}
-
-	public void injectValue(Object o) {
+    /**
+     * Constructor used by expression parser. Do not invoke directly.
+     */
+    ASTAnd(int id) {
+        super(id);
+    }
+
+    public ASTAnd() {
+        super(ExpressionParserTreeConstants.JJTAND);
+    }
+
+    public ASTAnd(Object[] nodes) {
+        super(ExpressionParserTreeConstants.JJTAND);
+        int len = nodes.length;
+        for (int i = 0; i < len; i++) {
+            jjtAddChild((Node) nodes[i], i);
+        }
+        
+        connectChildren();
+    }
+
+    public ASTAnd(Collection<? extends Node> nodes) {
+        super(ExpressionParserTreeConstants.JJTAND);
+        int len = nodes.size();
+        Iterator<? extends Node> it = nodes.iterator();
+        for (int i = 0; i < len; i++) {
+            jjtAddChild(it.next(), i);
+        }
+        
+        connectChildren();
+    }
+
+    @Override
+    protected Object evaluateNode(Object o) throws Exception {
+        int len = jjtGetNumChildren();
+        if (len == 0) {
+            return Boolean.FALSE;
+        }
+
+        ExpressionException ex = null;
+        for (int i = 0; i < len; i++) {
+            try {
+            if (!ConversionUtil.toBoolean(evaluateChild(i, o))) {
+                return Boolean.FALSE;
+            }
+            } catch (ExpressionException e) {
+                if (e.getCause() instanceof UnsupportedOperationException) {
+                    ex = e;
+                } else {
+                    throw e;
+                }
+        }
+        }
+
+        if (ex != null) {
+            throw ex;
+        }
+        return Boolean.TRUE;
+    }
+
+    /**
+     * Creates a copy of this expression node, without copying children.
+     */
+    @Override
+    public Expression shallowCopy() {
+        return new ASTAnd(id);
+    }
+
+    @Override
+    public int getType() {
+        return Expression.AND;
+    }
+
+    @Override
+    public void jjtClose() {
+        super.jjtClose();
+        flattenTree();
+    }
+
+    @Override
+    protected String getExpressionOperator(int index) {
+        return "and";
+    }
+
+    public void injectValue(Object o) {
 		// iterate through all operands, inject all possible
-		int len = jjtGetNumChildren();
-		for (int i = 0; i < len; i++) {
-			Node node = jjtGetChild(i);
-			if (node instanceof ValueInjector) {
-				((ValueInjector) node).injectValue(o);
-			}
-		}
-	}
+        int len = jjtGetNumChildren();
+        for (int i = 0; i < len; i++) {
+            Node node = jjtGetChild(i);
+            if (node instanceof ValueInjector) {
+                ((ValueInjector) node).injectValue(o);
+            }
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/b30e0fa2/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTOr.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTOr.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTOr.java
index 1f6ef24..f7ff414 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTOr.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTOr.java
@@ -23,6 +23,7 @@ import java.util.Collection;
 import java.util.Iterator;
 
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionException;
 import org.apache.cayenne.util.ConversionUtil;
 
 /**
@@ -34,70 +35,82 @@ public class ASTOr extends AggregateConditionNode {
 
 	private static final long serialVersionUID = 780157841581581297L;
 
-	ASTOr(int id) {
-		super(id);
-	}
-
-	public ASTOr() {
-		super(ExpressionParserTreeConstants.JJTOR);
-	}
-
-	public ASTOr(Object[] nodes) {
-		super(ExpressionParserTreeConstants.JJTOR);
-		int len = nodes.length;
-		for (int i = 0; i < len; i++) {
-			jjtAddChild((Node) nodes[i], i);
-		}
-		connectChildren();
-	}
-
-	public ASTOr(Collection<? extends Node> nodes) {
-		super(ExpressionParserTreeConstants.JJTOR);
-		int len = nodes.size();
-		Iterator<? extends Node> it = nodes.iterator();
-		for (int i = 0; i < len; i++) {
-			jjtAddChild(it.next(), i);
-		}
-		connectChildren();
-	}
-
-	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len == 0) {
-			return Boolean.FALSE;
-		}
-
-		for (int i = 0; i < len; i++) {
-			if (ConversionUtil.toBoolean(evaluateChild(i, o))) {
-				return Boolean.TRUE;
-			}
-		}
-
-		return Boolean.FALSE;
-	}
-
-	/**
-	 * Creates a copy of this expression node, without copying children.
-	 */
-	@Override
-	public Expression shallowCopy() {
-		return new ASTOr(id);
-	}
-
-	@Override
-	protected String getExpressionOperator(int index) {
-		return "or";
-	}
-
-	@Override
-	public int getType() {
-		return Expression.OR;
-	}
-
-	@Override
-	public void jjtClose() {
-		super.jjtClose();
-		flattenTree();
-	}
+    ASTOr(int id) {
+        super(id);
+    }
+
+    public ASTOr() {
+        super(ExpressionParserTreeConstants.JJTOR);
+    }
+
+    public ASTOr(Object[] nodes) {
+        super(ExpressionParserTreeConstants.JJTOR);
+        int len = nodes.length;
+        for (int i = 0; i < len; i++) {
+            jjtAddChild((Node) nodes[i], i);
+        }
+        connectChildren();
+    }
+
+    public ASTOr(Collection<? extends Node> nodes) {
+        super(ExpressionParserTreeConstants.JJTOR);
+        int len = nodes.size();
+        Iterator<? extends Node> it = nodes.iterator();
+        for (int i = 0; i < len; i++) {
+            jjtAddChild(it.next(), i);
+        }
+        connectChildren();
+    }
+
+    @Override
+    protected Object evaluateNode(Object o) throws Exception {
+        int len = jjtGetNumChildren();
+        if (len == 0) {
+            return Boolean.FALSE;
+        }
+
+        ExpressionException ex = null;
+        for (int i = 0; i < len; i++) {
+            try {
+            if (ConversionUtil.toBoolean(evaluateChild(i, o))) {
+                return Boolean.TRUE;
+            }
+            } catch (ExpressionException e) {
+                if (e.getCause() instanceof UnsupportedOperationException) {
+                    ex = e;
+                } else {
+                    throw e;
+                }
+        }
+        }
+
+        if (ex != null) {
+            throw ex;
+        }
+        return Boolean.FALSE;
+    }
+
+    /**
+     * Creates a copy of this expression node, without copying children.
+     */
+    @Override
+    public Expression shallowCopy() {
+        return new ASTOr(id);
+    }
+
+    @Override
+    protected String getExpressionOperator(int index) {
+        return "or";
+    }
+
+    @Override
+    public int getType() {
+        return Expression.OR;
+    }
+
+    @Override
+    public void jjtClose() {
+        super.jjtClose();
+        flattenTree();
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/b30e0fa2/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
index df6785b..7d37e80 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
@@ -66,7 +66,7 @@ abstract class Evaluator {
         Integer compare(Object lhs, Object rhs) {
 
             if (rhs == null) {
-                return 1;
+                throw new UnsupportedOperationException("Unsupported");
             } else {
                 return delegate.compare(lhs, rhs);
             }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/b30e0fa2/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluateInMemoryIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluateInMemoryIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluateInMemoryIT.java
new file mode 100644
index 0000000..e4a20e3
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluateInMemoryIT.java
@@ -0,0 +1,682 @@
+/*****************************************************************
+ *   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 org.apache.cayenne.Cayenne;
+import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+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.apache.cayenne.unit.util.TstBean;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.math.BigDecimal;
+import java.sql.Types;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class ExpressionEvaluateInMemoryIT extends ServerCase {
+
+    @Inject
+    private ServerRuntime runtime;
+
+    @Inject
+    private DataContext context;
+
+    @Inject
+    protected DBHelper dbHelper;
+
+    protected TableHelper tArtist;
+    protected TableHelper tPainting;
+
+    @Before
+    public void setUp() throws Exception {
+        tArtist = new TableHelper(dbHelper, "ARTIST");
+        tArtist.setColumns("ARTIST_ID", "ARTIST_NAME");
+
+        tPainting = new TableHelper(dbHelper, "PAINTING");
+        tPainting.setColumns("PAINTING_ID", "ARTIST_ID", "PAINTING_TITLE", "ESTIMATED_PRICE").setColumnTypes(
+                Types.INTEGER, Types.BIGINT, Types.VARCHAR, Types.DECIMAL);
+    }
+
+    protected void createTwoArtistsThreePaintings() throws Exception {
+
+        tArtist.insert(1, "artist1");
+        tArtist.insert(2, "artist2");
+        tPainting.insert(1, 1, "P1", 3000);
+        tPainting.insert(2, 2, "P2", 3000);
+        tPainting.insert(3, null, "P3", 3000);
+    }
+
+    @Test
+    public void testEvaluateOBJ_PATH_DataObject() throws Exception {
+        ASTObjPath node = new ASTObjPath("artistName");
+
+        Artist a1 = new Artist();
+        a1.setArtistName("abc");
+        assertEquals("abc", node.evaluate(a1));
+
+        Artist a2 = new Artist();
+        a2.setArtistName("123");
+        assertEquals("123", node.evaluate(a2));
+    }
+
+    @Test
+    public void testEvaluateOBJ_PATH_JavaBean() throws Exception {
+        ASTObjPath node = new ASTObjPath("property2");
+
+        TstBean b1 = new TstBean();
+        b1.setProperty2(1);
+        assertEquals(new Integer(1), node.evaluate(b1));
+
+        TstBean b2 = new TstBean();
+        b2.setProperty2(-3);
+        assertEquals(new Integer(-3), node.evaluate(b2));
+    }
+
+    @Test
+    public void testEvaluateOBJ_PATH_ObjEntity() throws Exception {
+        ASTObjPath node = new ASTObjPath("paintingArray.paintingTitle");
+
+        ObjEntity ae = runtime.getDataDomain().getEntityResolver().getObjEntity(Artist.class);
+
+        Object target = node.evaluate(ae);
+        assertTrue(target instanceof ObjAttribute);
+    }
+
+    @Test
+    public void testEvaluateDB_PATH_DbEntity() throws Exception {
+        Expression e = Expression.fromString("db:paintingArray.PAINTING_TITLE");
+
+        ObjEntity ae = runtime.getDataDomain().getEntityResolver().getObjEntity(Artist.class);
+        DbEntity ade = ae.getDbEntity();
+
+        Object objTarget = e.evaluate(ae);
+        assertTrue(objTarget instanceof DbAttribute);
+
+        Object dbTarget = e.evaluate(ade);
+        assertTrue(dbTarget instanceof DbAttribute);
+    }
+
+    @Test
+    public void testEvaluateEQUAL_TOBigDecimal() throws Exception {
+        BigDecimal bd1 = new BigDecimal("2.0");
+        BigDecimal bd2 = new BigDecimal("2.0");
+        BigDecimal bd3 = new BigDecimal("2.00");
+        BigDecimal bd4 = new BigDecimal("2.01");
+
+        Expression equalTo = new ASTEqual(new ASTObjPath(Painting.ESTIMATED_PRICE_PROPERTY), bd1);
+
+        Painting p = new Painting();
+        p.setEstimatedPrice(bd2);
+        assertTrue(equalTo.match(p));
+
+        // BigDecimals must compare regardless of the number of trailing zeros
+        // (see CAY-280)
+        p.setEstimatedPrice(bd3);
+        assertTrue(equalTo.match(p));
+
+        p.setEstimatedPrice(bd4);
+        assertFalse(equalTo.match(p));
+    }
+
+    @Test
+    public void testEvaluateEQUAL_TO() throws Exception {
+        Expression equalTo = new ASTEqual(new ASTObjPath("artistName"), "abc");
+        Expression notEqualTo = new ASTNotEqual(new ASTObjPath("artistName"), "abc");
+
+        Artist match = new Artist();
+        match.setArtistName("abc");
+        assertTrue(equalTo.match(match));
+        assertFalse(notEqualTo.match(match));
+
+        Artist noMatch = new Artist();
+        noMatch.setArtistName("123");
+        assertFalse("Failed: " + equalTo, equalTo.match(noMatch));
+        assertTrue("Failed: " + notEqualTo, notEqualTo.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateEQUAL_TO_Null() throws Exception {
+        Expression equalToNull = new ASTEqual(new ASTObjPath("artistName"), null);
+        Expression equalToNotNull = new ASTEqual(new ASTObjPath("artistName"), "abc");
+
+        Artist match = new Artist();
+        assertTrue(equalToNull.match(match));
+        assertFalse(equalToNotNull.match(match));
+
+        Artist noMatch = new Artist();
+        noMatch.setArtistName("abc");
+        assertFalse(equalToNull.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateNOT_EQUAL_TONull() throws Exception {
+        Expression notEqualToNull = new ASTNotEqual(new ASTObjPath("artistName"), null);
+        Expression notEqualToNotNull = new ASTNotEqual(new ASTObjPath("artistName"), "abc");
+
+        Artist match = new Artist();
+        assertFalse(notEqualToNull.match(match));
+        assertTrue(notEqualToNotNull.match(match));
+
+        Artist noMatch = new Artist();
+        noMatch.setArtistName("123");
+        assertTrue("Failed: " + notEqualToNull, notEqualToNull.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateEQUAL_TODataObject() throws Exception {
+        Artist a1 = (Artist) context.newObject("Artist");
+        Artist a2 = (Artist) context.newObject("Artist");
+        Painting p1 = (Painting) context.newObject("Painting");
+        Painting p2 = (Painting) context.newObject("Painting");
+        Painting p3 = (Painting) context.newObject("Painting");
+        
+        a1.setArtistName("a1");
+        a2.setArtistName("a2");
+        p1.setPaintingTitle("p1");
+        p2.setPaintingTitle("p2");
+        p3.setPaintingTitle("p3");
+        
+        context.commitChanges();
+        
+        p1.setToArtist(a1);
+        p2.setToArtist(a2);
+
+        Expression e = new ASTEqual(new ASTObjPath("toArtist"), a1);
+
+        assertTrue(e.match(p1));
+        assertFalse(e.match(p2));
+        assertFalse(e.match(p3));
+    }
+
+    @Test
+    public void testEvaluateEQUAL_TO_Temp_ObjectId() throws Exception {
+        Artist a1 = (Artist) context.newObject("Artist");
+        Artist a2 = (Artist) context.newObject("Artist");
+        Painting p1 = (Painting) context.newObject("Painting");
+        Painting p2 = (Painting) context.newObject("Painting");
+        Painting p3 = (Painting) context.newObject("Painting");
+
+        p1.setToArtist(a1);
+        p2.setToArtist(a2);
+
+        Expression e = new ASTEqual(new ASTObjPath("toArtist"), a1.getObjectId());
+
+        assertTrue(e.match(p1));
+        assertFalse(e.match(p2));
+        assertFalse(e.match(p3));
+    }
+
+    @Test
+    public void testEvaluateEQUAL_TO_Id() throws Exception {
+
+        createTwoArtistsThreePaintings();
+
+        Artist a1 = Cayenne.objectForPK(context, Artist.class, 1);
+        Painting p1 = Cayenne.objectForPK(context, Painting.class, 1);
+        Painting p2 = Cayenne.objectForPK(context, Painting.class, 2);
+        Painting p3 = Cayenne.objectForPK(context, Painting.class, 3);
+
+        Expression e = new ASTEqual(new ASTObjPath("toArtist"), Cayenne.intPKForObject(a1));
+
+        assertTrue(e.match(p1));
+        assertFalse(e.match(p2));
+        assertFalse(e.match(p3));
+    }
+    
+    public void testEvaluateEQUAL_TO_DbPath() throws Exception {
+        createTwoArtistsThreePaintings();
+
+        Artist a1 = Cayenne.objectForPK(context, Artist.class, 1);
+        Painting p1 = Cayenne.objectForPK(context, Painting.class, 1);
+        Painting p2 = Cayenne.objectForPK(context, Painting.class, 2);
+        Painting p3 = Cayenne.objectForPK(context, Painting.class, 3);
+
+        Expression e = new ASTEqual(new ASTDbPath("toArtist.ARTIST_NAME"), a1.getArtistName());
+        assertTrue(e.match(p1));
+        assertFalse(e.match(p2));
+        assertFalse(e.match(p3));
+    }
+
+    @Test
+    public void testEvaluateAND() throws Exception {
+        Expression e1 = new ASTEqual(new ASTObjPath("artistName"), "abc");
+        Expression e2 = new ASTEqual(new ASTObjPath("artistName"), "abc");
+
+        ASTAnd e = new ASTAnd(new Object[] { e1, e2 });
+
+        Artist match = new Artist();
+        match.setArtistName("abc");
+        assertTrue(e.match(match));
+
+        Artist noMatch = new Artist();
+        noMatch.setArtistName("123");
+        assertFalse(e.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateOR() throws Exception {
+        Expression e1 = new ASTEqual(new ASTObjPath("artistName"), "abc");
+        Expression e2 = new ASTEqual(new ASTObjPath("artistName"), "xyz");
+
+        ASTOr e = new ASTOr(new Object[] { e1, e2 });
+
+        Artist match1 = new Artist();
+        match1.setArtistName("abc");
+        assertTrue("Failed: " + e, e.match(match1));
+
+        Artist match2 = new Artist();
+        match2.setArtistName("xyz");
+        assertTrue("Failed: " + e, e.match(match2));
+
+        Artist noMatch = new Artist();
+        noMatch.setArtistName("123");
+        assertFalse("Failed: " + e, e.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateNOT() throws Exception {
+        ASTNot e = new ASTNot(new ASTEqual(new ASTObjPath("artistName"), "abc"));
+
+        Artist noMatch = new Artist();
+        noMatch.setArtistName("abc");
+        assertFalse(e.match(noMatch));
+
+        Artist match = new Artist();
+        match.setArtistName("123");
+        assertTrue("Failed: " + e, e.match(match));
+    }
+
+    @Test
+    public void testEvaluateLESS_THAN() throws Exception {
+        Expression e = new ASTLess(new ASTObjPath("estimatedPrice"), new BigDecimal(10000d));
+
+        Painting noMatch = new Painting();
+        noMatch.setEstimatedPrice(new BigDecimal(10001));
+        assertFalse("Failed: " + e, e.match(noMatch));
+
+        Painting noMatch1 = new Painting();
+        noMatch1.setEstimatedPrice(new BigDecimal(10000));
+        assertFalse("Failed: " + e, e.match(noMatch1));
+
+        Painting match = new Painting();
+        match.setEstimatedPrice(new BigDecimal(9999));
+        assertTrue("Failed: " + e, e.match(match));
+    }
+
+    @Test
+    public void testEvaluateLESS_THAN_Null() throws Exception {
+        Expression ltNull = new ASTLess(new ASTObjPath("estimatedPrice"), null);
+        Expression ltNotNull = new ASTLess(new ASTObjPath("estimatedPrice"), new BigDecimal(10000d));
+
+        Painting noMatch = new Painting();
+        assertFalse(ltNull.match(noMatch));
+        assertFalse(ltNotNull.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateLESS_THAN_EQUAL_TO() throws Exception {
+        Expression e = new ASTLessOrEqual(new ASTObjPath("estimatedPrice"), new BigDecimal(10000d));
+
+        Painting noMatch = new Painting();
+        noMatch.setEstimatedPrice(new BigDecimal(10001));
+        assertFalse(e.match(noMatch));
+
+        Painting match1 = new Painting();
+        match1.setEstimatedPrice(new BigDecimal(10000));
+        assertTrue(e.match(match1));
+
+        Painting match = new Painting();
+        match.setEstimatedPrice(new BigDecimal(9999));
+        assertTrue("Failed: " + e, e.match(match));
+    }
+
+    @Test
+    public void testEvaluateLESS_THAN_EQUAL_TO_Null() throws Exception {
+        Expression ltNull = new ASTLessOrEqual(new ASTObjPath("estimatedPrice"), null);
+        Expression ltNotNull = new ASTLessOrEqual(new ASTObjPath("estimatedPrice"), new BigDecimal(10000d));
+
+        Painting noMatch = new Painting();
+        assertFalse(ltNull.match(noMatch));
+        assertFalse(ltNotNull.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateGREATER_THAN() throws Exception {
+        Expression e = new ASTGreater(new ASTObjPath("estimatedPrice"), new BigDecimal(10000d));
+
+        Painting noMatch = new Painting();
+        noMatch.setEstimatedPrice(new BigDecimal(9999));
+        assertFalse(e.match(noMatch));
+
+        Painting noMatch1 = new Painting();
+        noMatch1.setEstimatedPrice(new BigDecimal(10000));
+        assertFalse(e.match(noMatch1));
+
+        Painting match = new Painting();
+        match.setEstimatedPrice(new BigDecimal(10001));
+        assertTrue("Failed: " + e, e.match(match));
+    }
+
+    @Test
+    public void testEvaluateGREATER_THAN_Null() throws Exception {
+        Expression gtNull = new ASTGreater(new ASTObjPath("estimatedPrice"), null);
+        Expression gtNotNull = new ASTGreater(new ASTObjPath("estimatedPrice"), new BigDecimal(10000d));
+
+        Painting noMatch = new Painting();
+        assertFalse(gtNull.match(noMatch));
+        assertFalse(gtNotNull.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateGREATER_THAN_EQUAL_TO() throws Exception {
+        Expression e = new ASTGreaterOrEqual(new ASTObjPath("estimatedPrice"), new BigDecimal(10000d));
+
+        Painting noMatch = new Painting();
+        noMatch.setEstimatedPrice(new BigDecimal(9999));
+        assertFalse(e.match(noMatch));
+
+        Painting match1 = new Painting();
+        match1.setEstimatedPrice(new BigDecimal(10000));
+        assertTrue(e.match(match1));
+
+        Painting match = new Painting();
+        match.setEstimatedPrice(new BigDecimal(10001));
+        assertTrue("Failed: " + e, e.match(match));
+    }
+
+    @Test
+    public void testEvaluateGREATER_THAN_EQUAL_TO_Null() throws Exception {
+        Expression gtNull = new ASTGreaterOrEqual(new ASTObjPath("estimatedPrice"), null);
+        Expression gtNotNull = new ASTGreaterOrEqual(new ASTObjPath("estimatedPrice"), new BigDecimal(10000d));
+
+        Painting noMatch = new Painting();
+        assertFalse(gtNull.match(noMatch));
+        assertFalse(gtNotNull.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateBETWEEN() throws Exception {
+        // evaluate both BETWEEN and NOT_BETWEEN
+        Expression between = new ASTBetween(new ASTObjPath("estimatedPrice"), new BigDecimal(10d), new BigDecimal(20d));
+        Expression notBetween = new ASTNotBetween(new ASTObjPath("estimatedPrice"), new BigDecimal(10d),
+                new BigDecimal(20d));
+
+        Painting noMatch = new Painting();
+        noMatch.setEstimatedPrice(new BigDecimal(21));
+        assertFalse(between.match(noMatch));
+        assertTrue(notBetween.match(noMatch));
+
+        Painting match1 = new Painting();
+        match1.setEstimatedPrice(new BigDecimal(20));
+        assertTrue(between.match(match1));
+        assertFalse(notBetween.match(match1));
+
+        Painting match2 = new Painting();
+        match2.setEstimatedPrice(new BigDecimal(10));
+        assertTrue("Failed: " + between, between.match(match2));
+        assertFalse("Failed: " + notBetween, notBetween.match(match2));
+
+        Painting match3 = new Painting();
+        match3.setEstimatedPrice(new BigDecimal(11));
+        assertTrue("Failed: " + between, between.match(match3));
+        assertFalse("Failed: " + notBetween, notBetween.match(match3));
+    }
+
+    @Test
+    public void testEvaluateBETWEEN_Null() throws Exception {
+        Expression btNull = new ASTBetween(new ASTObjPath("estimatedPrice"), new BigDecimal(10d), new BigDecimal(20d));
+        Expression btNotNull = new ASTNotBetween(new ASTObjPath("estimatedPrice"), new BigDecimal(10d),
+                new BigDecimal(20d));
+
+        Painting noMatch = new Painting();
+        assertFalse(btNull.match(noMatch));
+        assertFalse(btNotNull.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateIN() throws Exception {
+        Expression in = new ASTIn(new ASTObjPath("estimatedPrice"), new ASTList(new Object[] { new BigDecimal("10"),
+                new BigDecimal("20") }));
+
+        Expression notIn = new ASTNotIn(new ASTObjPath("estimatedPrice"), new ASTList(new Object[] {
+                new BigDecimal("10"), new BigDecimal("20") }));
+
+        Painting noMatch1 = new Painting();
+        noMatch1.setEstimatedPrice(new BigDecimal("21"));
+        assertFalse(in.match(noMatch1));
+        assertTrue(notIn.match(noMatch1));
+
+        Painting noMatch2 = new Painting();
+        noMatch2.setEstimatedPrice(new BigDecimal("11"));
+        assertFalse("Failed: " + in, in.match(noMatch2));
+        assertTrue("Failed: " + notIn, notIn.match(noMatch2));
+
+        Painting match1 = new Painting();
+        match1.setEstimatedPrice(new BigDecimal("20"));
+        assertTrue(in.match(match1));
+        assertFalse(notIn.match(match1));
+
+        Painting match2 = new Painting();
+        match2.setEstimatedPrice(new BigDecimal("10"));
+        assertTrue("Failed: " + in, in.match(match2));
+        assertFalse("Failed: " + notIn, notIn.match(match2));
+    }
+
+    @Test
+    public void testEvaluateIN_Null() throws Exception {
+        Expression in = new ASTIn(new ASTObjPath("estimatedPrice"), new ASTList(new Object[] {
+                new BigDecimal("10"), new BigDecimal("20") }));
+        Expression notIn = new ASTNotIn(new ASTObjPath("estimatedPrice"), new ASTList(new Object[] {
+                new BigDecimal("10"), new BigDecimal("20") }));
+
+        Painting noMatch = new Painting();
+        assertFalse(in.match(noMatch));
+        assertFalse(notIn.match(noMatch));
+    }
+
+    @Test
+    public void testEvaluateLIKE1() throws Exception {
+        Expression like = new ASTLike(new ASTObjPath("artistName"), "abc%d");
+        Expression notLike = new ASTNotLike(new ASTObjPath("artistName"), "abc%d");
+
+        Artist noMatch = new Artist();
+        noMatch.setArtistName("dabc");
+        assertFalse(like.match(noMatch));
+        assertTrue(notLike.match(noMatch));
+
+        Artist match1 = new Artist();
+        match1.setArtistName("abc123d");
+        assertTrue("Failed: " + like, like.match(match1));
+        assertFalse("Failed: " + notLike, notLike.match(match1));
+
+        Artist match2 = new Artist();
+        match2.setArtistName("abcd");
+        assertTrue("Failed: " + like, like.match(match2));
+        assertFalse("Failed: " + notLike, notLike.match(match2));
+    }
+
+    @Test
+    public void testEvaluateLIKE2() throws Exception {
+        Expression like = new ASTLike(new ASTObjPath("artistName"), "abc?d");
+        Expression notLike = new ASTNotLike(new ASTObjPath("artistName"), "abc?d");
+
+        Artist noMatch1 = new Artist();
+        noMatch1.setArtistName("dabc");
+        assertFalse(like.match(noMatch1));
+        assertTrue(notLike.match(noMatch1));
+
+        Artist noMatch2 = new Artist();
+        noMatch2.setArtistName("abc123d");
+        assertFalse("Failed: " + like, like.match(noMatch2));
+        assertTrue("Failed: " + notLike, notLike.match(noMatch2));
+
+        Artist match = new Artist();
+        match.setArtistName("abcXd");
+        assertTrue("Failed: " + like, like.match(match));
+        assertFalse("Failed: " + notLike, notLike.match(match));
+    }
+
+    @Test
+    public void testEvaluateLIKE3() throws Exception {
+        // test special chars
+        Expression like = new ASTLike(new ASTObjPath("artistName"), "/./");
+
+        Artist noMatch1 = new Artist();
+        noMatch1.setArtistName("/a/");
+        assertFalse(like.match(noMatch1));
+
+        Artist match = new Artist();
+        match.setArtistName("/./");
+        assertTrue("Failed: " + like, like.match(match));
+    }
+
+    @Test
+    public void testEvaluateLIKE_IGNORE_CASE() throws Exception {
+        Expression like = new ASTLikeIgnoreCase(new ASTObjPath("artistName"), "aBcD");
+        Expression notLike = new ASTNotLikeIgnoreCase(new ASTObjPath("artistName"), "aBcD");
+
+        Artist noMatch1 = new Artist();
+        noMatch1.setArtistName("dabc");
+        assertFalse(like.match(noMatch1));
+        assertTrue(notLike.match(noMatch1));
+
+        Artist match1 = new Artist();
+        match1.setArtistName("abcd");
+        assertTrue("Failed: " + like, like.match(match1));
+        assertFalse("Failed: " + notLike, notLike.match(match1));
+
+        Artist match2 = new Artist();
+        match2.setArtistName("ABcD");
+        assertTrue("Failed: " + like, like.match(match2));
+        assertFalse("Failed: " + notLike, notLike.match(match2));
+    }
+
+    @Test
+    public void testEvaluateADD() throws Exception {
+        Expression add = new ASTAdd(new Object[] { new Integer(1), new Double(5.5) });
+        assertEquals(6.5, ((Number) add.evaluate(null)).doubleValue(), 0.0001);
+    }
+
+    @Test
+    public void testEvaluateSubtract() throws Exception {
+        Expression subtract = new ASTSubtract(new Object[] { new Integer(1), new Double(0.1), new Double(0.2) });
+        assertEquals(0.7, ((Number) subtract.evaluate(null)).doubleValue(), 0.0001);
+    }
+
+    @Test
+    public void testEvaluateMultiply() throws Exception {
+        Expression multiply = new ASTMultiply(new Object[] { new Integer(2), new Double(3.5) });
+        assertEquals(7, ((Number) multiply.evaluate(null)).doubleValue(), 0.0001);
+    }
+
+    @Test
+    public void testEvaluateDivide() throws Exception {
+        Expression divide = new ASTDivide(new Object[] { new BigDecimal("7.0"), new BigDecimal("2.0") });
+        assertEquals(3.5, ((Number) divide.evaluate(null)).doubleValue(), 0.0001);
+    }
+
+    @Test
+    public void testEvaluateNegate() throws Exception {
+        assertEquals(-3, ((Number) new ASTNegate(new Integer(3)).evaluate(null)).intValue());
+        assertEquals(5, ((Number) new ASTNegate(new Integer(-5)).evaluate(null)).intValue());
+    }
+
+    @Test
+    public void testEvaluateTrue() throws Exception {
+        assertEquals(Boolean.TRUE, new ASTTrue().evaluate(null));
+    }
+
+    @Test
+    public void testEvaluateFalse() throws Exception {
+        assertEquals(Boolean.FALSE, new ASTFalse().evaluate(null));
+    }
+
+    public void testEvaluateNullCompare() throws Exception {
+    	assertFalse(new ASTGreater(new ASTObjPath("artistName"), "A").match(new Artist()));
+    }
+
+    public void testEvaluateCompareNull() throws Exception {
+    	Artist artist = new Artist();
+    	Artist a1 = artist;
+		a1.setArtistName("Name");
+		Expression expression = new ASTGreater(new ASTObjPath("artistName"), null);
+		assertFalse(expression.match(a1));
+		assertFalse(expression.notExp().match(a1));
+    }
+
+    public void testEvaluateEqualsNull() throws Exception {
+    	Artist a1 = new Artist();
+    	Expression isNull = Artist.ARTIST_NAME.isNull();
+		assertTrue(isNull.match(a1));
+		assertFalse(isNull.notExp().match(a1));
+    }
+
+    public void testEvaluateEqualsNullColumn() throws Exception {
+    	Expression equals = Expression.fromString("artistName = someOtherProperty");
+		assertFalse(equals.match(new Artist()));
+    }
+
+    public void testEvaluatNotEqualsNullColumn() throws Exception {
+    	Expression notEquals = Expression.fromString("artistName <> someOtherProperty");
+    	assertFalse(notEquals.match(new Artist()));
+    }
+
+    public void testNullAnd() {
+        Expression nullExp = Expression.fromString("null > 0");
+
+        ASTAnd nullAndTrue = new ASTAnd(new Object[] {nullExp, new ASTTrue()});
+        assertFalse(nullAndTrue.match(null));
+        assertFalse(nullAndTrue.notExp().match(null));
+
+        ASTAnd nullAndFalse = new ASTAnd(new Object[] {nullExp, new ASTFalse()});
+        assertFalse(nullAndFalse.match(null));
+        assertTrue(nullAndFalse.notExp().match(null));
+    }
+
+    public void testNullOr() {
+        Expression nullExp = Expression.fromString("null > 0");
+
+        ASTOr nullOrTrue = new ASTOr(new Object[] {nullExp, new ASTTrue()});
+        assertTrue(nullOrTrue.match(null));
+        assertFalse(nullOrTrue.notExp().match(null));
+
+        ASTOr nullOrFalse = new ASTOr(new Object[] {nullExp, new ASTFalse()});
+        assertFalse(nullOrFalse.match(null));
+        assertFalse(nullOrFalse.notExp().match(null));
+    }
+}


Mime
View raw message