This is an automated email from the ASF dual-hosted git repository.

ntimofeev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git


The following commit(s) were added to refs/heads/master by this push:
     new ab7fddb8a Add generics to FluentSelect to merge identical code in 
ObjectSelect and ColumnSelect
ab7fddb8a is described below

commit ab7fddb8a04eb06cbe79e50ca41a72a0159858de
Author: Nikita Timofeev <stari...@gmail.com>
AuthorDate: Fri Jul 15 18:46:37 2022 +0300

    Add generics to FluentSelect to merge identical code in ObjectSelect and 
ColumnSelect
---
 .../translator/select/DefaultSelectTranslator.java |   2 +-
 .../select/DefaultSelectTranslatorFactory.java     |   2 +-
 .../translator/select/FluentSelectWrapper.java     |   6 +-
 .../java/org/apache/cayenne/dba/AutoAdapter.java   |   2 +-
 .../java/org/apache/cayenne/dba/DbAdapter.java     |   2 +-
 .../org/apache/cayenne/dba/JdbcActionBuilder.java  |   2 +-
 .../java/org/apache/cayenne/dba/JdbcAdapter.java   |   2 +-
 .../apache/cayenne/dba/db2/DB2ActionBuilder.java   |   2 +-
 .../cayenne/dba/derby/DerbyActionBuilder.java      |   2 +-
 .../dba/firebird/FirebirdActionBuilder.java        |   2 +-
 .../org/apache/cayenne/dba/h2/H2ActionBuilder.java |   2 +-
 .../cayenne/dba/hsqldb/HSQLActionBuilder.java      |   2 +-
 .../cayenne/dba/ingres/IngresActionBuilder.java    |   2 +-
 .../cayenne/dba/mysql/MySQLActionBuilder.java      |   2 +-
 .../cayenne/dba/oracle/OracleActionBuilder.java    |   2 +-
 .../dba/postgres/PostgresActionBuilder.java        |   2 +-
 .../cayenne/dba/sqlite/SQLiteActionBuilder.java    |   2 +-
 .../dba/sqlserver/SQLServerActionBuilder.java      |   4 +-
 .../org/apache/cayenne/exp/ExpressionFactory.java  |   4 +-
 .../org/apache/cayenne/exp/parser/ASTSubquery.java |   2 +-
 .../org/apache/cayenne/query/ColumnSelect.java     | 361 +--------------------
 .../apache/cayenne/query/ColumnSelectMetadata.java |   4 +-
 .../org/apache/cayenne/query/FluentSelect.java     | 357 +++++++++++++++++++-
 .../query/FluentSelectPrefetchRouterAction.java    |   4 +-
 .../org/apache/cayenne/query/ObjectSelect.java     | 354 +-------------------
 .../apache/cayenne/query/ObjectSelectMetadata.java |  10 +-
 .../org/apache/cayenne/query/SQLActionVisitor.java |   2 +-
 .../org/apache/cayenne/query/ColumnSelectTest.java |  11 +-
 28 files changed, 408 insertions(+), 743 deletions(-)

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 203d1c1af..0aacf108f 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
@@ -76,7 +76,7 @@ public class DefaultSelectTranslator implements 
SelectTranslator {
         this.context = new TranslatorContext(query, adapter, entityResolver, 
null);
     }
 
-    public DefaultSelectTranslator(FluentSelect<?> query, DbAdapter adapter, 
EntityResolver entityResolver) {
+    public DefaultSelectTranslator(FluentSelect<?, ?> query, DbAdapter 
adapter, EntityResolver entityResolver) {
         this(new FluentSelectWrapper(query), adapter, entityResolver);
     }
 
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorFactory.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorFactory.java
index e429b27f0..0c38c9ab0 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorFactory.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorFactory.java
@@ -35,7 +35,7 @@ public class DefaultSelectTranslatorFactory implements 
SelectTranslatorFactory {
        @Override
        public SelectTranslator translator(Select<?> query, DbAdapter adapter, 
EntityResolver entityResolver) {
                if(query instanceof FluentSelect) {
-                       return 
adapter.getSelectTranslator((FluentSelect<?>)query, entityResolver);
+                       return adapter.getSelectTranslator((FluentSelect<?, 
?>)query, entityResolver);
                }
                throw new CayenneRuntimeException("Unsupported type of Select 
query %s", query);
        }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/FluentSelectWrapper.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/FluentSelectWrapper.java
index 3c4bed7a3..8eb442d9b 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/FluentSelectWrapper.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/FluentSelectWrapper.java
@@ -34,9 +34,9 @@ import org.apache.cayenne.query.QueryMetadata;
  */
 public class FluentSelectWrapper implements TranslatableQueryWrapper {
 
-    private final FluentSelect<?> select;
+    private final FluentSelect<?, ?> select;
 
-    public FluentSelectWrapper(FluentSelect<?> select) {
+    public FluentSelectWrapper(FluentSelect<?, ?> select) {
         this.select = Objects.requireNonNull(select);
     }
 
@@ -71,7 +71,7 @@ public class FluentSelectWrapper implements 
TranslatableQueryWrapper {
     }
 
     @Override
-    public FluentSelect<?> unwrap() {
+    public FluentSelect<?, ?> unwrap() {
         return select;
     }
 }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
index 45a374ce4..845154bfa 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
@@ -101,7 +101,7 @@ public class AutoAdapter implements DbAdapter {
         * @since 4.2
         */
        @Override
-       public SelectTranslator getSelectTranslator(FluentSelect<?> query, 
EntityResolver entityResolver) {
+       public SelectTranslator getSelectTranslator(FluentSelect<?, ?> query, 
EntityResolver entityResolver) {
                return getAdapter().getSelectTranslator(query, entityResolver);
        }
 
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
index c6bc77394..aa4ba1c18 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
@@ -56,7 +56,7 @@ public interface DbAdapter {
        /**
         * @since 4.2
         */
-       SelectTranslator getSelectTranslator(FluentSelect<?> query, 
EntityResolver entityResolver);
+       SelectTranslator getSelectTranslator(FluentSelect<?, ?> query, 
EntityResolver entityResolver);
 
        /**
         * @since 4.2
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcActionBuilder.java 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcActionBuilder.java
index 8433e6cd6..2542931d2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcActionBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcActionBuilder.java
@@ -70,7 +70,7 @@ public class JdbcActionBuilder implements SQLActionVisitor {
      * @since 4.2
      */
     @Override
-    public <T> SQLAction objectSelectAction(FluentSelect<T> query) {
+    public <T> SQLAction objectSelectAction(FluentSelect<T, ?> query) {
         return new SelectAction(query, dataNode);
     }
 
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
index 479b9da6c..06451fd6f 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
@@ -508,7 +508,7 @@ public class JdbcAdapter implements DbAdapter {
     }
 
     @Override
-    public SelectTranslator getSelectTranslator(FluentSelect<?> query, 
EntityResolver entityResolver) {
+    public SelectTranslator getSelectTranslator(FluentSelect<?, ?> query, 
EntityResolver entityResolver) {
         return new DefaultSelectTranslator(query, this, entityResolver);
     }
 
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2ActionBuilder.java 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2ActionBuilder.java
index 5ef0f5216..46f29ac07 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2ActionBuilder.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2ActionBuilder.java
@@ -42,7 +42,7 @@ public class DB2ActionBuilder extends JdbcActionBuilder {
      * @since 4.2
      */
     @Override
-    public <T> SQLAction objectSelectAction(FluentSelect<T> query) {
+    public <T> SQLAction objectSelectAction(FluentSelect<T, ?> query) {
         return new DB2SelectAction(query, dataNode);
     }
 }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyActionBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyActionBuilder.java
index 46ea873a5..15801be22 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyActionBuilder.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyActionBuilder.java
@@ -37,7 +37,7 @@ public class DerbyActionBuilder extends JdbcActionBuilder {
      * @since 4.2
      */
     @Override
-    public <T> SQLAction objectSelectAction(FluentSelect<T> query) {
+    public <T> SQLAction objectSelectAction(FluentSelect<T, ?> query) {
         return new DerbySelectAction(query, dataNode);
     }
 }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdActionBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdActionBuilder.java
index eca046e53..fb5b306ab 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdActionBuilder.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdActionBuilder.java
@@ -37,7 +37,7 @@ public class FirebirdActionBuilder extends JdbcActionBuilder {
      * @since 4.2
      */
     @Override
-    public <T> SQLAction objectSelectAction(FluentSelect<T> query) {
+    public <T> SQLAction objectSelectAction(FluentSelect<T, ?> query) {
         return new FirebirdSelectAction(query, dataNode);
     }
 }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2ActionBuilder.java 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2ActionBuilder.java
index 37c0a2187..36be4d966 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2ActionBuilder.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2ActionBuilder.java
@@ -36,7 +36,7 @@ public class H2ActionBuilder extends JdbcActionBuilder {
      * @since 4.2
      */
     @Override
-    public <T> SQLAction objectSelectAction(FluentSelect<T> query) {
+    public <T> SQLAction objectSelectAction(FluentSelect<T, ?> query) {
         return new H2SelectAction(query, dataNode);
     }
 }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLActionBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLActionBuilder.java
index 75aea976f..6a83b0b30 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLActionBuilder.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLActionBuilder.java
@@ -39,7 +39,7 @@ class HSQLActionBuilder extends JdbcActionBuilder {
      * @since 4.2
      */
     @Override
-    public <T> SQLAction objectSelectAction(FluentSelect<T> query) {
+    public <T> SQLAction objectSelectAction(FluentSelect<T, ?> query) {
         return new HSQLSelectAction(query, dataNode);
     }
 
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresActionBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresActionBuilder.java
index 76b874121..7efae8dca 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresActionBuilder.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresActionBuilder.java
@@ -36,7 +36,7 @@ public class IngresActionBuilder extends JdbcActionBuilder {
      * @since 4.2
      */
     @Override
-    public <T> SQLAction objectSelectAction(FluentSelect<T> query) {
+    public <T> SQLAction objectSelectAction(FluentSelect<T, ?> query) {
         return new IngresSelectAction(query, dataNode);
     }
 }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLActionBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLActionBuilder.java
index d446ce35b..b55f488fa 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLActionBuilder.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLActionBuilder.java
@@ -38,7 +38,7 @@ class MySQLActionBuilder extends JdbcActionBuilder {
      * @since 4.2
      */
     @Override
-    public <T> SQLAction objectSelectAction(FluentSelect<T> query) {
+    public <T> SQLAction objectSelectAction(FluentSelect<T, ?> query) {
         return new MySQLSelectAction(query, dataNode);
     }
 
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleActionBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleActionBuilder.java
index bb033f973..94fa21547 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleActionBuilder.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleActionBuilder.java
@@ -62,7 +62,7 @@ class OracleActionBuilder extends JdbcActionBuilder {
      * @since 4.2
      */
     @Override
-    public <T> SQLAction objectSelectAction(FluentSelect<T> query) {
+    public <T> SQLAction objectSelectAction(FluentSelect<T, ?> query) {
         return new OracleSelectAction(query, dataNode);
     }
 }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresActionBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresActionBuilder.java
index 3051c0eb1..191be3e4b 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresActionBuilder.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresActionBuilder.java
@@ -55,7 +55,7 @@ class PostgresActionBuilder extends JdbcActionBuilder {
      * @since 4.2
      */
     @Override
-    public <T> SQLAction objectSelectAction(FluentSelect<T> query) {
+    public <T> SQLAction objectSelectAction(FluentSelect<T, ?> query) {
         return new PostgresSelectAction(query, dataNode);
     }
 }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteActionBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteActionBuilder.java
index c2be85d8b..74e25fd97 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteActionBuilder.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteActionBuilder.java
@@ -42,7 +42,7 @@ class SQLiteActionBuilder extends JdbcActionBuilder {
      * @since 4.2
      */
     @Override
-    public <T> SQLAction objectSelectAction(FluentSelect<T> query) {
+    public <T> SQLAction objectSelectAction(FluentSelect<T, ?> query) {
         return new SQLiteSelectAction(query, dataNode);
     }
 }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java
index f7d78452a..ce806a5f6 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java
@@ -75,11 +75,11 @@ public class SQLServerActionBuilder extends 
JdbcActionBuilder {
         * @since 4.2
         */
        @Override
-       public <T> SQLAction objectSelectAction(FluentSelect<T> query) {
+       public <T> SQLAction objectSelectAction(FluentSelect<T, ?> query) {
                return new SQLServerSelectAction(query, dataNode, 
needInMemoryOffset(query));
        }
 
-       private boolean needInMemoryOffset(FluentSelect<?> query) {
+       private boolean needInMemoryOffset(FluentSelect<?, ?> query) {
                return query.getOrderings() == null || 
query.getOrderings().size() == 0
                                || version == null || version < 12;
        }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
index 7d3b207ac..094e352e4 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
@@ -1437,7 +1437,7 @@ public class ExpressionFactory {
         * @param subQuery {@link org.apache.cayenne.query.ObjectSelect} or 
{@link ColumnSelect}
         * @since 4.2
         */
-       public static Expression exists(FluentSelect<?> subQuery) {
+       public static Expression exists(FluentSelect<?, ?> subQuery) {
                return new ASTExists(new ASTSubquery(subQuery));
        }
 
@@ -1445,7 +1445,7 @@ public class ExpressionFactory {
         * @param subQuery {@link org.apache.cayenne.query.ObjectSelect} or 
{@link ColumnSelect}
         * @since 4.2
         */
-       public static Expression notExists(FluentSelect<?> subQuery) {
+       public static Expression notExists(FluentSelect<?, ?> subQuery) {
                return new ASTNotExists(new ASTSubquery(subQuery));
        }
 
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubquery.java 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubquery.java
index 32b5d7582..c2aa5fc9d 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubquery.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubquery.java
@@ -35,7 +35,7 @@ public class ASTSubquery extends SimpleNode {
 
     private final TranslatableQueryWrapper query;
 
-    public ASTSubquery(FluentSelect<?> query) {
+    public ASTSubquery(FluentSelect<?, ?> query) {
         this(new FluentSelectWrapper(query));
     }
 
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 dd74e6063..8af55e47d 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
@@ -19,9 +19,7 @@
 
 package org.apache.cayenne.query;
 
-import java.sql.Statement;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.function.Function;
@@ -34,9 +32,7 @@ import org.apache.cayenne.exp.property.ComparableProperty;
 import org.apache.cayenne.exp.property.NumericProperty;
 import org.apache.cayenne.exp.property.Property;
 import org.apache.cayenne.exp.property.PropertyFactory;
-import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.ObjEntity;
 
 /**
  * <p>A helper builder for queries selecting individual properties based on 
the root object.</p>
@@ -65,13 +61,10 @@ import org.apache.cayenne.map.ObjEntity;
  *
  * @since 4.0
  */
-public class ColumnSelect<T> extends FluentSelect<T> {
+public class ColumnSelect<T> extends FluentSelect<T, ColumnSelect<T>> {
 
-    private Collection<Property<?>> columns;
-    // package private for tests
-    boolean singleColumn = true;
-
-    ColumnSelectMetadata metaData = new ColumnSelectMetadata();
+    protected Collection<Property<?>> columns;
+    protected boolean singleColumn = true;
 
     protected ColumnSelect() {
     }
@@ -89,286 +82,9 @@ public class ColumnSelect<T> extends FluentSelect<T> {
         this.metaData.copyFromInfo(select.metaData);
     }
 
-    /**
-     * Sets the type of the entity to fetch without changing the return type of
-     * the query.
-     *
-     * @return this object
-     */
-    public ColumnSelect<T> entityType(Class<?> entityType) {
-        return resetEntity(entityType, null, null);
-    }
-
-    /**
-     * Sets the {@link ObjEntity} name to fetch without changing the return 
type
-     * of the query. This form is most often used for generic entities that
-     * don't map to a distinct class.
-     *
-     * @return this object
-     */
-    public ColumnSelect<T> entityName(String entityName) {
-        return resetEntity(null, entityName, null);
-    }
-
-    /**
-     * Sets the {@link DbEntity} name to fetch without changing the return type
-     * of the query. This form is most often used for generic entities that
-     * don't map to a distinct class.
-     *
-     * @return this object
-     */
-    public ColumnSelect<T> dbEntityName(String dbEntityName) {
-        return resetEntity(null, null, dbEntityName);
-    }
-
-    private ColumnSelect<T> resetEntity(Class<?> entityType, String 
entityName, String dbEntityName) {
-        this.entityType = entityType;
-        this.entityName = entityName;
-        this.dbEntityName = dbEntityName;
-        return this;
-    }
-
-    /**
-     * Appends a qualifier expression of this query. An equivalent to
-     * {@link #and(Expression...)} that can be used a syntactic sugar.
-     *
-     * @return this object
-     */
-    public ColumnSelect<T> where(Expression expression) {
-        return and(expression);
-    }
-
-    /**
-     * Appends a qualifier expression of this query, using provided expression
-     * String and an array of position parameters. This is an equivalent to
-     * calling "and".
-     *
-     * @return this object
-     */
-    public ColumnSelect<T> where(String expressionString, Object... 
parameters) {
-        return and(ExpressionFactory.exp(expressionString, parameters));
-    }
-
-    /**
-     * AND's provided expressions to the existing WHERE clause expression.
-     *
-     * @return this object
-     */
-    public ColumnSelect<T> and(Expression... expressions) {
-        if (expressions == null || expressions.length == 0) {
-            return this;
-        }
-
-        return and(Arrays.asList(expressions));
-    }
-
-    /**
-     * OR's provided expressions to the existing WHERE clause expression.
-     *
-     * @return this object
-     */
-    public ColumnSelect<T> or(Expression... expressions) {
-        if (expressions == null || expressions.length == 0) {
-            return this;
-        }
-
-        return or(Arrays.asList(expressions));
-    }
-
-    /**
-     * Add an ascending ordering on the given property. If there is already an 
ordering
-     * on this query then add this ordering with a lower priority.
-     *
-     * @param property the property to sort on
-     * @return this object
-     */
-    public ColumnSelect<T> orderBy(String property) {
-        return orderBy(new Ordering(property));
-    }
-
-    /**
-     * Add an ordering on the given property. If there is already an ordering
-     * on this query then add this ordering with a lower priority.
-     *
-     * @param property  the property to sort on
-     * @param sortOrder the direction of the ordering
-     * @return this object
-     */
-    public ColumnSelect<T> orderBy(String property, SortOrder sortOrder) {
-        return orderBy(new Ordering(property, sortOrder));
-    }
-
-    /**
-     * Add one or more orderings to this query.
-     *
-     * @return this object
-     */
-    public ColumnSelect<T> orderBy(Ordering... orderings) {
-
-        if (orderings == null) {
-            return this;
-        }
-
-        if (this.orderings == null) {
-            this.orderings = new ArrayList<>(orderings.length);
-        }
-
-        Collections.addAll(this.orderings, orderings);
-        return this;
-    }
-
-    /**
-     * Adds a list of orderings to this query.
-     *
-     * @return this object
-     */
-    public ColumnSelect<T> orderBy(Collection<Ordering> orderings) {
-
-        if (orderings == null) {
-            return this;
-        }
-
-        if (this.orderings == null) {
-            this.orderings = new ArrayList<>(orderings.size());
-        }
-
-        this.orderings.addAll(orderings);
-        return this;
-    }
-
-    /**
-     * Merges prefetch into the query prefetch tree.
-     *
-     * @return this object
-     */
-    public ColumnSelect<T> prefetch(PrefetchTreeNode prefetch) {
-        metaData.mergePrefetch(prefetch);
-        return this;
-    }
-
-    /**
-     * Merges a prefetch path with specified semantics into the query prefetch
-     * tree.
-     *
-     * @return this object
-     */
-    public ColumnSelect<T> prefetch(String path, int semantics) {
-        if (path == null) {
-            return this;
-        }
-        metaData.addPrefetch(path, semantics);
-        return this;
-    }
-
-    /**
-     * Resets query fetch limit - a parameter that defines max number of 
objects
-     * that should be ever be fetched from the database.
-     */
-    public ColumnSelect<T> limit(int fetchLimit) {
-        this.metaData.setFetchLimit(fetchLimit);
-        return this;
-    }
-
-    /**
-     * Resets query fetch offset - a parameter that defines how many objects
-     * should be skipped when reading data from the database.
-     */
-    public ColumnSelect<T> offset(int fetchOffset) {
-        this.metaData.setFetchOffset(fetchOffset);
-        return this;
-    }
-
-    /**
-     * Resets query page size. A non-negative page size enables query result
-     * pagination that saves memory and processing time for large lists if only
-     * parts of the result are ever going to be accessed.
-     */
-    public ColumnSelect<T> pageSize(int pageSize) {
-        this.metaData.setPageSize(pageSize);
-        return this;
-    }
-
-    /**
-     * Sets fetch size of the PreparedStatement generated for this query. Only
-     * non-negative values would change the default size.
-     *
-     * @see Statement#setFetchSize(int)
-     */
-    public ColumnSelect<T> statementFetchSize(int size) {
-        this.metaData.setStatementFetchSize(size);
-        return this;
-    }
-
-    /**
-     * Sets query timeout of PreparedStatement generated for this query.
-     * @see Statement#setQueryTimeout(int)
-     */
-    public ColumnSelect<T> queryTimeout(int timeout) {
-        this.metaData.setQueryTimeout(timeout);
-        return this;
-    }
-
-    public ColumnSelect<T> cacheStrategy(QueryCacheStrategy strategy) {
-        setCacheStrategy(strategy);
-        setCacheGroup(null);
-        return this;
-    }
-
-    public ColumnSelect<T> cacheStrategy(QueryCacheStrategy strategy, String 
cacheGroup) {
-        return cacheStrategy(strategy).cacheGroup(cacheGroup);
-    }
-
-    public ColumnSelect<T> cacheGroup(String cacheGroup) {
-        setCacheGroup(cacheGroup);
-        return this;
-    }
-
-    /**
-     * Instructs Cayenne to look for query results in the "local" cache when
-     * running the query. This is a short-hand notation for:
-     * <p>
-     * <pre>
-     * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
-     * </pre>
-     */
-    public ColumnSelect<T> localCache(String cacheGroup) {
-        return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
-    }
-
-    /**
-     * Instructs Cayenne to look for query results in the "local" cache when
-     * running the query. This is a short-hand notation for:
-     * <p>
-     * <pre>
-     * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
-     * </pre>
-     */
-    public ColumnSelect<T> localCache() {
-        return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
-    }
-
-    /**
-     * Instructs Cayenne to look for query results in the "shared" cache when
-     * running the query. This is a short-hand notation for:
-     * <p>
-     * <pre>
-     * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
-     * </pre>
-     */
-    public ColumnSelect<T> sharedCache(String cacheGroup) {
-        return cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
-    }
-
-    /**
-     * Instructs Cayenne to look for query results in the "shared" cache when
-     * running the query. This is a short-hand notation for:
-     * <p>
-     * <pre>
-     * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
-     * </pre>
-     */
-    public ColumnSelect<T> sharedCache() {
-        return cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+    @Override
+    protected ColumnSelectMetadata createMetadata() {
+        return new ColumnSelectMetadata();
     }
 
     /**
@@ -428,7 +144,7 @@ public class ColumnSelect<T> extends FluentSelect<T> {
     }
 
     @SuppressWarnings("unchecked")
-    protected  <E> ColumnSelect<E> column(Property<E> property) {
+    protected <E> ColumnSelect<E> column(Property<E> property) {
         if (this.columns == null) {
             this.columns = new ArrayList<>(1);
         } else {
@@ -509,62 +225,11 @@ public class ColumnSelect<T> extends FluentSelect<T> {
         return and(ExpressionFactory.exp(expressionString, parameters));
     }
 
-    /**
-     * AND's provided expressions to the existing WHERE or HAVING clause 
expression.
-     *
-     * @return this object
-     */
-    public ColumnSelect<T> and(Collection<Expression> expressions) {
-
-        if (expressions == null || expressions.isEmpty()) {
-            return this;
-        }
-
-        Collection<Expression> all;
-        Expression activeExpression = getActiveExpression();
-
-        if (activeExpression != null) {
-            all = new ArrayList<>(expressions.size() + 1);
-            all.add(activeExpression);
-            all.addAll(expressions);
-        } else {
-            all = expressions;
-        }
-
-        setActiveExpression(ExpressionFactory.and(all));
-        return this;
-    }
-
-    /**
-     * OR's provided expressions to the existing WHERE or HAVING clause 
expression.
-     *
-     * @return this object
-     */
-    public ColumnSelect<T> or(Collection<Expression> expressions) {
-        if (expressions == null || expressions.isEmpty()) {
-            return this;
-        }
-
-        Collection<Expression> all;
-        Expression activeExpression = getActiveExpression();
-
-        if (activeExpression != null) {
-            all = new ArrayList<>(expressions.size() + 1);
-            all.add(activeExpression);
-            all.addAll(expressions);
-        } else {
-            all = expressions;
-        }
-
-        setActiveExpression(ExpressionFactory.or(all));
-        return this;
-    }
-
     /**
      * Explicitly request distinct in query.
      */
     public ColumnSelect<T> distinct() {
-        metaData.setSuppressingDistinct(false);
+        getBaseMetaData().setSuppressingDistinct(false);
         this.distinct = true;
         return this;
     }
@@ -573,7 +238,7 @@ public class ColumnSelect<T> extends FluentSelect<T> {
      * Explicitly suppress distinct in query.
      */
     public ColumnSelect<T> suppressDistinct() {
-        metaData.setSuppressingDistinct(true);
+        getBaseMetaData().setSuppressingDistinct(true);
         this.distinct = false;
         return this;
     }
@@ -595,13 +260,13 @@ public class ColumnSelect<T> extends FluentSelect<T> {
     @Override
     public QueryMetadata getMetaData(EntityResolver resolver) {
         Object root = resolveRoot(resolver);
-        metaData.resolve(root, resolver, this);
+        getBaseMetaData().resolve(root, resolver, this);
         return metaData;
     }
 
     @Override
-    protected BaseQueryMetadata getBaseMetaData() {
-        return metaData;
+    protected ColumnSelectMetadata getBaseMetaData() {
+        return (ColumnSelectMetadata) metaData;
     }
 
     /**
@@ -618,7 +283,7 @@ public class ColumnSelect<T> extends FluentSelect<T> {
      */
     @SuppressWarnings("unchecked")
     public <E> ColumnSelect<E> map(Function<T, E> mapper) {
-        this.metaData.setResultMapper(mapper);
+        getBaseMetaData().setResultMapper(mapper);
         return (ColumnSelect<E>)this;
     }
 }
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelectMetadata.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelectMetadata.java
index 365853dce..76b780cdb 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelectMetadata.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/query/ColumnSelectMetadata.java
@@ -65,12 +65,12 @@ class ColumnSelectMetadata extends ObjectSelectMetadata {
        }
 
        @Override
-       protected void resolveAutoAliases(FluentSelect<?> query) {
+       protected void resolveAutoAliases(FluentSelect<?, ?> query) {
                super.resolveAutoAliases(query);
                resolveColumnsAliases(query);
        }
 
-       protected void resolveColumnsAliases(FluentSelect<?> query) {
+       protected void resolveColumnsAliases(FluentSelect<?, ?> query) {
         Collection<Property<?>> columns = query.getColumns();
         if(columns != null) {
             for(Property<?> property : columns) {
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 8dad2deb0..cf024f309 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
@@ -19,9 +19,14 @@
 
 package org.apache.cayenne.query;
 
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.ObjectContext;
@@ -29,6 +34,7 @@ import org.apache.cayenne.ResultBatchIterator;
 import org.apache.cayenne.ResultIterator;
 import org.apache.cayenne.ResultIteratorCallback;
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.property.Property;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.EntityResolver;
@@ -39,7 +45,7 @@ import org.apache.cayenne.map.ObjEntity;
  *
  * @since 4.0
  */
-public abstract class FluentSelect<T> extends AbstractQuery implements 
Select<T> {
+public abstract class FluentSelect<T, S extends FluentSelect<T, S>> extends 
AbstractQuery implements Select<T> {
 
     // root
     protected Class<?> entityType;
@@ -48,14 +54,19 @@ public abstract class FluentSelect<T> extends AbstractQuery 
implements Select<T>
 
     protected Expression where;
     protected Expression having;
-    boolean havingExpressionIsActive = false;
+    protected boolean havingExpressionIsActive = false;
 
     protected Collection<Ordering> orderings;
-    boolean distinct;
+    protected boolean distinct;
+
+    protected ObjectSelectMetadata metaData;
 
     protected FluentSelect() {
+        metaData = createMetadata();
     }
 
+    protected abstract ObjectSelectMetadata createMetadata();
+
     protected Object resolveRoot(EntityResolver resolver) {
         Object root;
         if (entityType != null) {
@@ -82,6 +93,340 @@ public abstract class FluentSelect<T> extends AbstractQuery 
implements Select<T>
         return root;
     }
 
+    /**
+     * Sets the type of the entity to fetch without changing the return type of
+     * the query.
+     *
+     * @return this object
+     */
+    public S entityType(Class<?> entityType) {
+        return resetEntity(entityType, null, null);
+    }
+
+    /**
+     * Sets the {@link ObjEntity} name to fetch without changing the return 
type
+     * of the query. This form is most often used for generic entities that
+     * don't map to a distinct class.
+     *
+     * @return this object
+     */
+    public S entityName(String entityName) {
+        return resetEntity(null, entityName, null);
+    }
+
+    /**
+     * Sets the {@link DbEntity} name to fetch without changing the return type
+     * of the query. This form is most often used for generic entities that
+     * don't map to a distinct class.
+     *
+     * @return this object
+     */
+    public S dbEntityName(String dbEntityName) {
+        return resetEntity(null, null, dbEntityName);
+    }
+
+    @SuppressWarnings("unchecked")
+    private S resetEntity(Class<?> entityType, String entityName, String 
dbEntityName) {
+        this.entityType = entityType;
+        this.entityName = entityName;
+        this.dbEntityName = dbEntityName;
+        return (S)this;
+    }
+
+    /**
+     * Appends a qualifier expression of this query. An equivalent to
+     * {@link #and(Expression...)} that can be used a syntactic sugar.
+     *
+     * @return this object
+     */
+    public S where(Expression expression) {
+        return and(expression);
+    }
+
+    /**
+     * Appends a qualifier expression of this query, using provided expression
+     * String and an array of position parameters. This is an equivalent to
+     * calling "and".
+     *
+     * @return this object
+     */
+    public S where(String expressionString, Object... parameters) {
+        return and(ExpressionFactory.exp(expressionString, parameters));
+    }
+
+    /**
+     * AND's provided expressions to the existing WHERE clause expression.
+     *
+     * @return this object
+     */
+    @SuppressWarnings("unchecked")
+    public S and(Expression... expressions) {
+        if (expressions == null || expressions.length == 0) {
+            return (S)this;
+        }
+        return and(Arrays.asList(expressions));
+    }
+
+    /**
+     * AND's provided expressions to the existing WHERE clause expression.
+     *
+     * @return this object
+     */
+
+    public S and(Collection<Expression> expressions) {
+        return joinExpression(expressions, ExpressionFactory::and);
+    }
+
+    @SuppressWarnings("unchecked")
+    protected S joinExpression(Collection<Expression> expressions, 
Function<Collection<Expression>, Expression> joiner) {
+        if (expressions == null || expressions.isEmpty()) {
+            return (S)this;
+        }
+
+        Collection<Expression> all;
+        Expression activeExpression = getActiveExpression();
+        if (activeExpression != null) {
+            all = new ArrayList<>(expressions.size() + 1);
+            all.add(activeExpression);
+            all.addAll(expressions);
+        } else {
+            all = expressions;
+        }
+
+        setActiveExpression(joiner.apply(all));
+        return (S)this;
+    }
+
+    /**
+     * OR's provided expressions to the existing WHERE clause expression.
+     *
+     * @return this object
+     */
+    @SuppressWarnings("unchecked")
+    public S or(Expression... expressions) {
+        if (expressions == null || expressions.length == 0) {
+            return (S)this;
+        }
+        return or(Arrays.asList(expressions));
+    }
+
+    /**
+     * OR's provided expressions to the existing WHERE clause expression.
+     *
+     * @return this object
+     */
+    public S or(Collection<Expression> expressions) {
+        return joinExpression(expressions, ExpressionFactory::or);
+    }
+
+    /**
+     * Add an ascending ordering on the given property. If there is already an 
ordering
+     * on this query then add this ordering with a lower priority.
+     *
+     * @param property the property to sort on
+     * @return this object
+     */
+    public S orderBy(String property) {
+        return orderBy(new Ordering(property));
+    }
+
+    /**
+     * Add an ordering on the given property. If there is already an ordering
+     * on this query then add this ordering with a lower priority.
+     *
+     * @param property  the property to sort on
+     * @param sortOrder the direction of the ordering
+     * @return this object
+     */
+    public S orderBy(String property, SortOrder sortOrder) {
+        return orderBy(new Ordering(property, sortOrder));
+    }
+
+    /**
+     * Add one or more orderings to this query.
+     *
+     * @return this object
+     */
+    @SuppressWarnings("unchecked")
+    public S orderBy(Ordering... orderings) {
+
+        if (orderings == null) {
+            return (S)this;
+        }
+
+        if (this.orderings == null) {
+            this.orderings = new ArrayList<>(orderings.length);
+        }
+
+        Collections.addAll(this.orderings, orderings);
+
+        return (S)this;
+    }
+
+    /**
+     * Adds a list of orderings to this query.
+     *
+     * @return this object
+     */
+    @SuppressWarnings("unchecked")
+    public S orderBy(Collection<Ordering> orderings) {
+
+        if (orderings == null) {
+            return (S)this;
+        }
+
+        if (this.orderings == null) {
+            this.orderings = new ArrayList<>(orderings.size());
+        }
+
+        this.orderings.addAll(orderings);
+
+        return (S)this;
+    }
+
+    /**
+     * Merges prefetch into the query prefetch tree.
+     *
+     * @return this object
+     */
+    @SuppressWarnings("unchecked")
+    public S prefetch(PrefetchTreeNode prefetch) {
+        getBaseMetaData().mergePrefetch(prefetch);
+        return (S)this;
+    }
+
+    /**
+     * Merges a prefetch path with specified semantics into the query prefetch 
tree.
+     *
+     * @return this object
+     */
+    @SuppressWarnings("unchecked")
+    public S prefetch(String path, int semantics) {
+        if (path == null) {
+            return (S)this;
+        }
+        getBaseMetaData().addPrefetch(path, semantics);
+        return (S)this;
+    }
+
+    /**
+     * Resets query fetch limit - a parameter that defines max number of 
objects
+     * that should be ever be fetched from the database.
+     */
+    @SuppressWarnings("unchecked")
+    public S limit(int fetchLimit) {
+        this.getBaseMetaData().setFetchLimit(fetchLimit);
+        return (S)this;
+    }
+
+    /**
+     * Resets query fetch offset - a parameter that defines how many objects
+     * should be skipped when reading data from the database.
+     */
+    @SuppressWarnings("unchecked")
+    public S offset(int fetchOffset) {
+        this.getBaseMetaData().setFetchOffset(fetchOffset);
+        return (S)this;
+    }
+
+    /**
+     * Resets query page size. A non-negative page size enables query result
+     * pagination that saves memory and processing time for large lists if only
+     * parts of the result are ever going to be accessed.
+     */
+    @SuppressWarnings("unchecked")
+    public S pageSize(int pageSize) {
+        this.getBaseMetaData().setPageSize(pageSize);
+        return (S)this;
+    }
+
+    /**
+     * Sets fetch size of the PreparedStatement generated for this query. Only
+     * non-negative values would change the default size.
+     *
+     * @see Statement#setFetchSize(int)
+     */
+    @SuppressWarnings("unchecked")
+    public S statementFetchSize(int size) {
+        this.getBaseMetaData().setStatementFetchSize(size);
+        return (S)this;
+    }
+
+    /**
+     * Sets query timeout of PreparedStatement generated for this query.
+     * @see Statement#setQueryTimeout(int)
+     */
+    @SuppressWarnings("unchecked")
+    public S queryTimeout(int timeout) {
+        this.getBaseMetaData().setQueryTimeout(timeout);
+        return (S)this;
+    }
+
+    @SuppressWarnings("unchecked")
+    public S cacheStrategy(QueryCacheStrategy strategy) {
+        setCacheStrategy(strategy);
+        setCacheGroup(null);
+        return (S)this;
+    }
+
+    public S cacheStrategy(QueryCacheStrategy strategy, String cacheGroup) {
+        return cacheStrategy(strategy).cacheGroup(cacheGroup);
+    }
+
+    @SuppressWarnings("unchecked")
+    public S cacheGroup(String cacheGroup) {
+        setCacheGroup(cacheGroup);
+        return (S)this;
+    }
+
+    /**
+     * Instructs Cayenne to look for query results in the "local" cache when
+     * running the query. This is a short-hand notation for:
+     * <p>
+     * <pre>
+     * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
+     * </pre>
+     */
+    public S localCache(String cacheGroup) {
+        return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
+    }
+
+    /**
+     * Instructs Cayenne to look for query results in the "local" cache when
+     * running the query. This is a short-hand notation for:
+     * <p>
+     * <pre>
+     * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+     * </pre>
+     */
+    public S localCache() {
+        return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+    }
+
+    /**
+     * Instructs Cayenne to look for query results in the "shared" cache when
+     * running the query. This is a short-hand notation for:
+     * <p>
+     * <pre>
+     * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
+     * </pre>
+     */
+    public S sharedCache(String cacheGroup) {
+        return cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
+    }
+
+    /**
+     * Instructs Cayenne to look for query results in the "shared" cache when
+     * running the query. This is a short-hand notation for:
+     * <p>
+     * <pre>
+     * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+     * </pre>
+     */
+    public S sharedCache() {
+        return cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+    }
+
     public int getStatementFetchSize() {
         return getBaseMetaData().getStatementFetchSize();
     }
@@ -139,7 +484,7 @@ public abstract class FluentSelect<T> extends AbstractQuery 
implements Select<T>
         return getBaseMetaData().getPrefetchTree();
     }
 
-    void setActiveExpression(Expression exp) {
+    protected void setActiveExpression(Expression exp) {
         if(havingExpressionIsActive) {
             having = exp;
         } else {
@@ -147,7 +492,7 @@ public abstract class FluentSelect<T> extends AbstractQuery 
implements Select<T>
         }
     }
 
-    Expression getActiveExpression() {
+    protected Expression getActiveExpression() {
         if(havingExpressionIsActive) {
             return having;
         } else {
@@ -196,7 +541,7 @@ public abstract class FluentSelect<T> extends AbstractQuery 
implements Select<T>
     }
 
     public boolean isFetchingDataRows() {
-        return false;
+        return getBaseMetaData().isFetchingDataRows();
     }
 
     protected void routePrefetches(QueryRouter router, EntityResolver 
resolver) {
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelectPrefetchRouterAction.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelectPrefetchRouterAction.java
index f72895ac6..7da944e4c 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelectPrefetchRouterAction.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/query/FluentSelectPrefetchRouterAction.java
@@ -35,7 +35,7 @@ import org.apache.cayenne.util.CayenneMapEntry;
  */
 class FluentSelectPrefetchRouterAction implements PrefetchProcessor {
 
-    FluentSelect<?> query;
+    FluentSelect<?, ?> query;
     QueryRouter router;
     EntityResolver resolver;
     ClassDescriptor classDescriptor;
@@ -43,7 +43,7 @@ class FluentSelectPrefetchRouterAction implements 
PrefetchProcessor {
     /**
      * Routes query prefetches, but not the query itself.
      */
-    void route(FluentSelect<?> query, QueryRouter router, EntityResolver 
resolver) {
+    void route(FluentSelect<?, ?> query, QueryRouter router, EntityResolver 
resolver) {
         if (!query.isFetchingDataRows() && query.getPrefetches() != null) {
 
             this.query = query;
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 58f65624c..bea18d1f3 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
@@ -18,11 +18,6 @@
  ****************************************************************/
 package org.apache.cayenne.query;
 
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -54,12 +49,10 @@ import org.apache.cayenne.map.ObjEntity;
  *
  * @since 4.0
  */
-public class ObjectSelect<T> extends FluentSelect<T> implements 
ParameterizedQuery {
+public class ObjectSelect<T> extends FluentSelect<T, ObjectSelect<T>> 
implements ParameterizedQuery {
 
     private static final long serialVersionUID = -156124021150949227L;
 
-    protected ObjectSelectMetadata metaData = new ObjectSelectMetadata();
-
     /**
      * Creates a ObjectSelect that selects objects of a given persistent class.
      */
@@ -153,64 +146,9 @@ public class ObjectSelect<T> extends FluentSelect<T> 
implements ParameterizedQue
     protected ObjectSelect() {
     }
 
-    /**
-     * Sets the type of the entity to fetch without changing the return type of
-     * the query.
-     *
-     * @return this object
-     */
-    public ObjectSelect<T> entityType(Class<?> entityType) {
-        return resetEntity(entityType, null, null);
-    }
-
-    /**
-     * Sets the {@link ObjEntity} name to fetch without changing the return 
type
-     * of the query. This form is most often used for generic entities that
-     * don't map to a distinct class.
-     *
-     * @return this object
-     */
-    public ObjectSelect<T> entityName(String entityName) {
-        return resetEntity(null, entityName, null);
-    }
-
-    /**
-     * Sets the {@link DbEntity} name to fetch without changing the return type
-     * of the query. This form is most often used for generic entities that
-     * don't map to a distinct class.
-     *
-     * @return this object
-     */
-    public ObjectSelect<T> dbEntityName(String dbEntityName) {
-        return resetEntity(null, null, dbEntityName);
-    }
-
-    private ObjectSelect<T> resetEntity(Class<?> entityType, String 
entityName, String dbEntityName) {
-        this.entityType = entityType;
-        this.entityName = entityName;
-        this.dbEntityName = dbEntityName;
-        return this;
-    }
-
-    /**
-     * Appends a qualifier expression of this query. An equivalent to
-     * {@link #and(Expression...)} that can be used a syntactic sugar.
-     *
-     * @return this object
-     */
-    public ObjectSelect<T> where(Expression expression) {
-        return and(expression);
-    }
-
-    /**
-     * Appends a qualifier expression of this query, using provided expression
-     * String and an array of position parameters. This is an equivalent to
-     * calling "and".
-     *
-     * @return this object
-     */
-    public ObjectSelect<T> where(String expressionString, Object... 
parameters) {
-        return and(ExpressionFactory.exp(expressionString, parameters));
+    @Override
+    protected ObjectSelectMetadata createMetadata() {
+        return new ObjectSelectMetadata();
     }
 
     /**
@@ -238,283 +176,6 @@ public class ObjectSelect<T> extends FluentSelect<T> 
implements ParameterizedQue
         return and(ExpressionFactory.exp(expressionString, parameters));
     }
 
-    /**
-     * AND's provided expressions to the existing WHERE clause expression.
-     *
-     * @return this object
-     */
-    public ObjectSelect<T> and(Expression... expressions) {
-        if (expressions == null || expressions.length == 0) {
-            return this;
-        }
-
-        return and(Arrays.asList(expressions));
-    }
-
-    /**
-     * AND's provided expressions to the existing WHERE clause expression.
-     *
-     * @return this object
-     */
-    public ObjectSelect<T> and(Collection<Expression> expressions) {
-
-        if (expressions == null || expressions.isEmpty()) {
-            return this;
-        }
-
-        Collection<Expression> all;
-        Expression activeExpression = getActiveExpression();
-
-        if(activeExpression != null) {
-            all = new ArrayList<>(expressions.size() + 1);
-            all.add(activeExpression);
-            all.addAll(expressions);
-        } else {
-            all = expressions;
-        }
-
-        setActiveExpression(ExpressionFactory.and(all));
-        return this;
-    }
-
-    /**
-     * OR's provided expressions to the existing WHERE clause expression.
-     *
-     * @return this object
-     */
-    public ObjectSelect<T> or(Expression... expressions) {
-        if (expressions == null || expressions.length == 0) {
-            return this;
-        }
-
-        return or(Arrays.asList(expressions));
-    }
-
-    /**
-     * OR's provided expressions to the existing WHERE clause expression.
-     *
-     * @return this object
-     */
-    public ObjectSelect<T> or(Collection<Expression> expressions) {
-        if (expressions == null || expressions.isEmpty()) {
-            return this;
-        }
-
-        Collection<Expression> all;
-        Expression activeExpression = getActiveExpression();
-
-        if(activeExpression != null) {
-            all = new ArrayList<>(expressions.size() + 1);
-            all.add(activeExpression);
-            all.addAll(expressions);
-        } else {
-            all = expressions;
-        }
-
-        setActiveExpression(ExpressionFactory.or(all));
-        return this;
-    }
-
-    /**
-     * Add an ascending ordering on the given property. If there is already an 
ordering
-     * on this query then add this ordering with a lower priority.
-     *
-     * @param property the property to sort on
-     * @return this object
-     */
-    public ObjectSelect<T> orderBy(String property) {
-        return orderBy(new Ordering(property));
-    }
-
-    /**
-     * Add an ordering on the given property. If there is already an ordering
-     * on this query then add this ordering with a lower priority.
-     *
-     * @param property  the property to sort on
-     * @param sortOrder the direction of the ordering
-     * @return this object
-     */
-    public ObjectSelect<T> orderBy(String property, SortOrder sortOrder) {
-        return orderBy(new Ordering(property, sortOrder));
-    }
-
-    /**
-     * Add one or more orderings to this query.
-     *
-     * @return this object
-     */
-    public ObjectSelect<T> orderBy(Ordering... orderings) {
-
-        if (orderings == null) {
-            return this;
-        }
-
-        if (this.orderings == null) {
-            this.orderings = new ArrayList<>(orderings.length);
-        }
-
-        Collections.addAll(this.orderings, orderings);
-
-        return this;
-    }
-
-    /**
-     * Adds a list of orderings to this query.
-     *
-     * @return this object
-     */
-    public ObjectSelect<T> orderBy(Collection<Ordering> orderings) {
-
-        if (orderings == null) {
-            return this;
-        }
-
-        if (this.orderings == null) {
-            this.orderings = new ArrayList<>(orderings.size());
-        }
-
-        this.orderings.addAll(orderings);
-
-        return this;
-    }
-
-    /**
-     * Merges prefetch into the query prefetch tree.
-     *
-     * @return this object
-     */
-    public ObjectSelect<T> prefetch(PrefetchTreeNode prefetch) {
-        metaData.mergePrefetch(prefetch);
-        return this;
-    }
-
-    /**
-     * Merges a prefetch path with specified semantics into the query prefetch
-     * tree.
-     *
-     * @return this object
-     */
-    public ObjectSelect<T> prefetch(String path, int semantics) {
-        if (path == null) {
-            return this;
-        }
-        metaData.addPrefetch(path, semantics);
-        return this;
-    }
-
-    /**
-     * Resets query fetch limit - a parameter that defines max number of 
objects
-     * that should be ever be fetched from the database.
-     */
-    public ObjectSelect<T> limit(int fetchLimit) {
-        this.metaData.setFetchLimit(fetchLimit);
-        return this;
-    }
-
-    /**
-     * Resets query fetch offset - a parameter that defines how many objects
-     * should be skipped when reading data from the database.
-     */
-    public ObjectSelect<T> offset(int fetchOffset) {
-        this.metaData.setFetchOffset(fetchOffset);
-        return this;
-    }
-
-    /**
-     * Resets query page size. A non-negative page size enables query result
-     * pagination that saves memory and processing time for large lists if only
-     * parts of the result are ever going to be accessed.
-     */
-    public ObjectSelect<T> pageSize(int pageSize) {
-        this.metaData.setPageSize(pageSize);
-        return this;
-    }
-
-    /**
-     * Sets fetch size of the PreparedStatement generated for this query. Only
-     * non-negative values would change the default size.
-     *
-     * @see Statement#setFetchSize(int)
-     */
-    public ObjectSelect<T> statementFetchSize(int size) {
-        this.metaData.setStatementFetchSize(size);
-        return this;
-    }
-
-    /**
-     * Sets query timeout for PreparedStatement generated for this query.
-     *
-     * @see Statement#setQueryTimeout(int)
-     * @since 4.2
-     */
-    public ObjectSelect<T> queryTimeout(int timeout) {
-        this.metaData.setQueryTimeout(timeout);
-        return this;
-    }
-
-    public ObjectSelect<T> cacheStrategy(QueryCacheStrategy strategy) {
-        setCacheStrategy(strategy);
-        setCacheGroup(null);
-        return this;
-    }
-
-    public ObjectSelect<T> cacheStrategy(QueryCacheStrategy strategy, String 
cacheGroup) {
-        return cacheStrategy(strategy).cacheGroup(cacheGroup);
-    }
-
-    public ObjectSelect<T> cacheGroup(String cacheGroup) {
-        setCacheGroup(cacheGroup);
-        return this;
-    }
-
-    /**
-     * Instructs Cayenne to look for query results in the "local" cache when
-     * running the query. This is a short-hand notation for:
-     * <p>
-     * <pre>
-     * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
-     * </pre>
-     */
-    public ObjectSelect<T> localCache(String cacheGroup) {
-        return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
-    }
-
-    /**
-     * Instructs Cayenne to look for query results in the "local" cache when
-     * running the query. This is a short-hand notation for:
-     * <p>
-     * <pre>
-     * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
-     * </pre>
-     */
-    public ObjectSelect<T> localCache() {
-        return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
-    }
-
-    /**
-     * Instructs Cayenne to look for query results in the "shared" cache when
-     * running the query. This is a short-hand notation for:
-     * <p>
-     * <pre>
-     * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
-     * </pre>
-     */
-    public ObjectSelect<T> sharedCache(String cacheGroup) {
-        return cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
-    }
-
-    /**
-     * Instructs Cayenne to look for query results in the "shared" cache when
-     * running the query. This is a short-hand notation for:
-     * <p>
-     * <pre>
-     * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
-     * </pre>
-     */
-    public ObjectSelect<T> sharedCache() {
-        return cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
-    }
-
     /**
      * Forces query to fetch DataRows. This automatically changes whatever
      * result type was set previously to "DataRow".
@@ -666,11 +327,6 @@ public class ObjectSelect<T> extends FluentSelect<T> 
implements ParameterizedQue
         return context.selectFirst(limit(1));
     }
 
-    @Override
-    public boolean isFetchingDataRows() {
-        return metaData.isFetchingDataRows();
-    }
-
     @Override
     public QueryMetadata getMetaData(EntityResolver resolver) {
         Object root = resolveRoot(resolver);
@@ -679,7 +335,7 @@ public class ObjectSelect<T> extends FluentSelect<T> 
implements ParameterizedQue
     }
 
     @Override
-    protected BaseQueryMetadata getBaseMetaData() {
+    protected ObjectSelectMetadata getBaseMetaData() {
         return metaData;
     }
 
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelectMetadata.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelectMetadata.java
index 164a7afb8..451480ea7 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelectMetadata.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelectMetadata.java
@@ -60,7 +60,7 @@ class ObjectSelectMetadata extends BaseQueryMetadata {
                return false;
        }
 
-       protected String makeCacheKey(FluentSelect<?> query, EntityResolver 
resolver) {
+       protected String makeCacheKey(FluentSelect<?, ?> query, EntityResolver 
resolver) {
 
                // create a unique key based on entity or columns, qualifier, 
ordering, fetch offset and limit
 
@@ -128,27 +128,27 @@ class ObjectSelectMetadata extends BaseQueryMetadata {
                return key.toString();
        }
 
-       protected void resolveAutoAliases(FluentSelect<?> query) {
+       protected void resolveAutoAliases(FluentSelect<?, ?> query) {
                resolveQualifierAliases(query);
         resolveOrderingAliases(query);
                resolveHavingQualifierAliases(query);
        }
 
-       protected void resolveQualifierAliases(FluentSelect<?> query) {
+       protected void resolveQualifierAliases(FluentSelect<?, ?> query) {
                Expression qualifier = query.getWhere();
                if (qualifier != null) {
                        resolveAutoAliases(qualifier);
                }
        }
 
-       protected void resolveHavingQualifierAliases(FluentSelect<?> query) {
+       protected void resolveHavingQualifierAliases(FluentSelect<?, ?> query) {
                Expression havingQualifier = query.getHaving();
                if(havingQualifier != null) {
                        resolveAutoAliases(havingQualifier);
                }
        }
 
-       protected void resolveOrderingAliases(FluentSelect<?> query) {
+       protected void resolveOrderingAliases(FluentSelect<?, ?> query) {
         Collection<Ordering> orderings = query.getOrderings();
         if(orderings != null) {
             for(Ordering ordering : orderings) {
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLActionVisitor.java 
b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLActionVisitor.java
index 02c586b13..e2f423f98 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLActionVisitor.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLActionVisitor.java
@@ -41,7 +41,7 @@ public interface SQLActionVisitor {
      * Creates an action to execute a FluentSelect.
      * @since 4.2
      */
-    <T> SQLAction objectSelectAction(FluentSelect<T> query);
+    <T> SQLAction objectSelectAction(FluentSelect<T,?> query);
 
     /**
      * Creates an action to execute a SQLTemplate.
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 ad336d041..2da4f1a11 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
@@ -107,10 +107,9 @@ public class ColumnSelectTest {
         assertNull(q.getWhere());
     }
 
-    @SuppressWarnings("unchecked")
     @Test
     public void columns() {
-        ColumnSelect q = new ColumnSelect();
+        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());
@@ -124,7 +123,7 @@ public class ColumnSelectTest {
 
     @Test
     public void havingExpression() {
-        ColumnSelect q = new ColumnSelect();
+        ColumnSelect<?> q = new ColumnSelect<>();
         assertNull(q.getHaving());
         assertNull(q.getWhere());
 
@@ -141,7 +140,7 @@ public class ColumnSelectTest {
 
     @Test
     public void havingString() {
-        ColumnSelect q = new ColumnSelect();
+        ColumnSelect<?> q = new ColumnSelect<>();
         assertNull(q.getHaving());
         assertNull(q.getWhere());
 
@@ -158,7 +157,7 @@ public class ColumnSelectTest {
 
     @Test
     public void and() {
-        ColumnSelect q = new ColumnSelect();
+        ColumnSelect<?> q = new ColumnSelect<>();
         assertNull(q.getHaving());
         assertNull(q.getWhere());
 
@@ -177,7 +176,7 @@ public class ColumnSelectTest {
 
     @Test
     public void or() {
-        ColumnSelect q = new ColumnSelect();
+        ColumnSelect<?> q = new ColumnSelect<>();
         assertNull(q.getHaving());
         assertNull(q.getWhere());
 

Reply via email to