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/17 08:10:40 UTC

cayenne git commit: - Fix erroneous return type in case of single parameter in ColumnSelect.columns() method - Require at least one Property in columns() method - QueryMetadata and its descendants cleanup

Repository: cayenne
Updated Branches:
  refs/heads/master e8ef29608 -> c4109dbe1


 - Fix erroneous return type in case of single parameter in ColumnSelect.columns() method
 - Require at least one Property in columns() method
 - QueryMetadata and its descendants cleanup


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

Branch: refs/heads/master
Commit: c4109dbe1dcabb28b6536ee5b89faf81d2ba6811
Parents: e8ef296
Author: Nikita Timofeev <st...@gmail.com>
Authored: Tue Jan 17 11:02:51 2017 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Tue Jan 17 11:02:51 2017 +0300

----------------------------------------------------------------------
 .../cayenne/lifecycle/id/StringIdQuery.java     |   5 +
 .../apache/cayenne/access/DataDomainQuery.java  |   9 +-
 .../cayenne/access/DataDomainQueryAction.java   |   3 +-
 .../access/ObjectsFromDataRowsQuery.java        |   5 +
 .../jdbc/reader/DefaultRowReaderFactory.java    |   4 +-
 .../apache/cayenne/cache/NestedQueryCache.java  | 116 +---------------
 .../apache/cayenne/query/BaseQueryMetadata.java |   8 ++
 .../org/apache/cayenne/query/ColumnSelect.java  |  81 +++++++----
 .../cayenne/query/DefaultQueryMetadata.java     |   8 ++
 .../org/apache/cayenne/query/ObjectSelect.java  |  10 +-
 .../org/apache/cayenne/query/QueryMetadata.java |  33 +++--
 .../cayenne/query/QueryMetadataProxy.java       | 135 +++++++++++++++++++
 .../cayenne/query/QueryMetadataWrapper.java     |  76 ++---------
 .../org/apache/cayenne/query/SelectQuery.java   |  29 ++++
 .../cayenne/query/SelectQueryMetadata.java      |  10 ++
 .../apache/cayenne/remote/IncrementalQuery.java |  82 +----------
 .../cayenne/remote/IncrementalSelectQuery.java  |  83 +-----------
 .../org/apache/cayenne/remote/RangeQuery.java   |  17 +--
 .../apache/cayenne/query/ColumnSelectTest.java  |   8 --
 .../apache/cayenne/query/MockQueryMetadata.java |   5 +
 .../cayenne/query/ObjectSelect_RunIT.java       |  10 +-
 21 files changed, 329 insertions(+), 408 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/id/StringIdQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/id/StringIdQuery.java b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/id/StringIdQuery.java
index aa98f02..a066a4c 100644
--- a/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/id/StringIdQuery.java
+++ b/cayenne-lifecycle/src/main/java/org/apache/cayenne/lifecycle/id/StringIdQuery.java
@@ -155,6 +155,11 @@ public class StringIdQuery implements Query {
                 return null;
             }
 
+            @Override
+            public boolean isSingleResultSetMapping() {
+                return false;
+            }
+
             public Query getOrginatingQuery() {
                 return null;
             }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQuery.java
index 2200f24..e341cdf 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQuery.java
@@ -21,7 +21,6 @@ package org.apache.cayenne.access;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.EntityResolver;
@@ -56,6 +55,14 @@ class DataDomainQuery implements Query, QueryMetadata {
         return null;
     }
 
+    /**
+     * @since 4.0
+     */
+    @Override
+    public boolean isSingleResultSetMapping() {
+        return false;
+    }
+
     public Query getOrginatingQuery() {
         return null;
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
index e1ef870..5e9e8bb 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java
@@ -487,8 +487,7 @@ class DataDomainQueryAction implements QueryRouter, OperationObserver {
                 if (rsMapping == null) {
                     converter = new SingleObjectConversionStrategy();
                 } else {
-
-                    if (rsMapping.size() == 1) {
+                    if (metadata.isSingleResultSetMapping()) {
                         if (rsMapping.get(0) instanceof EntityResultSegment) {
                             converter = new SingleObjectConversionStrategy();
                         } else {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectsFromDataRowsQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectsFromDataRowsQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectsFromDataRowsQuery.java
index e564f02..5a94fcd 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectsFromDataRowsQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/ObjectsFromDataRowsQuery.java
@@ -140,6 +140,11 @@ class ObjectsFromDataRowsQuery implements Query, QueryMetadata {
         return null;
     }
 
+    @Override
+    public boolean isSingleResultSetMapping() {
+        return false;
+    }
+
     public int getStatementFetchSize() {
         return 0;
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
index 8776516..b9cb766 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
@@ -60,7 +60,9 @@ public class DefaultRowReaderFactory implements RowReaderFactory {
 		int resultWidth = rsMapping.size();
 		if (resultWidth == 0) {
 			throw new CayenneRuntimeException("Empty result descriptor");
-		} else if (resultWidth == 1) {
+		}
+
+		if (queryMetadata.isSingleResultSetMapping()) {
 
 			Object segment = rsMapping.get(0);
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/main/java/org/apache/cayenne/cache/NestedQueryCache.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/cache/NestedQueryCache.java b/cayenne-server/src/main/java/org/apache/cayenne/cache/NestedQueryCache.java
index c4b9472..ec64aad 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/cache/NestedQueryCache.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/cache/NestedQueryCache.java
@@ -18,18 +18,10 @@
  ****************************************************************/
 package org.apache.cayenne.cache;
 
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.Procedure;
-import org.apache.cayenne.query.PrefetchTreeNode;
-import org.apache.cayenne.query.Query;
-import org.apache.cayenne.query.QueryCacheStrategy;
 import org.apache.cayenne.query.QueryMetadata;
-import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.query.QueryMetadataProxy;
 
 import java.util.List;
-import java.util.Map;
 
 /**
  * A {@link QueryCache} wrapper that introduces a key namespace on top of a
@@ -129,105 +121,11 @@ public class NestedQueryCache implements QueryCache {
     }
 
     private QueryMetadata qualifiedMetadata(QueryMetadata md) {
-        return new QualifiedKeyQueryMetadata(md);
-    }
-
-    final class QualifiedKeyQueryMetadata implements QueryMetadata {
-
-        private QueryMetadata mdDelegate;
-
-        QualifiedKeyQueryMetadata(QueryMetadata mdDelegate) {
-            this.mdDelegate = mdDelegate;
-        }
-
-        @Override
-        public String[] getCacheGroups() {
-            return mdDelegate.getCacheGroups();
-        }
-
-        @Override
-        public String getCacheKey() {
-            return qualifiedKey(mdDelegate.getCacheKey());
-        }
-
-        @Override
-        public QueryCacheStrategy getCacheStrategy() {
-            return mdDelegate.getCacheStrategy();
-        }
-
-        @Override
-        public ClassDescriptor getClassDescriptor() {
-            return mdDelegate.getClassDescriptor();
-        }
-
-        @Override
-        public DataMap getDataMap() {
-            return mdDelegate.getDataMap();
-        }
-
-        @Override
-        public DbEntity getDbEntity() {
-            return mdDelegate.getDbEntity();
-        }
-
-        @Override
-        public int getFetchLimit() {
-            return mdDelegate.getFetchLimit();
-        }
-
-        @Override
-        public int getFetchOffset() {
-            return mdDelegate.getFetchOffset();
-        }
-
-        @Override
-        public ObjEntity getObjEntity() {
-            return mdDelegate.getObjEntity();
-        }
-
-        @Override
-        public Query getOrginatingQuery() {
-            return mdDelegate.getOrginatingQuery();
-        }
-
-        @Override
-        public int getPageSize() {
-            return mdDelegate.getPageSize();
-        }
-
-        @Override
-        public PrefetchTreeNode getPrefetchTree() {
-            return mdDelegate.getPrefetchTree();
-        }
-
-        @Override
-        public Map<String, String> getPathSplitAliases() {
-            return mdDelegate.getPathSplitAliases();
-        }
-
-        @Override
-        public Procedure getProcedure() {
-            return mdDelegate.getProcedure();
-        }
-
-        @Override
-        public List<Object> getResultSetMapping() {
-            return mdDelegate.getResultSetMapping();
-        }
-
-        @Override
-        public boolean isFetchingDataRows() {
-            return mdDelegate.isFetchingDataRows();
-        }
-
-        @Override
-        public boolean isRefreshingObjects() {
-            return mdDelegate.isRefreshingObjects();
-        }
-
-        @Override
-        public int getStatementFetchSize() {
-            return mdDelegate.getStatementFetchSize();
-        }
+        return new QueryMetadataProxy(md) {
+            @Override
+            public String getCacheKey() {
+                return qualifiedKey(mdDelegate.getCacheKey());
+            }
+        };
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
index d85b85a..9c13e44 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
@@ -297,6 +297,14 @@ class BaseQueryMetadata implements QueryMetadata, XMLSerializable, Serializable
 	}
 
 	/**
+	 * @since 4.0
+	 */
+	@Override
+	public boolean isSingleResultSetMapping() {
+		return resultSetMapping != null && resultSetMapping.size() == 1;
+	}
+
+	/**
 	 * @since 1.2
 	 */
 	public PrefetchTreeNode getPrefetchTree() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/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 9aa2667..4058c28 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
@@ -20,8 +20,8 @@
 package org.apache.cayenne.query;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
@@ -29,29 +29,31 @@ 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 selecting query providing individual properties based on the root object.
- *
- * </p>
- * <p>
- *     It can be properties of the object itself or some function calls (including aggregate functions)
- * </p>
- * <p>
- * Usage examples:
- * <pre>
- *      // selecting list of names:
+ *     It can be properties of the object itself, properties of related entities
+ *     or some function calls (including aggregate functions).
+ * </p><p>
+ * Usage examples: <pre>
+ *      // select list of names:
  *      List&lt;String&gt; names = ColumnSelect.query(Artist.class, Artist.ARTIST_NAME).select(context);
  *
- *      // selecting count:
- *      long count = ColumnSelect.query(Artist.class, Property.COUNT).selectOne();
- * </pre>
- * </p>
+ *      // select count:
+ *      Property<Long> countProperty = Property.create(FunctionExpressionFactory.countExp(), Long.class);
+ *      long count = ColumnSelect.query(Artist.class, countProperty).selectOne();
+ *
+ *      // select only required properties of an entity:
+ *      List&lt;Object[]&gt; data = ColumnSelect.query(Artist.class, Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH)
+ *                                  .where(Artist.ARTIST_NAME.like("Picasso%))
+ *                                  .select(context);
+ * </pre></p>
  * @since 4.0
  */
 public class ColumnSelect<T> extends FluentSelect<T, ColumnSelect<T>> {
 
     private Collection<Property<?>> columns;
     private boolean havingExpressionIsActive = false;
+    private boolean singleColumn = true;
     private Expression having;
 
     /**
@@ -74,16 +76,20 @@ public class ColumnSelect<T> extends FluentSelect<T, ColumnSelect<T>> {
     /**
      *
      * @param entityType base persistent class that will be used as a root for this query
-     * @param columns columns to select
+     * @param firstColumn column to select
+     * @param otherColumns columns to select
      */
-    public static ColumnSelect<Object[]> query(Class<?> entityType, Property<?>... columns) {
-        return new ColumnSelect<Object[]>().entityType(entityType).columns(columns);
+    public static ColumnSelect<Object[]> query(Class<?> entityType, Property<?> firstColumn, Property<?>... otherColumns) {
+        return new ColumnSelect<Object[]>().entityType(entityType).columns(firstColumn, otherColumns);
     }
 
     protected ColumnSelect() {
         super();
     }
 
+    /**
+     * Copy constructor to convert ObjectSelect to ColumnSelect
+     */
     protected ColumnSelect(ObjectSelect<T> select) {
         super();
         this.entityType = select.entityType;
@@ -105,6 +111,7 @@ public class ColumnSelect<T> extends FluentSelect<T, ColumnSelect<T>> {
         SelectQuery<?> replacement = (SelectQuery)super.createReplacementQuery(resolver);
         replacement.setColumns(columns);
         replacement.setHavingQualifier(having);
+        replacement.setCanReturnScalarValue(singleColumn);
         return replacement;
     }
 
@@ -119,22 +126,37 @@ public class ColumnSelect<T> extends FluentSelect<T, ColumnSelect<T>> {
      *                                    .select(context);
      * </pre>
      *
-     * @param properties array of properties to select
+     * @param firstProperty first property
+     * @param otherProperties array of properties to select
      * @see ColumnSelect#column(Property)
+     * @see ColumnSelect#columns(Collection)
      */
     @SuppressWarnings("unchecked")
-    public ColumnSelect<Object[]> columns(Property<?>... properties) {
-        if (properties == null || properties.length == 0) {
-            return (ColumnSelect<Object[]>)this;
+    public ColumnSelect<Object[]> columns(Property<?> firstProperty, Property<?>... otherProperties) {
+        if (columns == null) {
+            columns = new ArrayList<>(otherProperties.length + 1);
         }
-
-        return columns(Arrays.asList(properties));
+        columns.add(firstProperty);
+        Collections.addAll(columns, otherProperties);
+        singleColumn = false;
+        return (ColumnSelect<Object[]>)this;
     }
 
+    /**
+     * <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>
+     * <p>
+     * @param properties collection of properties, <b>must</b> contain at least one element
+     * @see ColumnSelect#columns(Property, Property[])
+     */
     @SuppressWarnings("unchecked")
     public ColumnSelect<Object[]> columns(Collection<Property<?>> properties) {
-        if (properties == null || properties.isEmpty()) {
-            return (ColumnSelect<Object[]>)this;
+        if (properties == null){
+            throw new NullPointerException("properties is null");
+        }
+        if (properties.isEmpty()) {
+            throw new IllegalArgumentException("properties must contain at least one element");
         }
 
         if (this.columns == null) {
@@ -142,6 +164,7 @@ public class ColumnSelect<T> extends FluentSelect<T, ColumnSelect<T>> {
         }
 
         columns.addAll(properties);
+        singleColumn = false;
         return (ColumnSelect<Object[]>)this;
     }
 
@@ -149,16 +172,16 @@ public class ColumnSelect<T> extends FluentSelect<T, ColumnSelect<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[])} method as subsequent
+     * <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[])} method.</p>
+     * {@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[])
+     * @see ColumnSelect#columns(Property, Property[])
      */
     @SuppressWarnings("unchecked")
     protected  <E> ColumnSelect<E> column(Property<E> property) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/main/java/org/apache/cayenne/query/DefaultQueryMetadata.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/DefaultQueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/DefaultQueryMetadata.java
index 183b30f..f399023 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/DefaultQueryMetadata.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/DefaultQueryMetadata.java
@@ -66,6 +66,14 @@ class DefaultQueryMetadata implements QueryMetadata {
     }
 
     /**
+     * @since 4.0
+     */
+    @Override
+    public boolean isSingleResultSetMapping() {
+        return false;
+    }
+
+    /**
      * @since 3.0
      */
     public Query getOrginatingQuery() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/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 cf960b9..f4cbd0c 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
@@ -158,24 +158,24 @@ public class ObjectSelect<T> extends FluentSelect<T, ObjectSelect<T>> {
      * @see ColumnSelect#column(Property)
      */
     @SuppressWarnings("unchecked")
-    public ColumnSelect<Object[]> columns(Property<?>... properties) {
-        return new ColumnSelect<>(this).columns(properties);
+    public ColumnSelect<Object[]> columns(Property<?> firstProperty, Property<?>... properties) {
+        return new ColumnSelect<>(this).columns(firstProperty, properties);
     }
 
     /**
      * <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[])} method as subsequent
+     * <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[])} method.</p>
+     * {@link ColumnSelect#columns(Property, Property[])} method.</p>
      * <p>
      * <pre>
      * List&lt;String&gt; names = ObjectSelect.query(Artist.class).column(Artist.ARTIST_NAME).select(context);
      * </pre>
      *
      * @param property single property to select
-     * @see ColumnSelect#columns(Property[])
+     * @see ColumnSelect#columns(Property, Property[])
      */
     @SuppressWarnings("unchecked")
     public <E> ColumnSelect<E> column(Property<E> property) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java
index 2b0962f..a1dc464 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadata.java
@@ -39,13 +39,13 @@ public interface QueryMetadata {
      * Defines the name of the property for the query {@link #getFetchLimit() fetch limit}
      * .
      */
-    public static final String FETCH_LIMIT_PROPERTY = "cayenne.GenericSelectQuery.fetchLimit";
+    String FETCH_LIMIT_PROPERTY = "cayenne.GenericSelectQuery.fetchLimit";
 
     /**
      * Defines default query fetch limit, which is zero, meaning that all matching rows
      * should be fetched.
      */
-    public static final int FETCH_LIMIT_DEFAULT = 0;
+    int FETCH_LIMIT_DEFAULT = 0;
 
     /**
      * Defines the name of the property for the query {@link #getFetchOffset() fetch
@@ -53,7 +53,7 @@ public interface QueryMetadata {
      * 
      * @since 3.0
      */
-    public static final String FETCH_OFFSET_PROPERTY = "cayenne.GenericSelectQuery.fetchOffset";
+    String FETCH_OFFSET_PROPERTY = "cayenne.GenericSelectQuery.fetchOffset";
 
     /**
      * Defines default query fetch start index, which is 0, meaning that matching rows
@@ -61,31 +61,31 @@ public interface QueryMetadata {
      * 
      * @since 3.0
      */
-    public static final int FETCH_OFFSET_DEFAULT = 0;
+    int FETCH_OFFSET_DEFAULT = 0;
 
     /**
      * Defines the name of the property for the query {@link #getPageSize() page size}.
      */
-    public static final String PAGE_SIZE_PROPERTY = "cayenne.GenericSelectQuery.pageSize";
+    String PAGE_SIZE_PROPERTY = "cayenne.GenericSelectQuery.pageSize";
 
     /**
      * Defines default query page size, which is zero for no pagination.
      */
-    public static final int PAGE_SIZE_DEFAULT = 0;
+    int PAGE_SIZE_DEFAULT = 0;
 
-    public static final String FETCHING_DATA_ROWS_PROPERTY = "cayenne.GenericSelectQuery.fetchingDataRows";
+    String FETCHING_DATA_ROWS_PROPERTY = "cayenne.GenericSelectQuery.fetchingDataRows";
 
-    public static final boolean FETCHING_DATA_ROWS_DEFAULT = false;
+    boolean FETCHING_DATA_ROWS_DEFAULT = false;
 
     /**
      * @since 3.0
      */
-    public static final String CACHE_STRATEGY_PROPERTY = "cayenne.GenericSelectQuery.cacheStrategy";
+    String CACHE_STRATEGY_PROPERTY = "cayenne.GenericSelectQuery.cacheStrategy";
 
     /**
      * @since 3.0
      */
-    public static final String CACHE_GROUPS_PROPERTY = "cayenne.GenericSelectQuery.cacheGroups";
+    String CACHE_GROUPS_PROPERTY = "cayenne.GenericSelectQuery.cacheGroups";
 
     /**
      * Defines the name of the property for the query {@link #getStatementFetchSize() fetch
@@ -93,7 +93,7 @@ public interface QueryMetadata {
      * 
      * @since 3.0
      */
-    public static final String STATEMENT_FETCH_SIZE_PROPERTY = "cayenne.GenericSelectQuery.statementFetchSize";
+    String STATEMENT_FETCH_SIZE_PROPERTY = "cayenne.GenericSelectQuery.statementFetchSize";
 
     /**
      * Defines default query fetch start index, which is 0, meaning that matching rows
@@ -101,7 +101,7 @@ public interface QueryMetadata {
      * 
      * @since 3.0
      */
-    public static final int STATEMENT_FETCH_SIZE_DEFAULT = 0;
+    int STATEMENT_FETCH_SIZE_DEFAULT = 0;
 
     /**
      * @since 3.0
@@ -226,10 +226,17 @@ public interface QueryMetadata {
      * @since 3.0
      */
     List<Object> getResultSetMapping();
+
+    /**
+     * @return should the result be mapped to single object (scalar or entity)
+     * @see QueryMetadata#getResultSetMapping()
+     * @since 4.0
+     */
+    boolean isSingleResultSetMapping();
     
     /**
      * @return statement's fetch size
      * @since 3.0
      */
-    public int getStatementFetchSize();
+    int getStatementFetchSize();
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataProxy.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataProxy.java b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataProxy.java
new file mode 100644
index 0000000..2d8070c
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataProxy.java
@@ -0,0 +1,135 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.query;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.Procedure;
+import org.apache.cayenne.reflect.ClassDescriptor;
+
+/**
+ * @since 4.0
+ */
+public class QueryMetadataProxy implements QueryMetadata {
+    protected QueryMetadata mdDelegate;
+
+    protected QueryMetadataProxy(QueryMetadata mdDelegate) {
+        this.mdDelegate = mdDelegate;
+    }
+
+    @Override
+    public String[] getCacheGroups() {
+        return mdDelegate.getCacheGroups();
+    }
+
+    @Override
+    public String getCacheKey() {
+        return mdDelegate.getCacheKey();
+    }
+
+    @Override
+    public QueryCacheStrategy getCacheStrategy() {
+        return mdDelegate.getCacheStrategy();
+    }
+
+    @Override
+    public ClassDescriptor getClassDescriptor() {
+        return mdDelegate.getClassDescriptor();
+    }
+
+    @Override
+    public DataMap getDataMap() {
+        return mdDelegate.getDataMap();
+    }
+
+    @Override
+    public DbEntity getDbEntity() {
+        return mdDelegate.getDbEntity();
+    }
+
+    @Override
+    public int getFetchLimit() {
+        return mdDelegate.getFetchLimit();
+    }
+
+    @Override
+    public int getFetchOffset() {
+        return mdDelegate.getFetchOffset();
+    }
+
+    @Override
+    public ObjEntity getObjEntity() {
+        return mdDelegate.getObjEntity();
+    }
+
+    @Override
+    public Query getOrginatingQuery() {
+        return mdDelegate.getOrginatingQuery();
+    }
+
+    @Override
+    public int getPageSize() {
+        return mdDelegate.getPageSize();
+    }
+
+    @Override
+    public PrefetchTreeNode getPrefetchTree() {
+        return mdDelegate.getPrefetchTree();
+    }
+
+    @Override
+    public Map<String, String> getPathSplitAliases() {
+        return mdDelegate.getPathSplitAliases();
+    }
+
+    @Override
+    public Procedure getProcedure() {
+        return mdDelegate.getProcedure();
+    }
+
+    @Override
+    public List<Object> getResultSetMapping() {
+        return mdDelegate.getResultSetMapping();
+    }
+
+    @Override
+    public boolean isSingleResultSetMapping() {
+        return mdDelegate.isSingleResultSetMapping();
+    }
+
+    @Override
+    public boolean isFetchingDataRows() {
+        return mdDelegate.isFetchingDataRows();
+    }
+
+    @Override
+    public boolean isRefreshingObjects() {
+        return mdDelegate.isRefreshingObjects();
+    }
+
+    @Override
+    public int getStatementFetchSize() {
+        return mdDelegate.getStatementFetchSize();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataWrapper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataWrapper.java b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataWrapper.java
index fc8ec68..aab9d55 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataWrapper.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/QueryMetadataWrapper.java
@@ -20,30 +20,22 @@
 package org.apache.cayenne.query;
 
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.Procedure;
-import org.apache.cayenne.reflect.ClassDescriptor;
-
 /**
  * A wrapper for a QueryMetadata instance allowing that may override a subset of metadata
  * properties.
  * 
  * @since 1.2
  */
-class QueryMetadataWrapper implements QueryMetadata {
+class QueryMetadataWrapper extends QueryMetadataProxy {
 
     static final String CACHE_KEY_PROPERTY = "QueryMetadataWrapper.CacheKey";
 
-    QueryMetadata info;
     Map<String, Object> overrides;
 
     public QueryMetadataWrapper(QueryMetadata info) {
-        this.info = info;
+        super(info);
     }
 
     /**
@@ -62,50 +54,9 @@ class QueryMetadataWrapper implements QueryMetadata {
         return overrides != null && overrides.containsKey(key);
     }
 
-    /**
-     * @since 3.0
-     */
-    public List<Object> getResultSetMapping() {
-        return info.getResultSetMapping();
-    }
-
-    public DataMap getDataMap() {
-        return info.getDataMap();
-    }
-
-    public Procedure getProcedure() {
-        return info.getProcedure();
-    }
-
-    /**
-     * @since 3.0
-     */
-    public Map<String, String> getPathSplitAliases() {
-        return info.getPathSplitAliases();
-    }
-
-    public DbEntity getDbEntity() {
-        return info.getDbEntity();
-    }
-
-    public ObjEntity getObjEntity() {
-        return info.getObjEntity();
-    }
-
-    public Query getOrginatingQuery() {
-        return info.getOrginatingQuery();
-    }
-
-    /**
-     * @since 3.0
-     */
-    public ClassDescriptor getClassDescriptor() {
-        return info.getClassDescriptor();
-    }
-
     public String getCacheKey() {
         return (overrideExists(CACHE_KEY_PROPERTY)) ? (String) overrides
-                .get(CACHE_KEY_PROPERTY) : info.getCacheKey();
+                .get(CACHE_KEY_PROPERTY) : super.getCacheKey();
     }
 
     /**
@@ -115,18 +66,18 @@ class QueryMetadataWrapper implements QueryMetadata {
         return (overrideExists(QueryMetadata.CACHE_STRATEGY_PROPERTY))
                 ? (QueryCacheStrategy) overrides
                         .get(QueryMetadata.CACHE_STRATEGY_PROPERTY)
-                : info.getCacheStrategy();
+                : super.getCacheStrategy();
     }
 
     public String[] getCacheGroups() {
         return (overrideExists(QueryMetadata.CACHE_GROUPS_PROPERTY))
                 ? (String[]) overrides.get(QueryMetadata.CACHE_GROUPS_PROPERTY)
-                : info.getCacheGroups();
+                : super.getCacheGroups();
     }
 
     public boolean isFetchingDataRows() {
         if (!overrideExists(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY)) {
-            return info.isFetchingDataRows();
+            return super.isFetchingDataRows();
         }
 
         Boolean b = (Boolean) overrides.get(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY);
@@ -139,36 +90,29 @@ class QueryMetadataWrapper implements QueryMetadata {
 
     public int getPageSize() {
         if (!overrideExists(QueryMetadata.PAGE_SIZE_PROPERTY)) {
-            return info.getPageSize();
+            return super.getPageSize();
         }
 
         Number n = (Number) overrides.get(QueryMetadata.PAGE_SIZE_PROPERTY);
         return n != null ? n.intValue() : 0;
     }
 
-    public int getFetchOffset() {
-        return info.getFetchOffset();
-    }
-
     public int getFetchLimit() {
         if (!overrideExists(QueryMetadata.FETCH_LIMIT_PROPERTY)) {
-            return info.getFetchLimit();
+            return super.getFetchLimit();
         }
 
         Number n = (Number) overrides.get(QueryMetadata.FETCH_LIMIT_PROPERTY);
         return n != null ? n.intValue() : 0;
     }
 
-    public PrefetchTreeNode getPrefetchTree() {
-        return info.getPrefetchTree();
-    }
-
     public int getStatementFetchSize() {
         if (!overrideExists(QueryMetadata.STATEMENT_FETCH_SIZE_PROPERTY)) {
-            return info.getPageSize();
+            return super.getPageSize();
         }
 
         Number n = (Number) overrides.get(QueryMetadata.STATEMENT_FETCH_SIZE_PROPERTY);
         return n != null ? n.intValue() : 0;
     }
+
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
index 9c06d05..ceb86dc 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
@@ -69,6 +69,14 @@ public class SelectQuery<T> extends AbstractQuery implements ParameterizedQuery,
 	 */
 	protected Expression havingQualifier;
 
+	/**
+	 * <p>Flag that indicates whether this query can return single value or
+	 * it should always return some complex data (Object[] for now)</p>
+	 * <p>Default value is <b>true</b></p>
+	 * @since 4.0
+	 */
+	protected boolean canReturnScalarValue = true;
+
 	SelectQueryMetadata metaData = new SelectQueryMetadata();
 
 	/**
@@ -868,6 +876,7 @@ public class SelectQuery<T> extends AbstractQuery implements ParameterizedQuery,
 
 	/**
 	 * @since 4.0
+	 * @see SelectQuery#setCanReturnScalarValue(boolean)
 	 */
 	public void setColumns(Collection<Property<?>> columns) {
 		this.columns = columns;
@@ -884,6 +893,26 @@ public class SelectQuery<T> extends AbstractQuery implements ParameterizedQuery,
 	}
 
 	/**
+	 * <p>Flag that indicates whether this query can return single  value or
+	 * it should always return some complex data (Object[] for now)</p>
+	 * <p>Default value is <b>true</b></p>
+	 * @param canReturnScalarValue can this query return single value
+	 * @since 4.0
+	 * @see SelectQuery#setColumns
+	 */
+	public void setCanReturnScalarValue(boolean canReturnScalarValue) {
+		this.canReturnScalarValue = canReturnScalarValue;
+	}
+
+	/**
+	 * @return can this query return single value
+	 * @since 4.0
+	 */
+	public boolean canReturnScalarValue() {
+		return canReturnScalarValue;
+	}
+
+	/**
 	 * @since 4.0
 	 */
 	public Collection<Property<?>> getColumns() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
index 741190e..9519bb6 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQueryMetadata.java
@@ -38,6 +38,7 @@ class SelectQueryMetadata extends BaseQueryMetadata {
 	private static final long serialVersionUID = 7465922769303943945L;
 	
 	Map<String, String> pathSplitAliases;
+	boolean isSingleResultSetMapping;
 
 	@Override
 	void copyFromInfo(QueryMetadata info) {
@@ -57,6 +58,7 @@ class SelectQueryMetadata extends BaseQueryMetadata {
 
 			resolveAutoAliases(query);
 			buildResultSetMappingForColumns(query, resolver);
+			isSingleResultSetMapping = query.canReturnScalarValue() && super.isSingleResultSetMapping();
 
 			return true;
 		}
@@ -186,4 +188,12 @@ class SelectQueryMetadata extends BaseQueryMetadata {
 		}
 		resultSetMapping = result.getResolvedComponents(resolver);
 	}
+
+	/**
+	 * @since 4.0
+	 */
+	@Override
+	public boolean isSingleResultSetMapping() {
+		return isSingleResultSetMapping;
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java
index 94322d2..57d6d77 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalQuery.java
@@ -19,23 +19,14 @@
 
 package org.apache.cayenne.remote;
 
-import java.util.List;
-import java.util.Map;
-
-import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.Procedure;
-import org.apache.cayenne.query.PrefetchTreeNode;
 import org.apache.cayenne.query.Query;
-import org.apache.cayenne.query.QueryCacheStrategy;
 import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.QueryMetadataProxy;
 import org.apache.cayenne.query.QueryRouter;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.query.SQLActionVisitor;
-import org.apache.cayenne.reflect.ClassDescriptor;
 
 /**
  * A client wrapper for the incremental query that overrides the metadata to ensure that
@@ -61,9 +52,7 @@ class IncrementalQuery implements Query {
         // (IncrementalFaultList interception happens before cache interception). So
         // overriding caching settings in the metadata will only affect
         // ClientServerChannel behavior
-
-        return new QueryMetadata() {
-
+        return new QueryMetadataProxy(metadata) {
             public Query getOrginatingQuery() {
                 return null;
             }
@@ -71,73 +60,6 @@ class IncrementalQuery implements Query {
             public String getCacheKey() {
                 return cacheKey;
             }
-
-            public List<Object> getResultSetMapping() {
-                return metadata.getResultSetMapping();
-            }
-
-            public String[] getCacheGroups() {
-                return metadata.getCacheGroups();
-            }
-
-            /**
-             * @since 3.0
-             */
-            public QueryCacheStrategy getCacheStrategy() {
-                return metadata.getCacheStrategy();
-            }
-
-            public DataMap getDataMap() {
-                return metadata.getDataMap();
-            }
-
-            public DbEntity getDbEntity() {
-                return metadata.getDbEntity();
-            }
-
-            public int getFetchLimit() {
-                return metadata.getFetchLimit();
-            }
-
-            public int getFetchOffset() {
-                return metadata.getFetchOffset();
-            }
-
-            public ObjEntity getObjEntity() {
-                return metadata.getObjEntity();
-            }
-
-            public ClassDescriptor getClassDescriptor() {
-                return metadata.getClassDescriptor();
-            }
-
-            public int getPageSize() {
-                return metadata.getPageSize();
-            }
-
-            public PrefetchTreeNode getPrefetchTree() {
-                return metadata.getPrefetchTree();
-            }
-
-            public Procedure getProcedure() {
-                return metadata.getProcedure();
-            }
-
-            public Map<String, String> getPathSplitAliases() {
-                return metadata.getPathSplitAliases();
-            }
-
-            public boolean isFetchingDataRows() {
-                return metadata.isFetchingDataRows();
-            }
-
-            public boolean isRefreshingObjects() {
-                return metadata.isRefreshingObjects();
-            }
-
-            public int getStatementFetchSize() {
-                return metadata.getStatementFetchSize();
-            }
         };
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
index 846b5d1..59da8e0 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
@@ -28,22 +28,17 @@ import org.apache.cayenne.ResultIterator;
 import org.apache.cayenne.ResultIteratorCallback;
 import org.apache.cayenne.access.IncrementalFaultList;
 import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.Procedure;
 import org.apache.cayenne.query.Ordering;
 import org.apache.cayenne.query.PrefetchTreeNode;
 import org.apache.cayenne.query.Query;
-import org.apache.cayenne.query.QueryCacheStrategy;
 import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.QueryMetadataProxy;
 import org.apache.cayenne.query.QueryRouter;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.query.SQLActionVisitor;
 import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.query.SortOrder;
-import org.apache.cayenne.reflect.ClassDescriptor;
 import org.apache.cayenne.util.XMLEncoder;
 
 /**
@@ -75,9 +70,7 @@ class IncrementalSelectQuery<T> extends SelectQuery<T> {
 		// interception). So
 		// overriding caching settings in the metadata will only affect
 		// ClientServerChannel behavior
-
-		return new QueryMetadata() {
-
+		return new QueryMetadataProxy(metadata) {
 			public Query getOrginatingQuery() {
 				return null;
 			}
@@ -85,70 +78,6 @@ class IncrementalSelectQuery<T> extends SelectQuery<T> {
 			public String getCacheKey() {
 				return cacheKey;
 			}
-
-			public List<Object> getResultSetMapping() {
-				return metadata.getResultSetMapping();
-			}
-
-			public String[] getCacheGroups() {
-				return metadata.getCacheGroups();
-			}
-
-			public QueryCacheStrategy getCacheStrategy() {
-				return metadata.getCacheStrategy();
-			}
-
-			public DataMap getDataMap() {
-				return metadata.getDataMap();
-			}
-
-			public DbEntity getDbEntity() {
-				return metadata.getDbEntity();
-			}
-
-			public int getFetchLimit() {
-				return metadata.getFetchLimit();
-			}
-
-			public int getFetchOffset() {
-				return metadata.getFetchOffset();
-			}
-
-			public ObjEntity getObjEntity() {
-				return metadata.getObjEntity();
-			}
-
-			public ClassDescriptor getClassDescriptor() {
-				return metadata.getClassDescriptor();
-			}
-
-			public int getPageSize() {
-				return metadata.getPageSize();
-			}
-
-			public PrefetchTreeNode getPrefetchTree() {
-				return metadata.getPrefetchTree();
-			}
-
-			public Procedure getProcedure() {
-				return metadata.getProcedure();
-			}
-
-			public Map<String, String> getPathSplitAliases() {
-				return metadata.getPathSplitAliases();
-			}
-
-			public boolean isFetchingDataRows() {
-				return metadata.isFetchingDataRows();
-			}
-
-			public boolean isRefreshingObjects() {
-				return metadata.isRefreshingObjects();
-			}
-
-			public int getStatementFetchSize() {
-				return metadata.getStatementFetchSize();
-			}
 		};
 	}
 
@@ -188,7 +117,7 @@ class IncrementalSelectQuery<T> extends SelectQuery<T> {
 	}
 
 	@Override
-	public SelectQuery<T> createQuery(Map parameters) {
+	public SelectQuery<T> createQuery(Map<String, ?> parameters) {
 		return query.createQuery(parameters);
 	}
 
@@ -253,7 +182,7 @@ class IncrementalSelectQuery<T> extends SelectQuery<T> {
 	}
 
 	@Override
-	public void initWithProperties(Map properties) {
+	public void initWithProperties(Map<String, ?> properties) {
 		query.initWithProperties(properties);
 	}
 
@@ -273,12 +202,12 @@ class IncrementalSelectQuery<T> extends SelectQuery<T> {
 	}
 
 	@Override
-	public SelectQuery<T> queryWithParameters(Map parameters, boolean pruneMissing) {
+	public SelectQuery<T> queryWithParameters(Map<String, ?> parameters, boolean pruneMissing) {
 		return query.queryWithParameters(parameters, pruneMissing);
 	}
 
 	@Override
-	public SelectQuery<T> queryWithParameters(Map parameters) {
+	public SelectQuery<T> queryWithParameters(Map<String, ?> parameters) {
 		return query.queryWithParameters(parameters);
 	}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/main/java/org/apache/cayenne/remote/RangeQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/RangeQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/RangeQuery.java
index 4982d28..87ba3d3 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/RangeQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/remote/RangeQuery.java
@@ -22,16 +22,15 @@ package org.apache.cayenne.remote;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.Procedure;
-import org.apache.cayenne.query.PrefetchTreeNode;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.QueryCacheStrategy;
 import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.QueryMetadataProxy;
 import org.apache.cayenne.query.QueryRouter;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.query.SQLActionVisitor;
@@ -71,7 +70,7 @@ class RangeQuery implements Query {
     public QueryMetadata getMetaData(EntityResolver resolver) {
         final QueryMetadata originatingMetadata = originatingQuery.getMetaData(resolver);
 
-        return new QueryMetadata() {
+        return new QueryMetadataProxy(originatingMetadata) {
 
             public Query getOrginatingQuery() {
                 return originatingQuery;
@@ -81,6 +80,10 @@ class RangeQuery implements Query {
                 return null;
             }
 
+            public boolean isSingleResultSetMapping() {
+                return false;
+            }
+
             public String getCacheKey() {
                 return cacheKey;
             }
@@ -97,10 +100,6 @@ class RangeQuery implements Query {
                 return fetchLimit;
             }
 
-            public boolean isFetchingDataRows() {
-                return originatingMetadata.isFetchingDataRows();
-            }
-
             public int getPageSize() {
                 return 0;
             }
@@ -112,10 +111,6 @@ class RangeQuery implements Query {
                 return QueryCacheStrategy.getDefaultStrategy();
             }
 
-            public PrefetchTreeNode getPrefetchTree() {
-                return originatingMetadata.getPrefetchTree();
-            }
-
             public DataMap getDataMap() {
                 throw new UnsupportedOperationException();
             }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/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 c058916..9d4d057 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
@@ -62,8 +62,6 @@ public class ColumnSelectTest {
     public void columns() throws Exception {
         ColumnSelect q = ColumnSelect.query(Artist.class);
         assertNull(q.getColumns());
-        q.columns();
-        assertNull(q.getColumns());
         q.columns(Artist.ARTIST_NAME, Artist.PAINTING_ARRAY);
         assertEquals(Arrays.asList(Artist.ARTIST_NAME, Artist.PAINTING_ARRAY), q.getColumns());
 
@@ -154,11 +152,8 @@ public class ColumnSelectTest {
         assertEquals(null, q.getColumns());
 
         q.columns(Artist.ARTIST_NAME);
-        q.columns();
         q.columns(Artist.DATE_OF_BIRTH);
-        q.columns();
         q.columns(Artist.PAINTING_ARRAY);
-        q.columns();
 
         Collection<Property<?>> properties = Arrays.asList(Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH, Artist.PAINTING_ARRAY);
         assertEquals(properties, q.getColumns());
@@ -186,11 +181,8 @@ public class ColumnSelectTest {
         assertEquals(null, q.getColumns());
 
         q.column(Artist.ARTIST_NAME);
-        q.columns();
         q.column(Artist.DATE_OF_BIRTH);
-        q.columns();
         q.column(Artist.PAINTING_ARRAY);
-        q.columns();
 
         Collection<Property<?>> properties = Collections.<Property<?>>singletonList(Artist.PAINTING_ARRAY);
         assertEquals(properties, q.getColumns());

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/test/java/org/apache/cayenne/query/MockQueryMetadata.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/MockQueryMetadata.java b/cayenne-server/src/test/java/org/apache/cayenne/query/MockQueryMetadata.java
index 110a978..95f9b28 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/query/MockQueryMetadata.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/MockQueryMetadata.java
@@ -34,6 +34,11 @@ public class MockQueryMetadata implements QueryMetadata {
         return null;
     }
 
+    @Override
+    public boolean isSingleResultSetMapping() {
+        return false;
+    }
+
     public ObjEntity getObjEntity() {
         return null;
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c4109dbe/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_RunIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_RunIT.java b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_RunIT.java
index 30b4f0c..eb4095d 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_RunIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_RunIT.java
@@ -223,17 +223,15 @@ public class ObjectSelect_RunIT extends ServerCase {
 		assertEquals("artist1", a[4]);
 	}
 
-	@SuppressWarnings("ConstantConditions")
 	@Test
-	public void test_SelectFirst_EmptyColumns() throws Exception {
-		Object a = ObjectSelect.query(Artist.class)
-				.columns()
+	public void test_SelectFirst_SingleValueInColumns() throws Exception {
+		Object[] a = ObjectSelect.query(Artist.class)
+				.columns(Artist.ARTIST_NAME)
 				.where(Artist.ARTIST_NAME.like("artist%"))
 				.orderBy("db:ARTIST_ID")
 				.selectFirst(context);
 		assertNotNull(a);
-		assertTrue(a instanceof Artist);
-		assertEquals("artist1", ((Artist)a).getArtistName());
+		assertEquals("artist1", a[0]);
 	}
 
 	@Test