http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSQLTreeProcessor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSQLTreeProcessor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSQLTreeProcessor.java
new file mode 100644
index 0000000..e97fd2e
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSQLTreeProcessor.java
@@ -0,0 +1,189 @@
+/*****************************************************************
+ *   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.firebird;
+
+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.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.translator.select.BaseSQLTreeProcessor;
+import org.apache.cayenne.dba.firebird.sqltree.FirebirdLimitNode;
+import org.apache.cayenne.dba.firebird.sqltree.FirebirdSubstringFunctionNode;
+import org.apache.cayenne.util.ArrayUtil;
+
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.exp;
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.node;
+
+/**
+ * @since 4.2
+ */
+public class FirebirdSQLTreeProcessor extends BaseSQLTreeProcessor {
+
+    private static final int FIREBIRD_IN_BATCH_SIZE = 1500;
+
+    @Override
+    protected void onValueNode(Node parent, ValueNode child, int index) {
+        replaceChild(parent, index, new ValueNode(child.getValue(), 
child.isArray(), child.getAttribute()) {
+            @Override
+            protected void appendStringValue(QuotingAppendable buffer, 
CharSequence value) {
+                buffer.append("CAST(");
+                super.appendStringValue(buffer, value);
+                buffer.append(" AS 
VARCHAR(").append(value.length()).append("))");
+            }
+        });
+    }
+
+    @Override
+    protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int 
index) {
+        if(child.getLimit() == 0 && child.getOffset() == 0) {
+            return;
+        }
+        int from = child.getOffset() + 1;
+        int to = child.getLimit() == 0 ? Integer.MAX_VALUE : from + 
child.getLimit();
+        replaceChild(parent, index, new FirebirdLimitNode(from, to));
+    }
+
+    @Override
+    protected void onInNode(Node parent, InNode child, int index) {
+        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 1500 values
+        if(value instanceof Object[]) {
+            for(Object[] slice : ArrayUtil.sliceArray((Object[])value, 
FIREBIRD_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        } else if(value instanceof int[]) {
+            for(int[] slice : ArrayUtil.sliceArray((int[])value, 
FIREBIRD_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        } else if(value instanceof long[]) {
+            for(long[] slice : ArrayUtil.sliceArray((long[])value, 
FIREBIRD_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        } else if(value instanceof float[]) {
+            for(float[] slice : ArrayUtil.sliceArray((float[])value, 
FIREBIRD_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        } else if(value instanceof double[]) {
+            for(double[] slice : ArrayUtil.sliceArray((double[])value, 
FIREBIRD_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        } else if(value instanceof short[]) {
+            for(short[] slice : ArrayUtil.sliceArray((short[])value, 
FIREBIRD_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        } else if(value instanceof char[]) {
+            for(char[] slice : ArrayUtil.sliceArray((char[])value, 
FIREBIRD_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        } else if(value instanceof boolean[]) {
+            for(boolean[] slice : ArrayUtil.sliceArray((boolean[])value, 
FIREBIRD_IN_BATCH_SIZE)) {
+                newChildren.add(newSliceNode(child, arg, valueNode, slice));
+            }
+        } else if(value instanceof byte[]) {
+            for(byte[] slice : ArrayUtil.sliceArray((byte[])value, 
FIREBIRD_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++) {
+            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) {
+        switch (child.getFunctionName()) {
+            case "LENGTH":
+                replaceChild(parent, index, new FunctionNode("CHAR_LENGTH", 
child.getAlias()));
+                break;
+            case "LOCATE":
+                replaceChild(parent, index, new FunctionNode("POSITION", 
child.getAlias()));
+                break;
+            case "CONCAT":
+                replaceChild(parent, index, new OpExpressionNode("||"));
+                break;
+
+            case "SUBSTRING":
+                replaceChild(parent, index, new 
FirebirdSubstringFunctionNode(child.getAlias()));
+                break;
+
+            case "YEAR":
+            case "MONTH":
+            case "DAY":
+            case "DAY_OF_MONTH":
+            case "DAY_OF_WEEK":
+            case "DAY_OF_YEAR":
+            case "WEEK":
+            case "HOUR":
+            case "MINUTE":
+            case "SECOND":
+                Node functionReplacement = new FunctionNode("EXTRACT", 
child.getAlias(), true) {
+                    @Override
+                    public void appendChildrenSeparator(QuotingAppendable 
buffer, int childIdx) {
+                        buffer.append(' ');
+                    }
+                };
+
+                String partName = child.getFunctionName();
+                if("DAY_OF_MONTH".equals(partName)) {
+                    partName = "DAY";
+                } else if("DAY_OF_WEEK".equals(partName)) {
+                    partName = "WEEKDAY";
+                } else if("DAY_OF_YEAR".equals(partName)) {
+                    partName = "YEARDAY";
+                }
+                functionReplacement.addChild(new TextNode(partName + " FROM 
"));
+                replaceChild(parent, index, functionReplacement);
+                break;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSelectAction.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSelectAction.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSelectAction.java
new file mode 100644
index 0000000..184a909
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdSelectAction.java
@@ -0,0 +1,40 @@
+/*****************************************************************
+ *   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.firebird;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.jdbc.SelectAction;
+import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
+
+/**
+ * @since 4.1
+ */
+public class FirebirdSelectAction extends SelectAction {
+
+    public FirebirdSelectAction(SelectQuery<?> query, DataNode dataNode) {
+        super(query, dataNode);
+    }
+
+    @Override
+    protected int getInMemoryOffset(int queryOffset) {
+        return 0;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdLimitNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdLimitNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdLimitNode.java
new file mode 100644
index 0000000..9572e79
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdLimitNode.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.dba.firebird.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class FirebirdLimitNode extends Node {
+    private final int from;
+    private final int to;
+
+    public FirebirdLimitNode(int from, int to) {
+        this.from = from;
+        this.to = to;
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        buffer.append(" ROWS ");
+        if(from > 0) {
+            buffer.append(from).append(" TO ");
+        }
+        return buffer.append(to);
+    }
+
+    @Override
+    public Node copy() {
+        return new FirebirdLimitNode(from, to);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdSubstringFunctionNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdSubstringFunctionNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdSubstringFunctionNode.java
new file mode 100644
index 0000000..f7fe3e1
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/sqltree/FirebirdSubstringFunctionNode.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.dba.firebird.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * SUBSTRING function for Firebird
+ *
+ * It has following format:
+ *
+ * SUBSTRING (string FROM CAST(? AS INTEGER) FOR CAST(? AS INTEGER))
+ *
+ * @since 4.1
+ */
+public class FirebirdSubstringFunctionNode extends FunctionNode {
+    public FirebirdSubstringFunctionNode(String alias) {
+        super("SUBSTRING", alias);
+    }
+
+    @Override
+    public void appendChildrenSeparator(QuotingAppendable buffer, int 
childIdx) {
+        if(skipContent()) {
+            return;
+        }
+        if(childIdx == 0) {
+            buffer.append(" FROM CAST(");
+        } else if(childIdx == 1) {
+            buffer.append(" AS INTEGER) FOR CAST(");
+        }
+    }
+
+    @Override
+    public void appendChildrenEnd(QuotingAppendable buffer) {
+        if(skipContent()) {
+            return;
+        }
+        buffer.append(" AS INTEGER)");
+        super.appendChildrenEnd(buffer);
+    }
+
+    @Override
+    public Node copy() {
+        return new FirebirdSubstringFunctionNode(getAlias());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
index 69b9876..76d1db6 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
@@ -20,9 +20,7 @@
 package org.apache.cayenne.dba.frontbase;
 
 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.access.translator.select.SelectTranslator;
+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,8 +34,6 @@ 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.SelectQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
 import java.sql.Types;
@@ -45,6 +41,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.frontbase.com/";>FrontBase
@@ -79,20 +76,12 @@ public class FrontBaseAdapter extends JdbcAdapter {
                setSupportsBatchUpdates(true);
        }
 
-       /**
-        * @since 4.0
-        */
-       @Override
-       public SelectTranslator getSelectTranslator(SelectQuery<?> query, 
EntityResolver entityResolver) {
-               return new FrontBaseSelectTranslator(query, this, 
entityResolver);
-       }
-
-       /**
-        * @since 4.0
-        */
+    /**
+     * @since 4.2
+     */
        @Override
-       public QualifierTranslator getQualifierTranslator(QueryAssembler 
queryAssembler) {
-               return new FrontBaseQualifierTranslator(queryAssembler);
+       public Function<Node, Node> getSqlTreeProcessor() {
+               return new FrontBaseSQLTreeProcessor();
        }
 
        @Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseQualifierTranslator.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseQualifierTranslator.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseQualifierTranslator.java
deleted file mode 100644
index fa97c6d..0000000
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseQualifierTranslator.java
+++ /dev/null
@@ -1,134 +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.frontbase;
-
-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.ASTFunctionCall;
-
-/**
- * @since 4.0
- */
-public class FrontBaseQualifierTranslator extends QualifierTranslator {
-
-    private int substringArg = 0;
-
-    public FrontBaseQualifierTranslator(QueryAssembler queryAssembler) {
-        super(queryAssembler);
-    }
-
-    @Override
-    protected void appendFunction(ASTFunctionCall functionExpression) {
-        switch (functionExpression.getFunctionName()) {
-            case "CONCAT":
-                // noop
-                break;
-            case "LOCATE":
-                out.append("POSITION");
-                break;
-            case "LENGTH":
-                out.append("CHAR_LENGTH");
-                break;
-            case "SUBSTRING":
-                substringArg = 0;
-            default:
-                super.appendFunction(functionExpression);
-        }
-    }
-
-    @Override
-    protected void appendFunctionArgDivider(ASTFunctionCall 
functionExpression) {
-        switch (functionExpression.getFunctionName()) {
-            case "CONCAT":
-                out.append(" || ");
-                break;
-            case "LOCATE":
-                out.append(" IN ");
-                break;
-            case "SUBSTRING":
-                // SUBSTRING (str FROM offset FOR length)
-                switch (substringArg++) {
-                    case 0:
-                        out.append(" FROM ");
-                        break;
-                    case 1:
-                        out.append(" FOR ");
-                        break;
-                }
-                break;
-            default:
-                super.appendFunctionArgDivider(functionExpression);
-        }
-    }
-
-    @Override
-    protected void clearLastFunctionArgDivider(ASTFunctionCall 
functionExpression) {
-        switch (functionExpression.getFunctionName()) {
-            case "CONCAT":
-                out.delete(out.length() - " || ".length(), out.length());
-                break;
-            case "LOCATE":
-                out.delete(out.length() - " IN ".length(), out.length());
-                break;
-            case "SUBSTRING":
-                // no offset arg
-                if(substringArg == 2) {
-                    out.delete(out.length() - " FOR ".length(), out.length());
-                }
-                break;
-            default:
-                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_WEEK:
-            case DAY_OF_YEAR:
-            case WEEK:
-                throw new CayenneRuntimeException("Function %s() is 
unsupported in FrontBase.", functionExpression.getPartCamelCaseName());
-            case DAY_OF_MONTH:
-                out.append("DAY");
-                break;
-            default:
-                out.append(functionExpression.getPart().name());
-        }
-        out.append(" FROM ");
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSQLTreeProcessor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSQLTreeProcessor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSQLTreeProcessor.java
new file mode 100644
index 0000000..4fce3d7
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSQLTreeProcessor.java
@@ -0,0 +1,104 @@
+/*****************************************************************
+ *   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.frontbase;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+
+/**
+ * @since 4.2
+ */
+public class FrontBaseSQLTreeProcessor extends BaseSQLTreeProcessor {
+
+    @Override
+    protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+        switch (child.getFunctionName()) {
+            case "CONCAT":
+                replaceChild(parent, index, new OpExpressionNode("||"));
+                break;
+            case "LOCATE":
+                // POSITION (substr IN str)
+                replaceChild(parent, index, new FunctionNode("POSITION", 
child.getAlias()) {
+                    @Override
+                    public void appendChildrenSeparator(QuotingAppendable 
buffer, int childIdx) {
+                        buffer.append(" IN ");
+                    }
+                });
+                break;
+            case "LENGTH":
+                replaceChild(parent, index, new FunctionNode("CHAR_LENGTH", 
child.getAlias()));
+                break;
+            case "SUBSTRING":
+                // SUBSTRING (str FROM offset FOR length)
+                replaceChild(parent, index, new FunctionNode("SUBSTRING", 
child.getAlias()){
+                    @Override
+                    public void appendChildrenSeparator(QuotingAppendable 
buffer, int childIdx) {
+                        if(childIdx == 0) {
+                            buffer.append(" FROM ");
+                        } else if(childIdx == 1) {
+                            buffer.append(" FOR ");
+                        }
+                    }
+                });
+                break;
+            case "YEAR":
+            case "MONTH":
+            case "DAY":
+            case "DAY_OF_MONTH":
+            case "HOUR":
+            case "MINUTE":
+            case "SECOND":
+                Node functionReplacement = new 
ExtractFunctionNode(child.getAlias());
+                String functionName = child.getFunctionName();
+                if("DAY_OF_MONTH".equals(functionName)) {
+                    functionName = "DAY";
+                }
+                functionReplacement.addChild(new TextNode(functionName));
+                replaceChild(parent, index, functionReplacement);
+                break;
+
+            case "DAY_OF_WEEK":
+            case "DAY_OF_YEAR":
+            case "WEEK":
+                throw new CayenneRuntimeException("Function %s() is 
unsupported in FrontBase.", child.getFunctionName());
+        }
+    }
+
+    private static class ExtractFunctionNode extends FunctionNode {
+        public ExtractFunctionNode(String alias) {
+            super("EXTRACT", alias, true);
+        }
+
+        @Override
+        public void appendChildrenSeparator(QuotingAppendable buffer, int 
childIdx) {
+            buffer.append(" FROM ");
+        }
+
+        @Override
+        public Node copy() {
+            return new ExtractFunctionNode(getAlias());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSelectTranslator.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSelectTranslator.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSelectTranslator.java
deleted file mode 100644
index 1e71f5f..0000000
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseSelectTranslator.java
+++ /dev/null
@@ -1,52 +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.frontbase;
-
-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 FrontBaseSelectTranslator extends DefaultSelectTranslator {
-
-       static final String SELECT_PREFIX = "SELECT";
-
-       /**
-        * @since 4.0
-        */
-       public FrontBaseSelectTranslator(Query query, DbAdapter adapter, 
EntityResolver entityResolver) {
-               super(query, adapter, entityResolver);
-       }
-
-       @Override
-       protected void appendLimitAndOffsetClauses(StringBuilder buffer) {
-
-               int limit = queryMetadata.getFetchLimit();
-               if (limit > 0 && buffer.length() > SELECT_PREFIX.length()) {
-
-                       if (SELECT_PREFIX.equals(buffer.substring(0, 
SELECT_PREFIX.length()))) {
-                               buffer.insert(SELECT_PREFIX.length(), " TOP " + 
limit);
-                       }
-               }
-       }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2ActionBuilder.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2ActionBuilder.java 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2ActionBuilder.java
new file mode 100644
index 0000000..2cf2a10
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2ActionBuilder.java
@@ -0,0 +1,39 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.h2;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.dba.JdbcActionBuilder;
+import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
+
+/**
+ * @since 4.1
+ */
+public class H2ActionBuilder extends JdbcActionBuilder {
+    public H2ActionBuilder(DataNode node) {
+        super(node);
+    }
+
+    @Override
+    public <T> SQLAction objectSelectAction(SelectQuery<T> query) {
+        return new H2SelectAction(query, dataNode);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
index 6b47636..39cc43c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
@@ -19,6 +19,8 @@
 
 package org.apache.cayenne.dba.h2;
 
+import org.apache.cayenne.access.DataNode;
+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.ValueObjectTypeRegistry;
@@ -28,9 +30,12 @@ import org.apache.cayenne.dba.JdbcAdapter;
 import org.apache.cayenne.dba.PkGenerator;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.resource.ResourceLocator;
 
 import java.util.List;
+import java.util.function.Function;
 
 /**
  * DbAdapter implementation for <a href="http://www.h2database.com/";>H2
@@ -66,6 +71,19 @@ public class H2Adapter extends JdbcAdapter {
         }
     }
 
+    /**
+     * @since 4.2
+     */
+    @Override
+    public Function<Node, Node> getSqlTreeProcessor() {
+        return new H2SQLTreeProcessor();
+    }
+
+    @Override
+    public SQLAction getAction(Query query, DataNode node) {
+        return query.createSQLAction(new H2ActionBuilder(node));
+    }
+
     @Override
     protected PkGenerator createPkGenerator() {
         return new H2PkGenerator(this);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2SQLTreeProcessor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2SQLTreeProcessor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2SQLTreeProcessor.java
new file mode 100644
index 0000000..cd797f5
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2SQLTreeProcessor.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.dba.h2;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OffsetFetchNextNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+
+/**
+ * @since 4.2
+ */
+public class H2SQLTreeProcessor extends BaseSQLTreeProcessor {
+
+    @Override
+    protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int 
index) {
+        replaceChild(parent, index, new OffsetFetchNextNode(child), false);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2SelectAction.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2SelectAction.java 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2SelectAction.java
new file mode 100644
index 0000000..df7380d
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2SelectAction.java
@@ -0,0 +1,39 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.h2;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.jdbc.SelectAction;
+import org.apache.cayenne.query.SelectQuery;
+
+/**
+ * @since 4.1
+ */
+public class H2SelectAction extends SelectAction {
+
+    public H2SelectAction(SelectQuery<?> query, DataNode dataNode) {
+        super(query, dataNode);
+    }
+
+    @Override
+    protected int getInMemoryOffset(int queryOffset) {
+        return 0;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
index 64c59b3..99f4ae0 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
@@ -21,12 +21,9 @@ package org.apache.cayenne.dba.hsqldb;
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.translator.ParameterBinding;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
 import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
 import org.apache.cayenne.access.translator.ejbql.JdbcEJBQLTranslatorFactory;
-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;
@@ -40,18 +37,15 @@ import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.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;
-import java.sql.SQLException;
 import java.sql.Types;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.function.Function;
 
 /**
  * DbAdapter implementation for the <a href="http://hsqldb.sourceforge.net/";>
@@ -91,22 +85,11 @@ public class HSQLDBAdapter extends JdbcAdapter {
        }
 
        /**
-        * @since 4.0
-        */
-       @Override
-       public SelectTranslator getSelectTranslator(SelectQuery<?> query, 
EntityResolver entityResolver) {
-               return new HSQLSelectTranslator(query, this, entityResolver);
-       }
-
-       /**
-        * Returns a trimming translator.
-        * @since 4.0
+        * @since 4.2
         */
        @Override
-       public QualifierTranslator getQualifierTranslator(QueryAssembler 
queryAssembler) {
-               QualifierTranslator translator = new 
HSQLQualifierTranslator(queryAssembler);
-               translator.setCaseInsensitive(caseInsensitiveCollations);
-               return translator;
+       public Function<Node, Node> getSqlTreeProcessor() {
+               return new HSQLTreeProcessor();
        }
 
        /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLQualifierTranslator.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLQualifierTranslator.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLQualifierTranslator.java
deleted file mode 100644
index fce980c..0000000
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLQualifierTranslator.java
+++ /dev/null
@@ -1,86 +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.hsqldb;
-
-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.parser.ASTExtract;
-import org.apache.cayenne.exp.parser.ASTFunctionCall;
-import org.apache.cayenne.exp.parser.PatternMatchNode;
-
-/**
- * @since 4.0
- */
-public class HSQLQualifierTranslator extends TrimmingQualifierTranslator {
-
-    public HSQLQualifierTranslator(QueryAssembler queryAssembler) {
-        super(queryAssembler, HSQLDBAdapter.TRIM_FUNCTION);
-    }
-
-    @Override
-    protected void appendLikeEscapeCharacter(PatternMatchNode patternMatchNode)
-            throws IOException {
-
-        char escapeChar = patternMatchNode.getEscapeChar();
-
-        if ('?' == escapeChar) {
-            throw new CayenneRuntimeException("the escape character of '?' is 
illegal for LIKE clauses.");
-        }
-
-        if (0 != escapeChar) {
-            // this is a difference with super implementation - HSQL driver 
seems does not
-            // support JDBC escape syntax, so creating an explicit SQL escape:
-            out.append(" ESCAPE '");
-            out.append(escapeChar);
-            out.append("'");
-        }
-    }
-
-    @Override
-    protected void appendFunction(ASTFunctionCall functionExpression) {
-        // from documentation:
-        // CURRENT_TIME returns a value of TIME WITH TIME ZONE type.
-        // LOCALTIME returns a value of TIME type.
-        // CURTIME() is a synonym for LOCALTIME.
-        // use LOCALTIME to better align with other DBs
-        if("CURRENT_TIME".equals(functionExpression.getFunctionName())) {
-            out.append("LOCALTIME");
-        } else {
-            super.appendFunction(functionExpression);
-        }
-    }
-
-    @Override
-    protected void appendExtractFunction(ASTExtract functionExpression) {
-        switch (functionExpression.getPart()) {
-            case DAY_OF_WEEK:
-            case DAY_OF_MONTH:
-            case DAY_OF_YEAR:
-                // hsqldb 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/hsqldb/HSQLSelectTranslator.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLSelectTranslator.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLSelectTranslator.java
deleted file mode 100644
index ce3bb58..0000000
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLSelectTranslator.java
+++ /dev/null
@@ -1,57 +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.hsqldb;
-
-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 HSQLSelectTranslator extends DefaultSelectTranslator {
-
-       /**
-        * @since 4.0
-        */
-       public HSQLSelectTranslator(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 (offset > 0 || limit > 0) {
-                       buffer.append(" LIMIT ");
-
-                       // both OFFSET and LIMIT must be present, so come up 
with defaults
-                       // if one of
-                       // them is not set by the user
-                       if (limit == 0) {
-                               limit = Integer.MAX_VALUE;
-                       }
-
-                       buffer.append(limit).append(" OFFSET ").append(offset);
-               }
-       }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLTreeProcessor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLTreeProcessor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLTreeProcessor.java
new file mode 100644
index 0000000..152f3ca
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLTreeProcessor.java
@@ -0,0 +1,70 @@
+/*****************************************************************
+ *   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.hsqldb;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TrimmingColumnNode;
+
+/**
+ * @since 4.2
+ */
+public class HSQLTreeProcessor extends BaseSQLTreeProcessor {
+
+    @Override
+    protected void onColumnNode(Node parent, ColumnNode child, int index) {
+        replaceChild(parent, index, new TrimmingColumnNode(child));
+    }
+
+    @Override
+    protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+        Node replacement = getReplacementForFunction(child);
+        if(replacement != null) {
+            replaceChild(parent, index, replacement);
+        }
+    }
+
+    private Node getReplacementForFunction(FunctionNode child) {
+        switch (child.getFunctionName()) {
+            case "DAY_OF_MONTH":
+            case "DAY_OF_WEEK":
+            case "DAY_OF_YEAR":
+                // hsqldb variants are without '_'
+                return new FunctionNode(child.getFunctionName().replace("_", 
""), child.getAlias(), true);
+
+            case "CURRENT_DATE":
+            case "CURRENT_TIMESTAMP":
+                return new FunctionNode(child.getFunctionName(), 
child.getAlias(), false);
+            case "CURRENT_TIME":
+                // from documentation:
+                // CURRENT_TIME returns a value of TIME WITH TIME ZONE type.
+                // LOCALTIME returns a value of TIME type.
+                // CURTIME() is a synonym for LOCALTIME.
+                // use LOCALTIME to better align with other DBs
+                return new FunctionNode("LOCALTIME", child.getAlias(), false);
+            case "CONCAT":
+                return new OpExpressionNode("||");
+        }
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
index 60a39b6..08c156d 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
@@ -21,11 +21,8 @@ package org.apache.cayenne.dba.ingres;
 
 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.translator.select.TrimmingQualifierTranslator;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
@@ -37,16 +34,15 @@ import org.apache.cayenne.dba.PkGenerator;
 import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
-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;
 import java.sql.SQLException;
 import java.sql.Types;
 import java.util.List;
+import java.util.function.Function;
 
 /**
  * DbAdapter implementation for <a
@@ -75,17 +71,12 @@ public class IngresAdapter extends JdbcAdapter {
                setSupportsGeneratedKeys(true);
        }
 
-       /**
-        * @since 4.0
-        */
-       @Override
-       public SelectTranslator getSelectTranslator(SelectQuery<?> query, 
EntityResolver entityResolver) {
-               return new IngresSelectTranslator(query, this, entityResolver);
-       }
-
+    /**
+     * @since 4.2
+     */
        @Override
-       public QualifierTranslator getQualifierTranslator(QueryAssembler 
queryAssembler) {
-               return new IngresQualifierTranslator(queryAssembler);
+       public Function<Node, Node> getSqlTreeProcessor() {
+               return new IngressSQLTreeProcessor();
        }
 
        @Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresQualifierTranslator.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresQualifierTranslator.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresQualifierTranslator.java
deleted file mode 100644
index 8334ff4..0000000
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresQualifierTranslator.java
+++ /dev/null
@@ -1,122 +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.ingres;
-
-import java.io.IOException;
-
-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.Node;
-
-/**
- * @since 4.0
- */
-class IngresQualifierTranslator extends TrimmingQualifierTranslator {
-
-    IngresQualifierTranslator(QueryAssembler queryAssembler) {
-        super(queryAssembler, IngresAdapter.TRIM_FUNCTION);
-    }
-
-    @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 ingres version of LOCATE is different, so 
swap them back
-                swapNodeChildren((ASTFunctionCall)node, 0, 1);
-            }
-        }
-    }
-
-    @Override
-    protected void appendFunction(ASTFunctionCall functionExpression) {
-        if("CONCAT".equals(functionExpression.getFunctionName())) {
-            // noop
-        } else if("LOCATE".equals(functionExpression.getFunctionName())) {
-            // order of args in ingres version of LOCATE is different
-            // LOCATE(substr, str) -> LOCATE(str, substr)
-            out.append("LOCATE");
-            swapNodeChildren(functionExpression, 0, 1);
-        } else if("TRIM".equals(functionExpression.getFunctionName())) {
-            // simple TRIM removes only trailing spaces
-            out.append("LTRIM(RTRIM");
-        } else {
-            super.appendFunction(functionExpression);
-        }
-    }
-
-    @Override
-    protected void appendFunctionArgDivider(ASTFunctionCall 
functionExpression) {
-        if("CONCAT".equals(functionExpression.getFunctionName())) {
-            out.append(" + ");
-        } else {
-            super.appendFunctionArgDivider(functionExpression);
-        }
-    }
-
-    @Override
-    protected void appendFunctionArg(Object value, ASTFunctionCall 
functionExpression) throws IOException {
-        if("SUBSTRING".equals(functionExpression.getFunctionName())) {
-            out.append("CAST(");
-            super.appendFunctionArg(value, functionExpression);
-            clearLastFunctionArgDivider(functionExpression);
-            out.append(" AS INTEGER)");
-            appendFunctionArgDivider(functionExpression);
-        } else {
-            super.appendFunctionArg(value, functionExpression);
-        }
-    }
-
-    @Override
-    protected void clearLastFunctionArgDivider(ASTFunctionCall 
functionExpression) {
-        if("CONCAT".equals(functionExpression.getFunctionName())) {
-            out.delete(out.length() - " + ".length(), out.length());
-        } else {
-            super.clearLastFunctionArgDivider(functionExpression);
-            if("TRIM".equals(functionExpression.getFunctionName())) {
-                out.append(")");
-            }
-        }
-    }
-
-    @Override
-    protected void appendExtractFunction(ASTExtract functionExpression) {
-        switch (functionExpression.getPart()) {
-            case DAY_OF_WEEK:
-            case DAY_OF_MONTH:
-            case DAY_OF_YEAR:
-                // ingres variants are without '_'
-                out.append(functionExpression.getPart().name().replace("_", 
""));
-                break;
-            default:
-                appendFunction(functionExpression);
-        }
-    }
-
-    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/ingres/IngresSelectTranslator.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSelectTranslator.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSelectTranslator.java
deleted file mode 100644
index f466d5f..0000000
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSelectTranslator.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.ingres;
-
-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;
-
-public class IngresSelectTranslator extends DefaultSelectTranslator {
-    
-    /**
-     * @since 4.0
-     */
-    public IngresSelectTranslator(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 (offset > 0) {
-            buffer.append(" OFFSET ").append(offset);
-        }
-        
-        if (limit > 0) {
-            buffer.append(" FETCH NEXT ").append(limit).append(" ROWS ONLY ");
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngressSQLTreeProcessor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngressSQLTreeProcessor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngressSQLTreeProcessor.java
new file mode 100644
index 0000000..02a21fd
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngressSQLTreeProcessor.java
@@ -0,0 +1,108 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dba.ingres;
+
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OffsetFetchNextNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TrimmingColumnNode;
+import org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor;
+
+/**
+ * @since 4.2
+ */
+public class IngressSQLTreeProcessor extends BaseSQLTreeProcessor {
+
+    @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) {
+        replaceChild(parent, index, new OffsetFetchNextNode(child) {
+            @Override
+            public QuotingAppendable append(QuotingAppendable buffer) {
+                // OFFSET X FETCH NEXT Y ROWS ONLY
+                if(offset > 0) {
+                    buffer.append("OFFSET ").append(offset);
+                }
+                if(limit > 0) {
+                    buffer.append("FETCH NEXT ").append(limit).append(" ROWS 
ONLY");
+                }
+                return buffer;
+            }
+        });
+    }
+
+    @Override
+    protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+        switch (child.getFunctionName()) {
+            case "CONCAT":
+                replaceChild(parent, index, new OpExpressionNode("+"));
+                return;
+            case "LOCATE":
+                Node child0 = child.getChild(0);
+                child.replaceChild(0, child.getChild(1));
+                child.replaceChild(1, child0);
+                return;
+            case "SUBSTRING":
+                Node replacement = new FunctionNode("SUBSTRING", 
child.getAlias(), true) {
+                    @Override
+                    public void appendChildrenSeparator(QuotingAppendable 
buffer, int childIdx) {
+                        // 0, CAST(1 AS INTEGER), CAST(2 AS INTEGER)
+                        if(childIdx == 1 || childIdx == 2) {
+                            buffer.append(" AS INTEGER)");
+                        }
+                        if(childIdx == 0 || childIdx == 1) {
+                            buffer.append(", CAST(");
+                        }
+                    }
+
+                    @Override
+                    public void appendChildrenEnd(QuotingAppendable buffer) {
+                        buffer.append(" AS INTEGER)");
+                        super.appendChildrenEnd(buffer);
+                    }
+                };
+                replaceChild(parent, index, replacement);
+                return;
+            case "TRIM":
+                replaceChild(parent, index, new FunctionNode("RTRIM(LTRIM", 
child.getAlias(), true) {
+                    @Override
+                    public void appendChildrenEnd(QuotingAppendable buffer) {
+                        buffer.append(')');
+                        super.appendChildrenEnd(buffer);
+                    }
+                });
+                return;
+            case "DAY_OF_WEEK":
+            case "DAY_OF_MONTH":
+            case "DAY_OF_YEAR":
+                // ingres variants are without '_'
+                replaceChild(parent, index, new 
FunctionNode(child.getFunctionName().replace("_", ""), child.getAlias(), true));
+                return;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
index b50fbc0..5230990 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
@@ -21,12 +21,10 @@ package org.apache.cayenne.dba.mysql;
 
 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.ejbql.JdbcEJBQLTranslatorFactory;
-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.ByteArrayType;
 import org.apache.cayenne.access.types.CharType;
 import org.apache.cayenne.access.types.ExtendedType;
@@ -43,10 +41,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;
@@ -59,6 +55,7 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
+import java.util.function.Function;
 
 /**
  * DbAdapter implementation for <a href="http://www.mysql.com";>MySQL RDBMS</a>.
@@ -109,16 +106,12 @@ public class MySQLAdapter extends JdbcAdapter {
                return new DefaultQuotingStrategy("`", "`");
        }
 
+    /**
+     * @since 4.2
+     */
        @Override
-       public SelectTranslator getSelectTranslator(SelectQuery<?> query, 
EntityResolver entityResolver) {
-               return new MySQLSelectTranslator(query, this, entityResolver);
-       }
-
-       @Override
-       public QualifierTranslator getQualifierTranslator(QueryAssembler 
queryAssembler) {
-               QualifierTranslator translator = new 
MySQLQualifierTranslator(queryAssembler);
-               translator.setCaseInsensitive(caseInsensitiveCollations);
-               return translator;
+       public Function<Node, Node> getSqlTreeProcessor() {
+               return MySQLTreeProcessor.getInstance();
        }
 
        /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLQualifierTranslator.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLQualifierTranslator.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLQualifierTranslator.java
deleted file mode 100644
index 2b93c22..0000000
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLQualifierTranslator.java
+++ /dev/null
@@ -1,105 +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.mysql;
-
-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;
-
-class MySQLQualifierTranslator extends QualifierTranslator {
-
-    public MySQLQualifierTranslator(QueryAssembler queryAssembler) {
-        super(queryAssembler);
-    }
-
-    @Override
-    protected void appendLikeEscapeCharacter(PatternMatchNode patternMatchNode)
-            throws IOException {
-
-        char escapeChar = patternMatchNode.getEscapeChar();
-
-        if ('?' == escapeChar) {
-            throw new CayenneRuntimeException("the escape character of '?' is 
illegal for LIKE clauses.");
-        }
-
-        if (0 != escapeChar) {
-            // this is a difference with super implementation - MySQL driver 
does not
-            // support JDBC escape syntax, so creating an explicit SQL escape:
-            out.append(" ESCAPE '");
-            out.append(escapeChar);
-            out.append("'");
-        }
-    }
-
-    @Override
-    public void finishedChild(Expression node, int childIndex, boolean 
hasMoreChildren) {
-        
-        if (!caseInsensitive) {
-            super.finishedChild(node, childIndex, hasMoreChildren);
-        }
-        else {
-            
-            if (!hasMoreChildren) {
-                return;
-            }
-            
-            // if we have something except LIKE or NOT LIKE then no need in 
specific handling
-            if (node.getType() != Expression.LIKE
-                    && node.getType() != Expression.NOT_LIKE) {
-                super.finishedChild(node, childIndex, hasMoreChildren);
-                return;
-            }
-            
-            try {
-                Appendable out = (matchingObject) ? new StringBuilder() : 
this.out;
-                
-                switch (node.getType()) {
-                    case Expression.LIKE:
-                        out.append(" LIKE BINARY ");
-                        break;
-                    case Expression.NOT_LIKE:
-                        out.append(" NOT LIKE BINARY ");
-                        break;
-                }
-            }
-            catch (IOException ioex) {
-                throw new CayenneRuntimeException("Error appending content", 
ioex);
-            }
-        }
-    }
-
-    @Override
-    protected void appendExtractFunction(ASTExtract functionExpression) {
-        switch (functionExpression.getPart()) {
-            case DAY_OF_WEEK:
-            case DAY_OF_MONTH:
-            case DAY_OF_YEAR:
-                // mysql 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/mysql/MySQLSelectTranslator.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSelectTranslator.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSelectTranslator.java
deleted file mode 100644
index c9f0809..0000000
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSelectTranslator.java
+++ /dev/null
@@ -1,57 +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.mysql;
-
-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 MySQLSelectTranslator extends DefaultSelectTranslator {
-
-       /**
-        * @since 4.0
-        */
-       public MySQLSelectTranslator(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 (offset > 0 || limit > 0) {
-                       buffer.append(" LIMIT ");
-
-                       // both OFFSET and LIMIT must be present, so come up 
with defaults
-                       // if one of
-                       // them is not set by the user
-                       if (limit == 0) {
-                               limit = Integer.MAX_VALUE;
-                       }
-
-                       buffer.append(limit).append(" OFFSET ").append(offset);
-               }
-       }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLTreeProcessor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLTreeProcessor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLTreeProcessor.java
new file mode 100644
index 0000000..d816112
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLTreeProcessor.java
@@ -0,0 +1,68 @@
+/*****************************************************************
+ *   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.mysql;
+
+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.translator.select.BaseSQLTreeProcessor;
+import org.apache.cayenne.dba.mysql.sqltree.MysqlLikeNode;
+import org.apache.cayenne.dba.mysql.sqltree.MysqlLimitOffsetNode;
+
+/**
+ * @since 4.2
+ */
+public class MySQLTreeProcessor extends BaseSQLTreeProcessor {
+
+    private static final MySQLTreeProcessor INSTANCE = new 
MySQLTreeProcessor();
+
+    public static MySQLTreeProcessor getInstance() {
+        return INSTANCE;
+    }
+
+    private MySQLTreeProcessor() {
+    }
+
+    @Override
+    protected void onLikeNode(Node parent, LikeNode child, int index) {
+        if(!child.isIgnoreCase()) {
+            replaceChild(parent, index, new MysqlLikeNode(child.isNot(), 
child.getEscape()));
+        }
+    }
+
+    @Override
+    protected void onLimitOffsetNode(Node parent, LimitOffsetNode child, int 
index) {
+        Node replacement = new MysqlLimitOffsetNode(child.getLimit(), 
child.getOffset());
+        replaceChild(parent, index, replacement, false);
+    }
+
+    @Override
+    protected void onFunctionNode(Node parent, FunctionNode child, int index) {
+        String functionName = child.getFunctionName();
+        if("DAY_OF_MONTH".equals(functionName)
+                || "DAY_OF_WEEK".equals(functionName)
+                || "DAY_OF_YEAR".equals(functionName)) {
+            FunctionNode replacement = new 
FunctionNode(functionName.replace("_", ""), child.getAlias(), true);
+            replaceChild(parent, index, replacement);
+        }
+    }
+
+}

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

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/sqltree/MysqlLimitOffsetNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/sqltree/MysqlLimitOffsetNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/sqltree/MysqlLimitOffsetNode.java
new file mode 100644
index 0000000..9bc0105
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/sqltree/MysqlLimitOffsetNode.java
@@ -0,0 +1,40 @@
+/*****************************************************************
+ *   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.mysql.sqltree;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.LimitOffsetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+
+/**
+ * @since 4.2
+ */
+public class MysqlLimitOffsetNode extends LimitOffsetNode {
+
+    public MysqlLimitOffsetNode(int limit, int offset) {
+        // Per MySQL documentation: "To retrieve all rows from a certain 
offset up to the end of the result set,
+        // you can use some large number for the second parameter."
+        super(limit == 0 && offset > 0 ? Integer.MAX_VALUE : limit, offset);
+    }
+
+    @Override
+    public Node copy() {
+        return new MysqlLimitOffsetNode(limit, offset);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
index 1a30779..c245942 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
@@ -25,11 +25,10 @@ import java.sql.ResultSet;
 import java.sql.Types;
 import java.util.Iterator;
 import java.util.List;
+import java.util.function.Function;
 
 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.access.translator.select.SelectTranslator;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
 import org.apache.cayenne.access.types.ByteType;
 import org.apache.cayenne.access.types.CharType;
 import org.apache.cayenne.access.types.ExtendedType;
@@ -46,8 +45,6 @@ import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
 /**
@@ -78,11 +75,11 @@ public class OpenBaseAdapter extends JdbcAdapter {
     }
 
     /**
-     * @since 4.0
+     * @since 4.2
      */
     @Override
-    public SelectTranslator getSelectTranslator(SelectQuery<?> query, 
EntityResolver entityResolver) {
-        return new OpenBaseSelectTranslator(query, this, entityResolver);
+    public Function<Node, Node> getSqlTreeProcessor() {
+        return new OpenBaseSQLTreeProcessor();
     }
 
     @Override
@@ -126,14 +123,6 @@ public class OpenBaseAdapter extends JdbcAdapter {
     }
 
     /**
-     * Returns OpenBase-specific translator for queries.
-     */
-    @Override
-    public QualifierTranslator getQualifierTranslator(QueryAssembler 
queryAssembler) {
-        return new OpenBaseQualifierTranslator(queryAssembler);
-    }
-
-    /**
      * Creates and returns a primary key generator. Overrides superclass
      * implementation to return an instance of OpenBasePkGenerator that uses
      * built-in multi-server primary key generation.

Reply via email to