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/16 12:56:14 UTC

cayenne git commit: CAY-2231 Support for collections in new functional expressions and old math expressions

Repository: cayenne
Updated Branches:
  refs/heads/master 5c805019a -> 2667f0126


CAY-2231 Support for collections in new functional expressions and old math expressions


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

Branch: refs/heads/master
Commit: 2667f0126a6a2a3abafbb174898998aae66e8a80
Parents: 5c80501
Author: Nikita Timofeev <st...@gmail.com>
Authored: Thu Feb 16 15:49:52 2017 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Thu Feb 16 15:49:52 2017 +0300

----------------------------------------------------------------------
 .../org/apache/cayenne/exp/parser/ASTAbs.java   |   9 +-
 .../org/apache/cayenne/exp/parser/ASTAdd.java   |  33 +----
 .../exp/parser/ASTAggregateFunctionCall.java    |   7 +-
 .../cayenne/exp/parser/ASTBitwiseAnd.java       |  34 ++----
 .../cayenne/exp/parser/ASTBitwiseLeftShift.java |  34 ++----
 .../cayenne/exp/parser/ASTBitwiseNot.java       |  18 ++-
 .../apache/cayenne/exp/parser/ASTBitwiseOr.java |  39 ++----
 .../exp/parser/ASTBitwiseRightShift.java        |  34 +-----
 .../cayenne/exp/parser/ASTBitwiseXor.java       |  37 ++----
 .../apache/cayenne/exp/parser/ASTConcat.java    |  12 +-
 .../cayenne/exp/parser/ASTCurrentDate.java      |   7 +-
 .../cayenne/exp/parser/ASTCurrentTime.java      |   7 +-
 .../cayenne/exp/parser/ASTCurrentTimestamp.java |   7 +-
 .../apache/cayenne/exp/parser/ASTDivide.java    |  32 +----
 .../cayenne/exp/parser/ASTFunctionCall.java     |   2 +-
 .../apache/cayenne/exp/parser/ASTLength.java    |   9 +-
 .../apache/cayenne/exp/parser/ASTLocate.java    |  21 ++--
 .../org/apache/cayenne/exp/parser/ASTLower.java |   9 +-
 .../org/apache/cayenne/exp/parser/ASTMod.java   |  10 ++
 .../apache/cayenne/exp/parser/ASTMultiply.java  |  32 +----
 .../org/apache/cayenne/exp/parser/ASTSqrt.java  |   9 +-
 .../apache/cayenne/exp/parser/ASTSubstring.java |  22 ++--
 .../apache/cayenne/exp/parser/ASTSubtract.java  |  31 +----
 .../org/apache/cayenne/exp/parser/ASTTrim.java  |  10 +-
 .../org/apache/cayenne/exp/parser/ASTUpper.java |   9 +-
 .../exp/parser/EvaluatedBitwiseNode.java        |  57 +++++++++
 .../cayenne/exp/parser/EvaluatedMathNode.java   |  58 +++++++++
 .../cayenne/exp/parser/EvaluatedNode.java       |  85 +++++++++++++
 .../exp/parser/ASTFunctionCallStringIT.java     |   2 +-
 .../cayenne/exp/parser/ASTSubstringTest.java    |   2 +-
 .../ExpressionCollectionEvaluationIT.java       | 122 +++++++++++++++++++
 docs/doc/src/main/resources/RELEASE-NOTES.txt   |   1 +
 32 files changed, 497 insertions(+), 304 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAbs.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAbs.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAbs.java
index b5220bb..e49972c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAbs.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAbs.java
@@ -36,12 +36,17 @@ public class ASTAbs extends ASTFunctionCall {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
-        double n = ConversionUtil.toDouble(evaluateChild(0, o), 0.0);
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        double n = ConversionUtil.toDouble(o, 0.0);
         return Math.abs(n);
     }
 
     @Override
+    protected int getRequiredChildrenCount() {
+        return 1;
+    }
+
+    @Override
     public Expression shallowCopy() {
         return new ASTAbs(id);
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAdd.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAdd.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAdd.java
index de484ef..8554bdc 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAdd.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAdd.java
@@ -20,16 +20,16 @@
 package org.apache.cayenne.exp.parser;
 
 import java.math.BigDecimal;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 
 import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.util.ConversionUtil;
 
 /**
  * "Add" Expression.
  */
-public class ASTAdd extends SimpleNode {
+public class ASTAdd extends EvaluatedMathNode {
 
 	private static final long serialVersionUID = -8622963819149351988L;
 
@@ -42,13 +42,7 @@ public class ASTAdd extends SimpleNode {
 	}
 
 	public ASTAdd(Object[] nodes) {
-		super(ExpressionParserTreeConstants.JJTADD);
-		int len = nodes.length;
-		for (int i = 0; i < len; i++) {
-			jjtAddChild(wrapChild(nodes[i]), i);
-		}
-
-		connectChildren();
+		this(Arrays.asList(nodes));
 	}
 
 	public ASTAdd(Collection<?> nodes) {
@@ -58,29 +52,12 @@ public class ASTAdd extends SimpleNode {
 		for (int i = 0; i < len; i++) {
 			jjtAddChild(wrapChild(it.next()), i);
 		}
-
 		connectChildren();
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len == 0) {
-			return null;
-		}
-
-		BigDecimal result = null;
-		for (int i = 0; i < len; i++) {
-			BigDecimal value = ConversionUtil.toBigDecimal(evaluateChild(i, o));
-
-			if (value == null) {
-				return null;
-			}
-
-			result = (i == 0) ? value : result.add(value);
-		}
-
-		return result;
+	protected BigDecimal op(BigDecimal result, BigDecimal arg) {
+		return result.add(arg);
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAggregateFunctionCall.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAggregateFunctionCall.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAggregateFunctionCall.java
index 641f711..626edf8 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAggregateFunctionCall.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAggregateFunctionCall.java
@@ -35,7 +35,12 @@ public abstract class ASTAggregateFunctionCall extends ASTFunctionCall {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
+    protected int getRequiredChildrenCount() {
+        return 0;
+    }
+
+    @Override
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
         throw new UnsupportedOperationException("In-memory evaluation of aggregate functions not implemented yet.");
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseAnd.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseAnd.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseAnd.java
index 9fe8a2f..10fd9a5 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseAnd.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseAnd.java
@@ -18,18 +18,18 @@
  ****************************************************************/
 package org.apache.cayenne.exp.parser;
 
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 
 import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.util.ConversionUtil;
 
 /**
  * Bitwise conjunction (AND or '&amp;') expression
  * 
  * @since 3.1
  */
-public class ASTBitwiseAnd extends SimpleNode {
+public class ASTBitwiseAnd extends EvaluatedBitwiseNode {
 
 	private static final long serialVersionUID = -1482206814209874743L;
 
@@ -42,13 +42,7 @@ public class ASTBitwiseAnd extends SimpleNode {
 	}
 
 	public ASTBitwiseAnd(Object[] nodes) {
-		super(ExpressionParserTreeConstants.JJTBITWISEAND);
-		int len = nodes.length;
-		for (int i = 0; i < len; i++) {
-			jjtAddChild(wrapChild(nodes[i]), i);
-		}
-
-		connectChildren();
+		this(Arrays.asList(nodes));
 	}
 
 	public ASTBitwiseAnd(Collection<Object> nodes) {
@@ -58,27 +52,13 @@ public class ASTBitwiseAnd extends SimpleNode {
 		for (int i = 0; i < len; i++) {
 			jjtAddChild(wrapChild(it.next()), i);
 		}
+
+		connectChildren();
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len == 0) {
-			return null;
-		}
-
-		Long result = null;
-		for (int i = 0; i < len; i++) {
-			Long value = ConversionUtil.toLong(evaluateChild(i, o), Long.MIN_VALUE);
-
-			if (value == Long.MIN_VALUE) {
-				return null;
-			}
-
-			result = (i == 0) ? value : result & value;
-		}
-
-		return result;
+	protected long op(long result, long arg) {
+		return result & arg;
 	}
 
 	@Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseLeftShift.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseLeftShift.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseLeftShift.java
index d7c8606..2410129 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseLeftShift.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseLeftShift.java
@@ -18,18 +18,18 @@
  ****************************************************************/
 package org.apache.cayenne.exp.parser;
 
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 
 import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.util.ConversionUtil;
 
 /**
  * Bitwise left shift '&lt;&lt;' operation.
  * 
  * @since 4.0
  */
-public class ASTBitwiseLeftShift extends SimpleNode {
+public class ASTBitwiseLeftShift extends EvaluatedBitwiseNode {
 
 	private static final long serialVersionUID = -954784931828996446L;
 
@@ -42,13 +42,7 @@ public class ASTBitwiseLeftShift extends SimpleNode {
 	}
 
 	public ASTBitwiseLeftShift(Object[] nodes) {
-		super(ExpressionParserTreeConstants.JJTBITWISELEFTSHIFT);
-		int len = nodes.length;
-		for (int i = 0; i < len; i++) {
-			jjtAddChild(wrapChild(nodes[i]), i);
-		}
-
-		connectChildren();
+		this(Arrays.asList(nodes));
 	}
 
 	public ASTBitwiseLeftShift(Collection<Object> nodes) {
@@ -58,27 +52,13 @@ public class ASTBitwiseLeftShift extends SimpleNode {
 		for (int i = 0; i < len; i++) {
 			jjtAddChild(wrapChild(it.next()), i);
 		}
+
+		connectChildren();
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len == 0) {
-			return null;
-		}
-
-		Long result = null;
-		for (int i = 0; i < len; i++) {
-			Long value = ConversionUtil.toLong(evaluateChild(i, o), Long.MIN_VALUE);
-
-			if (value == Long.MIN_VALUE) {
-				return null;
-			}
-
-			result = (i == 0) ? value : result << value;
-		}
-
-		return result;
+	protected long op(long result, long arg) {
+		return result << arg;
 	}
 
 	@Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseNot.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseNot.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseNot.java
index a86761f..50a970e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseNot.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseNot.java
@@ -26,7 +26,7 @@ import org.apache.cayenne.util.ConversionUtil;
  * 
  * @since 3.1
  */
-public class ASTBitwiseNot extends SimpleNode {
+public class ASTBitwiseNot extends EvaluatedNode {
 	private static final long serialVersionUID = 1L;
 
 	ASTBitwiseNot(int id) {
@@ -44,21 +44,17 @@ public class ASTBitwiseNot extends SimpleNode {
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-
-		int len = jjtGetNumChildren();
-		if (len != 1) {
-			return Boolean.FALSE;
-		}
-
-		long value = ConversionUtil.toLong(evaluateChild(0, o), Long.MIN_VALUE);
-
+	protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+		long value = ConversionUtil.toLong(o, Long.MIN_VALUE);
 		if (value == Long.MIN_VALUE) {
 			return null;
 		}
-
 		return ~value;
+	}
 
+	@Override
+	protected int getRequiredChildrenCount() {
+		return 1;
 	}
 
 	@Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseOr.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseOr.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseOr.java
index 8b3060c..87ad19d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseOr.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseOr.java
@@ -18,11 +18,11 @@
  ****************************************************************/
 package org.apache.cayenne.exp.parser;
 
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 
 import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.util.ConversionUtil;
 
 
 /**
@@ -30,7 +30,7 @@ import org.apache.cayenne.util.ConversionUtil;
  * 
  * @since 3.1
  */
-public class ASTBitwiseOr extends SimpleNode {
+public class ASTBitwiseOr extends EvaluatedBitwiseNode {
 	private static final long serialVersionUID = 1L;
 
 	ASTBitwiseOr(int id) {
@@ -42,13 +42,7 @@ public class ASTBitwiseOr extends SimpleNode {
 	}
 	
 	public ASTBitwiseOr(Object[] nodes) {
-        super(ExpressionParserTreeConstants.JJTBITWISEOR);
-        int len = nodes.length;
-        for (int i = 0; i < len; i++) {
-            jjtAddChild(wrapChild(nodes[i]), i);
-        }
-        
-        connectChildren();
+        this(Arrays.asList(nodes));
 	}
 	
     public ASTBitwiseOr(Collection<Object> nodes) {
@@ -58,30 +52,15 @@ public class ASTBitwiseOr extends SimpleNode {
         for (int i = 0; i < len; i++) {
             jjtAddChild(wrapChild(it.next()), i);
         }
+        connectChildren();
     }
-	
-	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-        int len = jjtGetNumChildren();
-        if (len == 0) {
-            return null;
-        }
-
-        Long result = null;
-        for (int i = 0; i < len; i++) {
-            Long value = ConversionUtil.toLong(evaluateChild(i, o), Long.MIN_VALUE);
-
-            if (value == Long.MIN_VALUE) {
-                return null;
-            }
 
-            result = (i == 0) ? value : result | value;
-        }
-
-        return result;
-	}
+    @Override
+    protected long op(long result, long arg) {
+        return result | arg;
+    }
 
-	@Override
+    @Override
 	protected String getExpressionOperator(int index) {
 		return "|";
 	}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseRightShift.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseRightShift.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseRightShift.java
index 8e09f26..b34d3e1 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseRightShift.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseRightShift.java
@@ -18,18 +18,18 @@
  ****************************************************************/
 package org.apache.cayenne.exp.parser;
 
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 
 import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.util.ConversionUtil;
 
 /**
  * Bitwise right shift '&gt;&gt;' operation.
  * 
  * @since 4.0
  */
-public class ASTBitwiseRightShift extends SimpleNode {
+public class ASTBitwiseRightShift extends EvaluatedBitwiseNode {
 	private static final long serialVersionUID = 1L;
 
 	ASTBitwiseRightShift(int id) {
@@ -41,13 +41,7 @@ public class ASTBitwiseRightShift extends SimpleNode {
 	}
 
 	public ASTBitwiseRightShift(Object[] nodes) {
-		super(ExpressionParserTreeConstants.JJTBITWISERIGHTSHIFT);
-		int len = nodes.length;
-		for (int i = 0; i < len; i++) {
-			jjtAddChild(wrapChild(nodes[i]), i);
-		}
-
-		connectChildren();
+		this(Arrays.asList(nodes));
 	}
 
 	public ASTBitwiseRightShift(Collection<Object> nodes) {
@@ -57,28 +51,12 @@ public class ASTBitwiseRightShift extends SimpleNode {
 		for (int i = 0; i < len; i++) {
 			jjtAddChild(wrapChild(it.next()), i);
 		}
+		connectChildren();
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len == 0) {
-			return null;
-		}
-
-		Long result = null;
-		for (int i = 0; i < len; i++) {
-			Long value = ConversionUtil.toLong(evaluateChild(i, o),
-					Long.MIN_VALUE);
-
-			if (value == Long.MIN_VALUE) {
-				return null;
-			}
-
-			result = (i == 0) ? value : result >> value;
-		}
-
-		return result;
+	protected long op(long result, long arg) {
+		return result >> arg;
 	}
 
 	@Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseXor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseXor.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseXor.java
index dfbd38b..4585d65 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseXor.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBitwiseXor.java
@@ -18,18 +18,18 @@
  ****************************************************************/
 package org.apache.cayenne.exp.parser;
 
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 
 import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.util.ConversionUtil;
 
 /**
  * Bitwise exclusive disjunction (XOR or '^') operation.
  * 
  * @since 3.1
  */
-public class ASTBitwiseXor extends SimpleNode {
+public class ASTBitwiseXor extends EvaluatedBitwiseNode {
 	private static final long serialVersionUID = 1L;
 
 	ASTBitwiseXor(int id) {
@@ -41,13 +41,7 @@ public class ASTBitwiseXor extends SimpleNode {
 	}
 	
 	public ASTBitwiseXor(Object[] nodes) {
-        super(ExpressionParserTreeConstants.JJTBITWISEXOR);
-        int len = nodes.length;
-        for (int i = 0; i < len; i++) {
-            jjtAddChild(wrapChild(nodes[i]), i);
-        }
-        
-        connectChildren();
+        this(Arrays.asList(nodes));
 	}
 	
     public ASTBitwiseXor(Collection<Object> nodes) {
@@ -57,28 +51,13 @@ public class ASTBitwiseXor extends SimpleNode {
         for (int i = 0; i < len; i++) {
             jjtAddChild(wrapChild(it.next()), i);
         }
+        connectChildren();
     }
-	
-	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-        int len = jjtGetNumChildren();
-        if (len == 0) {
-            return null;
-        }
 
-        Long result = null;
-        for (int i = 0; i < len; i++) {
-            Long value = ConversionUtil.toLong(evaluateChild(i, o), Long.MIN_VALUE);
-
-            if (value == Long.MIN_VALUE) {
-                return null;
-            }
-
-            result = (i == 0) ? value : result ^ value;
-        }
-
-        return result;
-	}
+    @Override
+    protected long op(long result, long arg) {
+        return result ^ arg;
+    }
 
 	@Override
 	protected String getExpressionOperator(int index) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTConcat.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTConcat.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTConcat.java
index 1bfb336..9d4d954 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTConcat.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTConcat.java
@@ -36,10 +36,16 @@ public class ASTConcat extends ASTFunctionCall {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
+    protected int getRequiredChildrenCount() {
+        return 1;
+    }
+
+    @Override
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
         StringBuilder sb = new StringBuilder();
-        for(int i=0; i<getOperandCount(); i++) {
-            sb.append(ConversionUtil.toString(evaluateChild(i, o)));
+        sb.append(ConversionUtil.toString(o));
+        for(int i=1; i<evaluatedChildren.length; i++) {
+            sb.append(ConversionUtil.toString(evaluatedChildren[i]));
         }
         return sb.toString();
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentDate.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentDate.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentDate.java
index 035fd44..e2b3df2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentDate.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentDate.java
@@ -42,7 +42,12 @@ public class ASTCurrentDate extends ASTFunctionCall {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
+    protected int getRequiredChildrenCount() {
+        return 0;
+    }
+
+    @Override
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
         return new Date();
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTime.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTime.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTime.java
index 1ef54bf..292a264 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTime.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTime.java
@@ -42,7 +42,12 @@ public class ASTCurrentTime extends ASTFunctionCall {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
+    protected int getRequiredChildrenCount() {
+        return 0;
+    }
+
+    @Override
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
         return new Date();
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTimestamp.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTimestamp.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTimestamp.java
index e1d56c0..e14dd58 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTimestamp.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCurrentTimestamp.java
@@ -42,7 +42,12 @@ public class ASTCurrentTimestamp extends ASTFunctionCall {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
+    protected int getRequiredChildrenCount() {
+        return 0;
+    }
+
+    @Override
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
         return new Date();
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDivide.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDivide.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDivide.java
index 7e48b8c..b9759e1 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDivide.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTDivide.java
@@ -20,18 +20,18 @@
 package org.apache.cayenne.exp.parser;
 
 import java.math.BigDecimal;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 
 import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.util.ConversionUtil;
 
 /**
  * "Divide" expression.
  * 
  * @since 1.1
  */
-public class ASTDivide extends SimpleNode {
+public class ASTDivide extends EvaluatedMathNode {
 
 	private static final long serialVersionUID = -5086569683844539310L;
 
@@ -44,13 +44,7 @@ public class ASTDivide extends SimpleNode {
 	}
 
 	public ASTDivide(Object[] nodes) {
-		super(ExpressionParserTreeConstants.JJTDIVIDE);
-		int len = nodes.length;
-		for (int i = 0; i < len; i++) {
-			jjtAddChild(wrapChild(nodes[i]), i);
-		}
-
-		connectChildren();
+		this(Arrays.asList(nodes));
 	}
 
 	public ASTDivide(Collection<?> nodes) {
@@ -65,24 +59,8 @@ public class ASTDivide extends SimpleNode {
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len == 0) {
-			return null;
-		}
-
-		BigDecimal result = null;
-		for (int i = 0; i < len; i++) {
-			BigDecimal value = ConversionUtil.toBigDecimal(evaluateChild(i, o));
-
-			if (value == null) {
-				return null;
-			}
-
-			result = (i == 0) ? value : result.divide(value, BigDecimal.ROUND_HALF_EVEN);
-		}
-
-		return result;
+	protected BigDecimal op(BigDecimal result, BigDecimal arg) {
+		return result.divide(arg, BigDecimal.ROUND_HALF_EVEN);
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFunctionCall.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFunctionCall.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFunctionCall.java
index c8d7f06..2902db3 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFunctionCall.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFunctionCall.java
@@ -27,7 +27,7 @@ import org.apache.cayenne.exp.Expression;
 /**
  * @since 4.0
  */
-public abstract class ASTFunctionCall extends SimpleNode {
+public abstract class ASTFunctionCall extends EvaluatedNode {
 
     private String functionName;
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLength.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLength.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLength.java
index e12512e..91247db 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLength.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLength.java
@@ -36,8 +36,8 @@ public class ASTLength extends ASTFunctionCall {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
-        String s1 = ConversionUtil.toString(evaluateChild(0, o));
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        String s1 = ConversionUtil.toString(o);
         if (s1 == null) {
             return null;
         }
@@ -45,6 +45,11 @@ public class ASTLength extends ASTFunctionCall {
     }
 
     @Override
+    protected int getRequiredChildrenCount() {
+        return 1;
+    }
+
+    @Override
     public Expression shallowCopy() {
         return new ASTLength(id);
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLocate.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLocate.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLocate.java
index d26dbb5..addf4f6 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLocate.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLocate.java
@@ -40,24 +40,23 @@ public class ASTLocate extends ASTFunctionCall {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
-        int len = jjtGetNumChildren();
-        if (len < 2) {
-            return 0L;
-        }
-
-        String substr = ConversionUtil.toString(evaluateChild(0, o));
-        String str = ConversionUtil.toString(evaluateChild(1, o));
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        String substr = ConversionUtil.toString(o);
+        String str = ConversionUtil.toString(evaluatedChildren[1]);
         int offset = 0;
-        if(len > 2) {
-            offset = ConversionUtil.toInt(evaluateChild(2, o), 0);
+        if(evaluatedChildren.length > 2) {
+            offset = ConversionUtil.toInt(evaluatedChildren[2], 0);
         }
-
         // +1 to comply with SQL
         return str.indexOf(substr, offset) + 1;
     }
 
     @Override
+    protected int getRequiredChildrenCount() {
+        return 2;
+    }
+
+    @Override
     public Expression shallowCopy() {
         return new ASTLocate(id);
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLower.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLower.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLower.java
index 78b6f80..2a29a11 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLower.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLower.java
@@ -37,8 +37,13 @@ public class ASTLower extends ASTFunctionCall {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
-        String s1 = ConversionUtil.toString(evaluateChild(0, o));
+    protected int getRequiredChildrenCount() {
+        return 1;
+    }
+
+    @Override
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        String s1 = ConversionUtil.toString(o);
         if (s1 == null) {
             return null;
         }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMod.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMod.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMod.java
index c4f79a7..268fa9c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMod.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMod.java
@@ -46,6 +46,16 @@ public class ASTMod extends ASTFunctionCall {
     }
 
     @Override
+    protected int getRequiredChildrenCount() {
+        return 0;
+    }
+
+    @Override
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        return null;
+    }
+
+    @Override
     public Expression shallowCopy() {
         return new ASTMod(id);
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMultiply.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMultiply.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMultiply.java
index 4b7ee0e..edc9c70 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMultiply.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMultiply.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.exp.parser;
 
 import java.math.BigDecimal;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 
@@ -31,7 +32,7 @@ import org.apache.cayenne.util.ConversionUtil;
  * 
  * @since 1.1
  */
-public class ASTMultiply extends SimpleNode {
+public class ASTMultiply extends EvaluatedMathNode {
 
 	private static final long serialVersionUID = -8146316633842448974L;
 
@@ -44,13 +45,7 @@ public class ASTMultiply extends SimpleNode {
 	}
 
 	public ASTMultiply(Object[] nodes) {
-		super(ExpressionParserTreeConstants.JJTMULTIPLY);
-		int len = nodes.length;
-		for (int i = 0; i < len; i++) {
-			jjtAddChild(wrapChild(nodes[i]), i);
-		}
-
-		connectChildren();
+		this(Arrays.asList(nodes));
 	}
 
 	public ASTMultiply(Collection<?> nodes) {
@@ -60,27 +55,12 @@ public class ASTMultiply extends SimpleNode {
 		for (int i = 0; i < len; i++) {
 			jjtAddChild(wrapChild(it.next()), i);
 		}
+		connectChildren();
 	}
 
 	@Override
-	protected Object evaluateNode(Object o) throws Exception {
-		int len = jjtGetNumChildren();
-		if (len == 0) {
-			return null;
-		}
-
-		BigDecimal result = null;
-		for (int i = 0; i < len; i++) {
-			BigDecimal value = ConversionUtil.toBigDecimal(evaluateChild(i, o));
-
-			if (value == null) {
-				return null;
-			}
-
-			result = (i == 0) ? value : result.multiply(value);
-		}
-
-		return result;
+	protected BigDecimal op(BigDecimal result, BigDecimal arg) {
+		return result.multiply(arg);
 	}
 
 	/**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSqrt.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSqrt.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSqrt.java
index 71e8e32..41a7a4a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSqrt.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSqrt.java
@@ -36,8 +36,13 @@ public class ASTSqrt extends ASTFunctionCall {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
-        double n = ConversionUtil.toDouble(evaluateChild(0, o), 0.0);
+    protected int getRequiredChildrenCount() {
+        return 1;
+    }
+
+    @Override
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        double n = ConversionUtil.toDouble(o, 0.0);
         return Math.sqrt(n);
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubstring.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubstring.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubstring.java
index 33ac4e0..de3d4af 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubstring.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubstring.java
@@ -27,7 +27,6 @@ import org.apache.cayenne.util.ConversionUtil;
  */
 public class ASTSubstring extends ASTFunctionCall {
 
-
     ASTSubstring(int id) {
         super(id, "SUBSTRING");
     }
@@ -37,27 +36,28 @@ public class ASTSubstring extends ASTFunctionCall {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
-        int len = jjtGetNumChildren();
-        if (len != 3) {
-            return null;
-        }
-
-        String s1 = ConversionUtil.toString(evaluateChild(0, o));
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        String s1 = ConversionUtil.toString(o);
         if (s1 == null) {
             return null;
         }
 
-        int offset = ConversionUtil.toInt(evaluateChild(1, o), 0);
-        int length = ConversionUtil.toInt(evaluateChild(2, o), 0);
+        int offset = ConversionUtil.toInt(evaluatedChildren[1], 0);
+        int length = ConversionUtil.toInt(evaluatedChildren[2], 0);
         if(length == 0) {
             return null;
         }
 
-        return s1.substring(offset, offset + length);
+        return s1.substring(offset - 1, offset - 1 + length); // - 1 to comply with SQL
     }
 
     @Override
+    protected int getRequiredChildrenCount() {
+        return 3;
+    }
+
+
+    @Override
     public Expression shallowCopy() {
         return new ASTSubstring(id);
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubtract.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubtract.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubtract.java
index 732b050..ce68e40 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubtract.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubtract.java
@@ -21,18 +21,18 @@
 package org.apache.cayenne.exp.parser;
 
 import java.math.BigDecimal;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 
 import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.util.ConversionUtil;
 
 /**
  * "Subtract" expression.
  * 
  * @since 1.1
  */
-public class ASTSubtract extends SimpleNode {
+public class ASTSubtract extends EvaluatedMathNode {
     ASTSubtract(int id) {
         super(id);
     }
@@ -42,12 +42,7 @@ public class ASTSubtract extends SimpleNode {
     }
 
     public ASTSubtract(Object[] nodes) {
-        super(ExpressionParserTreeConstants.JJTSUBTRACT);
-        int len = nodes.length;
-        for (int i = 0; i < len; i++) {
-            jjtAddChild(wrapChild(nodes[i]), i);
-        }
-        connectChildren();
+        this(Arrays.asList(nodes));
     }
 
     public ASTSubtract(Collection<?> nodes) {
@@ -61,24 +56,8 @@ public class ASTSubtract extends SimpleNode {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
-        int len = jjtGetNumChildren();
-        if (len == 0) {
-            return null;
-        }
-
-        BigDecimal result = null;
-        for (int i = 0; i < len; i++) {
-            BigDecimal value = ConversionUtil.toBigDecimal(evaluateChild(i, o));
-
-            if (value == null) {
-                return null;
-            }
-
-            result = (i == 0) ? value : result.subtract(value);
-        }
-
-        return result;
+    protected BigDecimal op(BigDecimal result, BigDecimal arg) {
+        return result.subtract(arg);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTTrim.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTTrim.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTTrim.java
index f037dd0..5c4af1a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTTrim.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTTrim.java
@@ -41,12 +41,16 @@ public class ASTTrim extends ASTFunctionCall {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
-        String s1 = ConversionUtil.toString(evaluateChild(0, o));
+    protected int getRequiredChildrenCount() {
+        return 1;
+    }
+
+    @Override
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        String s1 = ConversionUtil.toString(o);
         if (s1 == null) {
             return null;
         }
-
         return s1.trim();
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTUpper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTUpper.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTUpper.java
index 60716fc..602edd3 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTUpper.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTUpper.java
@@ -36,8 +36,13 @@ public class ASTUpper extends ASTFunctionCall {
     }
 
     @Override
-    protected Object evaluateNode(Object o) throws Exception {
-        String s1 = ConversionUtil.toString(evaluateChild(0, o));
+    protected int getRequiredChildrenCount() {
+        return 1;
+    }
+
+    @Override
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        String s1 = ConversionUtil.toString(o);
         if (s1 == null) {
             return null;
         }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/EvaluatedBitwiseNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/EvaluatedBitwiseNode.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/EvaluatedBitwiseNode.java
new file mode 100644
index 0000000..c19deee
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/EvaluatedBitwiseNode.java
@@ -0,0 +1,57 @@
+/*****************************************************************
+ *   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.util.ConversionUtil;
+
+/**
+ * @since 4.0
+ */
+public abstract class EvaluatedBitwiseNode extends EvaluatedNode {
+
+    protected EvaluatedBitwiseNode(int i) {
+        super(i);
+    }
+
+    @Override
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        Long result = ConversionUtil.toLong(o, Long.MIN_VALUE);
+        if(result == Long.MIN_VALUE) {
+            return null;
+        }
+        for (int i = 1; i < evaluatedChildren.length; i++) {
+            Long value = ConversionUtil.toLong(evaluateChild(i, o), Long.MIN_VALUE);
+            if (value == Long.MIN_VALUE) {
+                return null;
+            }
+
+            result = op(result, value);
+        }
+
+        return result;
+    }
+
+    @Override
+    protected int getRequiredChildrenCount() {
+        return 1;
+    }
+
+    protected abstract long op(long result, long arg);
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/EvaluatedMathNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/EvaluatedMathNode.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/EvaluatedMathNode.java
new file mode 100644
index 0000000..d0177c2
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/EvaluatedMathNode.java
@@ -0,0 +1,58 @@
+/*****************************************************************
+ *   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 org.apache.cayenne.util.ConversionUtil;
+
+/**
+ * @since 4.0
+ */
+public abstract class EvaluatedMathNode extends EvaluatedNode {
+    protected EvaluatedMathNode(int i) {
+        super(i);
+    }
+
+    @Override
+    protected int getRequiredChildrenCount() {
+        return 1;
+    }
+
+    @Override
+    protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+        BigDecimal result = ConversionUtil.toBigDecimal(o);
+        if(result == null) {
+            return null;
+        }
+        for (int i = 1; i < evaluatedChildren.length; i++) {
+            BigDecimal value = ConversionUtil.toBigDecimal(evaluatedChildren[i]);
+            if (value == null) {
+                return null;
+            }
+            result = op(result, value);
+        }
+
+        return result;
+    }
+
+    abstract protected BigDecimal op(BigDecimal result, BigDecimal arg);
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/EvaluatedNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/EvaluatedNode.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/EvaluatedNode.java
new file mode 100644
index 0000000..96d962b
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/EvaluatedNode.java
@@ -0,0 +1,85 @@
+/*****************************************************************
+ *   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.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @since 4.0
+ */
+public abstract class EvaluatedNode extends SimpleNode {
+
+    protected EvaluatedNode(int i) {
+        super(i);
+    }
+
+    @Override
+    protected Object evaluateNode(Object o) throws Exception {
+        int len = jjtGetNumChildren();
+        int requiredLen = getRequiredChildrenCount();
+        if (len < requiredLen) {
+            return null;
+        }
+
+        if(requiredLen == 0) {
+            return evaluateSubNode(null, null);
+        }
+
+        final Object[] evaluatedChildren = new Object[len];
+        for(int i=0; i<len; i++) {
+            evaluatedChildren[i] = evaluateChild(i, o);
+        }
+
+        Object firstChild = evaluatedChildren[0];
+
+        // convert Map, keep Map keys
+        if(firstChild instanceof Map) {
+            @SuppressWarnings("unchecked")
+            Map<Object, Object> child = (Map<Object, Object>) firstChild;
+            Map<Object, Object> result = new HashMap<>(child.size());
+            for(Map.Entry<Object, Object> entry : child.entrySet()) {
+                result.put(entry.getKey(), evaluateSubNode(entry.getValue(), evaluatedChildren));
+            }
+            return result;
+        }
+
+        // convert collection
+        if (firstChild instanceof Collection) {
+            @SuppressWarnings("unchecked")
+            Collection<Object> child = (Collection<Object>) firstChild;
+            Collection<Object> result = new ArrayList<>(child.size());
+            for(Object c : child) {
+                result.add(evaluateSubNode(c, evaluatedChildren));
+            }
+            return result;
+        }
+
+        // convert scalar
+        return evaluateSubNode(firstChild, evaluatedChildren);
+    }
+
+    abstract protected int getRequiredChildrenCount();
+
+    abstract protected Object evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception;
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallStringIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallStringIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallStringIT.java
index 58a69dd..47d6373 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallStringIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTFunctionCallStringIT.java
@@ -163,7 +163,7 @@ public class ASTFunctionCallStringIT extends ServerCase {
     @Test
     public void testASTSubstringParse() {
         Expression exp = ExpressionFactory.exp("SUBSTRING('123456789', 3, 2)");
-        assertEquals("45", exp.evaluate(new Object()));
+        assertEquals("34", exp.evaluate(new Object()));
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTSubstringTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTSubstringTest.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTSubstringTest.java
index ee6a74a..2b35ab0 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTSubstringTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTSubstringTest.java
@@ -44,7 +44,7 @@ public class ASTSubstringTest {
 
         Object res = exp.evaluateNode(a);
         assertTrue(res instanceof String);
-        assertEquals("34567890", res);
+        assertEquals("23456789", res);
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionCollectionEvaluationIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionCollectionEvaluationIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionCollectionEvaluationIT.java
new file mode 100644
index 0000000..685a7d4
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionCollectionEvaluationIT.java
@@ -0,0 +1,122 @@
+/*****************************************************************
+ *   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.Collections;
+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.exp.Property;
+import org.apache.cayenne.query.ObjectSelect;
+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.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.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @since 4.0
+ */
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class ExpressionCollectionEvaluationIT extends ServerCase {
+
+    @Inject
+    private DataContext context;
+
+    @Inject
+    private DBHelper dbHelper;
+
+    @Before
+    public void createArtistsDataSet() throws Exception {
+        TableHelper tArtist = new TableHelper(dbHelper, "ARTIST");
+        tArtist.setColumns("ARTIST_ID", "ARTIST_NAME", "DATE_OF_BIRTH");
+        tArtist.insert(1, "artist1", new java.sql.Date(System.currentTimeMillis()));
+
+        TableHelper tGallery = new TableHelper(dbHelper, "GALLERY");
+        tGallery.setColumns("GALLERY_ID", "GALLERY_NAME");
+        tGallery.insert(1, "tate modern");
+
+        TableHelper tPaintings = new TableHelper(dbHelper, "PAINTING");
+        tPaintings.setColumns("PAINTING_ID", "PAINTING_TITLE", "ARTIST_ID", "GALLERY_ID", "ESTIMATED_PRICE");
+        for (int i = 1; i <= 3; i++) {
+            tPaintings.insert(i, i + "painting" + (Math.pow(10, i)), 1, 1, i * 100);
+        }
+
+        tPaintings.insert(4, "4painting", null, 1, 10000);
+//        tPaintings.insert(5, "5painting", 1, 1, null);
+    }
+
+    @Test
+    public void testSubstringWithCollection() {
+        testExpression("SUBSTRING(paintingArray.paintingTitle, 1, 1)", String.class);
+    }
+
+    @Test
+    public void testTrimWithCollection() {
+        testExpression("TRIM(paintingArray.paintingTitle)", String.class);
+    }
+
+    @Test
+    public void testUpperWithCollection() {
+        testExpression("UPPER(paintingArray.paintingTitle)", String.class);
+    }
+
+    @Test
+    public void testLowerWithCollection() {
+        testExpression("LOWER(paintingArray.paintingTitle)", String.class);
+    }
+
+    @Test
+    public void testLengthWithCollection() {
+        testExpression("LENGTH(paintingArray.paintingTitle)", Integer.class);
+    }
+
+    @Test
+    public void testConcatWithCollection() {
+        testExpression("CONCAT(paintingArray.paintingTitle, ' ', 'xyz')", String.class);
+    }
+
+    @Test
+    public void testMathWithCollection() {
+        testExpression("paintingArray.estimatedPrice + 2", BigDecimal.class);
+    }
+
+    private <T extends Comparable<T>> void testExpression(String expStr, Class<T> tClass) {
+        Expression exp = ExpressionFactory.exp(expStr);
+        Object res = exp.evaluate(ObjectSelect.query(Artist.class).prefetch(Artist.PAINTING_ARRAY.disjoint()).selectOne(context));
+        List<T> sqlResult = ObjectSelect.query(Artist.class).column(Property.create(exp, tClass)).orderBy("db:paintingArray.PAINTING_ID").select(context);
+
+        Collections.sort((List)res);
+        Collections.sort(sqlResult);
+
+        assertEquals(3, sqlResult.size());
+        assertEquals(res, sqlResult);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2667f012/docs/doc/src/main/resources/RELEASE-NOTES.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/RELEASE-NOTES.txt b/docs/doc/src/main/resources/RELEASE-NOTES.txt
index 94d9729..7a1cdf7 100644
--- a/docs/doc/src/main/resources/RELEASE-NOTES.txt
+++ b/docs/doc/src/main/resources/RELEASE-NOTES.txt
@@ -31,6 +31,7 @@ CAY-2212 cdbimport cleanup and configuration schema refactoring
 CAY-2223 JCacheQueryCache - a query cache provider to plug in JCache implementers
 CAY-2225 Extensible CacheInvalidationFilter logic
 CAY-2228 Deprecate multiple cache groups in caching and query API
+CAY-2231 Support for collections in new functional expressions and old math expressions
 CAY-2232 Proper conversion to String for new functional expressions
 CAY-2235 Deprecate Query.getDataMap() method