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/01/25 14:19:31 UTC

cayenne git commit: CAY-2209 shortcuts for aggregate functions

Repository: cayenne
Updated Branches:
  refs/heads/master 6f76f057a -> 3f8a8f196


CAY-2209 shortcuts for aggregate functions


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

Branch: refs/heads/master
Commit: 3f8a8f196ed2aca1a07470f33ae51d66251bda38
Parents: 6f76f05
Author: Nikita Timofeev <st...@gmail.com>
Authored: Wed Jan 25 17:17:24 2017 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Wed Jan 25 17:17:24 2017 +0300

----------------------------------------------------------------------
 .../java/org/apache/cayenne/exp/Property.java   |  22 +++
 .../org/apache/cayenne/query/ColumnSelect.java  | 125 +++++++++--------
 .../org/apache/cayenne/query/FluentSelect.java  |   2 +-
 .../org/apache/cayenne/query/ObjectSelect.java  | 123 +++++++++++++++--
 .../cayenne/access/DataContextBinaryPKIT.java   |   4 +-
 .../apache/cayenne/query/ColumnSelectIT.java    | 137 +++++++++++++------
 .../apache/cayenne/query/ColumnSelectTest.java  |  79 +++++++++--
 7 files changed, 364 insertions(+), 128 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f8a8f19/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
index 3ec518a..f1bb1ce 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/Property.java
@@ -50,6 +50,8 @@ import java.util.List;
  */
 public class Property<E> {
 
+    public static final Property<Long> COUNT = Property.create(FunctionExpressionFactory.countExp(), Long.class);
+
     /**
      * Name of the property in the object
      */
@@ -633,6 +635,26 @@ public class Property<E> {
         }
     }
 
+    public Property<Long> count() {
+        return create(FunctionExpressionFactory.countExp(getExpression()), Long.class);
+    }
+
+    public Property<E> max() {
+        return create(FunctionExpressionFactory.maxExp(getExpression()), getType());
+    }
+
+    public Property<E> min() {
+        return create(FunctionExpressionFactory.minExp(getExpression()), getType());
+    }
+
+    public Property<E> avg() {
+        return create(FunctionExpressionFactory.avgExp(getExpression()), getType());
+    }
+
+    public Property<E> sum() {
+        return create(FunctionExpressionFactory.sumExp(getExpression()), getType());
+    }
+
     public Property<E> alias(String alias) {
         return new Property<>(alias, this.getExpression(), this.getType());
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f8a8f19/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java
index 4058c28..c3cfee0 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelect.java
@@ -29,60 +29,40 @@ import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.map.EntityResolver;
 
 /**
- * <p>A selecting query providing individual properties based on the root object.</p>
+ * <p>A helper builder for queries selecting individual properties based on the root object.</p>
  * <p>
- *     It can be properties of the object itself, properties of related entities
+ *     It can be used to select properties of the object itself, properties of related entities
  *     or some function calls (including aggregate functions).
- * </p><p>
+ * </p>
+ * <p>
  * Usage examples: <pre>
+ * {@code
  *      // select list of names:
- *      List&lt;String&gt; names = ColumnSelect.query(Artist.class, Artist.ARTIST_NAME).select(context);
+ *      List<String> names = ObjectSelect.columnQuery(Artist.class, Artist.ARTIST_NAME).select(context);
  *
  *      // select count:
- *      Property<Long> countProperty = Property.create(FunctionExpressionFactory.countExp(), Long.class);
- *      long count = ColumnSelect.query(Artist.class, countProperty).selectOne();
+ *      long count = ObjectSelect.columnQuery(Artist.class, Property.COUNT).selectOne();
  *
  *      // select only required properties of an entity:
- *      List&lt;Object[]&gt; data = ColumnSelect.query(Artist.class, Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH)
+ *      List<Object[]> data = ObjectSelect.columnQuery(Artist.class, Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH)
  *                                  .where(Artist.ARTIST_NAME.like("Picasso%))
  *                                  .select(context);
- * </pre></p>
+ * }
+ * </pre>
+ * </p>
+ * <p><b>Note: this class can't be instantiated directly. Use {@link ObjectSelect}.</b></p>
+ * @see ObjectSelect#columnQuery(Class, Property)
+ *
  * @since 4.0
  */
 public class ColumnSelect<T> extends FluentSelect<T, ColumnSelect<T>> {
 
     private Collection<Property<?>> columns;
     private boolean havingExpressionIsActive = false;
-    private boolean singleColumn = true;
+    // package private for tests
+    boolean singleColumn = true;
     private Expression having;
 
-    /**
-     *
-     * @param entityType base persistent class that will be used as a root for this query
-     */
-    public static <T> ColumnSelect<T> query(Class<T> entityType) {
-        return new ColumnSelect<T>().entityType(entityType);
-    }
-
-    /**
-     *
-     * @param entityType base persistent class that will be used as a root for this query
-     * @param column single column to select
-     */
-    public static <E> ColumnSelect<E> query(Class<?> entityType, Property<E> column) {
-        return new ColumnSelect<>().entityType(entityType).column(column);
-    }
-
-    /**
-     *
-     * @param entityType base persistent class that will be used as a root for this query
-     * @param firstColumn column to select
-     * @param otherColumns columns to select
-     */
-    public static ColumnSelect<Object[]> query(Class<?> entityType, Property<?> firstColumn, Property<?>... otherColumns) {
-        return new ColumnSelect<Object[]>().entityType(entityType).columns(firstColumn, otherColumns);
-    }
-
     protected ColumnSelect() {
         super();
     }
@@ -116,14 +96,16 @@ public class ColumnSelect<T> extends FluentSelect<T, ColumnSelect<T>> {
     }
 
     /**
-     * <p>Select only specific properties.</p>
+     * <p>Add properties to select.</p>
      * <p>Can be any properties that can be resolved against root entity type
      * (root entity properties, function call expressions, properties of relationships, etc).</p>
      * <p>
      * <pre>
-     * List&lt;Object[]&gt; columns = ColumnSelect.query(Artist.class)
-     *                                    .columns(Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH)
+     * {@code
+     * List<Object[]> columns = ObjectSelect.columnQuery(Artist.class, Artist.ARTIST_NAME)
+     *                                    .columns(Artist.ARTIST_SALARY, Artist.DATE_OF_BIRTH)
      *                                    .select(context);
+     * }
      * </pre>
      *
      * @param firstProperty first property
@@ -143,7 +125,7 @@ public class ColumnSelect<T> extends FluentSelect<T, ColumnSelect<T>> {
     }
 
     /**
-     * <p>Select only specific properties.</p>
+     * <p>Add properties to select.</p>
      * <p>Can be any properties that can be resolved against root entity type
      * (root entity properties, function call expressions, properties of relationships, etc).</p>
      * <p>
@@ -168,21 +150,6 @@ public class ColumnSelect<T> extends FluentSelect<T, ColumnSelect<T>> {
         return (ColumnSelect<Object[]>)this;
     }
 
-    /**
-     * <p>Select one specific property.</p>
-     * <p>Can be any property that can be resolved against root entity type
-     * (root entity property, function call expression, property of relationships, etc)</p>
-     * <p>If you need several columns use {@link ColumnSelect#columns(Property, Property[])} method as subsequent
-     * call to this method will override previous columns set via this or
-     * {@link ColumnSelect#columns(Property, Property[])} method.</p>
-     * <p>
-     * <pre>
-     * List&lt;String&gt; names = ColumnSelect.query(Artist.class, Artist.ARTIST_NAME).select(context);
-     * </pre>
-     *
-     * @param property single property to select
-     * @see ColumnSelect#columns(Property, Property[])
-     */
     @SuppressWarnings("unchecked")
     protected  <E> ColumnSelect<E> column(Property<E> property) {
         if (this.columns == null) {
@@ -195,6 +162,54 @@ public class ColumnSelect<T> extends FluentSelect<T, ColumnSelect<T>> {
     }
 
     /**
+     * <p>Shortcut for {@link #columns(Property, Property[])} columns}(Property.COUNT)</p>
+     */
+    public ColumnSelect<Object[]> count() {
+        return columns(Property.COUNT);
+    }
+
+    /**
+     * <p>Select COUNT(property)</p>
+     * <p>Can return different result than COUNT(*) as it will count only non null values</p>
+     * @see ColumnSelect#count()
+     */
+    public ColumnSelect<Object[]> count(Property<?> property) {
+        return columns(property.count());
+    }
+
+    /**
+     * <p>Select minimum value of property</p>
+     * @see ColumnSelect#columns(Property, Property[])
+     */
+    public ColumnSelect<Object[]> min(Property<?> property) {
+        return columns(property.min());
+    }
+
+    /**
+     * <p>Select maximum value of property</p>
+     * @see ColumnSelect#columns(Property, Property[])
+     */
+    public ColumnSelect<Object[]> max(Property<?> property) {
+        return columns(property.max());
+    }
+
+    /**
+     * <p>Select average value of property</p>
+     * @see ColumnSelect#columns(Property, Property[])
+     */
+    public ColumnSelect<Object[]> avg(Property<?> property) {
+        return columns(property.avg());
+    }
+
+    /**
+     * <p>Select sum of values</p>
+     * @see ColumnSelect#columns(Property, Property[])
+     */
+    public <E extends Number> ColumnSelect<Object[]> sum(Property<E> property) {
+        return columns(property.sum());
+    }
+
+    /**
      * Appends a having qualifier expression of this query. An equivalent to
      * {@link #and(Expression...)} that can be used a syntactic sugar.
      *

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f8a8f19/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelect.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelect.java
index de772bc..1ceeb17 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelect.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelect.java
@@ -38,7 +38,7 @@ import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.ObjEntity;
 
 /**
- * Base class for ObjectSelect and ColumnSelect
+ * Base class for {@link ObjectSelect} and {@link ColumnSelect}
  *
  * @since 4.0
  */

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f8a8f19/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java
index f4cbd0c..262f18c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java
@@ -19,6 +19,7 @@
 package org.apache.cayenne.query;
 
 import org.apache.cayenne.DataRow;
+import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.map.DbEntity;
@@ -34,9 +35,9 @@ import java.util.List;
  * <pre>
  * {@code
  * Artist a = ObjectSelect
- * .query(Artist.class)
- * .where(Artist.NAME.eq("Picasso"))
- * .selectOne(context);
+ *      .query(Artist.class)
+ *      .where(Artist.NAME.eq("Picasso"))
+ *      .selectOne(context);
  * }
  * </pre>
  *
@@ -117,6 +118,28 @@ public class ObjectSelect<T> extends FluentSelect<T, ObjectSelect<T>> {
         return new ObjectSelect<DataRow>().fetchDataRows().dbEntityName(dbEntityName).where(expression);
     }
 
+    /**
+     * Creates a ColumnSelect that will fetch single property that can be resolved
+     * against a given {@link ObjEntity} class.
+     *
+     * @param entityType base persistent class that will be used as a root for this query
+     * @param column single column to select
+     */
+    protected static <E> ColumnSelect<E> columnQuery(Class<?> entityType, Property<E> column) {
+        return new ColumnSelect<>().entityType(entityType).column(column);
+    }
+
+    /**
+     * Creates a ColumnSelect that will fetch multiple columns of a given {@link ObjEntity}
+     *
+     * @param entityType base persistent class that will be used as a root for this query
+     * @param firstColumn column to select
+     * @param otherColumns columns to select
+     */
+    protected static ColumnSelect<Object[]> columnQuery(Class<?> entityType, Property<?> firstColumn, Property<?>... otherColumns) {
+        return new ColumnSelect<Object[]>().entityType(entityType).columns(firstColumn, otherColumns);
+    }
+
     protected ObjectSelect() {
     }
 
@@ -146,18 +169,19 @@ public class ObjectSelect<T> extends FluentSelect<T, ObjectSelect<T>> {
     /**
      * <p>Select only specific properties.</p>
      * <p>Can be any properties that can be resolved against root entity type
-     * (root entity properties, function call expressions, properties of relationships, etc).</p>
+     * (root entity's properties, function call expressions, properties of relationships, etc).</p>
      * <p>
      * <pre>
-     * List&lt;Object[]&gt; columns = ColumnSelect.query(Artist.class)
+     * {@code
+     * List<Object[]> columns = ObjectSelect.query(Artist.class)
      *                                    .columns(Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH)
      *                                    .select(context);
+     * }
      * </pre>
      *
      * @param properties array of properties to select
-     * @see ColumnSelect#column(Property)
+     * @see ObjectSelect#column(Property)
      */
-    @SuppressWarnings("unchecked")
     public ColumnSelect<Object[]> columns(Property<?> firstProperty, Property<?>... properties) {
         return new ColumnSelect<>(this).columns(firstProperty, properties);
     }
@@ -165,23 +189,92 @@ public class ObjectSelect<T> extends FluentSelect<T, ObjectSelect<T>> {
     /**
      * <p>Select one specific property.</p>
      * <p>Can be any property that can be resolved against root entity type
-     * (root entity property, function call expression, property of relationships, etc)</p>
-     * <p>If you need several columns use {@link ColumnSelect#columns(Property, Property[])} method as subsequent
-     * call to this method will override previous columns set via this or
-     * {@link ColumnSelect#columns(Property, Property[])} method.</p>
+     * (root entity's property, function call expression, property of relationships, etc)</p>
+     * <p>If you need several columns use {@link ObjectSelect#columns(Property, Property[])} method.</p>
      * <p>
      * <pre>
-     * List&lt;String&gt; names = ObjectSelect.query(Artist.class).column(Artist.ARTIST_NAME).select(context);
+     * {@code
+     * List<String> names = ObjectSelect.query(Artist.class)
+     *                                  .column(Artist.ARTIST_NAME)
+     *                                  .select(context);
+     * }
      * </pre>
-     *
+     * </p>
      * @param property single property to select
-     * @see ColumnSelect#columns(Property, Property[])
+     * @see ObjectSelect#columns(Property, Property[])
      */
-    @SuppressWarnings("unchecked")
     public <E> ColumnSelect<E> column(Property<E> property) {
         return new ColumnSelect<>(this).column(property);
     }
 
+    /**
+     * Select COUNT(*)
+     * @see ObjectSelect#column(Property)
+     */
+    public ColumnSelect<Long> count() {
+        return column(Property.COUNT);
+    }
+
+    /**
+     * <p>Select COUNT(property)</p>
+     * <p>Can return different result than COUNT(*) as it will count only non null values</p>
+     * @see ObjectSelect#count()
+     * @see ObjectSelect#column(Property)
+     */
+    public ColumnSelect<Long> count(Property<?> property) {
+        return column(property.count());
+    }
+
+    /**
+     * <p>Select minimum value of property</p>
+     * @see ObjectSelect#column(Property)
+     */
+    public <E> ColumnSelect<E> min(Property<E> property) {
+        return column(property.min());
+    }
+
+    /**
+     * <p>Select maximum value of property</p>
+     * @see ObjectSelect#column(Property)
+     */
+    public <E> ColumnSelect<E> max(Property<E> property) {
+        return column(property.max());
+    }
+
+    /**
+     * <p>Select average value of property</p>
+     * @see ObjectSelect#column(Property)
+     */
+    public <E> ColumnSelect<E> avg(Property<E> property) {
+        return column(property.avg());
+    }
+
+    /**
+     * <p>Select sum of values</p>
+     * @see ObjectSelect#column(Property)
+     */
+    public <E extends Number> ColumnSelect<E> sum(Property<E> property) {
+        return column(property.sum());
+    }
+
+    /**
+     * <p>Quick way to select count of records</p>
+     * <p>Usage:
+     * <pre>
+     * {@code
+     *     long count = ObjectSelect.query(Artist.class)
+     *                      .where(Artist.ARTIST_NAME.like("a%"))
+     *                      .selectCount(context);
+     * }
+     * </pre>
+     * </p>
+     * @param context to perform query
+     * @return count of rows
+     */
+    public long selectCount(ObjectContext context) {
+        return count().selectOne(context);
+    }
+
     public boolean isFetchingDataRows() {
         return fetchingDataRows;
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f8a8f19/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextBinaryPKIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextBinaryPKIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextBinaryPKIT.java
index 42e24e8..32b3b85 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextBinaryPKIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextBinaryPKIT.java
@@ -76,9 +76,7 @@ public class DataContextBinaryPKIT extends ServerCase {
             context.commitChanges();
             context.invalidateObjects(master, detail);
 
-            BinaryPKTest2 fetchedDetail = (BinaryPKTest2) context1.performQuery(
-                    new SelectQuery(BinaryPKTest2.class)).get(0);
-
+            BinaryPKTest2 fetchedDetail = new SelectQuery<>(BinaryPKTest2.class).select(context1).get(0);
             assertNotNull(fetchedDetail.readPropertyDirectly("toBinaryPKMaster"));
 
             BinaryPKTest1 fetchedMaster = fetchedDetail.getToBinaryPKMaster();

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f8a8f19/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
index 0e2f404..613b659 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectIT.java
@@ -19,6 +19,8 @@
 
 package org.apache.cayenne.query;
 
+import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.sql.Types;
 import java.text.DateFormat;
 import java.util.Locale;
@@ -30,6 +32,7 @@ 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.testdo.testmap.Painting;
 import org.apache.cayenne.unit.PostgresUnitDbAdapter;
 import org.apache.cayenne.unit.UnitDbAdapter;
 import org.apache.cayenne.unit.di.server.CayenneProjects;
@@ -39,7 +42,6 @@ import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
 
-import static org.apache.cayenne.exp.FunctionExpressionFactory.countExp;
 import static org.apache.cayenne.exp.FunctionExpressionFactory.substringExp;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
@@ -60,12 +62,13 @@ public class ColumnSelectIT extends ServerCase {
     private UnitDbAdapter unitDbAdapter;
 
     // Format: d/m/YY
-    private DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US);
+    private static final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US);
 
+    private TableHelper tArtist;
 
     @Before
     public void createArtistsDataSet() throws Exception {
-        TableHelper tArtist = new TableHelper(dbHelper, "ARTIST");
+        tArtist = new TableHelper(dbHelper, "ARTIST");
         tArtist.setColumns("ARTIST_ID", "ARTIST_NAME", "DATE_OF_BIRTH");
         tArtist.setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.DATE);
 
@@ -82,19 +85,17 @@ public class ColumnSelectIT extends ServerCase {
         tGallery.insert(1, "tate modern");
 
         TableHelper tPaintings = new TableHelper(dbHelper, "PAINTING");
-        tPaintings.setColumns("PAINTING_ID", "PAINTING_TITLE", "ARTIST_ID", "GALLERY_ID");
+        tPaintings.setColumns("PAINTING_ID", "PAINTING_TITLE", "ARTIST_ID", "GALLERY_ID", "ESTIMATED_PRICE");
         for (int i = 1; i <= 20; i++) {
-            tPaintings.insert(i, "painting" + i, i % 5 + 1, 1);
+            tPaintings.insert(i, "painting" + i, i % 5 + 1, 1, 22 - i);
         }
-        tPaintings.insert(21, "painting21", 2, 1);
+        tPaintings.insert(21, "painting21", 2, 1, 30);
     }
 
     @Test
     public void testSelectGroupBy() throws Exception {
-        Property<Long> count = Property.create(countExp(), Long.class);
-
-        Object[] result = ColumnSelect.query(Artist.class)
-                .columns(Artist.DATE_OF_BIRTH, count)
+        Object[] result = ObjectSelect.query(Artist.class)
+                .columns(Artist.DATE_OF_BIRTH, Property.COUNT)
                 .orderBy(Artist.DATE_OF_BIRTH.asc())
                 .selectFirst(context);
 
@@ -104,10 +105,8 @@ public class ColumnSelectIT extends ServerCase {
 
     @Test
     public void testSelectSimpleHaving() throws Exception {
-        Property<Long> count = Property.create(countExp(), Long.class);
-
-        Object[] result = ColumnSelect.query(Artist.class)
-                .columns(Artist.DATE_OF_BIRTH, count)
+        Object[] result = ObjectSelect.query(Artist.class)
+                .columns(Artist.DATE_OF_BIRTH, Property.COUNT)
                 .orderBy(Artist.DATE_OF_BIRTH.asc())
                 .having(Artist.DATE_OF_BIRTH.eq(dateFormat.parse("1/2/17")))
                 .selectOne(context);
@@ -119,9 +118,8 @@ public class ColumnSelectIT extends ServerCase {
     @Test(expected = Exception.class)
     public void testHavingOnNonGroupByColumn() throws Exception {
         Property<String> nameSubstr = Property.create(substringExp(Artist.ARTIST_NAME.path(), 1, 6), String.class);
-        Property<Long> count = Property.create(countExp(), Long.class);
 
-        Object[] q = ColumnSelect.query(Artist.class, nameSubstr, count)
+        Object[] q = ObjectSelect.columnQuery(Artist.class, nameSubstr, Property.COUNT)
                 .having(Artist.ARTIST_NAME.like("artist%"))
                 .selectOne(context);
         assertEquals("artist", q[0]);
@@ -130,10 +128,8 @@ public class ColumnSelectIT extends ServerCase {
 
     @Test
     public void testSelectRelationshipCount() throws Exception {
-        Property<Long> paintingCount = Property.create(countExp(Artist.PAINTING_ARRAY.path()), Long.class);
-
-        Object[] result = ColumnSelect.query(Artist.class)
-                .columns(Artist.DATE_OF_BIRTH, paintingCount)
+        Object[] result = ObjectSelect.query(Artist.class)
+                .columns(Artist.DATE_OF_BIRTH, Artist.PAINTING_ARRAY.count())
                 .orderBy(Artist.DATE_OF_BIRTH.asc())
                 .selectFirst(context);
         assertEquals(dateFormat.parse("1/1/17"), result[0]);
@@ -144,12 +140,10 @@ public class ColumnSelectIT extends ServerCase {
     public void testSelectHavingWithExpressionAlias() throws Exception {
 
         Property<String> nameSubstr = Property.create("name_substr", substringExp(Artist.ARTIST_NAME.path(), 1, 6), String.class);
-        Property<Long> count = Property.create(countExp(), Long.class);
-
         Object[] q = null;
         try {
-            q = ColumnSelect.query(Artist.class, nameSubstr, count)
-                    .having(count.gt(10L))
+            q = ObjectSelect.columnQuery(Artist.class, nameSubstr, Property.COUNT)
+                    .having(Property.COUNT.gt(10L))
                     .selectOne(context);
         } catch (CayenneRuntimeException ex) {
             if(unitDbAdapter.supportsExpressionInHaving()) {
@@ -167,12 +161,10 @@ public class ColumnSelectIT extends ServerCase {
     public void testSelectHavingWithExpressionNoAlias() throws Exception {
 
         Property<String> nameSubstr = Property.create(substringExp(Artist.ARTIST_NAME.path(), 1, 6), String.class);
-        Property<Long> count = Property.create(countExp(), Long.class);
-
         Object[] q = null;
         try {
-            q = ColumnSelect.query(Artist.class, nameSubstr, count)
-                    .having(count.gt(10L))
+            q = ObjectSelect.columnQuery(Artist.class, nameSubstr, Property.COUNT)
+                    .having(Property.COUNT.gt(10L))
                     .selectOne(context);
         } catch (CayenneRuntimeException ex) {
             if(unitDbAdapter.supportsExpressionInHaving()) {
@@ -189,13 +181,12 @@ public class ColumnSelectIT extends ServerCase {
     public void testSelectWhereAndHaving() throws Exception {
         Property<String> nameFirstLetter = Property.create(substringExp(Artist.ARTIST_NAME.path(), 1, 1), String.class);
         Property<String> nameSubstr = Property.create("name_substr", substringExp(Artist.ARTIST_NAME.path(), 1, 6), String.class);
-        Property<Long> count = Property.create(countExp(), Long.class);
 
         Object[] q = null;
         try {
-            q = ColumnSelect.query(Artist.class, nameSubstr, count)
+            q = ObjectSelect.columnQuery(Artist.class, nameSubstr, Property.COUNT)
                     .where(nameFirstLetter.eq("a"))
-                    .having(count.gt(10L))
+                    .having(Property.COUNT.gt(10L))
                     .selectOne(context);
         } catch (CayenneRuntimeException ex) {
             if(unitDbAdapter.supportsExpressionInHaving()) {
@@ -210,11 +201,11 @@ public class ColumnSelectIT extends ServerCase {
 
     @Test
     public void testSelectRelationshipCountHaving() throws Exception {
-        Property<Long> paintingCount = Property.create(countExp(Artist.PAINTING_ARRAY.path()), Long.class);
+        Property<Long> paintingCount = Artist.PAINTING_ARRAY.count();
 
         Object[] result = null;
         try {
-            result = ColumnSelect.query(Artist.class)
+            result = ObjectSelect.query(Artist.class)
                 .columns(Artist.ARTIST_NAME, paintingCount)
                 .having(paintingCount.gt(4L))
                 .selectOne(context);
@@ -236,12 +227,12 @@ public class ColumnSelectIT extends ServerCase {
             return;
         }
 
-        Property<Long> paintingCount = Property.create(countExp(Artist.PAINTING_ARRAY.path()), Long.class);
+        Property<Long> paintingCount = Artist.PAINTING_ARRAY.count();
         context.getEntityResolver().getDataMap("testmap").setQuotingSQLIdentifiers(true);
 
         Object[] result = null;
         try {
-            result = ColumnSelect.query(Artist.class)
+            result = ObjectSelect.query(Artist.class)
                     .columns(Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH, paintingCount)
                     .having(paintingCount.gt(4L))
                     .selectOne(context);
@@ -265,12 +256,10 @@ public class ColumnSelectIT extends ServerCase {
             return;
         }
 
-        Property<Long> count = Property.create(countExp(), Long.class);
         context.getEntityResolver().getDataMap("testmap").setQuotingSQLIdentifiers(true);
-
         try {
-            Object[] result = ColumnSelect.query(Artist.class)
-                    .columns(Artist.DATE_OF_BIRTH, count)
+            Object[] result = ObjectSelect.query(Artist.class)
+                    .columns(Artist.DATE_OF_BIRTH, Property.COUNT)
                     .orderBy(Artist.DATE_OF_BIRTH.asc())
                     .selectFirst(context);
 
@@ -280,4 +269,74 @@ public class ColumnSelectIT extends ServerCase {
             context.getEntityResolver().getDataMap("testmap").setQuotingSQLIdentifiers(false);
         }
     }
+
+    @Test
+    public void testAgregateOnRelation() throws Exception {
+        BigDecimal min = new BigDecimal(3);
+        BigDecimal max = new BigDecimal(30);
+        BigDecimal avg = new BigDecimal(BigInteger.valueOf(1290L), 2);
+        BigDecimal sum = new BigDecimal(258);
+
+        Property<BigDecimal> estimatedPrice = Artist.PAINTING_ARRAY.dot(Painting.ESTIMATED_PRICE);
+        Object[] minMaxAvgPrice = ObjectSelect.query(Artist.class)
+                .where(estimatedPrice.gte(min))
+                .min(estimatedPrice).max(estimatedPrice)
+                .avg(estimatedPrice)
+                .sum(estimatedPrice)
+                .count()
+                .selectOne(context);
+
+        assertEquals(0, min.compareTo((BigDecimal)minMaxAvgPrice[0]));
+        assertEquals(0, max.compareTo((BigDecimal)minMaxAvgPrice[1]));
+        assertEquals(0, avg.compareTo((BigDecimal)minMaxAvgPrice[2]));
+        assertEquals(0, sum.compareTo((BigDecimal)minMaxAvgPrice[3]));
+        assertEquals(20L, minMaxAvgPrice[4]);
+    }
+
+    @Test
+    public void testQueryCount() throws Exception {
+        long count = ObjectSelect
+                .columnQuery(Artist.class, Property.COUNT)
+                .selectOne(context);
+
+        assertEquals(20, count);
+
+        long count2 = ObjectSelect
+                .query(Artist.class)
+                .count()
+                .selectOne(context);
+
+        assertEquals(count, count2);
+
+        long count3 = ObjectSelect
+                .query(Artist.class)
+                .selectCount(context);
+
+        assertEquals(count, count3);
+    }
+
+    @Test
+    public void testQueryCountWithProperty() throws Exception {
+        tArtist.insert(21, "artist_21", null);
+        tArtist.insert(22, "artist_21", null);
+
+        long count = ObjectSelect
+                .columnQuery(Artist.class, Property.COUNT)
+                .selectOne(context);
+        assertEquals(22, count);
+
+        // COUNT(attribute) should return count of non null values of attribute
+        long count2 = ObjectSelect
+                .columnQuery(Artist.class, Artist.DATE_OF_BIRTH.count())
+                .selectOne(context);
+        assertEquals(20, count2);
+
+        long count3 = ObjectSelect
+                .query(Artist.class)
+                .count(Artist.DATE_OF_BIRTH)
+                .selectOne(context);
+        assertEquals(count2, count3);
+
+
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f8a8f19/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectTest.java b/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectTest.java
index 9d4d057..188455f 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ColumnSelectTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.query;
 
+import java.math.BigDecimal;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -27,6 +28,7 @@ import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.testdo.testmap.Painting;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
@@ -38,34 +40,81 @@ public class ColumnSelectTest {
 
     @Test
     public void query() throws Exception {
-        ColumnSelect<Artist> q = ColumnSelect.query(Artist.class);
+        ColumnSelect<Artist> q = new ColumnSelect<>();
         assertNull(q.getColumns());
         assertNull(q.getHaving());
+        assertNull(q.getWhere());
+    }
+
+    @Test
+    public void queryWithOneColumn() throws Exception {
+        ColumnSelect<String> q = ObjectSelect.columnQuery(Artist.class, Artist.ARTIST_NAME);
+        assertEquals(Collections.singletonList(Artist.ARTIST_NAME), q.getColumns());
+        assertTrue(q.singleColumn);
+        assertNull(q.getHaving());
+        assertNull(q.getWhere());
+    }
+
+    @Test
+    public void queryWithOneColumn2() throws Exception {
+        ColumnSelect<String> q = ObjectSelect.query(Artist.class).column(Artist.ARTIST_NAME);
+        assertEquals(Collections.singletonList(Artist.ARTIST_NAME), q.getColumns());
+        assertTrue(q.singleColumn);
+        assertNull(q.getHaving());
+        assertNull(q.getWhere());
     }
 
     @Test
-    public void queryWithColumn() throws Exception {
-        ColumnSelect<String> q = ColumnSelect.query(Artist.class, Artist.ARTIST_NAME);
-        assertEquals(Arrays.asList(Artist.ARTIST_NAME), q.getColumns());
+    public void queryWithOneColumn3() throws Exception {
+        ColumnSelect<Object[]> q = ObjectSelect.query(Artist.class).columns(Artist.ARTIST_NAME);
+        assertEquals(Collections.singletonList(Artist.ARTIST_NAME), q.getColumns());
+        assertFalse(q.singleColumn);
         assertNull(q.getHaving());
+        assertNull(q.getWhere());
     }
 
     @Test
-    public void queryWithColumns() throws Exception {
-        ColumnSelect<Object[]> q = ColumnSelect.query(Artist.class, Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH);
+    public void queryWithMultipleColumns() throws Exception {
+        ColumnSelect<Object[]> q = ObjectSelect.columnQuery(Artist.class, Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH);
         assertEquals(Arrays.asList(Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH), q.getColumns());
+        assertFalse(q.singleColumn);
+        assertNull(q.getHaving());
+        assertNull(q.getWhere());
+    }
+
+    @Test
+    public void queryCount() throws Exception {
+        ColumnSelect<Long> q = ObjectSelect.query(Artist.class).count();
+        assertEquals(Collections.singletonList(Property.COUNT), q.getColumns());
+        assertNull(q.getHaving());
+        assertNull(q.getWhere());
+    }
+
+    @Test
+    public void queryCountWithProperty() throws Exception {
+        ColumnSelect<Long> q = ObjectSelect.query(Artist.class).count(Artist.ARTIST_NAME);
+        assertEquals(Collections.singletonList(Artist.ARTIST_NAME.count()), q.getColumns());
         assertNull(q.getHaving());
+        assertNull(q.getWhere());
+    }
+
+    @Test
+    public void queryMinWithProperty() throws Exception {
+        ColumnSelect<BigDecimal> q = ObjectSelect.query(Artist.class).min(Artist.PAINTING_ARRAY.dot(Painting.ESTIMATED_PRICE));
+        assertEquals(Collections.singletonList(Artist.PAINTING_ARRAY.dot(Painting.ESTIMATED_PRICE).min()), q.getColumns());
+        assertNull(q.getHaving());
+        assertNull(q.getWhere());
     }
 
     @SuppressWarnings("unchecked")
     @Test
     public void columns() throws Exception {
-        ColumnSelect q = ColumnSelect.query(Artist.class);
+        ColumnSelect q = new ColumnSelect();
         assertNull(q.getColumns());
         q.columns(Artist.ARTIST_NAME, Artist.PAINTING_ARRAY);
         assertEquals(Arrays.asList(Artist.ARTIST_NAME, Artist.PAINTING_ARRAY), q.getColumns());
 
-        q = ColumnSelect.query(Artist.class, Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH);
+        q = ObjectSelect.columnQuery(Artist.class, Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH);
         assertEquals(Arrays.asList(Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH), q.getColumns());
         q.columns(Artist.PAINTING_ARRAY);
         assertEquals(Arrays.asList(Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH, Artist.PAINTING_ARRAY), q.getColumns());
@@ -74,7 +123,7 @@ public class ColumnSelectTest {
 
     @Test
     public void havingExpression() throws Exception {
-        ColumnSelect q = ColumnSelect.query(Artist.class);
+        ColumnSelect q = new ColumnSelect();
         assertNull(q.getHaving());
         assertNull(q.getWhere());
 
@@ -91,7 +140,7 @@ public class ColumnSelectTest {
 
     @Test
     public void havingString() throws Exception {
-        ColumnSelect q = ColumnSelect.query(Artist.class);
+        ColumnSelect q = new ColumnSelect();
         assertNull(q.getHaving());
         assertNull(q.getWhere());
 
@@ -108,7 +157,7 @@ public class ColumnSelectTest {
 
     @Test
     public void and() throws Exception {
-        ColumnSelect q = ColumnSelect.query(Artist.class);
+        ColumnSelect q = new ColumnSelect();
         assertNull(q.getHaving());
         assertNull(q.getWhere());
 
@@ -127,7 +176,7 @@ public class ColumnSelectTest {
 
     @Test
     public void or() throws Exception {
-        ColumnSelect q = ColumnSelect.query(Artist.class);
+        ColumnSelect q = new ColumnSelect();
         assertNull(q.getHaving());
         assertNull(q.getWhere());
 
@@ -147,7 +196,7 @@ public class ColumnSelectTest {
 
     @Test
     public void testColumnsAddByOne() {
-        ColumnSelect<Artist> q = ColumnSelect.query(Artist.class);
+        ColumnSelect<Artist> q = new ColumnSelect<>();
 
         assertEquals(null, q.getColumns());
 
@@ -161,7 +210,7 @@ public class ColumnSelectTest {
 
     @Test
     public void testColumnsAddAll() {
-        ColumnSelect<Artist> q = ColumnSelect.query(Artist.class);
+        ColumnSelect<Artist> q = new ColumnSelect<>();
 
         assertEquals(null, q.getColumns());
 
@@ -176,7 +225,7 @@ public class ColumnSelectTest {
 
     @Test
     public void testColumnAddByOne() {
-        ColumnSelect<Artist> q = ColumnSelect.query(Artist.class);
+        ColumnSelect<Artist> q = new ColumnSelect<>();
 
         assertEquals(null, q.getColumns());