cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ntimof...@apache.org
Subject cayenne git commit: CAY-2190: Aggregate functions and GROUP BY support - function expressions for: COUNT(*), COUNT(field), MIN, MAX, AVG, SUM - new methods in FunctionExpressionFactory
Date Mon, 09 Jan 2017 14:21:53 GMT
Repository: cayenne
Updated Branches:
  refs/heads/master 80d0287af -> 50fe31c93


CAY-2190: Aggregate functions and GROUP BY support
 - function expressions for: COUNT(*),COUNT(field),MIN,MAX,AVG,SUM
 - new methods in FunctionExpressionFactory


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

Branch: refs/heads/master
Commit: 50fe31c934d3fb2e50ab0ca5ae29676ce949b368
Parents: 80d0287
Author: Nikita Timofeev <stariy95@gmail.com>
Authored: Mon Jan 9 16:51:44 2017 +0300
Committer: Nikita Timofeev <stariy95@gmail.com>
Committed: Mon Jan 9 16:51:44 2017 +0300

----------------------------------------------------------------------
 .../org/apache/cayenne/test/jdbc/DBHelper.java  |   6 +-
 .../select/DefaultSelectTranslator.java         |  67 ++++++-
 .../translator/select/QualifierTranslator.java  |   6 +-
 .../SQLServerTrimmingQualifierTranslator.java   |   6 +
 .../java/org/apache/cayenne/exp/Expression.java |   6 +-
 .../cayenne/exp/FunctionExpressionFactory.java  |  47 +++++
 .../exp/parser/ASTAggregateFunctionCall.java    |  36 ++++
 .../apache/cayenne/exp/parser/ASTAsterisk.java  |  80 ++++++++
 .../org/apache/cayenne/exp/parser/ASTAvg.java   |  47 +++++
 .../org/apache/cayenne/exp/parser/ASTCount.java |  50 +++++
 .../org/apache/cayenne/exp/parser/ASTMax.java   |  47 +++++
 .../org/apache/cayenne/exp/parser/ASTMin.java   |  47 +++++
 .../org/apache/cayenne/exp/parser/ASTSum.java   |  47 +++++
 ...ectActionWithUnsupportedDistinctTypesIT.java |   4 +-
 .../exp/FunctionExpressionFactoryTest.java      |  53 +++++-
 .../cayenne/exp/parser/ASTAggregateTest.java    |  61 +++++++
 .../cayenne/query/ObjectSelect_AggregateIT.java | 181 +++++++++++++++++++
 .../query/ObjectSelect_PrimitiveColumnsIT.java  |  24 +++
 .../cayenne/testdo/testmap/auto/_Artist.java    |  10 +-
 19 files changed, 811 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/DBHelper.java
----------------------------------------------------------------------
diff --git a/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/DBHelper.java
b/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/DBHelper.java
index 0d6ce00..ec080c0 100644
--- a/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/DBHelper.java
+++ b/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/DBHelper.java
@@ -168,7 +168,11 @@ public class DBHelper {
 
 						st.setNull(i + 1, type);
 					} else {
-						st.setObject(i + 1, values[i]);
+						if(columnTypes != null) {
+							st.setObject(i + 1, values[i], columnTypes[i]);
+						} else {
+							st.setObject(i + 1, values[i]);
+						}
 					}
 				}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
index 34cf746..1ea302d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
@@ -26,6 +26,8 @@ import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.exp.TraversalHelper;
+import org.apache.cayenne.exp.parser.ASTAggregateFunctionCall;
 import org.apache.cayenne.exp.parser.ASTDbPath;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbAttribute;
@@ -94,6 +96,13 @@ public class DefaultSelectTranslator extends QueryAssembler implements
SelectTra
 	 */
 	boolean forcingDistinct;
 
+	/**
+	 * Does this SQL have any aggregate function
+	 */
+	boolean haveAggregate;
+	List<ColumnDescriptor> groupByColumns;
+
+
 	public DefaultSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver)
{
 		super(query, adapter, entityResolver);
 	}
@@ -186,6 +195,11 @@ public class DefaultSelectTranslator extends QueryAssembler implements
SelectTra
 			queryBuf.append(qualifierBuffer);
 		}
 
+		if(haveAggregate && !groupByColumns.isEmpty()) {
+			queryBuf.append(" GROUP BY ");
+			appendGroupByColumns(queryBuf, groupByColumns);
+		}
+
 		// append prebuilt ordering
 		if (orderingBuffer.length() > 0) {
 			queryBuf.append(" ORDER BY ").append(orderingBuffer);
@@ -226,6 +240,23 @@ public class DefaultSelectTranslator extends QueryAssembler implements
SelectTra
 	}
 
 	/**
+	 * Append columns to GROUP BY clause
+	 * use distinct from appendSelectColumns() method
+	 * as it has some incompatible overridden versions (i.e. in Oracle translator)
+	 *
+	 * @since 4.0
+	 */
+	protected void appendGroupByColumns(StringBuilder buffer, List<ColumnDescriptor> groupByColumns)
{
+		int columnCount = groupByColumns.size();
+		buffer.append(groupByColumns.get(0).getName());
+
+		for (int i = 1; i < columnCount; i++) {
+			buffer.append(", ");
+			buffer.append(groupByColumns.get(i).getName());
+		}
+	}
+
+	/**
 	 * Handles appending optional limit and offset clauses. This implementation
 	 * does nothing, deferring to subclasses to define the LIMIT/OFFSET clause
 	 * syntax.
@@ -305,8 +336,12 @@ public class DefaultSelectTranslator extends QueryAssembler implements
SelectTra
 		return columns;
 	}
 
+	/**
+	 * If query contains explicit column list, use only them
+	 */
 	<T> List<ColumnDescriptor> appendOverridedColumns(List<ColumnDescriptor>
columns, SelectQuery<T> query) {
 		QualifierTranslator qualifierTranslator = adapter.getQualifierTranslator(this);
+		groupByColumns = new ArrayList<>();
 		for(Property<?> property : query.getColumns()) {
 			StringBuilder builder = new StringBuilder();
 			qualifierTranslator.setOut(builder);
@@ -316,11 +351,37 @@ public class DefaultSelectTranslator extends QueryAssembler implements
SelectTra
 			ColumnDescriptor descriptor = new ColumnDescriptor(builder.toString(), type);
 			descriptor.setDataRowKey(property.getName());
 			columns.add(descriptor);
+
+			if(isAggregate(property)) {
+				haveAggregate = true;
+			} else {
+				groupByColumns.add(descriptor);
+			}
+		}
+
+		if(!haveAggregate) {
+			// if no expression with aggregation function found, we don't need this information
+			groupByColumns.clear();
 		}
 
 		return columns;
 	}
 
+	private boolean isAggregate(Property<?> property) {
+		final boolean[] isAggregate = new boolean[1];
+		Expression exp = property.getExpression();
+		exp.traverse(new TraversalHelper() {
+			@Override
+			public void startNode(Expression node, Expression parentNode) {
+				if(node instanceof ASTAggregateFunctionCall) {
+					isAggregate[0] = true;
+				}
+			}
+		});
+
+		return isAggregate[0];
+	}
+
 	<T> List<ColumnDescriptor> appendDbEntityColumns(List<ColumnDescriptor>
columns, SelectQuery<T> query) {
 
 		Set<ColumnTracker> attributes = new HashSet<>();
@@ -450,7 +511,7 @@ public class DefaultSelectTranslator extends QueryAssembler implements
SelectTra
 					if (relationship != null) {
 
 						String labelPrefix = pathExp.getPath();
-						DbEntity targetEntity = (DbEntity) relationship.getTargetEntity();
+						DbEntity targetEntity = relationship.getTargetEntity();
 
 						for (DbAttribute pk : targetEntity.getPrimaryKeys()) {
 
@@ -496,7 +557,7 @@ public class DefaultSelectTranslator extends QueryAssembler implements
SelectTra
 				// go via target OE to make sure that Java types are mapped
 				// correctly...
 				ObjRelationship targetRel = (ObjRelationship) prefetchExp.evaluate(oe);
-				ObjEntity targetEntity = (ObjEntity) targetRel.getTargetEntity();
+				ObjEntity targetEntity = targetRel.getTargetEntity();
 
 				String labelPrefix = dbPrefetch.getPath();
 				for (ObjAttribute oa : targetEntity.getAttributes()) {
@@ -518,7 +579,7 @@ public class DefaultSelectTranslator extends QueryAssembler implements
SelectTra
 				}
 
 				// append remaining target attributes such as keys
-				DbEntity targetDbEntity = (DbEntity) r.getTargetEntity();
+				DbEntity targetDbEntity = r.getTargetEntity();
 				for (DbAttribute attribute : targetDbEntity.getAttributes()) {
 					appendColumn(columns, null, attribute, attributes, labelPrefix + '.' + attribute.getName());
 				}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
index ad4b74a..3998c00 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java
@@ -383,6 +383,8 @@ public class QualifierTranslator extends QueryAssemblerHelper implements
Travers
 				out.append("1 = 1");
 			} else if (node.getType() == Expression.FALSE) {
 				out.append("1 = 0");
+			} else if (node.getType() == Expression.ASTERISK) {
+				out.append("*");
 			}
 		}
 
@@ -484,7 +486,9 @@ public class QualifierTranslator extends QueryAssemblerHelper implements
Travers
 			return true;
 		}
 
-		if (node.getType() == Expression.OBJ_PATH || node.getType() == Expression.DB_PATH) {
+		if (node.getType() == Expression.OBJ_PATH
+				|| node.getType() == Expression.DB_PATH
+				|| node.getType() == Expression.ASTERISK) {
 			return false;
 		}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java
b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java
index 9a4e9be..2f03bd7 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerTrimmingQualifierTranslator.java
@@ -129,6 +129,9 @@ class SQLServerTrimmingQualifierTranslator extends TrimmingQualifierTranslator
{
 			case "MOD":
 				// noop
 				break;
+			case "TRIM":
+				out.append("LTRIM(RTRIM");
+				break;
 			default:
 				super.appendFunction(functionExpression);
 		}
@@ -155,6 +158,9 @@ class SQLServerTrimmingQualifierTranslator extends TrimmingQualifierTranslator
{
 			out.delete(out.length() - " % ".length(), out.length());
 		} else {
 			super.clearLastFunctionArgDivider(functionExpression);
+			if("TRIM".equals(functionExpression.getFunctionName())) {
+				out.append(")");
+			}
 		}
 	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/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 a4534e2..a422f7e 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
@@ -25,7 +25,6 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.Serializable;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -156,6 +155,11 @@ public abstract class Expression implements Serializable, XMLSerializable
{
 	 */
 	public static final int FUNCTION_CALL = 45;
 
+	/**
+	 * @since 4.0
+	 */
+	public static final int ASTERISK = 46;
+
 	protected int type;
 
 	/**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/cayenne-server/src/main/java/org/apache/cayenne/exp/FunctionExpressionFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/FunctionExpressionFactory.java
b/cayenne-server/src/main/java/org/apache/cayenne/exp/FunctionExpressionFactory.java
index cfecdc7..0d17829 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/FunctionExpressionFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/FunctionExpressionFactory.java
@@ -20,15 +20,20 @@
 package org.apache.cayenne.exp;
 
 import org.apache.cayenne.exp.parser.ASTAbs;
+import org.apache.cayenne.exp.parser.ASTAvg;
 import org.apache.cayenne.exp.parser.ASTConcat;
+import org.apache.cayenne.exp.parser.ASTCount;
 import org.apache.cayenne.exp.parser.ASTLength;
 import org.apache.cayenne.exp.parser.ASTLocate;
 import org.apache.cayenne.exp.parser.ASTLower;
+import org.apache.cayenne.exp.parser.ASTMax;
+import org.apache.cayenne.exp.parser.ASTMin;
 import org.apache.cayenne.exp.parser.ASTMod;
 import org.apache.cayenne.exp.parser.ASTObjPath;
 import org.apache.cayenne.exp.parser.ASTScalar;
 import org.apache.cayenne.exp.parser.ASTSqrt;
 import org.apache.cayenne.exp.parser.ASTSubstring;
+import org.apache.cayenne.exp.parser.ASTSum;
 import org.apache.cayenne.exp.parser.ASTTrim;
 import org.apache.cayenne.exp.parser.ASTUpper;
 
@@ -285,4 +290,46 @@ public class FunctionExpressionFactory {
         }
         return new ASTConcat(expressions);
     }
+
+    /**
+     * @return Expression COUNT(&ast;)
+     */
+    public static Expression countExp() {
+        return new ASTCount();
+    }
+
+    /**
+     * @return Expression COUNT(exp)
+     */
+    public static Expression countExp(Expression exp) {
+        return new ASTCount(exp);
+    }
+
+    /**
+     * @return Expression MIN(exp)
+     */
+    public static Expression minExp(Expression exp) {
+        return new ASTMin(exp);
+    }
+
+    /**
+     * @return Expression MAX(exp)
+     */
+    public static Expression maxExp(Expression exp) {
+        return new ASTMax(exp);
+    }
+
+    /**
+     * @return Expression AVG(exp)
+     */
+    public static Expression avgExp(Expression exp) {
+        return new ASTAvg(exp);
+    }
+
+    /**
+     * @return SUM(exp) expression
+     */
+    public static Expression sumExp(Expression exp) {
+        return new ASTSum(exp);
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/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
new file mode 100644
index 0000000..d4dec9e
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAggregateFunctionCall.java
@@ -0,0 +1,36 @@
+/*****************************************************************
+ *   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;
+
+/**
+ * Base class for all aggregation functions expressions
+ * It's more like marker interface for now.
+ * @since 4.0
+ */
+public abstract class ASTAggregateFunctionCall extends ASTFunctionCall {
+
+    ASTAggregateFunctionCall(int id, String functionName) {
+        super(id, functionName);
+    }
+
+    ASTAggregateFunctionCall(String functionName, Object... nodes) {
+        super(functionName, nodes);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAsterisk.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAsterisk.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAsterisk.java
new file mode 100644
index 0000000..33b81cc
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAsterisk.java
@@ -0,0 +1,80 @@
+/*****************************************************************
+ *   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.io.IOException;
+import java.util.List;
+
+import org.apache.cayenne.exp.Expression;
+
+/**
+ * Asterisk operator for COUNT(*) expression.
+ * @since 4.0
+ */
+public class ASTAsterisk extends SimpleNode {
+
+    /**
+     * Constructor used by expression parser. Do not invoke directly.
+     */
+    ASTAsterisk(int id) {
+        super(id);
+    }
+
+    public ASTAsterisk() {
+        super(ExpressionParserTreeConstants.JJTMULTIPLY);
+    }
+
+    @Override
+    protected Object evaluateNode(Object o) throws Exception {
+        return o;
+    }
+
+    @Override
+    protected String getExpressionOperator(int index) {
+        throw new UnsupportedOperationException("No operator for '" + ExpressionParserTreeConstants.jjtNodeName[id]
+                + "'");
+    }
+
+    @Override
+    public Expression shallowCopy() {
+        return new ASTAsterisk(id);
+    }
+
+    @Override
+    public int getType() {
+        return Expression.ASTERISK;
+    }
+
+    /**
+     * @since 4.0
+     */
+    @Override
+    public void appendAsString(Appendable out) throws IOException {
+        out.append("*");
+    }
+
+    /**
+     * @since 4.0
+     */
+    @Override
+    public void appendAsEJBQL(List<Object> parameterAccumulator, Appendable out, String
rootId) throws IOException {
+        out.append("*");
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAvg.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAvg.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAvg.java
new file mode 100644
index 0000000..16146cf
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAvg.java
@@ -0,0 +1,47 @@
+/*****************************************************************
+ *   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.exp.Expression;
+
+/**
+ * @since 4.0
+ */
+public class ASTAvg extends ASTAggregateFunctionCall {
+
+    ASTAvg(int id) {
+        super(id, "AVG");
+    }
+
+    public ASTAvg(Expression expression) {
+        super("AVG", expression);
+    }
+
+
+    @Override
+    protected Object evaluateNode(Object o) throws Exception {
+        return o; // TODO: how to evaluate aggregation function?
+    }
+
+    @Override
+    public Expression shallowCopy() {
+        return new ASTAvg(id);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCount.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCount.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCount.java
new file mode 100644
index 0000000..72172f7
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTCount.java
@@ -0,0 +1,50 @@
+/*****************************************************************
+ *   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.exp.Expression;
+
+/**
+ * @since 4.0
+ */
+public class ASTCount extends ASTAggregateFunctionCall {
+
+    ASTCount(int id) {
+        super(id, "COUNT");
+    }
+
+    public ASTCount(Expression expression) {
+        super("COUNT", expression);
+    }
+
+    public ASTCount() {
+        super("COUNT", new ASTAsterisk());
+    }
+
+    @Override
+    protected Object evaluateNode(Object o) throws Exception {
+        return o; // TODO: how to evaluate aggregation function?
+    }
+
+    @Override
+    public Expression shallowCopy() {
+        return new ASTCount(id);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMax.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMax.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMax.java
new file mode 100644
index 0000000..b0356eb
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMax.java
@@ -0,0 +1,47 @@
+/*****************************************************************
+ *   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.exp.Expression;
+
+/**
+ * @since 4.0
+ */
+public class ASTMax extends ASTAggregateFunctionCall {
+
+    ASTMax(int id) {
+        super(id, "MAX");
+    }
+
+    public ASTMax(Expression expression) {
+        super("MAX", expression);
+    }
+
+
+    @Override
+    protected Object evaluateNode(Object o) throws Exception {
+        return o; // TODO: how to evaluate aggregation function?
+    }
+
+    @Override
+    public Expression shallowCopy() {
+        return new ASTMax(id);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMin.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMin.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMin.java
new file mode 100644
index 0000000..c55b5df
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTMin.java
@@ -0,0 +1,47 @@
+/*****************************************************************
+ *   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.exp.Expression;
+
+/**
+ * @since 4.0
+ */
+public class ASTMin extends ASTAggregateFunctionCall {
+
+    ASTMin(int id) {
+        super(id, "MIN");
+    }
+
+    public ASTMin(Expression expression) {
+        super("MIN", expression);
+    }
+
+
+    @Override
+    protected Object evaluateNode(Object o) throws Exception {
+        return o; // TODO: how to evaluate aggregation function?
+    }
+
+    @Override
+    public Expression shallowCopy() {
+        return new ASTMin(id);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSum.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSum.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSum.java
new file mode 100644
index 0000000..bcc5e12
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSum.java
@@ -0,0 +1,47 @@
+/*****************************************************************
+ *   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.exp.Expression;
+
+/**
+ * @since 4.0
+ */
+public class ASTSum extends ASTAggregateFunctionCall {
+
+    ASTSum(int id) {
+        super(id, "SUM");
+    }
+
+    public ASTSum(Expression expression) {
+        super("SUM", expression);
+    }
+
+
+    @Override
+    protected Object evaluateNode(Object o) throws Exception {
+        return o; // TODO: how to evaluate aggregation function?
+    }
+
+    @Override
+    public Expression shallowCopy() {
+        return new ASTSum(id);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SelectActionWithUnsupportedDistinctTypesIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SelectActionWithUnsupportedDistinctTypesIT.java
b/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SelectActionWithUnsupportedDistinctTypesIT.java
index b2f590bb..fca9bd0 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SelectActionWithUnsupportedDistinctTypesIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/SelectActionWithUnsupportedDistinctTypesIT.java
@@ -59,9 +59,9 @@ public class SelectActionWithUnsupportedDistinctTypesIT extends ServerCase
{
     @Before
     public void setUp() throws Exception {
         tProduct = new TableHelper(dbHelper, "PRODUCT");
-        tProduct.setColumns("ID", "LONGVARCHAR_COL").setColumnTypes(Types.INTEGER, Types.LONGNVARCHAR);
+        tProduct.setColumns("ID", "LONGVARCHAR_COL");
 
-        tCustomer = new TableHelper(dbHelper, "CUSTOMER").setColumnTypes(Types.INTEGER, Types.LONGNVARCHAR);
+        tCustomer = new TableHelper(dbHelper, "CUSTOMER");
         tCustomer.setColumns("ID", "LONGVARCHAR_COL");
 
         tComposition = new TableHelper(dbHelper, "COMPOSITION");

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/cayenne-server/src/test/java/org/apache/cayenne/exp/FunctionExpressionFactoryTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/FunctionExpressionFactoryTest.java
b/cayenne-server/src/test/java/org/apache/cayenne/exp/FunctionExpressionFactoryTest.java
index ab3443e..fd24836 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/FunctionExpressionFactoryTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/FunctionExpressionFactoryTest.java
@@ -20,20 +20,28 @@
 package org.apache.cayenne.exp;
 
 import org.apache.cayenne.exp.parser.ASTAbs;
+import org.apache.cayenne.exp.parser.ASTAsterisk;
+import org.apache.cayenne.exp.parser.ASTAvg;
 import org.apache.cayenne.exp.parser.ASTConcat;
+import org.apache.cayenne.exp.parser.ASTCount;
 import org.apache.cayenne.exp.parser.ASTLength;
 import org.apache.cayenne.exp.parser.ASTLocate;
 import org.apache.cayenne.exp.parser.ASTLower;
+import org.apache.cayenne.exp.parser.ASTMax;
+import org.apache.cayenne.exp.parser.ASTMin;
 import org.apache.cayenne.exp.parser.ASTMod;
 import org.apache.cayenne.exp.parser.ASTScalar;
 import org.apache.cayenne.exp.parser.ASTSqrt;
 import org.apache.cayenne.exp.parser.ASTSubstring;
+import org.apache.cayenne.exp.parser.ASTSum;
 import org.apache.cayenne.exp.parser.ASTTrim;
 import org.apache.cayenne.exp.parser.ASTUpper;
 import org.apache.cayenne.testdo.testmap.Artist;
 import org.junit.Test;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 
 /**
  * @since 4.0
@@ -191,5 +199,48 @@ public class FunctionExpressionFactoryTest {
         assertEquals(Artist.PAINTING_ARRAY.path(), exp2.getOperand(2));
     }
 
+    @Test
+    public void countTest() throws Exception {
+        Expression exp1 = FunctionExpressionFactory.countExp();
+        assertTrue(exp1 instanceof ASTCount);
+        assertEquals(1, exp1.getOperandCount());
+        assertEquals(new ASTAsterisk(), exp1.getOperand(0));
 
+        Expression exp2 = FunctionExpressionFactory.countExp(Artist.ARTIST_NAME.path());
+        assertTrue(exp2 instanceof ASTCount);
+        assertEquals(1, exp2.getOperandCount());
+        assertEquals(Artist.ARTIST_NAME.path(), exp2.getOperand(0));
+    }
+
+    @Test
+    public void minTest() throws Exception {
+        Expression exp1 = FunctionExpressionFactory.minExp(Artist.ARTIST_NAME.path());
+        assertTrue(exp1 instanceof ASTMin);
+        assertEquals(1, exp1.getOperandCount());
+        assertEquals(Artist.ARTIST_NAME.path(), exp1.getOperand(0));
+    }
+
+    @Test
+    public void maxTest() throws Exception {
+        Expression exp1 = FunctionExpressionFactory.maxExp(Artist.ARTIST_NAME.path());
+        assertTrue(exp1 instanceof ASTMax);
+        assertEquals(1, exp1.getOperandCount());
+        assertEquals(Artist.ARTIST_NAME.path(), exp1.getOperand(0));
+    }
+
+    @Test
+    public void avgTest() throws Exception {
+        Expression exp1 = FunctionExpressionFactory.avgExp(Artist.ARTIST_NAME.path());
+        assertTrue(exp1 instanceof ASTAvg);
+        assertEquals(1, exp1.getOperandCount());
+        assertEquals(Artist.ARTIST_NAME.path(), exp1.getOperand(0));
+    }
+
+    @Test
+    public void sumTest() throws Exception {
+        Expression exp1 = FunctionExpressionFactory.sumExp(Artist.ARTIST_NAME.path());
+        assertTrue(exp1 instanceof ASTSum);
+        assertEquals(1, exp1.getOperandCount());
+        assertEquals(Artist.ARTIST_NAME.path(), exp1.getOperand(0));
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTAggregateTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTAggregateTest.java
b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTAggregateTest.java
new file mode 100644
index 0000000..87ec3bf
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ASTAggregateTest.java
@@ -0,0 +1,61 @@
+/*****************************************************************
+ *   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.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @since 4.0
+ */
+public class ASTAggregateTest {
+
+    @Test
+    public void testAvgConstruct() throws Exception {
+        ASTAvg avg = new ASTAvg(null);
+        assertEquals("AVG", avg.getFunctionName());
+    }
+
+    @Test
+    public void testCountConstruct() throws Exception {
+        ASTCount count = new ASTCount(null);
+        assertEquals("COUNT", count.getFunctionName());
+    }
+
+    @Test
+    public void testMinConstruct() throws Exception {
+        ASTMin min = new ASTMin(null);
+        assertEquals("MIN", min.getFunctionName());
+    }
+
+    @Test
+    public void testMaxConstruct() throws Exception {
+        ASTMax max = new ASTMax(null);
+        assertEquals("MAX", max.getFunctionName());
+    }
+
+    @Test
+    public void testSumConstruct() throws Exception {
+        ASTSum sum = new ASTSum(null);
+        assertEquals("SUM", sum.getFunctionName());
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_AggregateIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_AggregateIT.java
b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_AggregateIT.java
new file mode 100644
index 0000000..6034d6b
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_AggregateIT.java
@@ -0,0 +1,181 @@
+/*****************************************************************
+ *   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.query;
+
+import java.sql.Types;
+import java.text.DateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.exp.Property;
+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.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.apache.cayenne.exp.FunctionExpressionFactory.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @since 4.0
+ */
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class ObjectSelect_AggregateIT 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.setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.DATE);
+
+        java.sql.Date[] dates = new java.sql.Date[5];
+        // Format: d/m/YY
+        DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US);
+        for(int i=1; i<=5; i++) {
+            dates[i-1] = new java.sql.Date(dateFormat.parse("1/" + i + "/17").getTime());
+        }
+        for (int i = 1; i <= 20; i++) {
+            tArtist.insert(i, "artist" + i, dates[i % 5]);
+        }
+
+        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");
+        for (int i = 1; i <= 20; i++) {
+            tPaintings.insert(i, "painting" + i, i % 5 + 1, 1);
+        }
+    }
+
+    @After
+    public void clearArtistsDataSet() throws Exception {
+        TableHelper tPaintings = new TableHelper(dbHelper, "PAINTING");
+        tPaintings.deleteAll();
+
+        TableHelper tArtist = new TableHelper(dbHelper, "ARTIST");
+        tArtist.deleteAll();
+
+        TableHelper tGallery = new TableHelper(dbHelper, "GALLERY");
+        tGallery.deleteAll();
+    }
+
+    @Test
+    public void testCount() throws Exception {
+        Property<Long> countProp = Property.create(countExp(), Long.class);
+
+        long count = ObjectSelect.query(Artist.class)
+                .column(countProp)
+                .selectOne(context);
+        assertEquals(20L, count);
+    }
+
+    @Test
+    @Ignore("Not all databases support AVG(DATE) aggregation")
+    public void testAvg() throws Exception {
+        Property<Date> avgProp = Property.create(avgExp(Artist.DATE_OF_BIRTH.path()),
Date.class);
+
+        Date avg = ObjectSelect.query(Artist.class)
+                .column(avgProp)
+                .selectOne(context);
+        DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US);
+        Date date = dateFormat.parse("1/3/17");
+        assertEquals(date, avg);
+    }
+
+    @Test
+    public void testMin() throws Exception {
+        Property<Date> minProp = Property.create(minExp(Artist.DATE_OF_BIRTH.path()),
Date.class);
+
+        Date avg = ObjectSelect.query(Artist.class)
+                .column(minProp)
+                .selectOne(context);
+        DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US);
+        Date date = dateFormat.parse("1/1/17");
+        assertEquals(date, avg);
+    }
+
+    @Test
+    public void testMax() throws Exception {
+        Property<Date> maxProp = Property.create(maxExp(Artist.DATE_OF_BIRTH.path()),
Date.class);
+
+        Date avg = ObjectSelect.query(Artist.class)
+                .column(maxProp)
+                .selectOne(context);
+        DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US);
+        Date date = dateFormat.parse("1/5/17");
+        assertEquals(date, avg);
+    }
+
+    @Test
+    public void testCountGroupBy() throws Exception {
+        Property<Long> countProp = Property.create(countExp(Artist.ARTIST_NAME.path()),
Long.class);
+
+        List<Object[]> count = ObjectSelect.query(Artist.class)
+                .columns(countProp, Artist.DATE_OF_BIRTH)
+                .orderBy(Artist.DATE_OF_BIRTH.asc())
+                .select(context);
+        DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US);
+        Date date = dateFormat.parse("1/2/17");
+        assertEquals(5L, count.size());
+        assertEquals(4L, count.get(1)[0]);
+        assertEquals(date, count.get(1)[1]);
+    }
+
+    @Test
+    public void testSelectRelationshipCount() throws Exception {
+        Property<Long> paintingCount = Property.create(countExp(Artist.PAINTING_ARRAY.path()),
Long.class);
+
+        long count = ObjectSelect.query(Artist.class)
+                .column(paintingCount)
+                .where(Artist.ARTIST_NAME.eq("artist1"))
+                .selectOne(context);
+        assertEquals(4L, count);
+    }
+
+    @Test
+    public void testSelectRelationshipCountWithAnotherField() throws Exception {
+        Property<Long> paintingCount = Property.create(countExp(Artist.PAINTING_ARRAY.path()),
Long.class);
+
+        Object[] result = ObjectSelect.query(Artist.class)
+                .columns(Artist.ARTIST_NAME, paintingCount)
+                .where(Artist.ARTIST_NAME.eq("artist1"))
+                .selectOne(context);
+        assertEquals("artist1", result[0]);
+        assertEquals(4L, result[1]);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_PrimitiveColumnsIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_PrimitiveColumnsIT.java
b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_PrimitiveColumnsIT.java
index 9dccf5b..9906375 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_PrimitiveColumnsIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_PrimitiveColumnsIT.java
@@ -35,6 +35,8 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import static org.apache.cayenne.exp.FunctionExpressionFactory.avgExp;
+import static org.apache.cayenne.exp.FunctionExpressionFactory.sumExp;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 
@@ -156,4 +158,26 @@ public class ObjectSelect_PrimitiveColumnsIT extends ServerCase {
         Object[] result = {11, true};
         assertArrayEquals(result, columns.get(0));
     }
+
+    @Test
+    public void testSum() throws Exception {
+        Property<Long> sumProp = Property.create(sumExp(PrimitivesTestEntity.INT_COLUMN.path()),
Long.class);
+
+        long sum = ObjectSelect.query(PrimitivesTestEntity.class)
+                .column(sumProp)
+                .selectOne(context);
+        assertEquals(2100, sum);
+    }
+
+    @Test
+    public void testAvg() throws Exception {
+        Property<Double> avgProp = Property.create(avgExp(PrimitivesTestEntity.INT_COLUMN.path()),
Double.class);
+
+        double avg = ObjectSelect.query(PrimitivesTestEntity.class)
+                .column(avgProp)
+                .selectOne(context);
+        assertEquals(105.0, avg, 0.00001);
+    }
+
+
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/50fe31c9/cayenne-server/src/test/java/org/apache/cayenne/testdo/testmap/auto/_Artist.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/testmap/auto/_Artist.java
b/cayenne-server/src/test/java/org/apache/cayenne/testdo/testmap/auto/_Artist.java
index 7d42fab..854e6fd 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/testmap/auto/_Artist.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/testmap/auto/_Artist.java
@@ -21,11 +21,11 @@ public abstract class _Artist extends CayenneDataObject {
 
     public static final String ARTIST_ID_PK_COLUMN = "ARTIST_ID";
 
-    public static final Property<String> ARTIST_NAME = new Property<>("artistName");
-    public static final Property<Date> DATE_OF_BIRTH = new Property<>("dateOfBirth");
-    public static final Property<List<ArtistExhibit>> ARTIST_EXHIBIT_ARRAY =
new Property<>("artistExhibitArray");
-    public static final Property<List<ArtGroup>> GROUP_ARRAY = new Property<>("groupArray");
-    public static final Property<List<Painting>> PAINTING_ARRAY = new Property<>("paintingArray");
+    public static final Property<String> ARTIST_NAME = Property.create("artistName",
String.class);
+    public static final Property<Date> DATE_OF_BIRTH = Property.create("dateOfBirth",
Date.class);
+    public static final Property<List<ArtistExhibit>> ARTIST_EXHIBIT_ARRAY =
Property.create("artistExhibitArray", List.class);
+    public static final Property<List<ArtGroup>> GROUP_ARRAY = Property.create("groupArray",
List.class);
+    public static final Property<List<Painting>> PAINTING_ARRAY = Property.create("paintingArray",
List.class);
 
     public void setArtistName(String artistName) {
         writeProperty("artistName", artistName);


Mime
View raw message