CAY-2466 New internal API to build SQL string

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

Branch: refs/heads/master
Commit: 89439015fc7e3fd88e8a4846bea953b43f003717
Parents: 9889375
Author: Nikita Timofeev <stari...@gmail.com>
Authored: Fri Jan 4 16:16:46 2019 +0300
Committer: Nikita Timofeev <stari...@gmail.com>
Committed: Fri Jan 4 17:20:44 2019 +0300

----------------------------------------------------------------------
 .../access/sqlbuilder/AliasedNodeBuilder.java   |  46 ++++
 .../access/sqlbuilder/ColumnNodeBuilder.java    |  86 +++++++
 .../access/sqlbuilder/ExistsNodeBuilder.java    |  42 ++++
 .../sqlbuilder/ExpressionNodeBuilder.java       | 129 ++++++++++
 .../access/sqlbuilder/ExpressionTrait.java      |  62 +++++
 .../access/sqlbuilder/FunctionNodeBuilder.java  |  56 +++++
 .../access/sqlbuilder/JoinNodeBuilder.java      |  54 ++++
 .../cayenne/access/sqlbuilder/JoinType.java     |  39 +++
 .../cayenne/access/sqlbuilder/NodeBuilder.java  |  32 +++
 .../access/sqlbuilder/NodeTreeVisitor.java      |  47 ++++
 .../access/sqlbuilder/OrderingNodeBuilder.java  |  56 +++++
 .../access/sqlbuilder/QuotingAppendable.java    |  41 ++++
 .../cayenne/access/sqlbuilder/SQLBuilder.java   | 161 ++++++++++++
 .../access/sqlbuilder/SQLGenerationContext.java |  35 +++
 .../access/sqlbuilder/SQLGenerationVisitor.java |  63 +++++
 .../access/sqlbuilder/SelectBuilder.java        | 169 +++++++++++++
 .../sqlbuilder/StringBuilderAppendable.java     |  76 ++++++
 .../access/sqlbuilder/TableNodeBuilder.java     |  65 +++++
 .../access/sqlbuilder/ValueNodeBuilder.java     |  48 ++++
 .../access/sqlbuilder/sqltree/BetweenNode.java  |  52 ++++
 .../sqlbuilder/sqltree/BitwiseNotNode.java      |  38 +++
 .../access/sqlbuilder/sqltree/ColumnNode.java   |  76 ++++++
 .../access/sqlbuilder/sqltree/DistinctNode.java |  43 ++++
 .../access/sqlbuilder/sqltree/EmptyNode.java    |  43 ++++
 .../access/sqlbuilder/sqltree/EqualNode.java    |  43 ++++
 .../access/sqlbuilder/sqltree/ExistsNode.java   |  38 +++
 .../sqlbuilder/sqltree/ExpressionNode.java      |  65 +++++
 .../access/sqlbuilder/sqltree/FromNode.java     |  43 ++++
 .../access/sqlbuilder/sqltree/FunctionNode.java | 127 ++++++++++
 .../access/sqlbuilder/sqltree/GroupByNode.java  |  43 ++++
 .../access/sqlbuilder/sqltree/HavingNode.java   |  37 +++
 .../access/sqlbuilder/sqltree/InNode.java       |  65 +++++
 .../access/sqlbuilder/sqltree/JoinNode.java     |  51 ++++
 .../access/sqlbuilder/sqltree/LikeNode.java     |  89 +++++++
 .../sqlbuilder/sqltree/LimitOffsetNode.java     |  58 +++++
 .../cayenne/access/sqlbuilder/sqltree/Node.java | 139 +++++++++++
 .../access/sqlbuilder/sqltree/NodeType.java     |  38 +++
 .../access/sqlbuilder/sqltree/NotEqualNode.java |  42 ++++
 .../access/sqlbuilder/sqltree/NotNode.java      |  37 +++
 .../sqlbuilder/sqltree/OffsetFetchNextNode.java |  53 ++++
 .../access/sqlbuilder/sqltree/OffsetNode.java   |  43 ++++
 .../sqlbuilder/sqltree/OpExpressionNode.java    |  44 ++++
 .../access/sqlbuilder/sqltree/OrderByNode.java  |  43 ++++
 .../access/sqlbuilder/sqltree/SelectNode.java   |  48 ++++
 .../sqlbuilder/sqltree/SelectResultNode.java    |  52 ++++
 .../sqltree/SimpleNodeTreeVisitor.java          |  46 ++++
 .../access/sqlbuilder/sqltree/SubqueryNode.java |  45 ++++
 .../access/sqlbuilder/sqltree/TableNode.java    |  50 ++++
 .../access/sqlbuilder/sqltree/TextNode.java     |  44 ++++
 .../access/sqlbuilder/sqltree/TopNode.java      |  44 ++++
 .../sqlbuilder/sqltree/TrimmingColumnNode.java  | 134 ++++++++++
 .../sqlbuilder/sqltree/UnescapedColumnNode.java |  52 ++++
 .../access/sqlbuilder/sqltree/ValueNode.java    | 246 +++++++++++++++++++
 .../access/sqlbuilder/sqltree/WhereNode.java    |  43 ++++
 .../access/sqlbuilder/SelectBuilderTest.java    | 131 ++++++++++
 55 files changed, 3592 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/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
new file mode 100644
index 0000000..50f07bd
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/AliasedNodeBuilder.java
@@ -0,0 +1,46 @@
+/*****************************************************************
+ *   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;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.EmptyNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode;
+
+/**
+ * @since 4.2
+ */
+class AliasedNodeBuilder implements NodeBuilder {
+
+    private final NodeBuilder nodeBuilder;
+    private final String alias;
+
+    AliasedNodeBuilder(NodeBuilder nodeBuilder, String alias) {
+        this.nodeBuilder = nodeBuilder;
+        this.alias = alias;
+    }
+
+    @Override
+    public Node build() {
+        Node root = new EmptyNode();
+        root.addChild(nodeBuilder.build());
+        root.addChild(new TextNode(" " + alias));
+        return root;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/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
new file mode 100644
index 0000000..8236744
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ColumnNodeBuilder.java
@@ -0,0 +1,86 @@
+/*****************************************************************
+ *   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;
+
+import java.util.Objects;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.UnescapedColumnNode;
+import org.apache.cayenne.map.DbAttribute;
+
+/**
+ * @since 4.2
+ */
+public class ColumnNodeBuilder implements ExpressionTrait {
+
+    private final String table;
+    private final String column;
+
+    private boolean unescaped;
+    private String alias;
+    private DbAttribute attribute;
+
+    ColumnNodeBuilder(String table, String field) {
+        this.table = table;
+        this.column = Objects.requireNonNull(field);
+    }
+
+    ColumnNodeBuilder(String table, DbAttribute attribute) {
+        this.table = table;
+        this.column = Objects.requireNonNull(attribute).getName();
+        this.attribute = attribute;
+    }
+
+    public ColumnNodeBuilder as(String alias) {
+        this.alias = alias;
+        return this;
+    }
+
+    public ColumnNodeBuilder unescaped() {
+        this.unescaped = true;
+        return this;
+    }
+
+    public ColumnNodeBuilder attribute(DbAttribute attribute) {
+        this.attribute = attribute;
+        return this;
+    }
+
+    public OrderingNodeBuilder desc() {
+        return new OrderingNodeBuilder(this).desc();
+    }
+
+    public OrderingNodeBuilder asc() {
+        return new OrderingNodeBuilder(this).asc();
+    }
+
+    @Override
+    public Node build() {
+        ColumnNode columnNode;
+        if(unescaped) {
+            columnNode = new UnescapedColumnNode(table, column, alias, 
attribute);
+        } else {
+            columnNode = new ColumnNode(table, column, alias, attribute);
+        }
+        return columnNode;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ExistsNodeBuilder.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ExistsNodeBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ExistsNodeBuilder.java
new file mode 100644
index 0000000..d71d38d
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ExistsNodeBuilder.java
@@ -0,0 +1,42 @@
+/*****************************************************************
+ *   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;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ExistsNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+class ExistsNodeBuilder implements NodeBuilder {
+
+    private final NodeBuilder builder;
+
+    ExistsNodeBuilder(NodeBuilder builder) {
+        this.builder = builder;
+    }
+
+    @Override
+    public Node build() {
+        Node node = new ExistsNode();
+        node.addChild(builder.build());
+        return node;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ExpressionNodeBuilder.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ExpressionNodeBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ExpressionNodeBuilder.java
new file mode 100644
index 0000000..2de4d7a
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ExpressionNodeBuilder.java
@@ -0,0 +1,129 @@
+/*****************************************************************
+ *   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;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.EqualNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.NotNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+
+/**
+ * @since 4.2
+ */
+public class ExpressionNodeBuilder implements ExpressionTrait {
+
+    private final NodeBuilder left;
+
+    ExpressionNodeBuilder(NodeBuilder left) {
+        this.left = left;
+    }
+
+    public ExpressionNodeBuilder and(NodeBuilder operand) {
+        return new ExpressionNodeBuilder(new ExpNodeBuilder(operand, "AND"));
+    }
+
+    public ExpressionNodeBuilder or(NodeBuilder operand) {
+        return new ExpressionNodeBuilder(new ExpNodeBuilder(operand, "OR"));
+    }
+
+    @Override
+    public ExpressionNodeBuilder plus(NodeBuilder operand) {
+        return new ExpressionNodeBuilder(new ExpNodeBuilder(operand, "+"));
+    }
+
+    @Override
+    public ExpressionNodeBuilder minus(NodeBuilder operand) {
+        return new ExpressionNodeBuilder(new ExpNodeBuilder(operand, "-"));
+    }
+
+    @Override
+    public ExpressionNodeBuilder mul(NodeBuilder operand) {
+        return new ExpressionNodeBuilder(new ExpNodeBuilder(operand, "*"));
+    }
+
+    @Override
+    public ExpressionNodeBuilder div(NodeBuilder operand) {
+        return new ExpressionNodeBuilder(new ExpNodeBuilder(operand, "/"));
+    }
+
+    @Override
+    public ExpressionNodeBuilder eq(NodeBuilder operand) {
+        return new ExpressionNodeBuilder(new ExpNodeBuilder(operand, "="));
+    }
+
+    @Override
+    public ExpressionNodeBuilder lt(NodeBuilder operand) {
+        return new ExpressionNodeBuilder(new ExpNodeBuilder(operand, "<"));
+    }
+
+    @Override
+    public ExpressionNodeBuilder gt(NodeBuilder operand) {
+        return new ExpressionNodeBuilder(new ExpNodeBuilder(operand, ">"));
+    }
+
+    @Override
+    public ExpressionNodeBuilder lte(NodeBuilder operand) {
+        return new ExpressionNodeBuilder(new ExpNodeBuilder(operand, "<="));
+    }
+
+    @Override
+    public ExpressionNodeBuilder gte(NodeBuilder operand) {
+        return new ExpressionNodeBuilder(new ExpNodeBuilder(operand, ">="));
+    }
+
+    public ExpressionNodeBuilder not() {
+        return new ExpressionNodeBuilder(() -> {
+            Node and = new NotNode();
+            and.addChild(left.build());
+            return and;
+        });
+    }
+
+    @Override
+    public Node build() {
+        return left.build();
+    }
+
+    private class ExpNodeBuilder implements NodeBuilder {
+
+        private final NodeBuilder operand;
+
+        private final String operation;
+
+        public ExpNodeBuilder(NodeBuilder operand, String operation) {
+            this.operand = operand;
+            this.operation = operation;
+        }
+
+        @Override
+        public Node build() {
+            Node node;
+            if(operation.equals("=")) {
+                node = new EqualNode();
+            } else {
+                node = new OpExpressionNode(operation);
+            }
+
+            node.addChild(left.build());
+            node.addChild(operand.build());
+            return node;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ExpressionTrait.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ExpressionTrait.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ExpressionTrait.java
new file mode 100644
index 0000000..91fd07c
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ExpressionTrait.java
@@ -0,0 +1,62 @@
+/*****************************************************************
+ *   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;
+
+/**
+ * @since 4.2
+ */
+interface ExpressionTrait extends NodeBuilder {
+
+    default ExpressionNodeBuilder lt(NodeBuilder operand) {
+        return new ExpressionNodeBuilder(this).lt(operand);
+    }
+
+    default ExpressionNodeBuilder gt(NodeBuilder operand) {
+        return new ExpressionNodeBuilder(this).gt(operand);
+    }
+
+    default ExpressionNodeBuilder lte(NodeBuilder operand) {
+        return new ExpressionNodeBuilder(this).lte(operand);
+    }
+
+    default ExpressionNodeBuilder gte(NodeBuilder operand) {
+        return new ExpressionNodeBuilder(this).gte(operand);
+    }
+
+    default ExpressionNodeBuilder eq(NodeBuilder nodeBuilder) {
+        return new ExpressionNodeBuilder(this).eq(nodeBuilder);
+    }
+
+    default ExpressionNodeBuilder plus(NodeBuilder nodeBuilder) {
+        return new ExpressionNodeBuilder(this).plus(nodeBuilder);
+    }
+
+    default ExpressionNodeBuilder minus(NodeBuilder nodeBuilder) {
+        return new ExpressionNodeBuilder(this).minus(nodeBuilder);
+    }
+
+    default ExpressionNodeBuilder mul(NodeBuilder nodeBuilder) {
+        return new ExpressionNodeBuilder(this).mul(nodeBuilder);
+    }
+
+    default ExpressionNodeBuilder div(NodeBuilder nodeBuilder) {
+        return new ExpressionNodeBuilder(this).div(nodeBuilder);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/FunctionNodeBuilder.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/FunctionNodeBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/FunctionNodeBuilder.java
new file mode 100644
index 0000000..030477d
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/FunctionNodeBuilder.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.sqlbuilder;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class FunctionNodeBuilder implements ExpressionTrait {
+
+    private final String functionName;
+    private final NodeBuilder[] args;
+
+    private String alias;
+
+    FunctionNodeBuilder(String functionName, NodeBuilder... args) {
+        this.functionName = functionName;
+        this.args = args;
+    }
+
+    public FunctionNodeBuilder as(String alias) {
+        this.alias = alias;
+        return this;
+    }
+
+    @Override
+    public Node build() {
+        Node functionNode = new FunctionNode(functionName, alias, true);
+
+        for(NodeBuilder arg : args) {
+            functionNode.addChild(arg.build());
+        }
+
+        return functionNode;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/JoinNodeBuilder.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/JoinNodeBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/JoinNodeBuilder.java
new file mode 100644
index 0000000..4ed7909
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/JoinNodeBuilder.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.sqlbuilder;
+
+import java.util.Objects;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.JoinNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class JoinNodeBuilder implements NodeBuilder {
+
+    private final JoinType joinType;
+    private final NodeBuilder table;
+
+    private NodeBuilder joinExp;
+
+    JoinNodeBuilder(JoinType joinType, NodeBuilder table) {
+        this.joinType = Objects.requireNonNull(joinType);
+        this.table = Objects.requireNonNull(table);
+    }
+
+    public JoinNodeBuilder on(NodeBuilder joinExp) {
+        this.joinExp = Objects.requireNonNull(joinExp);
+        return this;
+    }
+
+    @Override
+    public Node build() {
+        Node node = new JoinNode(joinType);
+        node.addChild(table.build());
+        node.addChild(joinExp.build());
+        return node;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/JoinType.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/JoinType.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/JoinType.java
new file mode 100644
index 0000000..b862849
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/JoinType.java
@@ -0,0 +1,39 @@
+/*****************************************************************
+ *   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;
+
+/**
+ * @since 4.2
+ */
+public enum JoinType {
+    INNER(" "),
+    LEFT(" LEFT "),
+    RIGHT(" RIGHT "),
+    OUTER(" FULL OUTER ");
+
+    private final String name;
+
+    JoinType(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/NodeBuilder.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/NodeBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/NodeBuilder.java
new file mode 100644
index 0000000..21d2034
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/NodeBuilder.java
@@ -0,0 +1,32 @@
+/*****************************************************************
+ *   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;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+@FunctionalInterface
+public interface NodeBuilder {
+
+    Node build();
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/NodeTreeVisitor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/NodeTreeVisitor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/NodeTreeVisitor.java
new file mode 100644
index 0000000..e1c01d1
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/NodeTreeVisitor.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.sqlbuilder;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public interface NodeTreeVisitor {
+
+    /**
+     * @param node to visit
+     * @return false if visitor should stop
+     */
+    boolean onNodeStart(Node node);
+
+    /**
+     * @param parent node
+     * @param child node
+     * @param index of this child in parent
+     * @param hasMore true if more children after this child
+     * @return false if visitor should stop
+     */
+    boolean onChildNodeStart(Node parent, Node child, int index, boolean 
hasMore);
+
+    void onChildNodeEnd(Node parent, Node child, int index, boolean hasMore);
+
+    void onNodeEnd(Node node);
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/OrderingNodeBuilder.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/OrderingNodeBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/OrderingNodeBuilder.java
new file mode 100644
index 0000000..cfc84e8
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/OrderingNodeBuilder.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.sqlbuilder;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.EmptyNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode;
+
+/**
+ * @since 4.2
+ */
+public class OrderingNodeBuilder implements NodeBuilder {
+
+    private final NodeBuilder column;
+
+    private String direction = "";
+
+    OrderingNodeBuilder(NodeBuilder column) {
+        this.column = column;
+    }
+
+    public OrderingNodeBuilder desc() {
+        direction = " DESC";
+        return this;
+    }
+
+    public OrderingNodeBuilder asc() {
+        direction = "";
+        return this;
+    }
+
+    @Override
+    public Node build() {
+        Node node = new EmptyNode();
+        node.addChild(column.build());
+        node.addChild(new TextNode(direction));
+        return node;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/QuotingAppendable.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/QuotingAppendable.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/QuotingAppendable.java
new file mode 100644
index 0000000..297cb63
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/QuotingAppendable.java
@@ -0,0 +1,41 @@
+/*****************************************************************
+ *   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;
+
+/**
+ * @since 4.2
+ */
+public interface QuotingAppendable extends Appendable {
+
+    @Override
+    QuotingAppendable append(CharSequence csq);
+
+    @Override
+    QuotingAppendable append(CharSequence csq, int start, int end);
+
+    @Override
+    QuotingAppendable append(char c);
+
+    QuotingAppendable append(int i);
+
+    QuotingAppendable appendQuoted(CharSequence csq);
+
+    SQLGenerationContext getContext();
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/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
new file mode 100644
index 0000000..0dcdfc1
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLBuilder.java
@@ -0,0 +1,161 @@
+/*****************************************************************
+ *   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;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.NodeType;
+import org.apache.cayenne.access.sqlbuilder.sqltree.SimpleNodeTreeVisitor;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode;
+
+/**
+ * @since 4.2
+ */
+public final class SQLBuilder {
+
+    public static SelectBuilder select(NodeBuilder... params) {
+        return new SelectBuilder(params);
+    }
+
+    public static TableNodeBuilder table(String table) {
+        return new TableNodeBuilder(table);
+    }
+
+    public static ColumnNodeBuilder column(String column) {
+        return new ColumnNodeBuilder(null, column);
+    }
+
+    public static JoinNodeBuilder join(NodeBuilder table) {
+        return new JoinNodeBuilder(JoinType.INNER, table);
+    }
+
+    public static JoinNodeBuilder leftJoin(NodeBuilder table) {
+        return new JoinNodeBuilder(JoinType.LEFT, table);
+    }
+
+    public static JoinNodeBuilder rightJoin(NodeBuilder table) {
+        return new JoinNodeBuilder(JoinType.RIGHT, table);
+    }
+
+    public static JoinNodeBuilder innerJoin(NodeBuilder table) {
+        return new JoinNodeBuilder(JoinType.INNER, table);
+    }
+
+    public static JoinNodeBuilder outerJoin(NodeBuilder table) {
+        return new JoinNodeBuilder(JoinType.OUTER, table);
+    }
+
+    public static ExpressionNodeBuilder exists(NodeBuilder builder) {
+        return new ExpressionNodeBuilder(new ExistsNodeBuilder(builder));
+    }
+
+    public static ValueNodeBuilder value(Object value) {
+        return new ValueNodeBuilder(value);
+    }
+
+    public static ExpressionNodeBuilder exp(NodeBuilder builder) {
+        return new ExpressionNodeBuilder(builder);
+    }
+
+    public static NodeBuilder node(Node node) {
+        return () -> node;
+    }
+
+    public static NodeBuilder aliased(NodeBuilder nodeBuilder, String alias) {
+        return new AliasedNodeBuilder(nodeBuilder, alias);
+    }
+
+    public static NodeBuilder aliased(Node node, String alias) {
+        if(suppressAlias(node)) {
+            return node(node);
+        }
+        return new AliasedNodeBuilder(node(node), alias);
+    }
+
+    public static NodeBuilder text(String text) {
+        return () -> new TextNode(text);
+    }
+
+    public static NodeBuilder all() {
+        return text(" *");
+    }
+
+    public static ExpressionNodeBuilder not(NodeBuilder value) {
+        return new ExpressionNodeBuilder(value).not();
+    }
+
+    public static FunctionNodeBuilder count(NodeBuilder value) {
+        return function("COUNT", value);
+    }
+
+    public static FunctionNodeBuilder count() {
+        return function("COUNT", column("*"));
+    }
+
+    public static FunctionNodeBuilder avg(NodeBuilder value) {
+        return function("AVG", value);
+    }
+
+    public static FunctionNodeBuilder min(NodeBuilder value) {
+        return function("MIN", value);
+    }
+
+    public static FunctionNodeBuilder max(NodeBuilder value) {
+        return function("MAX", value);
+    }
+
+    public static FunctionNodeBuilder function(String function, NodeBuilder... 
values) {
+        return new FunctionNodeBuilder(function, values);
+    }
+
+    public static OrderingNodeBuilder order(NodeBuilder expression) {
+        return new OrderingNodeBuilder(expression);
+    }
+
+    private SQLBuilder() {
+    }
+
+    private static boolean suppressAlias(Node node) {
+        return new SuppressAliasChecker().shouldSuppressForNode(node);
+    }
+
+    private static class SuppressAliasChecker extends SimpleNodeTreeVisitor {
+
+        private boolean suppressAlias;
+
+        public boolean shouldSuppressForNode(Node node) {
+            node.visit(this);
+            return suppressAlias;
+        }
+
+        @Override
+        public boolean onNodeStart(Node node) {
+            if(node.getType() == NodeType.COLUMN && ((ColumnNode) 
node).getAlias() != null) {
+                suppressAlias = true;
+                return false;
+            } else if(node.getType() == NodeType.FUNCTION && ((FunctionNode) 
node).getAlias() != null) {
+                suppressAlias = true;
+                return false;
+            }
+            return true;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
new file mode 100644
index 0000000..485fbbd
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
@@ -0,0 +1,35 @@
+/*****************************************************************
+ *   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;
+
+import java.util.Collection;
+
+import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.dba.DbAdapter;
+
+/**
+ * @since 4.2
+ */
+public interface SQLGenerationContext {
+
+    DbAdapter getAdapter();
+
+    Collection<DbAttributeBinding> getBindings();
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationVisitor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationVisitor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationVisitor.java
new file mode 100644
index 0000000..156909a
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationVisitor.java
@@ -0,0 +1,63 @@
+/*****************************************************************
+ *   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;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class SQLGenerationVisitor implements NodeTreeVisitor {
+
+    private final QuotingAppendable appendable;
+
+    public SQLGenerationVisitor(QuotingAppendable appendable) {
+        this.appendable = appendable;
+    }
+
+    @Override
+    public boolean onNodeStart(Node node) {
+        node.append(appendable);
+        node.appendChildrenStart(appendable);
+        return true;
+    }
+
+    @Override
+    public boolean onChildNodeStart(Node parent, Node child, int index, 
boolean hasMore) {
+        return true;
+    }
+
+    @Override
+    public void onChildNodeEnd(Node parent, Node child, int index, boolean 
hasMore) {
+        if(hasMore && parent != null) {
+            parent.appendChildrenSeparator(appendable, index);
+        }
+    }
+
+    @Override
+    public void onNodeEnd(Node node) {
+        node.appendChildrenEnd(appendable);
+    }
+
+    public String getSQLString() {
+        return appendable.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SelectBuilder.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SelectBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SelectBuilder.java
new file mode 100644
index 0000000..5c2a821
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SelectBuilder.java
@@ -0,0 +1,169 @@
+/*****************************************************************
+ *   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;
+
+import java.util.function.Supplier;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.DistinctNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FromNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.GroupByNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.HavingNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OrderByNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.SelectNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.SelectResultNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TopNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.WhereNode;
+
+/**
+ * @since 4.2
+ */
+public class SelectBuilder implements NodeBuilder {
+
+    private static final int SELECT_NODE    = 0;
+    private static final int FROM_NODE      = 1;
+    private static final int WHERE_NODE     = 2;
+    private static final int GROUPBY_NODE   = 3;
+    private static final int HAVING_NODE    = 4;
+    private static final int UNION_NODE     = 5;
+    private static final int ORDERBY_NODE   = 6;
+    private static final int LIMIT_NODE     = 7;
+
+    /**
+     * Main root of this query
+     */
+    private Node root;
+
+    /*
+     * Following nodes are all children of root,
+     * but we keep them here for quick access.
+     */
+    private Node[] nodes = new Node[LIMIT_NODE + 1];
+
+    SelectBuilder(NodeBuilder... selectExpressions) {
+        root = new SelectNode();
+        for(NodeBuilder exp : selectExpressions) {
+            node(SELECT_NODE, SelectResultNode::new).addChild(exp.build());
+        }
+    }
+
+    public SelectBuilder distinct() {
+        root.addChild(new DistinctNode());
+        return this;
+    }
+
+    public SelectBuilder top(int count) {
+        root.addChild(new TopNode(count));
+        return this;
+    }
+
+    public SelectBuilder result(NodeBuilder selectExpression) {
+        node(SELECT_NODE, 
SelectResultNode::new).addChild(selectExpression.build());
+        return this;
+    }
+
+    public SelectBuilder from(NodeBuilder table) {
+        node(FROM_NODE, FromNode::new).addChild(table.build());
+        return this;
+    }
+
+    public SelectBuilder from(NodeBuilder... tables) {
+        for(NodeBuilder next : tables) {
+            node(FROM_NODE, FromNode::new).addChild(next.build());
+        }
+        return this;
+    }
+
+    public SelectBuilder where(NodeBuilder... params) {
+        for(NodeBuilder next : params) {
+            node(WHERE_NODE, WhereNode::new).addChild(next.build());
+        }
+        return this;
+    }
+
+    public SelectBuilder where(Node node) {
+        node(WHERE_NODE, WhereNode::new).addChild(node);
+        return this;
+    }
+
+    public SelectBuilder orderBy(NodeBuilder... params) {
+        for(NodeBuilder next : params) {
+            node(ORDERBY_NODE, OrderByNode::new).addChild(next.build());
+        }
+        return this;
+    }
+
+    public SelectBuilder orderBy(NodeBuilder param) {
+        node(ORDERBY_NODE, OrderByNode::new).addChild(param.build());
+        return this;
+    }
+
+    public SelectBuilder groupBy(NodeBuilder... params) {
+        for(NodeBuilder next : params) {
+            node(GROUPBY_NODE, GroupByNode::new).addChild(next.build());
+        }
+        return this;
+    }
+
+    public SelectBuilder groupBy(Node node) {
+        node(GROUPBY_NODE, GroupByNode::new).addChild(node);
+        return this;
+    }
+
+    public SelectBuilder having(NodeBuilder... params) {
+        for(NodeBuilder next : params) {
+            node(HAVING_NODE, HavingNode::new).addChild(next.build());
+        }
+        return this;
+    }
+
+    public SelectBuilder having(Node node) {
+        node(HAVING_NODE, HavingNode::new).addChild(node);
+        return this;
+    }
+
+    public SelectBuilder limitOffset(int limit, int offset) {
+        nodes[LIMIT_NODE] = new LimitOffsetNode(limit, offset);
+        return this;
+    }
+
+    @Override
+    public Node build() {
+        for (Node next : nodes) {
+            if (next != null) {
+                root.addChild(next);
+            }
+        }
+        return root;
+    }
+
+    public Node getRoot() {
+        return root;
+    }
+
+    private Node node(int idx, Supplier<Node> nodeSupplier) {
+        if(nodes[idx] == null) {
+            nodes[idx] = nodeSupplier.get();
+        }
+        return nodes[idx];
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/StringBuilderAppendable.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/StringBuilderAppendable.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/StringBuilderAppendable.java
new file mode 100644
index 0000000..e0f24f7
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/StringBuilderAppendable.java
@@ -0,0 +1,76 @@
+/*****************************************************************
+ *   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;
+
+/**
+ * @since 4.2
+ */
+public class StringBuilderAppendable implements QuotingAppendable {
+
+    protected final StringBuilder builder;
+
+    public StringBuilderAppendable() {
+        this.builder = new StringBuilder();
+    }
+
+    @Override
+    public QuotingAppendable append(CharSequence csq) {
+        builder.append(csq);
+        return this;
+    }
+
+    @Override
+    public QuotingAppendable append(CharSequence csq, int start, int end) {
+        builder.append(csq, start, end);
+        return this;
+    }
+
+    @Override
+    public QuotingAppendable append(char c) {
+        builder.append(c);
+        return this;
+    }
+
+    @Override
+    public QuotingAppendable append(int c) {
+        builder.append(c);
+        return this;
+    }
+
+    @Override
+    public QuotingAppendable appendQuoted(CharSequence csq) {
+        builder.append(csq);
+        return this;
+    }
+
+    @Override
+    public SQLGenerationContext getContext() {
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return builder.toString();
+    }
+
+    public StringBuilder unwrap() {
+        return builder;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/TableNodeBuilder.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/TableNodeBuilder.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/TableNodeBuilder.java
new file mode 100644
index 0000000..681b209
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/TableNodeBuilder.java
@@ -0,0 +1,65 @@
+/*****************************************************************
+ *   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;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TableNode;
+import org.apache.cayenne.map.DbAttribute;
+
+/**
+ * @since 4.2
+ */
+public class TableNodeBuilder implements NodeBuilder {
+
+    private final String tableName;
+
+    private String alias;
+
+    TableNodeBuilder(String tableName) {
+        this.tableName = tableName;
+    }
+
+    public TableNodeBuilder as(String alias) {
+        this.alias = alias;
+        return this;
+    }
+
+    public ColumnNodeBuilder column(String column) {
+        return new ColumnNodeBuilder(tableName, column);
+    }
+
+    public ColumnNodeBuilder column(DbAttribute attribute) {
+        return new ColumnNodeBuilder(tableName, attribute);
+    }
+
+    public String getAlias() {
+        return alias;
+    }
+
+    public String getTableName() {
+        return tableName;
+    }
+
+    @Override
+    public Node build() {
+        return new TableNode(tableName, alias);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/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
new file mode 100644
index 0000000..5e027c6
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/ValueNodeBuilder.java
@@ -0,0 +1,48 @@
+/*****************************************************************
+ *   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;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ValueNode;
+import org.apache.cayenne.map.DbAttribute;
+
+/**
+ * @since 4.2
+ */
+public class ValueNodeBuilder implements NodeBuilder, ExpressionTrait {
+
+    private final Object value;
+
+    private DbAttribute attribute;
+
+    ValueNodeBuilder(Object value) {
+        this.value = value;
+    }
+
+    public ValueNodeBuilder attribute(DbAttribute attribute) {
+        this.attribute = attribute;
+        return this;
+    }
+
+    @Override
+    public Node build() {
+        return new ValueNode(value, attribute);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/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
new file mode 100644
index 0000000..1102a8a
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BetweenNode.java
@@ -0,0 +1,52 @@
+/*****************************************************************
+ *   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 BetweenNode extends ExpressionNode {
+
+    private final boolean not;
+
+    public BetweenNode(boolean not) {
+        this.not = not;
+    }
+
+    @Override
+    public void appendChildrenSeparator(QuotingAppendable buffer, int 
childIdx) {
+        if (childIdx == 0) {
+            if (not) {
+                buffer.append(" NOT");
+            }
+            buffer.append(" BETWEEN");
+        } else {
+            buffer.append(" AND");
+        }
+    }
+
+    @Override
+    public Node copy() {
+        return new BetweenNode(not);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BitwiseNotNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BitwiseNotNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BitwiseNotNode.java
new file mode 100644
index 0000000..8d14308
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/BitwiseNotNode.java
@@ -0,0 +1,38 @@
+/*****************************************************************
+ *   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 BitwiseNotNode extends ExpressionNode {
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer.append('!');
+    }
+
+    @Override
+    public Node copy() {
+        return new BitwiseNotNode();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/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
new file mode 100644
index 0000000..b610104
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ColumnNode.java
@@ -0,0 +1,76 @@
+/*****************************************************************
+ *   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;
+import org.apache.cayenne.map.DbAttribute;
+
+/**
+ * @since 4.2
+ */
+public class ColumnNode extends Node {
+
+    protected final String table;
+    protected final String column;
+    protected final String alias;
+    protected final DbAttribute attribute;
+
+    public ColumnNode(String table, String column, String alias, DbAttribute 
attribute) {
+        super(NodeType.COLUMN);
+        this.table = table;
+        this.column = column;
+        this.alias = alias;
+        this.attribute = attribute;
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        buffer.append(' ');
+        if (table != null) {
+            buffer.appendQuoted(table).append('.');
+        }
+        buffer.appendQuoted(column);
+        if (alias != null) {
+            buffer.append(' ').appendQuoted(alias);
+        }
+        return buffer;
+    }
+
+    public String getTable() {
+        return table;
+    }
+
+    public String getColumn() {
+        return column;
+    }
+
+    public String getAlias() {
+        return alias;
+    }
+
+    public DbAttribute getAttribute() {
+        return attribute;
+    }
+
+    @Override
+    public Node copy() {
+        return new ColumnNode(table, column, alias, attribute);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/DistinctNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/DistinctNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/DistinctNode.java
new file mode 100644
index 0000000..bf08c76
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/DistinctNode.java
@@ -0,0 +1,43 @@
+/*****************************************************************
+ *   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 DistinctNode extends Node {
+
+    public DistinctNode() {
+        super(NodeType.DISTINCT);
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer.append(" DISTINCT");
+    }
+
+    @Override
+    public Node copy() {
+        return new DistinctNode();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EmptyNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EmptyNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EmptyNode.java
new file mode 100644
index 0000000..1a01209
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EmptyNode.java
@@ -0,0 +1,43 @@
+/*****************************************************************
+ *   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 EmptyNode extends Node {
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer;
+    }
+
+    @Override
+    public String toString() {
+        return "EmptyNode";
+    }
+
+    @Override
+    public Node copy() {
+        return new EmptyNode();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EqualNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EqualNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EqualNode.java
new file mode 100644
index 0000000..b158c63
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EqualNode.java
@@ -0,0 +1,43 @@
+/*****************************************************************
+ *   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 EqualNode extends ExpressionNode {
+
+    public EqualNode() {
+        super(NodeType.EQUALITY);
+    }
+
+    @Override
+    public void appendChildrenSeparator(QuotingAppendable buffer, int 
childIdx) {
+        Node child = getChild(1);
+        if (child.getType() == NodeType.VALUE && ((ValueNode) 
child).getValue() == null) {
+            buffer.append(" IS NULL");
+        } else {
+            buffer.append(" =");
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExistsNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExistsNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExistsNode.java
new file mode 100644
index 0000000..8322e0b
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExistsNode.java
@@ -0,0 +1,38 @@
+/*****************************************************************
+ *   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 ExistsNode extends Node {
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer.append(" EXISTS");
+    }
+
+    @Override
+    public Node copy() {
+        return new ExistsNode();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/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
new file mode 100644
index 0000000..8344380
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
@@ -0,0 +1,65 @@
+/*****************************************************************
+ *   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 ExpressionNode extends Node {
+
+    public ExpressionNode() {
+    }
+
+    public ExpressionNode(NodeType nodeType) {
+        super(nodeType);
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer;
+    }
+
+    @Override
+    public void appendChildrenStart(QuotingAppendable buffer) {
+        if(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) {
+            buffer.append(" )");
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "{ExpressionNode}";
+    }
+
+    @Override
+    public Node copy() {
+        return new ExpressionNode();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FromNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FromNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FromNode.java
new file mode 100644
index 0000000..84d93ad
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FromNode.java
@@ -0,0 +1,43 @@
+/*****************************************************************
+ *   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 FromNode extends Node {
+
+    public FromNode() {
+        super(NodeType.FROM);
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer.append(" FROM");
+    }
+
+    @Override
+    public Node copy() {
+        return new FromNode();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/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
new file mode 100644
index 0000000..23b0fa7
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/FunctionNode.java
@@ -0,0 +1,127 @@
+/*****************************************************************
+ *   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.NodeTreeVisitor;
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+
+/**
+ * @since 4.2
+ */
+public class FunctionNode extends Node {
+
+    private final String functionName;
+    private final String alias;
+    private final boolean needParentheses;
+
+    public FunctionNode(String functionName, String alias) {
+        this(functionName, alias, true);
+    }
+
+    public FunctionNode(String functionName, String alias, boolean 
needParentheses) {
+        super(NodeType.FUNCTION);
+        this.functionName = functionName;
+        this.alias = alias;
+        this.needParentheses = needParentheses;
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        if(skipContent()) {
+            buffer.append(' ').append(alias);
+        } else {
+            buffer.append(' ').append(functionName);
+        }
+        return buffer;
+    }
+
+    @Override
+    public void visit(NodeTreeVisitor visitor) {
+        if(skipContent()) {
+            visitor.onNodeStart(this);
+            visitor.onNodeEnd(this);
+            return;
+        }
+        super.visit(visitor);
+    }
+
+    @Override
+    public void appendChildrenStart(QuotingAppendable buffer) {
+        if(skipContent()){
+            return;
+        }
+        if (needParentheses) {
+            buffer.append('(');
+        }
+    }
+
+    @Override
+    public void appendChildrenEnd(QuotingAppendable buffer) {
+        if(skipContent()){
+            return;
+        }
+
+        if (needParentheses) {
+            buffer.append(" )");
+        }
+
+        if (alias != null) {
+            buffer.append(" AS ").appendQuoted(alias);
+        }
+    }
+
+    @Override
+    public void appendChildrenSeparator(QuotingAppendable buffer, int 
childIdx) {
+        if(skipContent()) {
+            return;
+        }
+        buffer.append(',');
+    }
+
+    public String getFunctionName() {
+        return functionName;
+    }
+
+    public String getAlias() {
+        return alias;
+    }
+
+    @Override
+    public Node copy() {
+        return new FunctionNode(functionName, alias, needParentheses);
+    }
+
+    private boolean notInResultNode() {
+        // check if parent is of type RESULT
+        Node parent = getParent();
+        while(parent != null) {
+            if(parent.getType() == NodeType.RESULT) {
+                return false;
+            }
+            parent = parent.getParent();
+        }
+        return true;
+    }
+
+    protected boolean skipContent() {
+        // has alias and not in result node
+        return alias != null && notInResultNode();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/GroupByNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/GroupByNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/GroupByNode.java
new file mode 100644
index 0000000..382b7b8
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/GroupByNode.java
@@ -0,0 +1,43 @@
+/*****************************************************************
+ *   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 GroupByNode extends Node {
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer.append(" GROUP BY");
+    }
+
+    @Override
+    public void appendChildrenSeparator(QuotingAppendable buffer, int 
childIdx) {
+        buffer.append(',');
+    }
+
+    @Override
+    public Node copy() {
+        return new GroupByNode();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/HavingNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/HavingNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/HavingNode.java
new file mode 100644
index 0000000..c705c2e
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/HavingNode.java
@@ -0,0 +1,37 @@
+/*****************************************************************
+ *   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 HavingNode extends Node {
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer.append(" HAVING");
+    }
+
+    @Override
+    public Node copy() {
+        return new HavingNode();
+    }
+}

Reply via email to