Repository: cayenne
Updated Branches:
  refs/heads/master 988937524 -> 89439015f


http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InNode.java
new file mode 100644
index 0000000..fd08903
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InNode.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 InNode extends Node {
+
+    private final boolean not;
+
+    public InNode(boolean not) {
+        super(NodeType.IN);
+        this.not = not;
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer;
+    }
+
+    @Override
+    public void appendChildrenSeparator(QuotingAppendable buffer, int 
childInd) {
+        if (childInd == 0) {
+            if (not) {
+                buffer.append(" NOT");
+            }
+            buffer.append(" IN (");
+        }
+    }
+
+    @Override
+    public void appendChildrenEnd(QuotingAppendable buffer) {
+        buffer.append(')');
+    }
+
+    @Override
+    public Node copy() {
+        return new InNode(not);
+    }
+
+    public boolean isNot() {
+        return not;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/JoinNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/JoinNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/JoinNode.java
new file mode 100644
index 0000000..fcb556d
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/JoinNode.java
@@ -0,0 +1,51 @@
+/*****************************************************************
+ *   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.JoinType;
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+
+/**
+ * @since 4.2
+ */
+public class JoinNode extends Node {
+
+    private final JoinType joinType;
+
+    public JoinNode(JoinType joinType) {
+        super(NodeType.JOIN);
+        this.joinType = joinType;
+    }
+
+    @Override
+    public Node copy() {
+        return new JoinNode(joinType);
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer.append(joinType.getName()).append("JOIN");
+    }
+
+    @Override
+    public void appendChildrenSeparator(QuotingAppendable buffer, int 
childInd) {
+        buffer.append(" ON");
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/LikeNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/LikeNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/LikeNode.java
new file mode 100644
index 0000000..5b4037c
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/LikeNode.java
@@ -0,0 +1,89 @@
+/*****************************************************************
+ *   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;
+
+/**
+ * expressions: LIKE, ILIKE, NOT LIKE, NOT ILIKE + ESCAPE
+ *
+ * @since 4.2
+ */
+public class LikeNode extends ExpressionNode {
+
+    protected final boolean ignoreCase;
+    protected final boolean not;
+    protected final char escape;
+
+    public LikeNode(boolean ignoreCase, boolean not, char escape) {
+        super(NodeType.LIKE);
+        this.ignoreCase = ignoreCase;
+        this.not = not;
+        this.escape = escape;
+    }
+
+    @Override
+    public void appendChildrenStart(QuotingAppendable buffer) {
+        if(ignoreCase) {
+            buffer.append(" UPPER(");
+        }
+    }
+
+    @Override
+    public void appendChildrenSeparator(QuotingAppendable buffer, int 
childIdx) {
+        if(ignoreCase) {
+            buffer.append(')');
+        }
+        if(not) {
+            buffer.append(" NOT");
+        }
+        buffer.append(" LIKE");
+        if(ignoreCase) {
+            buffer.append(" UPPER(");
+        }
+    }
+
+    @Override
+    public void appendChildrenEnd(QuotingAppendable buffer) {
+        if(ignoreCase) {
+            buffer.append(')');
+        }
+        if(escape != 0) {
+            buffer.append(" ESCAPE '").append(escape).append('\'');
+        }
+    }
+
+    @Override
+    public Node copy() {
+        return new LikeNode(ignoreCase, not, escape);
+    }
+
+    public boolean isIgnoreCase() {
+        return ignoreCase;
+    }
+
+    public boolean isNot() {
+        return not;
+    }
+
+    public char getEscape() {
+        return escape;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/LimitOffsetNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/LimitOffsetNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/LimitOffsetNode.java
new file mode 100644
index 0000000..1141077
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/LimitOffsetNode.java
@@ -0,0 +1,58 @@
+/*****************************************************************
+ *   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 LimitOffsetNode extends Node {
+
+    protected final int limit;
+    protected final int offset;
+
+    public LimitOffsetNode(int limit, int offset) {
+        super(NodeType.LIMIT_OFFSET);
+        this.limit = limit;
+        this.offset = offset;
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        if(limit == 0 && offset == 0) {
+            return buffer;
+        }
+        return buffer.append(" LIMIT ").append(limit).append(" OFFSET 
").append(offset);
+    }
+
+    public int getLimit() {
+        return limit;
+    }
+
+    public int getOffset() {
+        return offset;
+    }
+
+    @Override
+    public Node copy() {
+        return new LimitOffsetNode(limit, offset);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/Node.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/Node.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/Node.java
new file mode 100644
index 0000000..d535028
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/Node.java
@@ -0,0 +1,139 @@
+/*****************************************************************
+ *   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 java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cayenne.access.sqlbuilder.NodeTreeVisitor;
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.StringBuilderAppendable;
+
+
+/**
+ * @since 4.2
+ */
+public abstract class Node {
+
+    protected Node parent;
+
+    protected List<Node> children;
+
+    protected final NodeType type;
+
+    public Node(NodeType type) {
+        this.type = type;
+    }
+
+    public Node() {
+        this(NodeType.UNDEFINED);
+    }
+
+    public Node addChild(int index, Node node) {
+        children.add(index, node);
+        node.setParent(this);
+        return this;
+    }
+
+    public Node addChild(Node node) {
+        if(children == null) {
+            children = new ArrayList<>(4);
+        }
+        children.add(node);
+        node.setParent(this);
+        return this;
+    }
+
+    public Node getChild(int idx) {
+        return children.get(idx);
+    }
+
+    public int getChildrenCount() {
+        if(children == null) {
+            return 0;
+        }
+        return children.size();
+    }
+
+    public void replaceChild(int idx, Node node) {
+        children.set(idx, node).setParent(null);
+        node.setParent(this);
+    }
+
+    public Node getParent() {
+        return parent;
+    }
+
+    public void setParent(Node parent) {
+        this.parent = parent;
+    }
+
+    public void visit(NodeTreeVisitor visitor) {
+        if(!visitor.onNodeStart(this)) {
+            return;
+        }
+        int count = getChildrenCount();
+        for(int i=0; i<count; i++) {
+            if(!visitor.onChildNodeStart(this, getChild(i), i, i < (count - 
1))) {
+                return;
+            }
+            getChild(i).visit(visitor);
+            visitor.onChildNodeEnd(this, getChild(i), i, i < (count - 1));
+        }
+        visitor.onNodeEnd(this);
+    }
+
+    /**
+     * @return deep copy(i.e. with copies of all children) of this node
+     */
+    @SuppressWarnings("unchecked")
+    public <T extends Node> T deepCopy() {
+        Node node = this.copy();
+        if(children != null) {
+            node.children = new ArrayList<>(children.size());
+            for(Node child : children) {
+                node.children.add(child.deepCopy());
+            }
+        }
+        return (T)node;
+    }
+
+    @Override
+    public String toString() {
+        return "Node {" + append(new StringBuilderAppendable()).toString() + 
"}";
+    }
+
+    public NodeType getType() {
+        return type;
+    }
+
+    public abstract Node copy();
+
+    public abstract QuotingAppendable append(QuotingAppendable buffer);
+
+    public void appendChildrenSeparator(QuotingAppendable buffer, int 
childInd) {
+    }
+
+    public void appendChildrenStart(QuotingAppendable buffer) {
+    }
+
+    public void appendChildrenEnd(QuotingAppendable buffer) {
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java
new file mode 100644
index 0000000..8c6985a
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.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;
+
+/**
+ * @since 4.2
+ */
+public enum NodeType {
+    UNDEFINED,
+    VALUE,
+    COLUMN,
+    LIMIT_OFFSET,
+    FUNCTION,
+    EQUALITY,
+    LIKE,
+    DISTINCT,
+    IN,
+    RESULT,
+    WHERE,
+    JOIN, FROM
+}

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

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

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OffsetFetchNextNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OffsetFetchNextNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OffsetFetchNextNode.java
new file mode 100644
index 0000000..b60498b
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OffsetFetchNextNode.java
@@ -0,0 +1,53 @@
+/*****************************************************************
+ *   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 OffsetFetchNextNode extends LimitOffsetNode {
+
+    public OffsetFetchNextNode(LimitOffsetNode node) {
+        super(node.getLimit(), node.getOffset());
+    }
+
+    private OffsetFetchNextNode(int limit, int offset) {
+        super(limit, offset);
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        // OFFSET X ROWS FETCH NEXT Y ROWS ONLY
+        if(offset > 0) {
+            buffer.append(" OFFSET ").append(offset).append(" ROWS");
+        }
+        if(limit > 0) {
+            buffer.append(" FETCH NEXT ").append(limit).append(" ROWS ONLY");
+        }
+        return buffer;
+    }
+
+    @Override
+    public Node copy() {
+        return new OffsetFetchNextNode(limit, offset);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OffsetNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OffsetNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OffsetNode.java
new file mode 100644
index 0000000..b43e593
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OffsetNode.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 OffsetNode extends Node {
+    private final int offset;
+
+    public OffsetNode(int offset) {
+        this.offset = offset;
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer.append(" OFFSET ").append(offset);
+    }
+
+    @Override
+    public Node copy() {
+        return new OffsetNode(offset);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/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
new file mode 100644
index 0000000..7099000
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/OpExpressionNode.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.sqlbuilder.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+
+/**
+ * @since 4.2
+ */
+public class OpExpressionNode extends ExpressionNode {
+
+    private final String op;
+
+    public OpExpressionNode(String op) {
+        this.op = op;
+    }
+
+    @Override
+    public void appendChildrenSeparator(QuotingAppendable buffer, int 
childInd) {
+        buffer.append(' ').append(op);
+    }
+
+    @Override
+    public Node copy() {
+        return new OpExpressionNode(op);
+    }
+}

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

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SelectNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SelectNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SelectNode.java
new file mode 100644
index 0000000..997de9f
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SelectNode.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.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+
+/**
+ * @since 4.2
+ */
+public class SelectNode extends Node {
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable builder) {
+        if(parent != null) {
+            builder.append(" (");
+        }
+        return builder.append("SELECT");
+    }
+
+    @Override
+    public void appendChildrenEnd(QuotingAppendable buffer) {
+        if(parent != null) {
+            buffer.append(')');
+        }
+    }
+
+    @Override
+    public Node copy() {
+        return new SelectNode();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SelectResultNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SelectResultNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SelectResultNode.java
new file mode 100644
index 0000000..b500324
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SelectResultNode.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 SelectResultNode extends Node {
+
+    public SelectResultNode() {
+        super(NodeType.RESULT);
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer;
+    }
+
+    @Override
+    public void appendChildrenSeparator(QuotingAppendable buffer, int 
childIdx) {
+        buffer.append(',');
+    }
+
+    @Override
+    public String toString() {
+        return "{SelectResultNode}";
+    }
+
+    @Override
+    public Node copy() {
+        return new SelectResultNode();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SimpleNodeTreeVisitor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SimpleNodeTreeVisitor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SimpleNodeTreeVisitor.java
new file mode 100644
index 0000000..d619e2f
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SimpleNodeTreeVisitor.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.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.NodeTreeVisitor;
+
+/**
+ * @since 4.2
+ */
+public abstract class SimpleNodeTreeVisitor implements NodeTreeVisitor {
+
+    @Override
+    public boolean onNodeStart(Node node) {
+        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) {
+    }
+
+    @Override
+    public 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/sqltree/SubqueryNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SubqueryNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SubqueryNode.java
new file mode 100644
index 0000000..86b753f
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SubqueryNode.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.sqlbuilder.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+
+/**
+ * @since 4.2
+ */
+public class SubqueryNode extends Node {
+
+    private final String subqueryType;
+
+    public SubqueryNode(String subqueryType) {
+        this.subqueryType = subqueryType;
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer.append(subqueryType);
+    }
+
+    @Override
+    public Node copy() {
+        return new SubqueryNode(subqueryType);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TableNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TableNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TableNode.java
new file mode 100644
index 0000000..ed8661b
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TableNode.java
@@ -0,0 +1,50 @@
+/*****************************************************************
+ *   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 TableNode extends Node {
+
+    private final String tableName;
+    private final String alias;
+
+    public TableNode(String tableName, String alias) {
+        this.tableName = tableName;
+        this.alias = alias;
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        buffer.append(' ').appendQuoted(tableName);
+        if (alias != null) {
+            buffer.append(' ').appendQuoted(alias);
+        }
+        return buffer;
+    }
+
+    @Override
+    public Node copy() {
+        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/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
new file mode 100644
index 0000000..45ee5cf
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TextNode.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.sqlbuilder.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+
+/**
+ * @since 4.2
+ */
+public class TextNode extends Node {
+
+    private final CharSequence text;
+
+    public TextNode(CharSequence text) {
+        this.text = text;
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer.append(text);
+    }
+
+    @Override
+    public Node copy() {
+        return new TextNode(text);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TopNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TopNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TopNode.java
new file mode 100644
index 0000000..56b4629
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TopNode.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.sqlbuilder.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+
+/**
+ * @since 4.2
+ */
+public class TopNode extends Node {
+
+    private final int count;
+
+    public TopNode(int count) {
+        this.count = count;
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer.append(" TOP ").append(count);
+    }
+
+    @Override
+    public Node copy() {
+        return new TopNode(count);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TrimmingColumnNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TrimmingColumnNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TrimmingColumnNode.java
new file mode 100644
index 0000000..c4c252b
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TrimmingColumnNode.java
@@ -0,0 +1,134 @@
+/*****************************************************************
+ *   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 java.sql.Types;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+
+/**
+ * @since 4.2
+ */
+public class TrimmingColumnNode extends Node {
+
+    protected final ColumnNode columnNode ;
+
+    public TrimmingColumnNode(ColumnNode columnNode) {
+        this.columnNode = columnNode;
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        boolean isResult = isResultNode();
+        if(columnNode.getAlias() == null || isResult) {
+            if(isCharType()) {
+                appendRtrim(buffer);
+                appendAlias(buffer, isResult);
+            } else if(isComparisionWithClob()) {
+                appendClobColumnNode(buffer);
+                appendAlias(buffer, isResult);
+            } else {
+                columnNode.append(buffer);
+            }
+        } else {
+            appendAlias(buffer, false);
+        }
+
+        return buffer;
+    }
+
+    private boolean isComparisionWithClob() {
+        return (getParent().getType() == NodeType.EQUALITY
+                || getParent().getType() == NodeType.LIKE)
+                && columnNode.getAttribute() != null
+                && columnNode.getAttribute().getType() == Types.CLOB;
+    }
+
+    protected void appendRtrim(QuotingAppendable buffer) {
+        buffer.append(" RTRIM(");
+        appendColumnNode(buffer);
+        buffer.append(")");
+    }
+
+    private boolean isCharType() {
+        return columnNode.getAttribute() != null
+                && columnNode.getAttribute().getType() == Types.CHAR;
+    }
+
+    protected boolean isResultNode() {
+        Node parent = getParent();
+        while(parent != null) {
+            if(parent.getType() == NodeType.RESULT) {
+                return true;
+            }
+            parent = parent.getParent();
+        }
+        return false;
+    }
+
+    protected void appendClobColumnNode(QuotingAppendable buffer) {
+        buffer.append(" CAST(");
+        appendColumnNode(buffer);
+        buffer.append(" AS VARCHAR(").append(getColumnSize()).append("))");
+    }
+
+    protected void appendColumnNode(QuotingAppendable buffer) {
+        if (columnNode.getTable() != null) {
+            buffer.appendQuoted(columnNode.getTable()).append('.');
+        }
+        buffer.appendQuoted(columnNode.getColumn());
+    }
+
+    protected void appendAlias(QuotingAppendable buffer, boolean isResult) {
+        if(!isResult) {
+            return;
+        }
+        if (columnNode.getAlias() != null) {
+            buffer.append(' ').appendQuoted(columnNode.getAlias());
+        }
+    }
+
+    protected int getColumnSize() {
+        int size = columnNode.getAttribute().getMaxLength();
+        if(size > 0) {
+            return size;
+        }
+
+        int siblings = getParent().getChildrenCount();
+        for(int i=0; i<siblings; i++) {
+            Node sibling = getParent().getChild(i);
+            if(sibling == this) {
+                continue;
+            }
+            if(sibling.getType() == NodeType.VALUE) {
+                if(((ValueNode)sibling).getValue() instanceof CharSequence) {
+                    return ((CharSequence) 
((ValueNode)sibling).getValue()).length();
+                }
+            }
+        }
+
+        return 255;
+    }
+
+    @Override
+    public Node copy() {
+        return new TrimmingColumnNode(columnNode.deepCopy());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/UnescapedColumnNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/UnescapedColumnNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/UnescapedColumnNode.java
new file mode 100644
index 0000000..c3c8b38
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/UnescapedColumnNode.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;
+import org.apache.cayenne.map.DbAttribute;
+
+/**
+ * @since 4.2
+ */
+public class UnescapedColumnNode extends ColumnNode {
+
+    public UnescapedColumnNode(String table, String column, String alias, 
DbAttribute attribute) {
+        super(table, column, alias, attribute);
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        buffer.append(' ');
+        if (table != null) {
+            buffer.append(table).append('.');
+        }
+        buffer.append(column);
+        if (alias != null) {
+            buffer.append(' ').appendQuoted(alias);
+        }
+
+        return buffer;
+    }
+
+    @Override
+    public Node copy() {
+        return new UnescapedColumnNode(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/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
new file mode 100644
index 0000000..2ba5fbc
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
@@ -0,0 +1,246 @@
+/*****************************************************************
+ *   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.CayenneRuntimeException;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
+import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.types.ExtendedType;
+import org.apache.cayenne.map.DbAttribute;
+
+/**
+ * @since 4.2
+ */
+public class ValueNode extends Node {
+
+    private final Object value;
+    // Used as hint for type of this value
+    private final DbAttribute attribute;
+
+    public ValueNode(Object value, DbAttribute attribute) {
+        super(NodeType.VALUE);
+        this.value = value;
+        this.attribute = attribute;
+    }
+
+    public Object getValue() {
+        return value;
+    }
+
+    public DbAttribute getAttribute() {
+        return attribute;
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        appendValue(value, buffer);
+        return buffer;
+    }
+
+    private void appendValue(Object val, QuotingAppendable buffer) {
+        if(val == null) {
+            return;
+        }
+
+        boolean isArray = val.getClass().isArray();
+        if(isArray) {
+            if(val instanceof short[]) {
+                appendValue((short[])val, buffer);
+            } else if(val instanceof char[]) {
+                appendValue((char[])val, buffer);
+            } else if(val instanceof int[]) {
+                appendValue((int[])val, buffer);
+            } else if(val instanceof long[]) {
+                appendValue((long[])val, buffer);
+            } else if(val instanceof float[]) {
+                appendValue((float[])val, buffer);
+            } else if(val instanceof double[]) {
+                appendValue((double[])val, buffer);
+            } else if(val instanceof boolean[]) {
+                appendValue((boolean[])val, buffer);
+            } else if(val instanceof Object[]) {
+                appendValue((Object[]) val, buffer);
+            } else if(val instanceof byte[]) {
+                // append byte[] array as single object
+                appendObjectValue(buffer, val);
+            } else {
+                throw new CayenneRuntimeException("Unsupported array type %s", 
val.getClass().getName());
+            }
+        } else {
+            if(val instanceof Persistent) {
+                appendValue((Persistent) val, buffer);
+            } else if(val instanceof ObjectId) {
+                appendValue((ObjectId) val, buffer);
+            } else if(val instanceof CharSequence) {
+                appendStringValue(buffer, (CharSequence)val);
+            } else {
+                appendObjectValue(buffer, val);
+            }
+        }
+    }
+
+    protected void appendObjectValue(QuotingAppendable buffer, Object value) {
+        if(value == null) {
+            return;
+        }
+        if(buffer.getContext() == null) {
+            buffer.append(' ').append(value.toString());
+        } else {
+            buffer.append(" ?");
+            addValueBinding(buffer, value);
+        }
+    }
+
+    protected void appendStringValue(QuotingAppendable buffer, CharSequence 
value) {
+        if(buffer.getContext() == null) {
+            buffer.append(" '").append(value).append("'");
+        } else {
+            // value can't be null here
+            buffer.append(" ?");
+            addValueBinding(buffer, value);
+        }
+    }
+
+    protected void addValueBinding(QuotingAppendable buffer, Object value) {
+        // value can't be null here
+        SQLGenerationContext context = buffer.getContext();
+        // allow translation in out-of-context scope, to be able to use as a 
standalone SQL generator
+        ExtendedType extendedType = 
context.getAdapter().getExtendedTypes().getRegisteredType(value.getClass());
+        DbAttributeBinding binding = new DbAttributeBinding(attribute);
+        binding.setStatementPosition(context.getBindings().size() + 1);
+        binding.setExtendedType(extendedType);
+        binding.setValue(value);
+        context.getBindings().add(binding);
+    }
+
+    private void appendValue(Persistent value, QuotingAppendable buffer) {
+        appendValue(value.getObjectId(), buffer);
+    }
+
+    private void appendValue(ObjectId value, QuotingAppendable buffer) {
+        for(Object idVal: value.getIdSnapshot().values()) {
+            appendValue(idVal, buffer);
+        }
+    }
+
+    private void appendValue(short[] val, QuotingAppendable buffer) {
+        boolean first = true;
+        for(short i : val) {
+            if(first) {
+                first = false;
+            } else {
+                buffer.append(',');
+            }
+            appendValue(i, buffer);
+        }
+    }
+
+    private void appendValue(char[] val, QuotingAppendable buffer) {
+        boolean first = true;
+        for(char i : val) {
+            if(first) {
+                first = false;
+            } else {
+                buffer.append(',');
+            }
+            appendValue(i, buffer);
+        }
+    }
+
+    private void appendValue(int[] val, QuotingAppendable buffer) {
+        boolean first = true;
+        for(int i : val) {
+            if(first) {
+                first = false;
+            } else {
+                buffer.append(',');
+            }
+            appendValue(i, buffer);
+        }
+    }
+
+    private void appendValue(long[] val, QuotingAppendable buffer) {
+        boolean first = true;
+        for(long i : val) {
+            if(first) {
+                first = false;
+            } else {
+                buffer.append(',');
+            }
+            appendValue(i, buffer);
+        }
+    }
+
+    private void appendValue(float[] val, QuotingAppendable buffer) {
+        boolean first = true;
+        for(float i : val) {
+            if(first) {
+                first = false;
+            } else {
+                buffer.append(',');
+            }
+            appendValue(i, buffer);
+        }
+    }
+
+    private void appendValue(double[] val, QuotingAppendable buffer) {
+        boolean first = true;
+        for(double i : val) {
+            if(first) {
+                first = false;
+            } else {
+                buffer.append(',');
+            }
+            appendValue(i, buffer);
+        }
+    }
+
+    private void appendValue(boolean[] val, QuotingAppendable buffer) {
+        boolean first = true;
+        for(boolean 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) {
+            if(first) {
+                first = false;
+            } else {
+                buffer.append(',');
+            }
+            appendValue(i, buffer);
+        }
+    }
+
+    @Override
+    public Node copy() {
+        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/WhereNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/WhereNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/WhereNode.java
new file mode 100644
index 0000000..68cc65e
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/WhereNode.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 WhereNode extends Node {
+
+    public WhereNode() {
+        super(NodeType.WHERE);
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer.append(" WHERE");
+    }
+
+    @Override
+    public Node copy() {
+        return new WhereNode();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/89439015/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/SelectBuilderTest.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/SelectBuilderTest.java
 
b/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/SelectBuilderTest.java
new file mode 100644
index 0000000..74e8fe3
--- /dev/null
+++ 
b/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/SelectBuilderTest.java
@@ -0,0 +1,131 @@
+/*****************************************************************
+ *   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.SelectNode;
+import org.junit.Test;
+
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.*;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.2
+ */
+public class SelectBuilderTest {
+
+    @Test
+    public void testSelect() {
+        SelectBuilder builder = new SelectBuilder();
+        Node node = builder.build();
+        assertThat(node, instanceOf(SelectNode.class));
+        assertSQL("SELECT", node);
+    }
+
+    @Test
+    public void testSelectColumns() {
+        SelectBuilder builder = new SelectBuilder(column("a"), 
table("c").column("b"));
+        Node node = builder.build();
+        assertThat(node, instanceOf(SelectNode.class));
+        assertSQL("SELECT a, c.b", node);
+    }
+
+    @Test
+    public void testSelectFrom() {
+        SelectBuilder builder = new 
SelectBuilder(column("a")).from(table("b"));
+        Node node = builder.build();
+        assertThat(node, instanceOf(SelectNode.class));
+        assertSQL("SELECT a FROM b", node);
+    }
+
+    @Test
+    public void testSelectFromWhere() {
+        SelectBuilder builder = new SelectBuilder(column("a"))
+                .from(table("b"))
+                .where(column("a").eq(value(123)));
+        Node node = builder.build();
+        assertThat(node, instanceOf(SelectNode.class));
+        assertSQL("SELECT a FROM b WHERE a = 123", node);
+    }
+
+    @Test
+    public void testSelectFromWhereNull() {
+        SelectBuilder builder = new SelectBuilder(column("a"))
+                .from(table("b"))
+                .where(column("a").eq(value(null)));
+        Node node = builder.build();
+        assertThat(node, instanceOf(SelectNode.class));
+        assertSQL("SELECT a FROM b WHERE a IS NULL", node);
+    }
+
+    @Test
+    public void testSelectFromWhereComplex() {
+        SelectBuilder builder = new SelectBuilder(column("a"))
+                .from(table("b"))
+                
.where(column("a").eq(value(123)).and(column("c").lt(column("d"))));
+        Node node = builder.build();
+        assertThat(node, instanceOf(SelectNode.class));
+        assertSQL("SELECT a FROM b WHERE ( a = 123 ) AND ( c < d )", node);
+    }
+
+    @Test
+    public void testComplexQuery() {
+        Node node = select(
+                        table("a").column("ARTIST_ID").as("a_id"),
+                        
count(table("p").column("PAINTING_TITLE")).as("p_count"))
+                .distinct()
+                .from(table("ARTIST").as("a"))
+                .from(leftJoin(table("PAINTING").as("p"))
+                        .on(table("a").column("ARTIST_ID")
+                                .eq(table("p").column("ARTIST_ID"))
+                                
.and(table("p").column("ESTIMATED_PRICE").gt(value(10)))))
+                .where(
+                        table("a").column("ARTIST_NAME")
+                                .eq(value("Picasso"))
+                                .and(exists(select(all())
+                                        .from(table("GALLERY").as("g"))
+                                        
.where(table("g").column("GALLERY_ID").eq(table("p").column("GALLERY_ID")))))
+                                .and(value(1).eq(value(1)))
+                                .or(value(false))
+                )
+                .groupBy(table("a").column("ARTIST_ID"))
+                
.having(not(count(table("p").column("PAINTING_TITLE")).gt(value(3))))
+                .orderBy(column("p_count").desc(), column("a_id").asc())
+                .build();
+        assertThat(node, instanceOf(SelectNode.class));
+        assertSQL("SELECT DISTINCT" +
+                    " a.ARTIST_ID a_id, COUNT( p.PAINTING_TITLE ) AS p_count" +
+                " FROM ARTIST a" +
+                " LEFT JOIN PAINTING p ON ( a.ARTIST_ID = p.ARTIST_ID ) AND ( 
p.ESTIMATED_PRICE > 10 )" +
+                " WHERE ( ( ( a.ARTIST_NAME = 'Picasso' )" +
+                    " AND EXISTS (SELECT * FROM GALLERY g WHERE g.GALLERY_ID = 
p.GALLERY_ID) )" +
+                    " AND ( 1 = 1 ) ) OR false" +
+                " GROUP BY a.ARTIST_ID" +
+                " HAVING NOT ( COUNT( p.PAINTING_TITLE ) > 3 )" +
+                " ORDER BY p_count DESC, a_id", node);
+    }
+
+    private void assertSQL(String expected, Node node) {
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
StringBuilderAppendable());
+        node.visit(visitor);
+        assertEquals(expected, visitor.getSQLString());
+    }
+}
\ No newline at end of file

Reply via email to