cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ntimof...@apache.org
Subject [04/11] cayenne git commit: CAY-2465 New SelectTranslator implementation
Date Wed, 09 Jan 2019 10:46:55 GMT
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseJoinStack.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseJoinStack.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseJoinStack.java
deleted file mode 100644
index 9add9df..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseJoinStack.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.openbase;
-
-import java.util.List;
-
-import org.apache.cayenne.access.translator.select.JoinStack;
-import org.apache.cayenne.access.translator.select.JoinTreeNode;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-
-/**
- * OpenBase does not support standard JOIN keyword and have strange syntax for
- * defining inner/outer joins
- * 
- * @see http ://www.openbase.com/help/KnowledgeBase/
- *      400_OpenBaseSQL/401_SelectStatements.html
- * @since 3.0
- */
-class OpenBaseJoinStack extends JoinStack {
-
-	protected OpenBaseJoinStack(DbAdapter dbAdapter, QueryAssembler assembler) {
-		super(dbAdapter, assembler);
-	}
-
-	@Override
-	protected void appendJoinSubtree(StringBuilder out, JoinTreeNode node) {
-		DbRelationship relationship = node.getRelationship();
-
-		if (relationship == null) {
-			return;
-		}
-
-		DbEntity targetEntity = relationship.getTargetEntity();
-		String targetAlias = node.getTargetTableAlias();
-
-		out.append(", ").append(targetEntity.getFullyQualifiedName()).append(' ').append(targetAlias);
-
-		for (JoinTreeNode child : node.getChildren()) {
-			appendJoinSubtree(out, child);
-		}
-	}
-
-	@Override
-	protected void appendQualifier(StringBuilder out, boolean firstQualifierElement) {
-		boolean first = firstQualifierElement;
-		for (JoinTreeNode node : rootNode.getChildren()) {
-			if (!first) {
-				out.append(" AND ");
-			}
-			appendQualifierSubtree(out, node);
-			first = false;
-		}
-	}
-
-	protected void appendQualifierSubtree(StringBuilder out, JoinTreeNode node) {
-		DbRelationship relationship = node.getRelationship();
-
-		String srcAlias = node.getSourceTableAlias();
-		String targetAlias = node.getTargetTableAlias();
-
-		List<DbJoin> joins = relationship.getJoins();
-		int len = joins.size();
-		for (int i = 0; i < len; i++) {
-			DbJoin join = joins.get(i);
-
-			if (i > 0) {
-				out.append(" AND ");
-			}
-
-			out.append(srcAlias).append('.').append(join.getSourceName());
-
-			switch (node.getJoinType()) {
-			case INNER:
-				out.append(" = ");
-				break;
-			case LEFT_OUTER:
-				out.append(" * ");
-				break;
-			default:
-				throw new IllegalArgumentException("Unsupported join type: " + node.getJoinType());
-			}
-
-			out.append(targetAlias).append('.').append(join.getTargetName());
-
-		}
-
-		for (JoinTreeNode child : node.getChildren()) {
-			out.append(" AND ");
-			appendQualifierSubtree(out, child);
-		}
-
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseQualifierTranslator.java
deleted file mode 100644
index dbedc60..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseQualifierTranslator.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.openbase;
-
-import java.io.IOException;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.PatternMatchNode;
-import org.apache.cayenne.map.DbAttribute;
-
-/**
- * Translates query qualifier to SQL. Used as a helper class by query
- * translators.
- * 
- * @since 1.1
- */
-public class OpenBaseQualifierTranslator extends QualifierTranslator {
-
-	public OpenBaseQualifierTranslator(QueryAssembler queryAssembler) {
-		super(queryAssembler);
-	}
-
-	@Override
-	public void startNode(Expression node, Expression parentNode) {
-
-		if (node.getOperandCount() == 2) {
-			// binary nodes are the only ones that currently require this
-			detectObjectMatch(node);
-
-			if (parenthesisNeeded(node, parentNode)) {
-				out.append('(');
-			}
-
-			// super implementation has special handling
-			// of LIKE_IGNORE_CASE and NOT_LIKE_IGNORE_CASE
-			// OpenBase is case-insensitive by default
-			// ...
-
-		} else {
-			super.startNode(node, parentNode);
-		}
-	}
-
-	@Override
-	public void endNode(Expression node, Expression parentNode) {
-		if (node.getOperandCount() == 2) {
-
-			try {
-				// check if we need to use objectMatchTranslator to finish
-				// building the
-				// expression
-				if (matchingObject) {
-					appendObjectMatch();
-				}
-
-				if (PatternMatchNode.class.isAssignableFrom(node.getClass())) {
-					appendLikeEscapeCharacter((PatternMatchNode) node);
-				}
-
-				if (parenthesisNeeded(node, parentNode)) {
-					out.append(')');
-				}
-
-				// super implementation has special handling
-				// of LIKE_IGNORE_CASE and NOT_LIKE_IGNORE_CASE
-				// OpenBase is case-insensitive by default
-				// ...
-			} catch (IOException ioex) {
-				throw new CayenneRuntimeException("Error appending content", ioex);
-			}
-		} else {
-			super.endNode(node, parentNode);
-		}
-	}
-
-	@Override
-	protected void appendLiteralDirect(Object val, DbAttribute attr, Expression parentExpression) throws IOException {
-
-		// Special handling of string matching is needed:
-		// Case-sensitive LIKE must be converted to [x][Y][z] format
-		if (val instanceof String
-				&& (parentExpression.getType() == Expression.LIKE || parentExpression.getType() == Expression.NOT_LIKE)) {
-
-			val = caseSensitiveLikePattern((String) val);
-		}
-
-		super.appendLiteralDirect(val, attr, parentExpression);
-	}
-
-	private String caseSensitiveLikePattern(String pattern) {
-		int len = pattern.length();
-		StringBuilder buffer = new StringBuilder(len * 3);
-
-		for (int i = 0; i < len; i++) {
-			char c = pattern.charAt(i);
-			if (c == '%' || c == '?') {
-				buffer.append(c);
-			} else {
-				buffer.append("[").append(c).append("]");
-			}
-		}
-
-		return buffer.toString();
-	}
-
-	@Override
-	public void finishedChild(Expression node, int childIndex, boolean hasMoreChildren) {
-		if (!hasMoreChildren) {
-			return;
-		}
-
-		// super implementation has special handling
-		// of LIKE_IGNORE_CASE and NOT_LIKE_IGNORE_CASE
-		// OpenBase is case-insensitive by default
-		// ...
-
-		try {
-			switch (node.getType()) {
-
-			case Expression.LIKE_IGNORE_CASE:
-				finishedChildNodeAppendExpression(node, " LIKE ");
-				break;
-			case Expression.NOT_LIKE_IGNORE_CASE:
-				finishedChildNodeAppendExpression(node, " NOT LIKE ");
-				break;
-			default:
-				super.finishedChild(node, childIndex, hasMoreChildren);
-			}
-		} catch (IOException ioex) {
-			throw new CayenneRuntimeException("Error appending content", ioex);
-		}
-	}
-
-	private void finishedChildNodeAppendExpression(Expression node, String operation) throws IOException {
-		Appendable out = matchingObject ? new StringBuilder() : this.out;
-		out.append(operation);
-		if (matchingObject) {
-			objectMatchTranslator.setOperation(out.toString());
-			objectMatchTranslator.setExpression(node);
-		}
-	}
-
-	@Override
-	protected void appendExtractFunction(ASTExtract functionExpression) {
-		switch (functionExpression.getPart()) {
-			case DAY_OF_WEEK:
-			case DAY_OF_MONTH:
-			case DAY_OF_YEAR:
-				// openbase variants are without '_'
-				out.append(functionExpression.getPart().name().replace("_", ""));
-				break;
-			default:
-				appendFunction(functionExpression);
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseSQLTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseSQLTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseSQLTreeProcessor.java
new file mode 100644
index 0000000..29eaaa1
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseSQLTreeProcessor.java
@@ -0,0 +1,109 @@
+/*****************************************************************
+ *   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.dba.openbase;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LikeNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.NodeType;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ValueNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+
+/**
+ * @since 4.2
+ */
+public class OpenBaseSQLTreeProcessor extends BaseSQLTreeProcessor {
+
+    @Override
+    protected void onLikeNode(Node parent, LikeNode child, int index) {
+        // OpenBase is case-insensitive by default
+        if(child.isIgnoreCase()) {
+            replaceChild(parent, index, new LikeNode(false, child.isNot(), child.getEscape()));
+        }
+    }
+
+    @Override
+    protected void onValueNode(Node parent, ValueNode child, int index) {
+        // Special handling of string matching is needed:
+        // Case-sensitive LIKE must be converted to [x][Y][z] format
+        if(parent.getType() == NodeType.LIKE) {
+            if(!((LikeNode)parent).isIgnoreCase() && child.getValue() instanceof CharSequence) {
+                replaceChild(parent, index,
+                        new ValueNode(caseSensitiveLikePattern((CharSequence)child.getValue()), child.isArray(), child.getAttribute()));
+            }
+        }
+    }
+
+    @Override
+    protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) {
+        replaceChild(parent, index, new OpenBaseLimitNode(child));
+    }
+
+    @Override
+    protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+        switch (child.getFunctionName()) {
+            case "DAY_OF_WEEK":
+            case "DAY_OF_MONTH":
+            case "DAY_OF_YEAR":
+                replaceChild(parent, index, new FunctionNode(child.getFunctionName().replace("_", ""), child.getAlias()));
+                break;
+        }
+    }
+
+    private String caseSensitiveLikePattern(CharSequence pattern) {
+        int len = pattern.length();
+        StringBuilder buffer = new StringBuilder(len * 3);
+
+        for (int i = 0; i < len; i++) {
+            char c = pattern.charAt(i);
+            if (c == '%' || c == '?') {
+                buffer.append(c);
+            } else {
+                buffer.append("[").append(c).append("]");
+            }
+        }
+
+        return buffer.toString();
+    }
+
+    private static class OpenBaseLimitNode extends Node {
+
+        private final LimitOffsetNode child;
+
+        public OpenBaseLimitNode(LimitOffsetNode child) {
+            this.child = child;
+        }
+
+        @Override
+        public QuotingAppendable append(QuotingAppendable buffer) {
+            if(child.getLimit() > 0) {
+                buffer.append(" RETURN RESULTS ").append(child.getLimit());
+            }
+            return buffer;
+        }
+
+        @Override
+        public Node copy() {
+            return new OpenBaseLimitNode(child);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseSelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseSelectTranslator.java
deleted file mode 100644
index e4ff7cc..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseSelectTranslator.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.openbase;
-
-import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
-import org.apache.cayenne.access.translator.select.JoinStack;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-
-/**
- * @since 1.2
- */
-class OpenBaseSelectTranslator extends DefaultSelectTranslator {
-
-	/**
-	 * @since 4.0
-	 */
-	public OpenBaseSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
-		super(query, adapter, entityResolver);
-	}
-
-	@Override
-	protected JoinStack createJoinStack() {
-		return new OpenBaseJoinStack(getAdapter(), this);
-	}
-
-	@Override
-	protected void appendLimitAndOffsetClauses(StringBuilder buffer) {
-		int limit = queryMetadata.getFetchLimit();
-		if (limit > 0) {
-			buffer.append(" RETURN RESULTS ").append(limit);
-		}
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
index 60f820e..a51f4d8 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
@@ -20,19 +20,14 @@
 package org.apache.cayenne.dba.oracle;
 
 import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
-import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
 import java.lang.reflect.Method;
@@ -84,14 +79,6 @@ public class Oracle8Adapter extends OracleAdapter {
 	}
 
 	/**
-	 * @since 4.0
-	 */
-	@Override
-	public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
-		return new Oracle8SelectTranslator(query, this, entityResolver);
-	}
-
-	/**
 	 * Uses OracleActionBuilder to create the right action.
 	 */
 	@Override
@@ -109,10 +96,4 @@ public class Oracle8Adapter extends OracleAdapter {
 		return super.findResource(name);
 	}
 
-	@Override
-	public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
-		QualifierTranslator translator = new Oracle8QualifierTranslator(queryAssembler);
-		translator.setCaseInsensitive(caseInsensitiveCollations);
-		return translator;
-	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8JoinStack.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8JoinStack.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8JoinStack.java
deleted file mode 100644
index 183775a..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8JoinStack.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.oracle;
-
-import java.util.List;
-
-import org.apache.cayenne.access.translator.select.JoinStack;
-import org.apache.cayenne.access.translator.select.JoinTreeNode;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-
-/**
- * @since 3.0
- */
-// cloned from OpenBaseJoin stack... need better strategies of reuse...
-class Oracle8JoinStack extends JoinStack {
-
-	Oracle8JoinStack(DbAdapter dbAdapter, QueryAssembler assembler) {
-		super(dbAdapter, assembler);
-	}
-
-	@Override
-	protected void appendJoinSubtree(StringBuilder out, JoinTreeNode node) {
-		DbRelationship relationship = node.getRelationship();
-
-		if (relationship == null) {
-			return;
-		}
-
-		DbEntity targetEntity = relationship.getTargetEntity();
-		String targetAlias = node.getTargetTableAlias();
-
-		out.append(", ").append(targetEntity.getFullyQualifiedName()).append(' ').append(targetAlias);
-
-		for (JoinTreeNode child : node.getChildren()) {
-			appendJoinSubtree(out, child);
-		}
-	}
-
-	@Override
-	protected void appendQualifier(StringBuilder out, boolean firstQualifierElement) {
-		boolean first = firstQualifierElement;
-		for (JoinTreeNode node : rootNode.getChildren()) {
-			if (!first) {
-				out.append(" AND ");
-			}
-			appendQualifierSubtree(out, node);
-			first = false;
-		}
-	}
-
-	protected void appendQualifierSubtree(StringBuilder out, JoinTreeNode node) {
-		DbRelationship relationship = node.getRelationship();
-
-		String srcAlias = node.getSourceTableAlias();
-		String targetAlias = node.getTargetTableAlias();
-
-		List<DbJoin> joins = relationship.getJoins();
-		int len = joins.size();
-		for (int i = 0; i < len; i++) {
-			DbJoin join = joins.get(i);
-
-			if (i > 0) {
-				out.append(" AND ");
-			}
-
-			out.append(srcAlias).append('.').append(join.getSourceName());
-
-			switch (node.getJoinType()) {
-			case INNER:
-				out.append(" = ");
-				break;
-			case LEFT_OUTER:
-				out.append(" * ");
-				break;
-			default:
-				throw new IllegalArgumentException("Unsupported join type: " + node.getJoinType());
-			}
-
-			out.append(targetAlias).append('.').append(join.getTargetName());
-		}
-
-		for (JoinTreeNode child : node.getChildren()) {
-			out.append(" AND ");
-			appendQualifierSubtree(out, child);
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8QualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8QualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8QualifierTranslator.java
deleted file mode 100644
index d756392..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8QualifierTranslator.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.oracle;
-
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-
-/**
- * Extends the TrimmingQualifierTranslator that Cayenne normally uses for
- * Oracle. Overrides doAppendPart() to wrap the qualifierBuffer in parentheses
- * if it contains an "OR" expression. This avoids a bug that can happen on
- * Oracle8 if the query also contains a join.
- * 
- * @since 3.0
- */
-class Oracle8QualifierTranslator extends OracleQualifierTranslator {
-
-	public Oracle8QualifierTranslator(QueryAssembler queryAssembler) {
-		super(queryAssembler);
-	}
-
-	@Override
-	protected void doAppendPart() {
-		super.doAppendPart();
-
-		if (out instanceof StringBuilder) {
-			StringBuilder buffer = (StringBuilder) out;
-			if (buffer.indexOf(" OR ") != -1) {
-				buffer.insert(0, '(');
-				buffer.append(')');
-			}
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8SelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8SelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8SelectTranslator.java
deleted file mode 100644
index 6df771c..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8SelectTranslator.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.oracle;
-
-import org.apache.cayenne.access.translator.select.JoinStack;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-
-/**
- * @since 3.0
- */
-class Oracle8SelectTranslator extends OracleSelectTranslator {
-
-	/**
-	 * @since 4.0
-	 */
-	public Oracle8SelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
-		super(query, adapter, entityResolver);
-	}
-
-	/**
-	 * Returns an old style joint stack for Oracle8 that does not support
-	 * explicit join syntax.
-	 */
-	@Override
-	protected JoinStack createJoinStack() {
-		return new Oracle8JoinStack(getAdapter(), this);
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
index 77f2be1..cc58f0d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
@@ -21,11 +21,9 @@ package org.apache.cayenne.dba.oracle;
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.ByteType;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
@@ -39,12 +37,10 @@ import org.apache.cayenne.dba.PkGenerator;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.query.BatchQuery;
 import org.apache.cayenne.query.InsertBatchQuery;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
-import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.query.UpdateBatchQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
@@ -57,6 +53,7 @@ import java.sql.Types;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Function;
 
 /**
  * DbAdapter implementation for <a href="http://www.oracle.com">Oracle RDBMS
@@ -171,11 +168,11 @@ public class OracleAdapter extends JdbcAdapter {
 	}
 
 	/**
-	 * @since 4.0
+	 * @since 4.2
 	 */
 	@Override
-	public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
-		return new OracleSelectTranslator(query, this, entityResolver);
+	public Function<Node, Node> getSqlTreeProcessor() {
+		return new OracleSQLTreeProcessor();
 	}
 
 	/**
@@ -281,16 +278,6 @@ public class OracleAdapter extends JdbcAdapter {
 	}
 
 	/**
-	 * Returns a trimming translator.
-	 */
-	@Override
-	public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
-		QualifierTranslator translator = new Oracle8QualifierTranslator(queryAssembler);
-		translator.setCaseInsensitive(caseInsensitiveCollations);
-		return translator;
-	}
-
-	/**
 	 * Uses OracleActionBuilder to create the right action.
 	 *
 	 * @since 1.2

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleQualifierTranslator.java
deleted file mode 100644
index fcc76a4..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleQualifierTranslator.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.oracle;
-
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.ASTFunctionCall;
-import org.apache.cayenne.exp.parser.ASTIn;
-import org.apache.cayenne.exp.parser.ASTList;
-import org.apache.cayenne.exp.parser.ASTNotIn;
-import org.apache.cayenne.exp.parser.ASTPath;
-import org.apache.cayenne.exp.parser.Node;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Function;
-
-/**
- * Oracle qualifier translator. In particular, trims INs with more than 1000
- * elements to an OR-set of INs with &lt;= 1000 elements
- */
-public class OracleQualifierTranslator extends TrimmingQualifierTranslator {
-
-	public OracleQualifierTranslator(QueryAssembler queryAssembler) {
-		super(queryAssembler, OracleAdapter.TRIM_FUNCTION);
-	}
-
-	@Override
-	protected void doAppendPart(Expression rootNode) {
-		if (rootNode == null) {
-			return;
-		}
-		rootNode = rootNode.transform(new INTrimmer());
-		rootNode.traverse(this);
-	}
-
-	public static class INTrimmer implements Function<Object, Object> {
-
-		public Expression trimmedInExpression(Expression exp, int maxInSize) {
-			Expression list = (Expression) exp.getOperand(1);
-			Object[] objects = (Object[]) list.evaluate(null);
-
-			if (objects.length <= maxInSize) {
-				return exp;
-			}
-
-			Expression trimmed = trimmedInExpression((ASTPath) exp.getOperand(0), objects, maxInSize);
-			if (exp instanceof ASTNotIn) {
-				return trimmed.notExp();
-			}
-			return trimmed;
-		}
-
-		Expression trimmedInExpression(ASTPath path, Object[] values, int maxInSize) {
-			Expression res = null;
-
-			List<Object> in = new ArrayList<>(maxInSize);
-			for (Object v : values) {
-				in.add(v);
-				if (in.size() == maxInSize) {
-					Expression inExp = new ASTIn(path, new ASTList(in));
-					res = res != null ? res.orExp(inExp) : inExp;
-					in = new ArrayList<>(maxInSize);
-				}
-			}
-			if (in.size() > 0) {
-				Expression inExp = new ASTIn(path, new ASTList(in));
-				res = res != null ? res.orExp(inExp) : inExp;
-			}
-			return res;
-		}
-
-		public Object apply(Object input) {
-			if (input instanceof ASTIn || input instanceof ASTNotIn) {
-				return trimmedInExpression((Expression) input, 1000);
-			}
-			return input;
-		}
-	}
-
-	/**
-	 * @since 4.0
-	 */
-	@Override
-	public void endNode(Expression node, Expression parentNode) {
-		super.endNode(node, parentNode);
-		if(node.getType() == Expression.FUNCTION_CALL) {
-			if("LOCATE".equals(((ASTFunctionCall)node).getFunctionName())) {
-				// order of args in INSTR is different, so swap them back
-				swapNodeChildren((ASTFunctionCall)node, 0, 1);
-			}
-		}
-	}
-
-	/**
-	 * @since 4.0
-	 */
-	@Override
-	protected void appendFunction(ASTFunctionCall functionExpression) {
-		if("CONCAT".equals(functionExpression.getFunctionName())) {
-			// CONCAT(x, y, z) -> (x || y || z)
-		} else if("SUBSTRING".equals(functionExpression.getFunctionName())) {
-			out.append("SUBSTR");
-		} else if("LOCATE".equals(functionExpression.getFunctionName())) {
-			// LOCATE(substr, str) -> INSTR(str, substr)
-			out.append("INSTR");
-			swapNodeChildren(functionExpression, 0, 1);
-		} else if("CURRENT_TIME".equals(functionExpression.getFunctionName())) {
-			out.append("{fn CURTIME()}");
-		} else {
-			super.appendFunction(functionExpression);
-		}
-	}
-
-	/**
-	 * @since 4.0
-	 */
-	@Override
-	protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) {
-		if("CONCAT".equals(functionExpression.getFunctionName())) {
-			out.append(" || ");
-		} else {
-			super.appendFunctionArgDivider(functionExpression);
-		}
-	}
-
-	/**
-	 * @since 4.0
-	 */
-	@Override
-	protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) {
-		if("CONCAT".equals(functionExpression.getFunctionName())) {
-			out.delete(out.length() - " || ".length(), out.length());
-		} else {
-			super.clearLastFunctionArgDivider(functionExpression);
-		}
-
-		if(functionExpression instanceof ASTExtract) {
-			switch (((ASTExtract)functionExpression).getPart()) {
-				case DAY_OF_YEAR:
-					out.append(", 'DDD'");
-					break;
-				case DAY_OF_WEEK:
-					out.append(", 'D'");
-					break;
-				case WEEK:
-					out.append(", 'IW'");
-					break;
-			}
-			out.append(")");
-		}
-	}
-
-	@Override
-	protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
-		if (node.getType() == Expression.FUNCTION_CALL) {
-			if (node instanceof ASTExtract) {
-				return false;
-			}
-		}
-
-		return super.parenthesisNeeded(node, parentNode);
-	}
-
-	@Override
-	protected void appendExtractFunction(ASTExtract functionExpression) {
-		switch (functionExpression.getPart()) {
-			// use TO_CHAR(date, format) function for parts that is unsupported by EXTRACT()
-			case DAY_OF_YEAR:
-			case DAY_OF_WEEK:
-			case WEEK:
-				out.append("TO_CHAR(");
-				break;
-			case DAY_OF_MONTH:
-				out.append("EXTRACT(DAY FROM ");
-				break;
-			default:
-				out.append("EXTRACT(");
-				out.append(functionExpression.getPart().name());
-				out.append(" FROM ");
-		}
-
-	}
-
-	/**
-	 * @since 4.0
-	 */
-	private void swapNodeChildren(Node node, int i, int j) {
-		Node ni = node.jjtGetChild(i);
-		Node nj = node.jjtGetChild(j);
-		node.jjtAddChild(ni, j);
-		node.jjtAddChild(nj, i);
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSQLTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSQLTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSQLTreeProcessor.java
new file mode 100644
index 0000000..ba84579
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSQLTreeProcessor.java
@@ -0,0 +1,241 @@
+/*****************************************************************
+ *   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.dba.oracle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cayenne.access.sqlbuilder.ExpressionNodeBuilder;
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.SelectBuilder;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.EmptyNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.InNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.NodeType;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ValueNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TrimmingColumnNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+import org.apache.cayenne.util.ArrayUtil;
+
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.*;
+
+/**
+ * @since 4.2
+ */
+public class OracleSQLTreeProcessor extends BaseSQLTreeProcessor {
+
+    private static final int ORACLE_IN_BATCH_SIZE = 1000;
+
+    private SelectBuilder selectBuilder;
+
+    private Node root;
+
+    @Override
+    protected void onResultNode(Node parent, Node child, int index) {
+        for(int i=0; i<child.getChildrenCount(); i++) {
+            child.replaceChild(i, aliased(child.getChild(i), "c" + i).build());
+        }
+    }
+
+    @Override
+    protected void onColumnNode(Node parent, ColumnNode child, int index) {
+        replaceChild(parent, index, new TrimmingColumnNode(child));
+    }
+
+    @Override
+    protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) {
+        if(child.getLimit() > 0 || child.getOffset() > 0) {
+            int limit = child.getLimit();
+            int offset = child.getOffset();
+            int max = (limit <= 0) ? Integer.MAX_VALUE : limit + offset;
+
+            /*
+             Transform query with limit/offset into following form:
+             SELECT * FROM (
+                SELECT tid.*, ROWNUM rnum
+                FROM ( MAIN_QUERY ) tid
+                WHERE ROWNUM <= OFFSET + LIMIT
+             ) WHERE rnum > OFFSET
+             */
+            selectBuilder = select(all())
+                    .from(select(text(" tid.*"), text(" ROWNUM rnum")) // using text not column to avoid quoting
+                            .from(aliased(() -> root, "tid"))
+                            .where(exp(text(" ROWNUM")).lte(value(max))))
+                    .where(exp(text(" rnum")).gt(value(offset)));
+        }
+        parent.replaceChild(index, new EmptyNode());
+    }
+
+    @Override
+    protected void onInNode(Node parent, InNode child, int index) {
+        boolean not = child.isNot();
+        Node arg = child.getChild(0);
+        Node childNode = child.getChild(1);
+        if(childNode.getType() != NodeType.VALUE) {
+            return;
+        }
+
+        ValueNode valueNode = (ValueNode)childNode;
+        Object value = valueNode.getValue();
+        if(!value.getClass().isArray()) {
+            return;
+        }
+
+        List<Node> newChildren = new ArrayList<>();
+
+        // need to slice for batches of 1000 values
+        if(value instanceof Object[]) {
+            for(Object[] slice : ArrayUtil.sliceArray((Object[])value, ORACLE_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        } else if(value instanceof int[]) {
+            for(int[] slice : ArrayUtil.sliceArray((int[])value, ORACLE_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        } else if(value instanceof long[]) {
+            for(long[] slice : ArrayUtil.sliceArray((long[])value, ORACLE_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        } else if(value instanceof float[]) {
+            for(float[] slice : ArrayUtil.sliceArray((float[])value, ORACLE_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        } else if(value instanceof double[]) {
+            for(double[] slice : ArrayUtil.sliceArray((double[])value, ORACLE_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        } else if(value instanceof short[]) {
+            for(short[] slice : ArrayUtil.sliceArray((short[])value, ORACLE_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        } else if(value instanceof char[]) {
+            for(char[] slice : ArrayUtil.sliceArray((char[])value, ORACLE_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        } else if(value instanceof boolean[]) {
+            for(boolean[] slice : ArrayUtil.sliceArray((boolean[])value, ORACLE_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        } else if(value instanceof byte[]) {
+            for(byte[] slice : ArrayUtil.sliceArray((byte[])value, ORACLE_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        }
+
+        ExpressionNodeBuilder exp = exp(node(newChildren.get(0)));
+        for(int i=1; i<newChildren.size(); i++) {
+            if(not) {
+                exp = exp.and(node(newChildren.get(i)));
+            } else {
+                exp = exp.or(node(newChildren.get(i)));
+            }
+        }
+        parent.replaceChild(index, exp.build());
+    }
+
+    private InNode newSliceNode(InNode child, Node arg, ValueNode valueNode, Object slice) {
+        InNode nextNode = new InNode(child.isNot());
+        nextNode.addChild(arg.deepCopy());
+        nextNode.addChild(new ValueNode(slice, valueNode.isArray(), valueNode.getAttribute()));
+        return nextNode;
+    }
+
+    @Override
+    protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+        String functionName = child.getFunctionName();
+        Node functionReplacement = null;
+        switch (functionName) {
+            case "LOCATE":
+                functionReplacement = new FunctionNode("INSTR", child.getAlias(), true);
+                for(int i=0; i<=1; i++) {
+                    functionReplacement.addChild(child.getChild(1-i));
+                }
+                parent.replaceChild(index, functionReplacement);
+                return;
+
+            case "DAY_OF_YEAR":
+            case "DAY_OF_WEEK":
+            case "WEEK":
+                functionReplacement = new FunctionNode("TO_CHAR", child.getAlias(), true);
+                functionReplacement.addChild(child.getChild(0));
+                if("DAY_OF_YEAR".equals(functionName)) {
+                    functionName = "'DDD'";
+                } else if("DAY_OF_WEEK".equals(functionName)) {
+                    functionName = "'D'";
+                } else {
+                    functionName = "'IW'";
+                }
+                functionReplacement.addChild(new TextNode(functionName));
+                parent.replaceChild(index, functionReplacement);
+                return;
+
+            case "SUBSTRING":
+                functionReplacement = new FunctionNode("SUBSTR", child.getAlias(), true);
+                break;
+            case "CONCAT":
+                functionReplacement = new OpExpressionNode("||");
+                break;
+            case "CURRENT_TIMESTAMP":
+            case "CURRENT_DATE":
+                functionReplacement = new FunctionNode(functionName, child.getAlias(), false);
+                break;
+            case "CURRENT_TIME":
+                functionReplacement = new FunctionNode("{fn CURTIME()}", child.getAlias(), false);
+                break;
+            case "YEAR":
+            case "MONTH":
+            case "DAY":
+            case "DAY_OF_MONTH":
+            case "HOUR":
+            case "MINUTE":
+            case "SECOND":
+                functionReplacement = new FunctionNode("EXTRACT", child.getAlias(), true) {
+                    @Override
+                    public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
+                        buffer.append(' ');
+                    }
+                };
+                if("DAY_OF_MONTH".equals(functionName)) {
+                    functionName = "DAY";
+                }
+                functionReplacement.addChild(new TextNode(functionName + " FROM "));
+                break;
+        }
+
+        if(functionReplacement != null) {
+            replaceChild(parent, index, functionReplacement);
+        }
+    }
+
+    @Override
+    public Node apply(Node node) {
+        root = node;
+        super.apply(node);
+        if(selectBuilder != null) {
+            return selectBuilder.build();
+        }
+        return node;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSelectTranslator.java
deleted file mode 100644
index c98aad9..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSelectTranslator.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.oracle;
-
-import java.util.List;
-
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-
-/**
- * Select translator that implements Oracle-specific optimizations.
- * 
- */
-class OracleSelectTranslator extends DefaultSelectTranslator {
-
-	/**
-	 * @since 4.0
-	 */
-	public OracleSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
-		super(query, adapter, entityResolver);
-	}
-
-	@Override
-	protected void appendLimitAndOffsetClauses(StringBuilder buffer) {
-		int offset = queryMetadata.getFetchOffset();
-		int limit = queryMetadata.getFetchLimit();
-
-		if (limit > 0 || offset > 0) {
-			int max = (limit <= 0) ? Integer.MAX_VALUE : limit + offset;
-
-			buffer.insert(0, "select * from ( select tid.*, ROWNUM rnum from (");
-			buffer.append(") tid where ROWNUM <=").append(max).append(") where rnum  > ").append(offset);
-		}
-	}
-
-	@Override
-	protected void appendSelectColumns(StringBuilder buffer, List<String> selectColumnExpList) {
-
-		// we need to add aliases to all columns to make fetch
-		// limit and offset work properly on Oracle (see CAY-1266)
-
-		// append columns (unroll the loop's first element)
-		int columnCount = selectColumnExpList.size();
-		buffer.append(selectColumnExpList.get(0));
-		if(!selectColumnExpList.get(0).contains(" AS ")) {
-			buffer.append(" AS c0");
-		}
-
-		// assume there is at least 1 element
-		for (int i = 1; i < columnCount; i++) {
-			buffer.append(", ");
-			buffer.append(selectColumnExpList.get(i));
-			if(!selectColumnExpList.get(i).contains(" AS ")) {
-				buffer.append(" AS c").append(i);
-			}
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgreSQLTreeProcessor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgreSQLTreeProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgreSQLTreeProcessor.java
new file mode 100644
index 0000000..9bbc34e
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgreSQLTreeProcessor.java
@@ -0,0 +1,83 @@
+/*****************************************************************
+ *   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.dba.postgres;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LikeNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TrimmingColumnNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+import org.apache.cayenne.dba.postgres.sqltree.PositionFunctionNode;
+import org.apache.cayenne.dba.postgres.sqltree.PostgresExtractFunctionNode;
+import org.apache.cayenne.dba.postgres.sqltree.PostgresLikeNode;
+import org.apache.cayenne.dba.postgres.sqltree.PostgresLimitOffsetNode;
+
+/**
+ * @since 4.2
+ */
+public class PostgreSQLTreeProcessor extends BaseSQLTreeProcessor {
+
+    private static final Set<String> EXTRACT_FUNCTION_NAMES = new HashSet<>(Arrays.asList(
+        "DAY_OF_MONTH", "DAY", "MONTH", "HOUR", "WEEK", "YEAR", "DAY_OF_WEEK", "DAY_OF_YEAR", "MINUTE", "SECOND"
+    ));
+
+    @Override
+    protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int index) {
+        replaceChild(parent, index, new PostgresLimitOffsetNode(child), false);
+    }
+
+    @Override
+    protected void onColumnNode(Node parent, ColumnNode child, int index) {
+        replaceChild(parent, index, new TrimmingColumnNode(child));
+    }
+
+    @Override
+    protected void onLikeNode(Node parent, LikeNode child, int index) {
+        if(child.isIgnoreCase()) {
+            replaceChild(parent, index, new PostgresLikeNode(child.isNot(), child.getEscape()));
+        }
+    }
+
+    @Override
+    protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+        Node replacement = null;
+        String functionName = child.getFunctionName();
+        if(EXTRACT_FUNCTION_NAMES.contains(functionName)) {
+            replacement = new PostgresExtractFunctionNode(functionName);
+        } else if("CURRENT_DATE".equals(functionName)
+                || "CURRENT_TIME".equals(functionName)
+                || "CURRENT_TIMESTAMP".equals(functionName)) {
+            replacement = new FunctionNode(functionName, child.getAlias(), false);
+        } else if("LOCATE".equals(functionName)) {
+            replacement = new PositionFunctionNode(child.getAlias());
+        }
+
+        if(replacement != null) {
+            replaceChild(parent, index, replacement);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
index dbee328..24033e1 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
@@ -21,10 +21,8 @@ package org.apache.cayenne.dba.postgres;
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
 import org.apache.cayenne.access.translator.ParameterBinding;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.CharType;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
@@ -39,10 +37,8 @@ import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
-import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
 import java.sql.PreparedStatement;
@@ -52,6 +48,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.function.Function;
 
 /**
  * DbAdapter implementation for <a href="http://www.postgresql.org">PostgreSQL
@@ -80,12 +77,12 @@ public class PostgresAdapter extends JdbcAdapter {
 		setSupportsGeneratedKeys(true);
 	}
 
-	/**
-	 * @since 4.0
-	 */
+    /**
+     * @since 4.2
+     */
 	@Override
-	public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
-		return new PostgresSelectTranslator(query, this, entityResolver);
+	public Function<Node, Node> getSqlTreeProcessor() {
+		return new PostgreSQLTreeProcessor();
 	}
 
 	/**
@@ -258,16 +255,6 @@ public class PostgresAdapter extends JdbcAdapter {
 	}
 
 	/**
-	 * Returns a trimming translator.
-	 */
-	@Override
-	public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
-		QualifierTranslator translator = new PostgresQualifierTranslator(queryAssembler);
-		translator.setCaseInsensitive(caseInsensitiveCollations);
-		return translator;
-	}
-
-	/**
 	 * @see JdbcAdapter#createPkGenerator()
 	 */
 	@Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresQualifierTranslator.java
deleted file mode 100644
index 3fdc22c..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresQualifierTranslator.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.postgres;
-
-import java.io.IOException;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.ASTFunctionCall;
-import org.apache.cayenne.exp.parser.PatternMatchNode;
-
-/**
- * Uses Postgres extensions to optimize various translations.
- * 
- * @since 1.1
- */
-public class PostgresQualifierTranslator extends TrimmingQualifierTranslator {
-
-	public PostgresQualifierTranslator(QueryAssembler queryAssembler) {
-		super(queryAssembler, "RTRIM");
-	}
-
-	@Override
-	public void startNode(Expression node, Expression parentNode) {
-		// super implementation has special handling of LIKE_IGNORE_CASE and NOT_LIKE_IGNORE_CASE
-		// Postgres uses ILIKE
-		boolean likeIgnoreCase = (node.getType() == Expression.LIKE_IGNORE_CASE || node.getType() == Expression.NOT_LIKE_IGNORE_CASE);
-		if (likeIgnoreCase) {
-			// binary nodes are the only ones that currently require this
-			detectObjectMatch(node);
-			if (parenthesisNeeded(node, parentNode)) {
-				out.append('(');
-			}
-		} else {
-			super.startNode(node, parentNode);
-		}
-	}
-
-	@Override
-	public void endNode(Expression node, Expression parentNode) {
-		// super implementation has special handling of LIKE_IGNORE_CASE and NOT_LIKE_IGNORE_CASE
-		// Postgres uses ILIKE
-		boolean likeIgnoreCase = (node.getType() == Expression.LIKE_IGNORE_CASE || node.getType() == Expression.NOT_LIKE_IGNORE_CASE);
-
-		if (likeIgnoreCase) {
-			try {
-				if (matchingObject) {
-					appendObjectMatch();
-				}
-
-				if (PatternMatchNode.class.isAssignableFrom(node.getClass())) {
-					appendLikeEscapeCharacter((PatternMatchNode) node);
-				}
-
-				if (parenthesisNeeded(node, parentNode)) {
-					out.append(')');
-				}
-			} catch (IOException ioex) {
-				throw new CayenneRuntimeException("Error appending content", ioex);
-			}
-		} else {
-			super.endNode(node, parentNode);
-		}
-	}
-
-	@Override
-	public void finishedChild(Expression node, int childIndex, boolean hasMoreChildren) {
-		if (!hasMoreChildren) {
-			return;
-		}
-
-		try {
-			// use ILIKE
-			switch (node.getType()) {
-				case Expression.LIKE_IGNORE_CASE:
-					finishedChildNodeAppendExpression(node, " ILIKE ");
-					break;
-				case Expression.NOT_LIKE_IGNORE_CASE:
-					finishedChildNodeAppendExpression(node, " NOT ILIKE ");
-					break;
-				default:
-					super.finishedChild(node, childIndex, hasMoreChildren);
-			}
-		} catch (IOException ioex) {
-			throw new CayenneRuntimeException("Error appending content", ioex);
-		}
-	}
-
-	private void finishedChildNodeAppendExpression(Expression node, String operation) throws IOException {
-		Appendable buf = matchingObject ? new StringBuilder() : this.out;
-		buf.append(operation);
-		if (matchingObject) {
-			objectMatchTranslator.setOperation(buf.toString());
-			objectMatchTranslator.setExpression(node);
-		}
-	}
-
-    /**
-     * @since 4.0
-     */
-	@Override
-	protected void appendFunction(ASTFunctionCall functionExpression) {
-		if("LOCATE".equals(functionExpression.getFunctionName())) {
-			out.append("POSITION");
-		} else {
-			super.appendFunction(functionExpression);
-		}
-	}
-
-    /**
-     * @since 4.0
-     */
-	@Override
-	protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) {
-		if("LOCATE".equals(functionExpression.getFunctionName())) {
-			out.append(" in ");
-		} else {
-			super.appendFunctionArgDivider(functionExpression);
-		}
-	}
-
-    /**
-     * @since 4.0
-     */
-	@Override
-	protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) {
-		if("LOCATE".equals(functionExpression.getFunctionName())) {
-			out.delete(out.length() - " in ".length(), out.length());
-		} else {
-			super.clearLastFunctionArgDivider(functionExpression);
-		}
-
-		if(functionExpression instanceof ASTExtract) {
-			out.append(")");
-		}
-	}
-
-	@Override
-	protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
-		if (node.getType() == Expression.FUNCTION_CALL) {
-			if (node instanceof ASTExtract) {
-				return false;
-			}
-		}
-
-		return super.parenthesisNeeded(node, parentNode);
-	}
-
-
-	@Override
-	protected void appendExtractFunction(ASTExtract functionExpression) {
-		out.append("EXTRACT(");
-		switch (functionExpression.getPart()) {
-			case DAY_OF_MONTH:
-				out.append("day");
-				break;
-			case DAY_OF_WEEK:
-				out.append("dow");
-				break;
-			case DAY_OF_YEAR:
-				out.append("doy");
-				break;
-			default:
-				out.append(functionExpression.getPartCamelCaseName());
-		}
-
-		out.append(" FROM ");
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresSelectTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresSelectTranslator.java
deleted file mode 100644
index 8d1eded..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresSelectTranslator.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.postgres;
-
-import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.Query;
-
-/**
- * @since 1.2
- */
-class PostgresSelectTranslator extends DefaultSelectTranslator {
-
-	/**
-	 * @since 4.0
-	 */
-	public PostgresSelectTranslator(Query query, DbAdapter adapter, EntityResolver entityResolver) {
-		super(query, adapter, entityResolver);
-	}
-
-	@Override
-	protected void appendLimitAndOffsetClauses(StringBuilder buffer) {
-
-		// limit results
-		int offset = queryMetadata.getFetchOffset();
-		int limit = queryMetadata.getFetchLimit();
-
-		if (limit > 0) {
-			buffer.append(" LIMIT ").append(limit);
-		}
-
-		if (offset > 0) {
-			buffer.append(" OFFSET ").append(offset);
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PositionFunctionNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PositionFunctionNode.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PositionFunctionNode.java
new file mode 100644
index 0000000..39c1df7
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PositionFunctionNode.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.dba.postgres.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class PositionFunctionNode extends FunctionNode {
+    public PositionFunctionNode(String alias) {
+        super("POSITION", alias, true);
+    }
+
+    @Override
+    public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
+        buffer.append(" IN ");
+    }
+
+    @Override
+    public Node copy() {
+        return new PositionFunctionNode(getAlias());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresExtractFunctionNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresExtractFunctionNode.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresExtractFunctionNode.java
new file mode 100644
index 0000000..ff4224b
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresExtractFunctionNode.java
@@ -0,0 +1,59 @@
+/*****************************************************************
+ *   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.dba.postgres.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class PostgresExtractFunctionNode extends Node {
+    private final String functionName;
+
+    public PostgresExtractFunctionNode(String functionName) {
+        this.functionName = functionName;
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        buffer.append(" EXTRACT(");
+        if ("DAY_OF_MONTH".equals(functionName)) {
+            buffer.append("day");
+        } else if ("DAY_OF_WEEK".equals(functionName)) {
+            buffer.append("dow");
+        } else if ("DAY_OF_YEAR".equals(functionName)) {
+            buffer.append("doy");
+        } else {
+            buffer.append(functionName);
+        }
+        return buffer.append(" FROM ");
+    }
+
+    @Override
+    public void appendChildrenEnd(QuotingAppendable buffer) {
+        buffer.append(")");
+    }
+
+    @Override
+    public Node copy() {
+        return new PostgresExtractFunctionNode(functionName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLikeNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLikeNode.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLikeNode.java
new file mode 100644
index 0000000..4f0e236
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLikeNode.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.dba.postgres.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LikeNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class PostgresLikeNode extends LikeNode {
+    public PostgresLikeNode(boolean isNot, char escape) {
+        super(false, isNot, escape);
+    }
+
+    @Override
+    public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
+        if (not) {
+            buffer.append(" NOT");
+        }
+        buffer.append(" ILIKE ");
+    }
+
+    @Override
+    public Node copy() {
+        return new PostgresLikeNode(isNot(), getEscape());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLimitOffsetNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLimitOffsetNode.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLimitOffsetNode.java
new file mode 100644
index 0000000..045091a
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/sqltree/PostgresLimitOffsetNode.java
@@ -0,0 +1,60 @@
+/*****************************************************************
+ *   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.dba.postgres.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class PostgresLimitOffsetNode extends LimitOffsetNode {
+
+    public PostgresLimitOffsetNode(LimitOffsetNode node) {
+        super(node.getLimit(), node.getOffset());
+    }
+
+    private PostgresLimitOffsetNode(int limit, int offset) {
+        super(limit, offset);
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        if(limit == 0 && offset == 0) {
+            return buffer;
+        }
+
+        if(limit > 0) {
+            buffer.append(" LIMIT ").append(limit);
+        }
+
+        if(offset > 0) {
+            buffer.append(" OFFSET ").append(offset);
+        }
+
+        return buffer;
+    }
+
+    @Override
+    public Node copy() {
+        return new PostgresLimitOffsetNode(limit, offset);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteActionBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteActionBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteActionBuilder.java
index 5f5cdeb..c4cc53c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteActionBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteActionBuilder.java
@@ -22,6 +22,7 @@ import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.dba.JdbcActionBuilder;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.query.SQLTemplate;
+import org.apache.cayenne.query.SelectQuery;
 
 /**
  * @since 3.0
@@ -36,4 +37,12 @@ class SQLiteActionBuilder extends JdbcActionBuilder {
     public SQLAction sqlAction(SQLTemplate query) {
         return new SQLiteSQLTemplateAction(query, dataNode);
     }
+
+    /**
+     * @since 4.1
+     */
+    @Override
+    public <T> SQLAction objectSelectAction(SelectQuery<T> query) {
+        return new SQLiteSelectAction(query, dataNode);
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
index 2542282..20943ee 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
@@ -19,8 +19,7 @@
 package org.apache.cayenne.dba.sqlite;
 
 import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
@@ -36,11 +35,11 @@ import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.resource.ResourceLocator;
 
-import java.sql.Types;
 import java.util.Calendar;
 import java.util.Collection;
 import java.util.GregorianCalendar;
 import java.util.List;
+import java.util.function.Function;
 
 /**
  * A SQLite database adapter that works with Zentus JDBC driver. See
@@ -87,11 +86,11 @@ public class SQLiteAdapter extends JdbcAdapter {
     }
 
     /**
-     * @since 4.0
+     * @since 4.2
      */
     @Override
-    public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
-        return new SQLiteQualifierTranslator(queryAssembler);
+    public Function<Node, Node> getSqlTreeProcessor() {
+        return new SQLiteTreeProcessor();
     }
 
     @Override
@@ -112,18 +111,6 @@ public class SQLiteAdapter extends JdbcAdapter {
         return query.createSQLAction(new SQLiteActionBuilder(node));
     }
 
-    private int mapNTypes(int sqlType) {
-        switch (sqlType) {
-            case Types.NCHAR : return Types.CHAR;
-            case Types.NCLOB : return Types.CLOB;
-            case Types.NVARCHAR : return Types.VARCHAR;
-            case Types.LONGNVARCHAR : return Types.LONGVARCHAR;
-
-            default:
-                return sqlType;
-        }
-    }
-
     /**
      * Appends AUTOINCREMENT clause to the column definition for generated columns.
      */

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteQualifierTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteQualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteQualifierTranslator.java
deleted file mode 100644
index d31019b..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteQualifierTranslator.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.sqlite;
-
-import org.apache.cayenne.access.translator.select.QualifierTranslator;
-import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.ASTFunctionCall;
-import org.apache.cayenne.exp.parser.Node;
-
-/**
- * @since 4.0
- */
-public class SQLiteQualifierTranslator extends QualifierTranslator {
-
-    public SQLiteQualifierTranslator(QueryAssembler queryAssembler) {
-        super(queryAssembler);
-    }
-
-    @Override
-    public void endNode(Expression node, Expression parentNode) {
-        super.endNode(node, parentNode);
-        if(node.getType() == Expression.FUNCTION_CALL) {
-            if("LOCATE".equals(((ASTFunctionCall)node).getFunctionName())) {
-                // order of args in INSTR is different, so swap them back
-                swapNodeChildren((ASTFunctionCall)node, 0, 1);
-            }
-        }
-    }
-
-    @Override
-    protected void appendFunction(ASTFunctionCall functionExpression) {
-        switch (functionExpression.getFunctionName()) {
-            case "MOD":
-            case "CONCAT":
-                // noop
-                break;
-            case "SUBSTRING":
-                out.append("SUBSTR");
-                break;
-            case "LOCATE":
-                // LOCATE(substr, str) -> INSTR(str, substr)
-                out.append("INSTR");
-                swapNodeChildren(functionExpression, 0, 1);
-                break;
-            default:
-                super.appendFunction(functionExpression);
-        }
-    }
-
-    @Override
-    protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) {
-        switch (functionExpression.getFunctionName()) {
-            case "MOD":
-                out.append(" % ");
-                break;
-            case "CONCAT":
-                out.append(" || ");
-                break;
-            default:
-                super.appendFunctionArgDivider(functionExpression);
-        }
-    }
-
-    @Override
-    protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) {
-        switch (functionExpression.getFunctionName()) {
-            case "MOD":
-                out.delete(out.length() - 3, out.length());
-                break;
-            case "CONCAT":
-                out.delete(out.length() - 4, out.length());
-                break;
-            default:
-                super.clearLastFunctionArgDivider(functionExpression);
-        }
-        if(functionExpression instanceof ASTExtract) {
-            out.append(") as integer)");
-        }
-    }
-
-    @Override
-    protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
-        if (node.getType() == Expression.FUNCTION_CALL) {
-            if (node instanceof ASTExtract) {
-                return false;
-            }
-        }
-
-        return super.parenthesisNeeded(node, parentNode);
-    }
-
-
-    /**
-     * Translates to cast(strftime('format', column) as integer).
-     * Depends on connection property "date_class", can be set in connection URL (date_class=text).
-     *
-     * https://www.sqlite.org/lang_datefunc.html
-     */
-    @Override
-    protected void appendExtractFunction(ASTExtract functionExpression) {
-        out.append("cast(strftime(");
-
-        switch (functionExpression.getPart()) {
-            case YEAR:
-                out.append("'%Y'");
-                break;
-            case MONTH:
-                out.append("'%m'");
-                break;
-            case WEEK:
-                out.append("'%W'");
-                break;
-            case DAY:
-            case DAY_OF_MONTH:
-                out.append("'%d'");
-                break;
-            case DAY_OF_WEEK:
-                out.append("'%w'");
-                break;
-            case DAY_OF_YEAR:
-                out.append("'%j'");
-                break;
-            case HOUR:
-                out.append("'%H'");
-                break;
-            case MINUTE:
-                out.append("'%M'");
-                break;
-            case SECOND:
-                out.append("'%S'");
-                break;
-        }
-
-        out.append(", ");
-    }
-
-    private void swapNodeChildren(Node node, int i, int j) {
-        Node ni = node.jjtGetChild(i);
-        Node nj = node.jjtGetChild(j);
-        node.jjtAddChild(ni, j);
-        node.jjtAddChild(nj, i);
-    }
-}


Mime
View raw message