CAY-2465 New SelectTranslator implementation

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

Branch: refs/heads/master
Commit: f6b2dac9667343928324cd7ce364ee6e5df17275
Parents: eb0373e
Author: Nikita Timofeev <stari...@gmail.com>
Authored: Wed Jan 9 11:41:46 2019 +0300
Committer: Nikita Timofeev <stari...@gmail.com>
Committed: Wed Jan 9 11:41:46 2019 +0300

----------------------------------------------------------------------
 .../access/MixedResultIncrementalFaultList.java |   12 +-
 .../cayenne/access/jdbc/ColumnDescriptor.java   |    7 +
 .../access/sqlbuilder/AliasedNodeBuilder.java   |    6 +-
 .../access/sqlbuilder/ColumnNodeBuilder.java    |    7 +-
 .../cayenne/access/sqlbuilder/SQLBuilder.java   |   15 +
 .../access/sqlbuilder/ValueNodeBuilder.java     |    9 +-
 .../access/sqlbuilder/sqltree/AliasedNode.java  |   55 +
 .../access/sqlbuilder/sqltree/BetweenNode.java  |    4 +
 .../access/sqlbuilder/sqltree/ColumnNode.java   |    6 +-
 .../sqlbuilder/sqltree/ExpressionNode.java      |    4 +-
 .../access/sqlbuilder/sqltree/FunctionNode.java |    6 +-
 .../sqlbuilder/sqltree/OpExpressionNode.java    |    4 +
 .../access/sqlbuilder/sqltree/TextNode.java     |    4 +
 .../access/sqlbuilder/sqltree/ValueNode.java    |   27 +-
 .../translator/select/BaseColumnExtractor.java  |   44 +
 .../translator/select/BaseSQLTreeProcessor.java |  128 +++
 .../select/ColumnDescriptorStage.java           |   56 +
 .../translator/select/ColumnExtractor.java      |   33 +
 .../translator/select/ColumnExtractorStage.java |   54 +
 .../translator/select/ColumnSelectWrapper.java  |   84 ++
 .../select/CustomColumnSetExtractor.java        |  133 +++
 .../select/DataObjectMatchTranslator.java       |  152 ---
 .../select/DbEntityColumnExtractor.java         |   45 +
 .../translator/select/DbPathProcessor.java      |  108 ++
 .../select/DefaultQuotingAppendable.java        |   47 +
 .../select/DefaultSelectTranslator.java         | 1076 ++----------------
 .../select/DescriptorColumnExtractor.java       |  140 +++
 .../access/translator/select/DistinctStage.java |   71 ++
 .../access/translator/select/GroupByStage.java  |   54 +
 .../select/HavingTranslationStage.java          |   40 +
 .../translator/select/IdColumnExtractor.java    |   49 +
 .../access/translator/select/JoinStack.java     |  219 ----
 .../access/translator/select/JoinTreeNode.java  |  145 ---
 .../translator/select/LimitOffsetStage.java     |   37 +
 .../translator/select/ObjPathProcessor.java     |  132 +++
 .../translator/select/ObjectSelectWrapper.java  |   84 ++
 .../access/translator/select/OrderingStage.java |   81 ++
 .../translator/select/OrderingTranslator.java   |  127 ---
 .../translator/select/PathComponents.java       |   95 ++
 .../access/translator/select/PathProcessor.java |  129 +++
 .../select/PathTranslationResult.java           |   51 +
 .../translator/select/PathTranslator.java       |   60 +
 .../translator/select/PrefetchNodeStage.java    |  128 +++
 .../select/QualifierTranslationStage.java       |   74 ++
 .../translator/select/QualifierTranslator.java  | 1026 ++++++-----------
 .../translator/select/QueryAssembler.java       |  197 ----
 .../translator/select/QueryAssemblerHelper.java |  488 --------
 .../translator/select/ResultNodeDescriptor.java |  138 +++
 .../translator/select/SQLGenerationStage.java   |   44 +
 .../translator/select/SelectQueryWrapper.java   |   84 ++
 .../access/translator/select/TableTree.java     |  109 ++
 .../access/translator/select/TableTreeNode.java |   75 ++
 .../translator/select/TableTreeStage.java       |  104 ++
 .../select/TranslatableQueryWrapper.java        |   57 +
 .../translator/select/TranslationStage.java     |   27 +
 .../translator/select/TranslatorContext.java    |  245 ++++
 .../select/TrimmingQualifierTranslator.java     |   92 --
 .../org/apache/cayenne/dba/AutoAdapter.java     |    8 +-
 .../java/org/apache/cayenne/dba/DbAdapter.java  |   10 +-
 .../cayenne/dba/DefaultQuotingStrategy.java     |   48 +-
 .../org/apache/cayenne/dba/JdbcAdapter.java     |   25 +-
 .../org/apache/cayenne/dba/QuotingStrategy.java |   20 +-
 .../cayenne/dba/db2/DB2ActionBuilder.java       |    6 +
 .../org/apache/cayenne/dba/db2/DB2Adapter.java  |   12 +-
 .../cayenne/dba/db2/DB2QualifierTranslator.java |  174 ---
 .../cayenne/dba/db2/DB2SQLTreeProcessor.java    |   85 ++
 .../apache/cayenne/dba/db2/DB2SelectAction.java |   39 +
 .../cayenne/dba/derby/DerbyActionBuilder.java   |   40 +
 .../apache/cayenne/dba/derby/DerbyAdapter.java  |   23 +-
 .../dba/derby/DerbyQualifierTranslator.java     |  141 ---
 .../dba/derby/DerbySQLTreeProcessor.java        |   81 ++
 .../cayenne/dba/derby/DerbySelectAction.java    |   39 +
 .../dba/derby/sqltree/DerbyValueNode.java       |   51 +
 .../dba/firebird/FirebirdActionBuilder.java     |   41 +
 .../cayenne/dba/firebird/FirebirdAdapter.java   |   21 +-
 .../firebird/FirebirdQualifierTranslator.java   |  181 ---
 .../dba/firebird/FirebirdSQLTreeProcessor.java  |  189 +++
 .../dba/firebird/FirebirdSelectAction.java      |   40 +
 .../dba/firebird/sqltree/FirebirdLimitNode.java |   50 +
 .../sqltree/FirebirdSubstringFunctionNode.java  |   65 ++
 .../cayenne/dba/frontbase/FrontBaseAdapter.java |   25 +-
 .../frontbase/FrontBaseQualifierTranslator.java |  134 ---
 .../frontbase/FrontBaseSQLTreeProcessor.java    |  104 ++
 .../frontbase/FrontBaseSelectTranslator.java    |   52 -
 .../apache/cayenne/dba/h2/H2ActionBuilder.java  |   39 +
 .../org/apache/cayenne/dba/h2/H2Adapter.java    |   18 +
 .../cayenne/dba/h2/H2SQLTreeProcessor.java      |   37 +
 .../apache/cayenne/dba/h2/H2SelectAction.java   |   39 +
 .../cayenne/dba/hsqldb/HSQLDBAdapter.java       |   27 +-
 .../dba/hsqldb/HSQLQualifierTranslator.java     |   86 --
 .../dba/hsqldb/HSQLSelectTranslator.java        |   57 -
 .../cayenne/dba/hsqldb/HSQLTreeProcessor.java   |   70 ++
 .../cayenne/dba/ingres/IngresAdapter.java       |   23 +-
 .../dba/ingres/IngresQualifierTranslator.java   |  122 --
 .../dba/ingres/IngresSelectTranslator.java      |   49 -
 .../dba/ingres/IngressSQLTreeProcessor.java     |  108 ++
 .../apache/cayenne/dba/mysql/MySQLAdapter.java  |   21 +-
 .../dba/mysql/MySQLQualifierTranslator.java     |  105 --
 .../dba/mysql/MySQLSelectTranslator.java        |   57 -
 .../cayenne/dba/mysql/MySQLTreeProcessor.java   |   68 ++
 .../dba/mysql/sqltree/MysqlLikeNode.java        |   46 +
 .../dba/mysql/sqltree/MysqlLimitOffsetNode.java |   40 +
 .../cayenne/dba/openbase/OpenBaseAdapter.java   |   21 +-
 .../cayenne/dba/openbase/OpenBaseJoinStack.java |  115 --
 .../openbase/OpenBaseQualifierTranslator.java   |  177 ---
 .../dba/openbase/OpenBaseSQLTreeProcessor.java  |  109 ++
 .../dba/openbase/OpenBaseSelectTranslator.java  |   53 -
 .../cayenne/dba/oracle/Oracle8Adapter.java      |   19 -
 .../cayenne/dba/oracle/Oracle8JoinStack.java    |  108 --
 .../dba/oracle/Oracle8QualifierTranslator.java  |   49 -
 .../dba/oracle/Oracle8SelectTranslator.java     |   47 -
 .../cayenne/dba/oracle/OracleAdapter.java       |   23 +-
 .../dba/oracle/OracleQualifierTranslator.java   |  212 ----
 .../dba/oracle/OracleSQLTreeProcessor.java      |  241 ++++
 .../dba/oracle/OracleSelectTranslator.java      |   78 --
 .../dba/postgres/PostgreSQLTreeProcessor.java   |   83 ++
 .../cayenne/dba/postgres/PostgresAdapter.java   |   27 +-
 .../postgres/PostgresQualifierTranslator.java   |  189 ---
 .../dba/postgres/PostgresSelectTranslator.java  |   54 -
 .../postgres/sqltree/PositionFunctionNode.java  |   43 +
 .../sqltree/PostgresExtractFunctionNode.java    |   59 +
 .../dba/postgres/sqltree/PostgresLikeNode.java  |   46 +
 .../sqltree/PostgresLimitOffsetNode.java        |   60 +
 .../cayenne/dba/sqlite/SQLiteActionBuilder.java |    9 +
 .../cayenne/dba/sqlite/SQLiteAdapter.java       |   23 +-
 .../dba/sqlite/SQLiteQualifierTranslator.java   |  162 ---
 .../cayenne/dba/sqlite/SQLiteSelectAction.java  |   43 +
 .../cayenne/dba/sqlite/SQLiteTreeProcessor.java |  100 ++
 .../cayenne/dba/sqlserver/SQLServerAdapter.java |   23 +-
 .../sqlserver/SQLServerSelectTranslator.java    |   54 -
 .../dba/sqlserver/SQLServerTreeProcessor.java   |  128 +++
 .../SQLServerTrimmingQualifierTranslator.java   |  207 ----
 .../sqlserver/sqltree/SQLServerColumnNode.java  |   46 +
 .../cayenne/dba/sybase/SybaseAdapter.java       |   25 +-
 .../dba/sybase/SybaseQualifierTranslator.java   |  114 --
 .../dba/sybase/SybaseSelectTranslator.java      |   52 -
 .../org/apache/cayenne/query/ColumnSelect.java  |    7 +
 .../cayenne/query/SelectQueryMetadata.java      |   13 +-
 .../java/org/apache/cayenne/util/ArrayUtil.java |  260 +++++
 .../cayenne/FlattenedRelationshipsIT.java       |    3 +-
 .../select/BaseColumnExtractorTest.java         |   43 +
 .../select/ColumnDescriptorStageTest.java       |   55 +
 .../select/CustomColumnSetExtractorTest.java    |   94 ++
 .../select/DbEntityColumnExtractorTest.java     |  121 ++
 .../select/DefaultObjectSelectTranslatorIT.java |  136 +++
 .../select/DefaultSelectTranslatorIT.java       |  249 ++--
 .../select/DescriptorColumnExtractorTest.java   |   97 ++
 .../translator/select/DistinctStageTest.java    |  103 ++
 .../translator/select/GroupByStageTest.java     |   86 ++
 .../select/HavingTranslationStageTest.java      |   90 ++
 .../select/IdColumnExtractorTest.java           |  113 ++
 .../translator/select/LimitOffsetStageTest.java |   65 ++
 .../select/MockQueryMetadataBuilder.java        |  100 ++
 .../select/MockQueryWrapperBuilder.java         |  137 +++
 .../select/MockTranslatorContext.java           |   35 +
 .../translator/select/ObjPathProcessorIT.java   |   92 ++
 .../translator/select/ObjPathProcessorIT2.java  |   91 ++
 .../translator/select/ObjPathProcessorIT3.java  |   82 ++
 .../translator/select/ObjPathProcessorIT4.java  |   72 ++
 .../translator/select/OrderingStageTest.java    |  110 ++
 .../translator/select/OrderingTranslatorIT.java |  136 ---
 .../translator/select/PathComponentsTest.java   |   77 ++
 .../select/QualifierTranslationStageTest.java   |   89 ++
 .../select/QualifierTranslatorIT.java           |  164 ---
 .../select/QualifierTranslatorTest.java         |  524 +++++++++
 .../translator/select/QueryAssemblerIT.java     |   61 -
 .../translator/select/TstQueryAssembler.java    |   70 --
 .../cayenne/exp/property/PathAliasesIT.java     |    4 +-
 .../apache/cayenne/query/ColumnSelectIT.java    |   56 +-
 .../cayenne/unit/IngresUnitDbAdapter.java       |    8 +
 .../org/apache/cayenne/util/ArrayUtilTest.java  |  354 ++++++
 171 files changed, 9096 insertions(+), 6744 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/MixedResultIncrementalFaultList.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/MixedResultIncrementalFaultList.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/MixedResultIncrementalFaultList.java
index 9ca2136..f8c02b8 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/MixedResultIncrementalFaultList.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/MixedResultIncrementalFaultList.java
@@ -46,7 +46,6 @@ import org.apache.cayenne.util.Util;
  * if there is no Persistent objects in the result Collection it will be 
iterated as is, without faulting anything.
  *
  * @see QueryMetadata#getPageSize()
- * @see org.apache.cayenne.access.translator.select.DefaultSelectTranslator
  * @see org.apache.cayenne.query.SelectQueryMetadata
  *
  * @since 4.0
@@ -142,7 +141,10 @@ class MixedResultIncrementalFaultList<E> extends 
IncrementalFaultList<E> {
                 for (int i = fromIndex; i < toIndex; i++) {
                     Object[] object = (Object[])elements.get(i);
                     if (helper.unresolvedSuspect(object[dataIdx])) {
-                        quals.add(buildIdQualifier(dataIdx, object));
+                        Expression nextQualifier = buildIdQualifier(dataIdx, 
object);
+                        if(nextQualifier != null) {
+                            quals.add(nextQualifier);
+                        }
                     }
                 }
 
@@ -188,7 +190,9 @@ class MixedResultIncrementalFaultList<E> extends 
IncrementalFaultList<E> {
     Expression buildIdQualifier(int index, Object[] data) {
         Map<String, Object> map;
         if(data[index] instanceof Map) {
-            map = (Map<String, Object>)data[index];
+            map = (Map<String, Object>) data[index];
+        } else if(data[index] == null) {
+            return null;
         } else {
             map = new HashMap<>();
             int i = 0;
@@ -235,6 +239,8 @@ class MixedResultIncrementalFaultList<E> extends 
IncrementalFaultList<E> {
                         return false;
                     }
                 }
+            } else if(dataInTheList[dataIdx] == null) {
+                return false;
             } else {
                 for(Object id : map.values()) {
                     if (!dataInTheList[dataIdx++].equals(id)) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
index 632f5e3..90cd61b 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
@@ -161,6 +161,13 @@ public class ColumnDescriptor {
     }
 
     /**
+     * @since 4.2
+     */
+    public void setAttribute(DbAttribute attribute) {
+        this.attribute = attribute;
+    }
+
+    /**
      * Returns true if another object is a ColumnDescriptor with the same name,
      * name prefix, table and procedure names. Other fields are ignored in the
      * equality test.

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/AliasedNodeBuilder.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/AliasedNodeBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/AliasedNodeBuilder.java
index 50f07bd..aae7835 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/AliasedNodeBuilder.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/AliasedNodeBuilder.java
@@ -19,9 +19,8 @@
 
 package org.apache.cayenne.access.sqlbuilder;
 
-import org.apache.cayenne.access.sqlbuilder.sqltree.EmptyNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.AliasedNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
-import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode;
 
 /**
  * @since 4.2
@@ -38,9 +37,8 @@ class AliasedNodeBuilder implements NodeBuilder {
 
     @Override
     public Node build() {
-        Node root = new EmptyNode();
+        Node root = new AliasedNode(alias);
         root.addChild(nodeBuilder.build());
-        root.addChild(new TextNode(" " + alias));
         return root;
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ColumnNodeBuilder.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ColumnNodeBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ColumnNodeBuilder.java
index 8236744..55c1032 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ColumnNodeBuilder.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ColumnNodeBuilder.java
@@ -74,13 +74,10 @@ public class ColumnNodeBuilder implements ExpressionTrait {
 
     @Override
     public Node build() {
-        ColumnNode columnNode;
         if(unescaped) {
-            columnNode = new UnescapedColumnNode(table, column, alias, 
attribute);
-        } else {
-            columnNode = new ColumnNode(table, column, alias, attribute);
+            return new UnescapedColumnNode(table, column, alias, attribute);
         }
-        return columnNode;
+        return new ColumnNode(table, column, alias, attribute);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLBuilder.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLBuilder.java
index 0dcdfc1..dfa3d2e 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLBuilder.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLBuilder.java
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.access.sqlbuilder;
 
+import org.apache.cayenne.access.sqlbuilder.sqltree.AliasedNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
@@ -87,6 +88,17 @@ public final class SQLBuilder {
         if(suppressAlias(node)) {
             return node(node);
         }
+
+        if(node instanceof FunctionNode) {
+            ((FunctionNode) node).setAlias(alias);
+            return node(node);
+        }
+
+        if(node instanceof ColumnNode) {
+            ((ColumnNode) node).setAlias(alias);
+            return node(node);
+        }
+
         return new AliasedNodeBuilder(node(node), alias);
     }
 
@@ -154,6 +166,9 @@ public final class SQLBuilder {
             } else if(node.getType() == NodeType.FUNCTION && ((FunctionNode) 
node).getAlias() != null) {
                 suppressAlias = true;
                 return false;
+            } else if(node instanceof AliasedNode) {
+                suppressAlias = true;
+                return false;
             }
             return true;
         }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ValueNodeBuilder.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ValueNodeBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ValueNodeBuilder.java
index 5e027c6..4e3e659 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ValueNodeBuilder.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ValueNodeBuilder.java
@@ -32,6 +32,8 @@ public class ValueNodeBuilder implements NodeBuilder, 
ExpressionTrait {
 
     private DbAttribute attribute;
 
+    private boolean isArray;
+
     ValueNodeBuilder(Object value) {
         this.value = value;
     }
@@ -41,8 +43,13 @@ public class ValueNodeBuilder implements NodeBuilder, 
ExpressionTrait {
         return this;
     }
 
+    public ValueNodeBuilder array(boolean isArray) {
+        this.isArray = isArray;
+        return this;
+    }
+
     @Override
     public Node build() {
-        return new ValueNode(value, attribute);
+        return new ValueNode(value, isArray, attribute);
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/AliasedNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/AliasedNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/AliasedNode.java
new file mode 100644
index 0000000..524fc14
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/AliasedNode.java
@@ -0,0 +1,55 @@
+/*****************************************************************
+ *   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.access.sqlbuilder.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+
+/**
+ * @since 4.2
+ */
+public class AliasedNode extends Node {
+
+    protected final String alias;
+
+    public AliasedNode(String alias) {
+        this.alias = alias;
+    }
+
+    @Override
+    public Node copy() {
+        return new AliasedNode(alias);
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer;
+    }
+
+    @Override
+    public void appendChildrenEnd(QuotingAppendable buffer) {
+        super.appendChildrenEnd(buffer);
+        buffer.append(' ').append(alias);
+    }
+
+    public String getAlias() {
+        return alias;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BetweenNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BetweenNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BetweenNode.java
index 1102a8a..6826931 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BetweenNode.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BetweenNode.java
@@ -49,4 +49,8 @@ public class BetweenNode extends ExpressionNode {
     public Node copy() {
         return new BetweenNode(not);
     }
+
+    public boolean isNot() {
+        return not;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ColumnNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ColumnNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ColumnNode.java
index b610104..9c49a4b 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ColumnNode.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ColumnNode.java
@@ -29,8 +29,8 @@ public class ColumnNode extends Node {
 
     protected final String table;
     protected final String column;
-    protected final String alias;
     protected final DbAttribute attribute;
+    protected String alias;
 
     public ColumnNode(String table, String column, String alias, DbAttribute 
attribute) {
         super(NodeType.COLUMN);
@@ -65,6 +65,10 @@ public class ColumnNode extends Node {
         return alias;
     }
 
+    public void setAlias(String alias) {
+        this.alias = alias;
+    }
+
     public DbAttribute getAttribute() {
         return attribute;
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
index 8344380..3e9795e 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
@@ -41,14 +41,14 @@ public class ExpressionNode extends Node {
 
     @Override
     public void appendChildrenStart(QuotingAppendable buffer) {
-        if(parent.type != NodeType.WHERE && parent.type != NodeType.JOIN) {
+        if(parent != null && parent.type != NodeType.WHERE && parent.type != 
NodeType.JOIN) {
             buffer.append(" (");
         }
     }
 
     @Override
     public void appendChildrenEnd(QuotingAppendable buffer) {
-        if(parent.type != NodeType.WHERE && parent.type != NodeType.JOIN) {
+        if(parent != null && parent.type != NodeType.WHERE && parent.type != 
NodeType.JOIN) {
             buffer.append(" )");
         }
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FunctionNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FunctionNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FunctionNode.java
index 23b0fa7..3673ed0 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FunctionNode.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FunctionNode.java
@@ -28,8 +28,8 @@ import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
 public class FunctionNode extends Node {
 
     private final String functionName;
-    private final String alias;
     private final boolean needParentheses;
+    private String alias;
 
     public FunctionNode(String functionName, String alias) {
         this(functionName, alias, true);
@@ -103,6 +103,10 @@ public class FunctionNode extends Node {
         return alias;
     }
 
+    public void setAlias(String alias) {
+        this.alias = alias;
+    }
+
     @Override
     public Node copy() {
         return new FunctionNode(functionName, alias, needParentheses);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OpExpressionNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OpExpressionNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OpExpressionNode.java
index 7099000..6b1b55d 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OpExpressionNode.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OpExpressionNode.java
@@ -41,4 +41,8 @@ public class OpExpressionNode extends ExpressionNode {
     public Node copy() {
         return new OpExpressionNode(op);
     }
+
+    public String getOp() {
+        return op;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TextNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TextNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TextNode.java
index 45ee5cf..afd0f47 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TextNode.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TextNode.java
@@ -41,4 +41,8 @@ public class TextNode extends Node {
     public Node copy() {
         return new TextNode(text);
     }
+
+    public CharSequence getText() {
+        return text;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
index 2ba5fbc..2911397 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
@@ -34,12 +34,14 @@ import org.apache.cayenne.map.DbAttribute;
 public class ValueNode extends Node {
 
     private final Object value;
+    private final boolean isArray;
     // Used as hint for type of this value
     private final DbAttribute attribute;
 
-    public ValueNode(Object value, DbAttribute attribute) {
+    public ValueNode(Object value, boolean isArray, DbAttribute attribute) {
         super(NodeType.VALUE);
         this.value = value;
+        this.isArray = isArray;
         this.attribute = attribute;
     }
 
@@ -51,6 +53,10 @@ public class ValueNode extends Node {
         return attribute;
     }
 
+    public boolean isArray() {
+        return isArray;
+    }
+
     @Override
     public QuotingAppendable append(QuotingAppendable buffer) {
         appendValue(value, buffer);
@@ -62,8 +68,7 @@ public class ValueNode extends Node {
             return;
         }
 
-        boolean isArray = val.getClass().isArray();
-        if(isArray) {
+        if(isArray && val.getClass().isArray()) {
             if(val instanceof short[]) {
                 appendValue((short[])val, buffer);
             } else if(val instanceof char[]) {
@@ -82,7 +87,7 @@ public class ValueNode extends Node {
                 appendValue((Object[]) val, buffer);
             } else if(val instanceof byte[]) {
                 // append byte[] array as single object
-                appendObjectValue(buffer, val);
+                appendValue((byte[])val, buffer);
             } else {
                 throw new CayenneRuntimeException("Unsupported array type %s", 
val.getClass().getName());
             }
@@ -227,6 +232,18 @@ public class ValueNode extends Node {
         }
     }
 
+    private void appendValue(byte[] val, QuotingAppendable buffer) {
+        boolean first = true;
+        for(byte i : val) {
+            if(first) {
+                first = false;
+            } else {
+                buffer.append(',');
+            }
+            appendValue(i, buffer);
+        }
+    }
+
     private void appendValue(Object[] val, QuotingAppendable buffer) {
         boolean first = true;
         for(Object i : val) {
@@ -241,6 +258,6 @@ public class ValueNode extends Node {
 
     @Override
     public Node copy() {
-        return new ValueNode(value, attribute);
+        return new ValueNode(value, isArray, attribute);
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/BaseColumnExtractor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/BaseColumnExtractor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/BaseColumnExtractor.java
new file mode 100644
index 0000000..20cc5f7
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/BaseColumnExtractor.java
@@ -0,0 +1,44 @@
+/*****************************************************************
+ *   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.access.translator.select;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.map.DbAttribute;
+
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.table;
+
+/**
+ * @since 4.2
+ */
+abstract class BaseColumnExtractor implements ColumnExtractor {
+
+    protected final TranslatorContext context;
+
+    BaseColumnExtractor(TranslatorContext context) {
+        this.context = context;
+    }
+
+    protected void addDbAttribute(String prefix, String labelPrefix, 
DbAttribute dba) {
+        String alias = context.getTableTree().aliasForPath(prefix);
+        String dataRowKey = labelPrefix != null ? labelPrefix + '.' + 
dba.getName() : dba.getName();
+        Node columnNode = table(alias).column(dba).build();
+        context.addResultNode(columnNode, dataRowKey).setDbAttribute(dba);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/BaseSQLTreeProcessor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/BaseSQLTreeProcessor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/BaseSQLTreeProcessor.java
new file mode 100644
index 0000000..2119e26
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/BaseSQLTreeProcessor.java
@@ -0,0 +1,128 @@
+/*****************************************************************
+ *   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.access.translator.select;
+
+import java.util.function.Function;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.DistinctNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.InNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LikeNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.SimpleNodeTreeVisitor;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ValueNode;
+
+
+/**
+ * @since 4.2
+ */
+public class BaseSQLTreeProcessor extends SimpleNodeTreeVisitor implements 
Function<Node, Node> {
+
+    @Override
+    public Node apply(Node node) {
+        node.visit(this);
+        return node;
+    }
+
+    protected void onValueNode(Node parent, ValueNode child, int index) {
+    }
+
+    protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+    }
+
+    protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int 
index) {
+    }
+
+    protected void onColumnNode(Node parent, ColumnNode child, int index) {
+    }
+
+    protected void onInNode(Node parent, InNode child, int index) {
+    }
+
+    protected void onLikeNode(Node parent, LikeNode child, int index) {
+    }
+
+    protected void onResultNode(Node parent, Node child, int index) {
+    }
+
+    protected void onDistinctNode(Node parent, DistinctNode child, int index) {
+    }
+
+    protected void onUndefinedNode(Node parent, Node child, int index) {
+    }
+
+    protected void replaceChild(Node parent, int index, Node newChild) {
+        replaceChild(parent, index, newChild, true);
+    }
+
+    protected void replaceChild(Node parent, int index, Node newChild, boolean 
transferChildren) {
+        if (transferChildren) {
+            Node oldChild = parent.getChild(index);
+            for (int i = 0; i < oldChild.getChildrenCount(); i++) {
+                newChild.addChild(oldChild.getChild(i));
+            }
+        }
+        parent.replaceChild(index, newChild);
+    }
+
+    @Override
+    public boolean onChildNodeStart(Node parent, Node child, int index, 
boolean hasMore) {
+        switch (child.getType()) {
+            case VALUE:
+                onValueNode(parent, (ValueNode) child, index);
+                break;
+
+            case FUNCTION:
+                onFunctionNode(parent, (FunctionNode) child, index);
+                break;
+
+            case LIMIT_OFFSET:
+                onLimitOffsetNode(parent, (LimitOffsetNode) child, index);
+                break;
+
+            case COLUMN:
+                onColumnNode(parent, (ColumnNode) child, index);
+                break;
+
+            case IN:
+                onInNode(parent, (InNode) child, index);
+                break;
+
+            case LIKE:
+                onLikeNode(parent, (LikeNode) child, index);
+                break;
+
+            case RESULT:
+                onResultNode(parent, child, index);
+                break;
+
+            case DISTINCT:
+                onDistinctNode(parent, (DistinctNode) child, index);
+                break;
+
+            default:
+                onUndefinedNode(parent, child, index);
+                break;
+        }
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStage.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStage.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStage.java
new file mode 100644
index 0000000..d15803a
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStage.java
@@ -0,0 +1,56 @@
+/*****************************************************************
+ *   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.access.translator.select;
+
+import org.apache.cayenne.access.jdbc.ColumnDescriptor;
+import org.apache.cayenne.map.DbAttribute;
+
+/**
+ * @since 4.2
+ */
+class ColumnDescriptorStage implements TranslationStage {
+
+    @Override
+    public void perform(TranslatorContext context) {
+        int i = 0;
+        for(ResultNodeDescriptor resultNode : context.getResultNodeList()) {
+            context.getSelectBuilder().result(resultNode::getNode);
+
+            if(!resultNode.isInDataRow()) {
+                continue;
+            }
+
+            String name;
+            DbAttribute attribute = resultNode.getDbAttribute();
+            if(attribute != null) {
+                name = attribute.getName();
+            } else {
+                // generated name
+                name = "c" + i++;
+            }
+
+            ColumnDescriptor descriptor = new ColumnDescriptor(name, 
resultNode.getJdbcType(), resultNode.getJavaType());
+            descriptor.setAttribute(attribute);
+            descriptor.setDataRowKey(resultNode.getDataRowKey());
+
+            context.getColumnDescriptors().add(descriptor);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnExtractor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnExtractor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnExtractor.java
new file mode 100644
index 0000000..e1551a7
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnExtractor.java
@@ -0,0 +1,33 @@
+/*****************************************************************
+ *   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.access.translator.select;
+
+/**
+ * @since 4.2
+ */
+interface ColumnExtractor {
+
+    void extract(String prefix);
+
+    default void extract() {
+        extract(null);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnExtractorStage.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnExtractorStage.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnExtractorStage.java
new file mode 100644
index 0000000..cd51d77
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnExtractorStage.java
@@ -0,0 +1,54 @@
+/*****************************************************************
+ *   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.access.translator.select;
+
+/**
+ * Get result columns based on query, options are: <ol>
+ *    <li> for column queries - defined set of columns
+ *    <li> for paginated or nested queries - root pk columns
+ *    <li> for queries with defined class descriptor - full column set 
including flattened
+ *    <li> for everything else - all columns of the root db entity
+ * </ol>
+ *
+ * @since 4.2
+ */
+class ColumnExtractorStage implements TranslationStage {
+
+    @Override
+    public void perform(TranslatorContext context) {
+        ColumnExtractor extractor;
+
+        if(context.getQuery().getColumns() != null && 
!context.getQuery().getColumns().isEmpty()) {
+            extractor = new CustomColumnSetExtractor(context, 
context.getQuery().getColumns());
+        } else if (context.getParentContext() != null || 
context.getMetadata().getPageSize() > 0) {
+            if(context.getMetadata().getObjEntity() != null) {
+                extractor = new IdColumnExtractor(context, 
context.getMetadata().getObjEntity());
+            } else {
+                extractor = new IdColumnExtractor(context, 
context.getMetadata().getDbEntity());
+            }
+        } else if (context.getMetadata().getClassDescriptor() != null) {
+            extractor = new DescriptorColumnExtractor(context, 
context.getMetadata().getClassDescriptor());
+        } else {
+            extractor = new DbEntityColumnExtractor(context);
+        }
+
+        extractor.extract();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnSelectWrapper.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnSelectWrapper.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnSelectWrapper.java
new file mode 100644
index 0000000..21a0069
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ColumnSelectWrapper.java
@@ -0,0 +1,84 @@
+/*****************************************************************
+ *   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.access.translator.select;
+
+import java.util.Collection;
+import java.util.Objects;
+
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.property.BaseProperty;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.query.ColumnSelect;
+import org.apache.cayenne.query.Ordering;
+import org.apache.cayenne.query.PrefetchTreeNode;
+import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.Select;
+
+/**
+ * @since 4.2
+ */
+public class ColumnSelectWrapper implements TranslatableQueryWrapper {
+
+    private final ColumnSelect<?> columnSelect;
+
+    public ColumnSelectWrapper(ColumnSelect<?> columnSelect) {
+        this.columnSelect = Objects.requireNonNull(columnSelect);
+    }
+
+    @Override
+    public boolean isDistinct() {
+        return columnSelect.isDistinct();
+    }
+
+    @Override
+    public QueryMetadata getMetaData(EntityResolver resolver) {
+        return columnSelect.getMetaData(resolver);
+    }
+
+    @Override
+    public PrefetchTreeNode getPrefetchTree() {
+        return columnSelect.getPrefetches();
+    }
+
+    @Override
+    public Expression getQualifier() {
+        return columnSelect.getWhere();
+    }
+
+    @Override
+    public Collection<Ordering> getOrderings() {
+        return columnSelect.getOrderings();
+    }
+
+    @Override
+    public Collection<BaseProperty<?>> getColumns() {
+        return columnSelect.getColumns();
+    }
+
+    @Override
+    public Expression getHavingQualifier() {
+        return columnSelect.getHaving();
+    }
+
+    @Override
+    public Select<?> unwrap() {
+        return columnSelect;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractor.java
new file mode 100644
index 0000000..534fd3a
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/CustomColumnSetExtractor.java
@@ -0,0 +1,133 @@
+/*****************************************************************
+ *   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.access.translator.select;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.parser.ASTPath;
+import org.apache.cayenne.exp.property.BaseProperty;
+import org.apache.cayenne.map.JoinType;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.util.Util;
+
+/**
+ * @since 4.2
+ */
+class CustomColumnSetExtractor implements ColumnExtractor {
+
+    private final TranslatorContext context;
+    private final Collection<BaseProperty<?>> columns;
+
+    CustomColumnSetExtractor(TranslatorContext context, 
Collection<BaseProperty<?>> columns) {
+        this.context = context;
+        this.columns = columns;
+    }
+
+    @Override
+    public void extract(String prefix) {
+        for (BaseProperty<?> property : columns) {
+            if (isFullObjectProp(property)) {
+                extractFullObject(prefix, property);
+            } else {
+                extractSimpleProperty(property);
+            }
+        }
+    }
+
+    private void extractSimpleProperty(BaseProperty<?> property) {
+        Node sqlNode = context.getQualifierTranslator().translate(property);
+        context.addResultNode(sqlNode, true, property, property.getAlias());
+    }
+
+    private boolean isFullObjectProp(BaseProperty<?> property) {
+        int expressionType = property.getExpression().getType();
+
+        // forbid direct selection of toMany relationships columns
+        if(property.getType() != null && (expressionType == 
Expression.OBJ_PATH || expressionType == Expression.DB_PATH)
+                && (Collection.class.isAssignableFrom(property.getType())
+                || Map.class.isAssignableFrom(property.getType()))) {
+            throw new CayenneRuntimeException("Can't directly select toMany 
relationship columns. " +
+                    "Either select it with aggregate functions like count() or 
with flat() function to select full related objects.");
+        }
+
+        // evaluate ObjPath with Persistent type as toOne relations and use it 
as full object
+        return expressionType == Expression.FULL_OBJECT
+                || (property.getType() != null
+                    && expressionType == Expression.OBJ_PATH
+                    && Persistent.class.isAssignableFrom(property.getType()));
+    }
+
+    private void extractFullObject(String prefix, BaseProperty<?> property) {
+        prefix = calculatePrefix(prefix, property);
+        ensureJoin(prefix);
+
+        ObjEntity entity = 
context.getResolver().getObjEntity(property.getType());
+
+        ColumnExtractor extractor;
+        if(context.getMetadata().getPageSize() > 0) {
+            extractor = new IdColumnExtractor(context, entity);
+        } else {
+            ClassDescriptor descriptor = 
context.getResolver().getClassDescriptor(entity.getName());
+            extractor = new DescriptorColumnExtractor(context, descriptor);
+        }
+
+        int index = context.getResultNodeList().size();
+
+        // extract required columns of entity
+        extractor.extract(prefix);
+
+        // Reset data row key as ObjectResolver expects it to match attribute 
name.
+        // Maybe we should change resolver, as it seems cleaner to have path 
from root as prefix in data row key.
+        for(int i=index; i<context.getResultNodeList().size(); i++) {
+            context.getResultNodeList().get(i).setDataRowKey(null);
+        }
+    }
+
+    private String calculatePrefix(String prefix, BaseProperty<?> property) {
+        Expression propertyExpression = property.getExpression();
+        int expressionType = property.getExpression().getType();
+
+        if(expressionType == Expression.FULL_OBJECT && 
propertyExpression.getOperandCount() > 0) {
+            Object op = propertyExpression.getOperand(0);
+            if(op instanceof ASTPath) {
+                prefix = ((ASTPath) op).getPath();
+            }
+        } else if(propertyExpression instanceof ASTPath) {
+            prefix = ((ASTPath) propertyExpression).getPath();
+        }
+
+        return prefix;
+    }
+
+    private void ensureJoin(String prefix) {
+        // ensure all joins for given property
+        if(!Util.isEmptyString(prefix)) {
+            PathTranslationResult result = 
context.getPathTranslator().translatePath(context.getMetadata().getObjEntity(), 
prefix);
+            result.getDbRelationship().ifPresent(relationship
+                    -> 
context.getTableTree().addJoinTable(result.getFinalPath(), relationship, 
JoinType.LEFT_OUTER));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DataObjectMatchTranslator.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DataObjectMatchTranslator.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DataObjectMatchTranslator.java
deleted file mode 100644
index 07f53f1..0000000
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DataObjectMatchTranslator.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*****************************************************************
- *   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.access.translator.select;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.ObjectId;
-import org.apache.cayenne.Persistent;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-
-/**
- */
-public class DataObjectMatchTranslator {
-
-       protected Map<String, DbAttribute> attributes;
-       protected Map<String, Object> values;
-       protected String operation;
-       protected Expression expression;
-       protected DbRelationship relationship;
-       protected String joinSplitAlias;
-
-       public Expression getExpression() {
-               return expression;
-       }
-
-       public void setExpression(Expression expression) {
-               this.expression = expression;
-       }
-
-       public void reset() {
-               attributes = null;
-               values = null;
-               operation = null;
-               expression = null;
-               relationship = null;
-       }
-
-       /**
-        * Initializes itself to do translation of the match ending with a
-        * DbRelationship.
-        * 
-        * @since 3.0
-        */
-       public void setRelationship(DbRelationship rel, String joinSplitAlias) {
-               this.relationship = rel;
-               this.joinSplitAlias = joinSplitAlias;
-               attributes = new HashMap<>(rel.getJoins().size() * 2);
-
-               if (rel.isToMany() || !rel.isToPK()) {
-
-                       // match on target PK
-                       DbEntity ent = rel.getTargetEntity();
-
-                       // index by name
-                       for (DbAttribute pkAttr : ent.getPrimaryKeys()) {
-                               attributes.put(pkAttr.getName(), pkAttr);
-                       }
-               } else {
-
-                       // match on this FK
-                       for (DbJoin join : rel.getJoins()) {
-                               // index by target name
-                               attributes.put(join.getTargetName(), 
join.getSource());
-                       }
-               }
-       }
-
-       public void setDataObject(Persistent obj) {
-               if (obj == null) {
-                       values = Collections.emptyMap();
-                       return;
-               }
-
-               setObjectId(obj.getObjectId());
-       }
-
-       /**
-        * @since 1.2
-        */
-       public void setObjectId(ObjectId id) {
-               if (id == null) {
-                       throw new CayenneRuntimeException(
-                                       "Null ObjectId, probably an attempt to 
use TRANSIENT object as a query parameter.");
-               } else if (id.isTemporary()) {
-                       throw new CayenneRuntimeException(
-                                       "Temporary id, probably an attempt to 
use NEW object as a query parameter.");
-               } else {
-                       values = id.getIdSnapshot();
-               }
-       }
-
-       public Iterator<String> keys() {
-               if (attributes == null) {
-                       throw new IllegalStateException("An attempt to use 
uninitialized DataObjectMatchTranslator: "
-                                       + "[attributes: null, values: " + 
values + "]");
-               }
-
-               return attributes.keySet().iterator();
-       }
-
-       /**
-        * @since 3.0
-        */
-       public String getJoinSplitAlias() {
-               return joinSplitAlias;
-       }
-
-       public DbRelationship getRelationship() {
-               return relationship;
-       }
-
-       public DbAttribute getAttribute(String key) {
-               return attributes.get(key);
-       }
-
-       public Object getValue(String key) {
-               return values.get(key);
-       }
-
-       public void setOperation(String operation) {
-               this.operation = operation;
-       }
-
-       public String getOperation() {
-               return operation;
-       }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbEntityColumnExtractor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbEntityColumnExtractor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbEntityColumnExtractor.java
new file mode 100644
index 0000000..3354fac
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbEntityColumnExtractor.java
@@ -0,0 +1,45 @@
+/*****************************************************************
+ *   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.access.translator.select;
+
+import java.util.Objects;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+/**
+ * @since 4.2
+ */
+class DbEntityColumnExtractor extends BaseColumnExtractor {
+
+    private final DbEntity dbEntity;
+
+    DbEntityColumnExtractor(TranslatorContext context) {
+        super(context);
+        this.dbEntity = 
Objects.requireNonNull(context.getMetadata().getDbEntity(), "No root entity");
+    }
+
+    @Override
+    public void extract(String prefix) {
+        for(DbAttribute attribute : dbEntity.getAttributes()) {
+            addDbAttribute(prefix, prefix, attribute);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbPathProcessor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbPathProcessor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbPathProcessor.java
new file mode 100644
index 0000000..cccea12
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DbPathProcessor.java
@@ -0,0 +1,108 @@
+/*****************************************************************
+ *   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.access.translator.select;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.JoinType;
+
+/**
+ * @since 4.2
+ */
+class DbPathProcessor extends PathProcessor<DbEntity> {
+
+    DbPathProcessor(TranslatorContext context, DbEntity entity, String 
parentPath) {
+        super(context, entity);
+        if(parentPath != null) {
+            currentDbPath.append(parentPath);
+        }
+    }
+
+    @Override
+    protected void processNormalAttribute(String next) {
+        DbAttribute dbAttribute = entity.getAttribute(next);
+        if(dbAttribute != null) {
+            processAttribute(dbAttribute);
+            return;
+        }
+
+        DbRelationship relationship = entity.getRelationship(next);
+        if(relationship != null) {
+            entity = relationship.getTargetEntity();
+            processRelationship(relationship);
+            return;
+        }
+
+        throw new IllegalStateException("Unable to resolve path: " + 
currentDbPath.toString() + "." + next);
+    }
+
+    @Override
+    protected void processAliasedAttribute(String next, String alias) {
+        DbRelationship relationship = entity.getRelationship(alias);
+        if(relationship == null) {
+            throw new IllegalStateException("Non-relationship aliased path 
part: " + alias);
+        }
+
+        processRelationship(relationship);
+    }
+
+    private void processAttribute(DbAttribute attribute) {
+        addAttribute(currentDbPath.toString(), attribute);
+        appendCurrentPath(attribute.getName());
+    }
+
+    private void processRelationship(DbRelationship relationship) {
+        if (lastComponent) {
+            // if this is a last relationship in the path, it needs special 
handling
+            processRelTermination(relationship);
+        } else {
+            appendCurrentPath(relationship.getName());
+            context.getTableTree().addJoinTable(currentDbPath.toString(), 
relationship, JoinType.LEFT_OUTER);
+            if(!relationship.isToMany()) {
+                String path = currentDbPath.toString();
+                for (DbAttribute attribute : 
relationship.getTargetEntity().getPrimaryKeys()) {
+                    addAttribute(path, attribute);
+                }
+            }
+        }
+    }
+
+    protected void processRelTermination(DbRelationship rel) {
+        this.relationship = rel;
+        String path = currentDbPath.toString();
+        appendCurrentPath(rel.getName());
+
+        if (rel.isToMany() || !rel.isToPK()) {
+            // match on target PK
+            context.getTableTree().addJoinTable(currentDbPath.toString(), rel, 
JoinType.LEFT_OUTER);
+            path = currentDbPath.toString();
+            for(DbAttribute attribute : 
rel.getTargetEntity().getPrimaryKeys()) {
+                addAttribute(path, attribute);
+            }
+        } else {
+            for(DbJoin join : rel.getJoins()) {
+                addAttribute(path, join.getSource());
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultQuotingAppendable.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultQuotingAppendable.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultQuotingAppendable.java
new file mode 100644
index 0000000..712cc5f
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultQuotingAppendable.java
@@ -0,0 +1,47 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.StringBuilderAppendable;
+
+/**
+ * @since 4.2
+ */
+public class DefaultQuotingAppendable extends StringBuilderAppendable {
+
+    private final TranslatorContext context;
+
+    public DefaultQuotingAppendable(TranslatorContext context) {
+        super();
+        this.context = context;
+    }
+
+    @Override
+    public QuotingAppendable appendQuoted(CharSequence content) {
+        
context.getQuotingStrategy().quotedIdentifier(context.getRootDbEntity(), 
content, builder);
+        return this;
+    }
+
+    @Override
+    public TranslatorContext getContext() {
+        return context;
+    }
+}

Reply via email to