cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ntimof...@apache.org
Subject [cayenne] branch master updated: CAY-2659 Use new SQLBuilder utility to generate SQL for batch queries
Date Fri, 29 May 2020 08:12:39 GMT
This is an automated email from the ASF dual-hosted git repository.

ntimofeev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git


The following commit(s) were added to refs/heads/master by this push:
     new a6f80d0  CAY-2659 Use new SQLBuilder utility to generate SQL for batch queries
     new 5c291b0  Merge pull request #425 from stariy95/4.2-FEATURE-sql-builder-batch-queries-translator
a6f80d0 is described below

commit a6f80d058a2b0c80485f04f8175b75ef38f7c8da
Author: Nikita Timofeev <stariy95@gmail.com>
AuthorDate: Thu May 28 14:22:42 2020 +0300

    CAY-2659 Use new SQLBuilder utility to generate SQL for batch queries
---
 .../apache/cayenne/access/jdbc/BatchAction.java    |   4 +
 .../ExpressionNode.java => BaseBuilder.java}       |  55 +++---
 ...QLGenerationContext.java => DeleteBuilder.java} |  24 ++-
 .../cayenne/access/sqlbuilder/InsertBuilder.java   |  54 ++++++
 .../cayenne/access/sqlbuilder/SQLBuilder.java      |  12 ++
 .../access/sqlbuilder/SQLGenerationContext.java    |   6 +
 .../cayenne/access/sqlbuilder/SelectBuilder.java   |  38 +---
 ...QLGenerationContext.java => UpdateBuilder.java} |  32 ++-
 .../sqltree/{NodeType.java => DeleteNode.java}     |  26 ++-
 .../access/sqlbuilder/sqltree/EqualNode.java       |   5 +-
 .../access/sqlbuilder/sqltree/ExpressionNode.java  |  10 +-
 ...{ExpressionNode.java => InsertColumnsNode.java} |  30 ++-
 .../sqltree/{NodeType.java => InsertNode.java}     |  25 ++-
 .../access/sqlbuilder/sqltree/NodeType.java        |   5 +-
 .../sqltree/{EqualNode.java => SetNode.java}       |  24 ++-
 .../sqlbuilder/sqltree/TrimmingColumnNode.java     |  18 +-
 .../sqltree/{NodeType.java => UpdateNode.java}     |  26 +--
 .../{ExpressionNode.java => ValuesNode.java}       |  32 +--
 .../translator/batch/BaseBatchTranslator.java      |  89 +++++++++
 .../translator/batch/BatchTranslatorContext.java   |  72 +++++++
 .../batch/DefaultBatchTranslatorFactory.java       |  14 +-
 .../translator/batch/DeleteBatchTranslator.java    |  94 +++------
 .../translator/batch/InsertBatchTranslator.java    | 136 ++++---------
 .../batch/SoftDeleteBatchTranslator.java           | 180 +++++++----------
 .../batch/SoftDeleteTranslatorFactory.java         | 138 ++++++-------
 .../translator/batch/UpdateBatchTranslator.java    | 129 ++++---------
 .../batch/{ => legacy}/DefaultBatchTranslator.java |   5 +-
 .../DefaultBatchTranslatorFactory.java             |   6 +-
 .../batch/{ => legacy}/DeleteBatchTranslator.java  |   4 +-
 .../batch/{ => legacy}/InsertBatchTranslator.java  |   4 +-
 .../{ => legacy}/SoftDeleteBatchTranslator.java    | 215 +++++++++++----------
 .../{ => legacy}/SoftDeleteTranslatorFactory.java  | 142 +++++++-------
 .../batch/{ => legacy}/UpdateBatchTranslator.java  |   4 +-
 .../select/DefaultQuotingAppendable.java           |   7 +-
 .../translator/select/TranslatorContext.java       |   2 +-
 .../dba/oracle/Oracle8LOBBatchTranslator.java      |   4 +-
 .../cayenne/access/jdbc/BatchActionLockingIT.java  |   8 +-
 .../access/sqlbuilder/DeleteBuilderTest.java       |  57 ++++++
 .../access/sqlbuilder/InsertBuilderTest.java       |  87 +++++++++
 .../access/sqlbuilder/UpdateBuilderTest.java       |  73 +++++++
 .../translator/batch/DeleteBatchTranslatorIT.java  |  44 +++--
 .../translator/batch/InsertBatchTranslatorIT.java  |  22 ++-
 .../batch/SoftDeleteBatchTranslatorIT.java         |  12 +-
 .../translator/batch/UpdateBatchTranslatorIT.java  |  66 ++++---
 .../{ => legacy}/DefaultBatchTranslatorIT.java     |   3 +-
 .../{ => legacy}/DeleteBatchTranslatorIT.java      |   3 +-
 .../{ => legacy}/InsertBatchTranslatorIT.java      |   3 +-
 .../{ => legacy}/SoftDeleteBatchTranslatorIT.java  |   4 +-
 .../{ => legacy}/UpdateBatchTranslatorIT.java      |   3 +-
 49 files changed, 1180 insertions(+), 876 deletions(-)

diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java
index ad54cbb..6f798e3 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java
@@ -158,6 +158,10 @@ public class BatchAction extends BaseSQLAction {
 	protected void runAsIndividualQueries(Connection connection, BatchTranslator translator,
 			OperationObserver delegate, boolean generatesKeys) throws SQLException, Exception {
 
+		if(query.getRows().isEmpty()) {
+			return;
+		}
+
 		JdbcEventLogger logger = dataNode.getJdbcEventLogger();
 		boolean useOptimisticLock = query.isUsingOptimisticLocking();
 
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/BaseBuilder.java
similarity index 52%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
copy to cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/BaseBuilder.java
index 0199ec3..1f8ea45 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/BaseBuilder.java
@@ -17,49 +17,50 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.access.sqlbuilder.sqltree;
+package org.apache.cayenne.access.sqlbuilder;
 
+import java.util.function.Supplier;
 
-import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
 
 /**
  * @since 4.2
  */
-public class ExpressionNode extends Node {
+public abstract class BaseBuilder implements NodeBuilder {
+    /**
+     * Main root of this query
+     */
+    protected final Node root;
 
-    public ExpressionNode() {
-    }
+    /*
+     * Following nodes are all children of root,
+     * but we keep them here for quick access.
+     */
+    protected final Node[] nodes;
 
-    public ExpressionNode(NodeType nodeType) {
-        super(nodeType);
+    public BaseBuilder(Node root, int size) {
+        this.root = root;
+        this.nodes = new Node[size];
     }
 
-    @Override
-    public QuotingAppendable append(QuotingAppendable buffer) {
-        return buffer;
-    }
-
-    @Override
-    public void appendChildrenStart(QuotingAppendable buffer) {
-        if(parent != null && parent.type != NodeType.WHERE && parent.type != NodeType.JOIN) {
-            buffer.append(" (");
+    protected Node node(int idx, Supplier<Node> nodeSupplier) {
+        if(nodes[idx] == null) {
+            nodes[idx] = nodeSupplier.get();
         }
+        return nodes[idx];
     }
 
     @Override
-    public void appendChildrenEnd(QuotingAppendable buffer) {
-        if(parent != null && parent.type != NodeType.WHERE && parent.type != NodeType.JOIN) {
-            buffer.append(" )");
+    public Node build() {
+        for (Node next : nodes) {
+            if (next != null) {
+                root.addChild(next);
+            }
         }
+        return root;
     }
 
-    @Override
-    public String toString() {
-        return "{ExpressionNode}";
-    }
-
-    @Override
-    public Node copy() {
-        return new ExpressionNode();
+    public Node getRoot() {
+        return root;
     }
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/DeleteBuilder.java
similarity index 59%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
copy to cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/DeleteBuilder.java
index 02090c5..9d00f2e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/DeleteBuilder.java
@@ -19,17 +19,27 @@
 
 package org.apache.cayenne.access.sqlbuilder;
 
-import java.util.Collection;
-
-import org.apache.cayenne.access.translator.DbAttributeBinding;
-import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.access.sqlbuilder.sqltree.DeleteNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TableNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.WhereNode;
 
 /**
  * @since 4.2
  */
-public interface SQLGenerationContext {
+public class DeleteBuilder extends BaseBuilder {
+
+    private static final int TABLE_NODE = 0;
+    private static final int WHERE_NODE = 1;
 
-    DbAdapter getAdapter();
+    public DeleteBuilder(String table) {
+        super(new DeleteNode(), WHERE_NODE + 1);
+        node(TABLE_NODE, () -> new TableNode(table, null));
+    }
 
-    Collection<DbAttributeBinding> getBindings();
+    public DeleteBuilder where(NodeBuilder expression) {
+        if(expression != null) {
+            node(WHERE_NODE, WhereNode::new).addChild(expression.build());
+        }
+        return this;
+    }
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/InsertBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/InsertBuilder.java
new file mode 100644
index 0000000..2c112f5
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/InsertBuilder.java
@@ -0,0 +1,54 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.sqlbuilder;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.InsertColumnsNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.InsertNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TableNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.ValuesNode;
+
+/**
+ * @since 4.2
+ */
+public class InsertBuilder extends BaseBuilder {
+
+    /*
+    INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('X_AUTHOR', 200)
+    */
+
+    private static final int TABLE_NODE   = 0;
+    private static final int COLUMNS_NODE = 1;
+    private static final int VALUES_NODE  = 2;
+
+    public InsertBuilder(String table) {
+        super(new InsertNode(), VALUES_NODE + 1);
+        node(TABLE_NODE, () -> new TableNode(table, null));
+    }
+
+    public InsertBuilder column(ColumnNodeBuilder columnNode) {
+        node(COLUMNS_NODE, InsertColumnsNode::new).addChild(columnNode.build());
+        return this;
+    }
+
+    public InsertBuilder value(ValueNodeBuilder valueNode) {
+        node(VALUES_NODE, ValuesNode::new).addChild(valueNode.build());
+        return this;
+    }
+}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLBuilder.java
index ac84aeb..b5b92b3 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLBuilder.java
@@ -36,6 +36,18 @@ public final class SQLBuilder {
         return new SelectBuilder(params);
     }
 
+    public static InsertBuilder insert(String table) {
+        return new InsertBuilder(table);
+    }
+
+    public static UpdateBuilder update(String table) {
+        return new UpdateBuilder(table);
+    }
+
+    public static DeleteBuilder delete(String table) {
+        return new DeleteBuilder(table);
+    }
+
     public static TableNodeBuilder table(String table) {
         return new TableNodeBuilder(table);
     }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
index 02090c5..7e0b759 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
@@ -23,6 +23,8 @@ import java.util.Collection;
 
 import org.apache.cayenne.access.translator.DbAttributeBinding;
 import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.map.DbEntity;
 
 /**
  * @since 4.2
@@ -32,4 +34,8 @@ public interface SQLGenerationContext {
     DbAdapter getAdapter();
 
     Collection<DbAttributeBinding> getBindings();
+
+    QuotingStrategy getQuotingStrategy();
+
+    DbEntity getRootDbEntity();
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SelectBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SelectBuilder.java
index 702a68c..6517d0d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SelectBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SelectBuilder.java
@@ -19,8 +19,6 @@
 
 package org.apache.cayenne.access.sqlbuilder;
 
-import java.util.function.Supplier;
-
 import org.apache.cayenne.access.sqlbuilder.sqltree.DistinctNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.FromNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.GroupByNode;
@@ -36,7 +34,7 @@ import org.apache.cayenne.access.sqlbuilder.sqltree.WhereNode;
 /**
  * @since 4.2
  */
-public class SelectBuilder implements NodeBuilder {
+public class SelectBuilder extends BaseBuilder {
 
     private static final int SELECT_NODE    = 0;
     private static final int FROM_NODE      = 1;
@@ -47,19 +45,8 @@ public class SelectBuilder implements NodeBuilder {
     private static final int ORDERBY_NODE   = 6;
     private static final int LIMIT_NODE     = 7;
 
-    /**
-     * Main root of this query
-     */
-    private Node root;
-
-    /*
-     * Following nodes are all children of root,
-     * but we keep them here for quick access.
-     */
-    private Node[] nodes = new Node[LIMIT_NODE + 1];
-
     SelectBuilder(NodeBuilder... selectExpressions) {
-        root = new SelectNode();
+        super(new SelectNode(), LIMIT_NODE + 1);
         for(NodeBuilder exp : selectExpressions) {
             node(SELECT_NODE, SelectResultNode::new).addChild(exp.build());
         }
@@ -145,25 +132,4 @@ public class SelectBuilder implements NodeBuilder {
         return this;
     }
 
-    @Override
-    public Node build() {
-        for (Node next : nodes) {
-            if (next != null) {
-                root.addChild(next);
-            }
-        }
-        return root;
-    }
-
-    public Node getRoot() {
-        return root;
-    }
-
-    private Node node(int idx, Supplier<Node> nodeSupplier) {
-        if(nodes[idx] == null) {
-            nodes[idx] = nodeSupplier.get();
-        }
-        return nodes[idx];
-    }
-
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/UpdateBuilder.java
similarity index 51%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
copy to cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/UpdateBuilder.java
index 02090c5..429ac8f 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/UpdateBuilder.java
@@ -19,17 +19,35 @@
 
 package org.apache.cayenne.access.sqlbuilder;
 
-import java.util.Collection;
-
-import org.apache.cayenne.access.translator.DbAttributeBinding;
-import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.access.sqlbuilder.sqltree.SetNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.TableNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.UpdateNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.WhereNode;
 
 /**
  * @since 4.2
  */
-public interface SQLGenerationContext {
+public class UpdateBuilder extends BaseBuilder {
+
+    private static final int TABLE_NODE = 0;
+    private static final int SET_NODE   = 1;
+    private static final int WHERE_NODE = 2;
+
+    public UpdateBuilder(String table) {
+        super(new UpdateNode(), WHERE_NODE + 1);
+        node(TABLE_NODE, () -> new TableNode(table, null));
+    }
+
+    public UpdateBuilder set(NodeBuilder setExpression) {
+        node(SET_NODE, SetNode::new).addChild(setExpression.build());
+        return this;
+    }
 
-    DbAdapter getAdapter();
+    public UpdateBuilder where(NodeBuilder expression) {
+        if(expression != null) {
+            node(WHERE_NODE, WhereNode::new).addChild(expression.build());
+        }
+        return this;
+    }
 
-    Collection<DbAttributeBinding> getBindings();
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/DeleteNode.java
similarity index 76%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java
copy to cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/DeleteNode.java
index b5a6a63..98f73d9 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/DeleteNode.java
@@ -16,23 +16,21 @@
  *  specific language governing permissions and limitations
  *  under the License.
  ****************************************************************/
-
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+
 /**
  * @since 4.2
  */
-public enum NodeType {
-    UNDEFINED,
-    VALUE,
-    COLUMN,
-    LIMIT_OFFSET,
-    FUNCTION,
-    EQUALITY,
-    LIKE,
-    DISTINCT,
-    IN,
-    RESULT,
-    WHERE,
-    JOIN, FROM
+public class DeleteNode extends Node {
+    @Override
+    public Node copy() {
+        return new DeleteNode();
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer.append("DELETE FROM");
+    }
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EqualNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EqualNode.java
index 50b4ba0..fc64616 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EqualNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EqualNode.java
@@ -33,7 +33,10 @@ public class EqualNode extends ExpressionNode {
     @Override
     public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
         Node child = getChild(1);
-        if (child.getType() == NodeType.VALUE && ((ValueNode) child).getValue() == null) {
+        if (child.getType() == NodeType.VALUE
+                && ((ValueNode) child).getValue() == null
+                && getParent() != null
+                && getParent().getType() != NodeType.UPDATE_SET) {
             buffer.append(" IS");
         } else {
             buffer.append(" =");
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
index 0199ec3..cf4a219 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
@@ -41,14 +41,20 @@ public class ExpressionNode extends Node {
 
     @Override
     public void appendChildrenStart(QuotingAppendable buffer) {
-        if(parent != null && parent.type != NodeType.WHERE && parent.type != NodeType.JOIN) {
+        if(parent != null
+                && parent.type != NodeType.WHERE
+                && parent.type != NodeType.JOIN
+                && parent.type != NodeType.UPDATE_SET) {
             buffer.append(" (");
         }
     }
 
     @Override
     public void appendChildrenEnd(QuotingAppendable buffer) {
-        if(parent != null && parent.type != NodeType.WHERE && parent.type != NodeType.JOIN) {
+        if(parent != null
+                && parent.type != NodeType.WHERE
+                && parent.type != NodeType.JOIN
+                && parent.type != NodeType.UPDATE_SET) {
             buffer.append(" )");
         }
     }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertColumnsNode.java
similarity index 73%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
copy to cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertColumnsNode.java
index 0199ec3..6b6aaa9 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertColumnsNode.java
@@ -19,19 +19,20 @@
 
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
-
 import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
 
 /**
  * @since 4.2
  */
-public class ExpressionNode extends Node {
+public class InsertColumnsNode extends Node {
 
-    public ExpressionNode() {
+    public InsertColumnsNode() {
+        super(NodeType.INSERT_COLUMNS);
     }
 
-    public ExpressionNode(NodeType nodeType) {
-        super(nodeType);
+    @Override
+    public Node copy() {
+        return new InsertColumnsNode();
     }
 
     @Override
@@ -40,26 +41,17 @@ public class ExpressionNode extends Node {
     }
 
     @Override
-    public void appendChildrenStart(QuotingAppendable buffer) {
-        if(parent != null && parent.type != NodeType.WHERE && parent.type != NodeType.JOIN) {
-            buffer.append(" (");
-        }
+    public void appendChildrenSeparator(QuotingAppendable buffer, int childInd) {
+        buffer.append(',');
     }
 
     @Override
     public void appendChildrenEnd(QuotingAppendable buffer) {
-        if(parent != null && parent.type != NodeType.WHERE && parent.type != NodeType.JOIN) {
-            buffer.append(" )");
-        }
+        buffer.append(')');
     }
 
     @Override
-    public String toString() {
-        return "{ExpressionNode}";
-    }
-
-    @Override
-    public Node copy() {
-        return new ExpressionNode();
+    public void appendChildrenStart(QuotingAppendable buffer) {
+        buffer.append('(');
     }
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertNode.java
similarity index 76%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java
copy to cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertNode.java
index b5a6a63..69a8f1d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/InsertNode.java
@@ -19,20 +19,19 @@
 
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+
 /**
  * @since 4.2
  */
-public enum NodeType {
-    UNDEFINED,
-    VALUE,
-    COLUMN,
-    LIMIT_OFFSET,
-    FUNCTION,
-    EQUALITY,
-    LIKE,
-    DISTINCT,
-    IN,
-    RESULT,
-    WHERE,
-    JOIN, FROM
+public class InsertNode extends Node {
+    @Override
+    public Node copy() {
+        return new InsertNode();
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer.append("INSERT INTO");
+    }
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java
index b5a6a63..af5b222 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java
@@ -34,5 +34,8 @@ public enum NodeType {
     IN,
     RESULT,
     WHERE,
-    JOIN, FROM
+    JOIN,
+    FROM,
+    UPDATE_SET,
+    INSERT_COLUMNS
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EqualNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SetNode.java
similarity index 78%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EqualNode.java
copy to cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SetNode.java
index 50b4ba0..eb57d2d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/EqualNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/SetNode.java
@@ -24,20 +24,24 @@ import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
 /**
  * @since 4.2
  */
-public class EqualNode extends ExpressionNode {
+public class SetNode extends Node {
 
-    public EqualNode() {
-        super(NodeType.EQUALITY);
+    public SetNode() {
+        super(NodeType.UPDATE_SET);
     }
 
     @Override
-    public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
-        Node child = getChild(1);
-        if (child.getType() == NodeType.VALUE && ((ValueNode) child).getValue() == null) {
-            buffer.append(" IS");
-        } else {
-            buffer.append(" =");
-        }
+    public Node copy() {
+        return new SetNode();
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer.append(" SET");
     }
 
+    @Override
+    public void appendChildrenSeparator(QuotingAppendable buffer, int childIdx) {
+        buffer.append(',');
+    }
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TrimmingColumnNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TrimmingColumnNode.java
index d8830f0..d409064 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TrimmingColumnNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/TrimmingColumnNode.java
@@ -55,6 +55,9 @@ public class TrimmingColumnNode extends Node {
     }
 
     private boolean isComparisionWithClob() {
+        if(isInsertOrUpdateSet()) {
+            return false;
+        }
         return (getParent().getType() == NodeType.EQUALITY
                 || getParent().getType() == NodeType.LIKE)
                 && columnNode.getAttribute() != null
@@ -75,7 +78,10 @@ public class TrimmingColumnNode extends Node {
     protected boolean isAllowedForTrimming() {
         Node parent = getParent();
         while(parent != null) {
-            if(parent.getType() == NodeType.JOIN || parent.getType() == NodeType.FUNCTION) {
+            if(parent.getType() == NodeType.JOIN
+                    || parent.getType() == NodeType.FUNCTION
+                    || parent.getType() == NodeType.UPDATE_SET
+                    || parent.getType() == NodeType.INSERT_COLUMNS) {
                 return false;
             }
             parent = parent.getParent();
@@ -84,9 +90,17 @@ public class TrimmingColumnNode extends Node {
     }
 
     protected boolean isResultNode() {
+        return isParentOfType(NodeType.RESULT);
+    }
+
+    protected boolean isInsertOrUpdateSet() {
+        return isParentOfType(NodeType.UPDATE_SET) || isParentOfType(NodeType.INSERT_COLUMNS);
+    }
+
+    protected boolean isParentOfType(NodeType nodeType) {
         Node parent = getParent();
         while(parent != null) {
-            if(parent.getType() == NodeType.RESULT) {
+            if(parent.getType() == nodeType) {
                 return true;
             }
             parent = parent.getParent();
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/UpdateNode.java
similarity index 77%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java
copy to cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/UpdateNode.java
index b5a6a63..a5f0782 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/NodeType.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/UpdateNode.java
@@ -19,20 +19,20 @@
 
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
+import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+
 /**
  * @since 4.2
  */
-public enum NodeType {
-    UNDEFINED,
-    VALUE,
-    COLUMN,
-    LIMIT_OFFSET,
-    FUNCTION,
-    EQUALITY,
-    LIKE,
-    DISTINCT,
-    IN,
-    RESULT,
-    WHERE,
-    JOIN, FROM
+public class UpdateNode extends Node {
+
+    @Override
+    public Node copy() {
+        return new UpdateNode();
+    }
+
+    @Override
+    public QuotingAppendable append(QuotingAppendable buffer) {
+        return buffer.append("UPDATE");
+    }
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValuesNode.java
similarity index 71%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
copy to cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValuesNode.java
index 0199ec3..fb82fc7 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ExpressionNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValuesNode.java
@@ -19,47 +19,35 @@
 
 package org.apache.cayenne.access.sqlbuilder.sqltree;
 
-
 import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
 
 /**
  * @since 4.2
  */
-public class ExpressionNode extends Node {
-
-    public ExpressionNode() {
-    }
+public class ValuesNode extends Node {
 
-    public ExpressionNode(NodeType nodeType) {
-        super(nodeType);
+    @Override
+    public Node copy() {
+        return new ValuesNode();
     }
 
     @Override
     public QuotingAppendable append(QuotingAppendable buffer) {
-        return buffer;
+        return buffer.append(" VALUES");
     }
 
     @Override
     public void appendChildrenStart(QuotingAppendable buffer) {
-        if(parent != null && parent.type != NodeType.WHERE && parent.type != NodeType.JOIN) {
-            buffer.append(" (");
-        }
+        buffer.append('(');
     }
 
     @Override
-    public void appendChildrenEnd(QuotingAppendable buffer) {
-        if(parent != null && parent.type != NodeType.WHERE && parent.type != NodeType.JOIN) {
-            buffer.append(" )");
-        }
+    public void appendChildrenSeparator(QuotingAppendable buffer, int childInd) {
+        buffer.append(',');
     }
 
     @Override
-    public String toString() {
-        return "{ExpressionNode}";
-    }
-
-    @Override
-    public Node copy() {
-        return new ExpressionNode();
+    public void appendChildrenEnd(QuotingAppendable buffer) {
+        buffer.append(')');
     }
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java
new file mode 100644
index 0000000..93da8e8
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java
@@ -0,0 +1,89 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.batch;
+
+import java.util.List;
+
+import org.apache.cayenne.access.sqlbuilder.ExpressionNodeBuilder;
+import org.apache.cayenne.access.sqlbuilder.NodeBuilder;
+import org.apache.cayenne.access.sqlbuilder.SQLBuilder;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationVisitor;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.select.DefaultQuotingAppendable;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.query.BatchQuery;
+
+/**
+ * @since 4.2
+ * @param <T> type of the batch query to translate
+ */
+public abstract class BaseBatchTranslator<T extends BatchQuery> {
+
+    protected final BatchTranslatorContext<T> context;
+
+    protected DbAttributeBinding[] bindings;
+
+    public BaseBatchTranslator(T query, DbAdapter adapter) {
+        this.context = new BatchTranslatorContext<>(query, adapter);
+    }
+
+    public DbAttributeBinding[] getBindings() {
+        return bindings;
+    }
+
+    /**
+     * This method applies {@link org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor} to the
+     * provided SQL tree node and generates SQL string from it.
+     *
+     * @param nodeBuilder SQL tree node builder
+     * @return SQL string
+     */
+    protected String doTranslate(NodeBuilder nodeBuilder) {
+        Node node = nodeBuilder.build();
+        // convert to database flavour
+        node = context.getAdapter().getSqlTreeProcessor().apply(node);
+        // generate SQL
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new DefaultQuotingAppendable(context));
+        node.visit(visitor);
+
+        bindings = context.getBindings().toArray(new DbAttributeBinding[0]);
+        return visitor.getSQLString();
+    }
+
+    abstract protected boolean isNullAttribute(DbAttribute attribute);
+
+    protected ExpressionNodeBuilder buildQualifier(List<DbAttribute> attributeList) {
+        ExpressionNodeBuilder eq = null;
+        for (DbAttribute attr : attributeList) {
+            Integer value = isNullAttribute(attr) ? null : 1;
+            ExpressionNodeBuilder next = SQLBuilder
+                    .column(attr.getName()).attribute(attr)
+                    .eq(SQLBuilder.value(value).attribute(attr));
+            if(eq == null) {
+                eq = next;
+            } else {
+                eq = eq.and(next);
+            }
+        }
+        return eq;
+    }
+}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorContext.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorContext.java
new file mode 100644
index 0000000..17352dd
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorContext.java
@@ -0,0 +1,72 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.batch;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
+import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.query.BatchQuery;
+
+/**
+ * @since 4.2
+ * @param <T> type of the {@link BatchQuery}
+ */
+class BatchTranslatorContext<T extends BatchQuery> implements SQLGenerationContext {
+
+    private final T query;
+    private final DbAdapter adapter;
+    private final List<DbAttributeBinding> bindings;
+
+    BatchTranslatorContext(T query, DbAdapter adapter) {
+        this.query = query;
+        this.adapter = adapter;
+        this.bindings = new ArrayList<>();
+    }
+
+    @Override
+    public DbAdapter getAdapter() {
+        return adapter;
+    }
+
+    @Override
+    public Collection<DbAttributeBinding> getBindings() {
+        return bindings;
+    }
+
+    @Override
+    public QuotingStrategy getQuotingStrategy() {
+        return adapter.getQuotingStrategy();
+    }
+
+    @Override
+    public DbEntity getRootDbEntity() {
+        return query.getDbEntity();
+    }
+
+    public T getQuery() {
+        return query;
+    }
+}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslatorFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslatorFactory.java
index 639bded..b0d8f3c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslatorFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslatorFactory.java
@@ -28,7 +28,7 @@ import org.apache.cayenne.query.UpdateBatchQuery;
 /**
  * Default implementation of {@link BatchTranslatorFactory}.
  * 
- * @since 4.0
+ * @since 4.2
  */
 public class DefaultBatchTranslatorFactory implements BatchTranslatorFactory {
 
@@ -37,24 +37,24 @@ public class DefaultBatchTranslatorFactory implements BatchTranslatorFactory {
         if (query instanceof InsertBatchQuery) {
             return insertTranslator((InsertBatchQuery) query, adapter);
         } else if (query instanceof UpdateBatchQuery) {
-            return updateTranslator((UpdateBatchQuery) query, adapter, trimFunction);
+            return updateTranslator((UpdateBatchQuery) query, adapter);
         } else if (query instanceof DeleteBatchQuery) {
-            return deleteTranslator((DeleteBatchQuery) query, adapter, trimFunction);
+            return deleteTranslator((DeleteBatchQuery) query, adapter);
         } else {
             throw new CayenneRuntimeException("Unsupported batch query: %s", query);
         }
     }
 
-    protected BatchTranslator deleteTranslator(DeleteBatchQuery query, DbAdapter adapter, String trimFunction) {
-        return new DeleteBatchTranslator(query, adapter, trimFunction);
+    protected BatchTranslator deleteTranslator(DeleteBatchQuery query, DbAdapter adapter) {
+        return new DeleteBatchTranslator(query, adapter);
     }
 
     protected BatchTranslator insertTranslator(InsertBatchQuery query, DbAdapter adapter) {
         return new InsertBatchTranslator(query, adapter);
     }
 
-    protected BatchTranslator updateTranslator(UpdateBatchQuery query, DbAdapter adapter, String trimFunction) {
-        return new UpdateBatchTranslator(query, adapter, trimFunction);
+    protected BatchTranslator updateTranslator(UpdateBatchQuery query, DbAdapter adapter) {
+        return new UpdateBatchTranslator(query, adapter);
     }
 
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java
index e5915a3..87441ac 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java
@@ -19,98 +19,52 @@
 
 package org.apache.cayenne.access.translator.batch;
 
-import java.util.Iterator;
-import java.util.List;
-
+import org.apache.cayenne.access.sqlbuilder.DeleteBuilder;
+import org.apache.cayenne.access.sqlbuilder.SQLBuilder;
 import org.apache.cayenne.access.translator.DbAttributeBinding;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.query.BatchQueryRow;
 import org.apache.cayenne.query.DeleteBatchQuery;
 
 /**
- * Translator for delete BatchQueries. Creates parameterized DELETE SQL
- * statements.
+ * @since 4.2
  */
-public class DeleteBatchTranslator extends DefaultBatchTranslator {
+public class DeleteBatchTranslator extends BaseBatchTranslator<DeleteBatchQuery> implements BatchTranslator {
 
-    public DeleteBatchTranslator(DeleteBatchQuery query, DbAdapter adapter, String trimFunction) {
-        super(query, adapter, trimFunction);
+    public DeleteBatchTranslator(DeleteBatchQuery query, DbAdapter adapter) {
+        super(query, adapter);
     }
 
     @Override
-    protected String createSql() {
-
-        QuotingStrategy strategy = adapter.getQuotingStrategy();
-
-        StringBuilder buffer = new StringBuilder("DELETE FROM ");
-        buffer.append(strategy.quotedFullyQualifiedName(query.getDbEntity()));
-
-        applyQualifier(buffer);
-
-        return buffer.toString();
+    public String getSql() {
+        DeleteBuilder deleteBuilder = SQLBuilder
+                .delete(context.getRootDbEntity().getFullyQualifiedName())
+                .where(buildQualifier(context.getQuery().getDbAttributes()));
+        return doTranslate(deleteBuilder);
     }
 
-    /**
-     * Appends WHERE clause to SQL string
-     */
-    protected void applyQualifier(StringBuilder buffer) {
-        buffer.append(" WHERE ");
-
-        DeleteBatchQuery deleteBatch = (DeleteBatchQuery) query;
-        Iterator<DbAttribute> i = deleteBatch.getDbAttributes().iterator();
-        while (i.hasNext()) {
-            DbAttribute attribute = i.next();
-            appendDbAttribute(buffer, attribute);
-            buffer.append(deleteBatch.isNull(attribute) ? " IS NULL" : " = ?");
-
-            if (i.hasNext()) {
-                buffer.append(" AND ");
-            }
-        }
+    @Override
+    protected boolean isNullAttribute(DbAttribute attribute) {
+        return context.getQuery().isNull(attribute);
     }
 
     @Override
-    protected DbAttributeBinding[] createBindings() {
-        DeleteBatchQuery deleteBatch = (DeleteBatchQuery) query;
-        List<DbAttribute> attributes = deleteBatch.getDbAttributes();
-        int len = attributes.size();
-
-        DbAttributeBinding[] bindings = new DbAttributeBinding[len];
-
-        for (int i = 0; i < len; i++) {
-            bindings[i] = new DbAttributeBinding(attributes.get(i));
+    public DbAttributeBinding[] updateBindings(BatchQueryRow row) {
+        DeleteBatchQuery deleteBatch = context.getQuery();
+        for(int i=0, position=0; i<deleteBatch.getDbAttributes().size(); i++) {
+            position = updateBinding(row.getValue(i), position);
         }
-
         return bindings;
     }
 
-    @Override
-    protected DbAttributeBinding[] doUpdateBindings(BatchQueryRow row) {
-
-        int len = bindings.length;
-
-        DeleteBatchQuery deleteBatch = (DeleteBatchQuery) query;
-
-        for (int i = 0, j = 1; i < len; i++) {
-
-            DbAttributeBinding b = bindings[i];
-
-            // skip null attributes... they are translated as "IS NULL"
-            if (deleteBatch.isNull(b.getAttribute())) {
-                b.exclude();
-            } else {
-                Object value = row.getValue(i);
-                ExtendedType extendedType = value != null
-                        ? adapter.getExtendedTypes().getRegisteredType(value.getClass())
-                        : adapter.getExtendedTypes().getDefaultType();
-
-                b.include(j++, value, extendedType);
-            }
+    protected int updateBinding(Object value, int position) {
+        // skip null attributes... they are translated as "IS NULL"
+        if(value != null) {
+            ExtendedType<?> extendedType = context.getAdapter().getExtendedTypes().getRegisteredType(value.getClass());
+            bindings[position].include(++position, value, extendedType);
         }
-
-        return bindings;
+        return position;
     }
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java
index 8c70532..aff48d7 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java
@@ -19,129 +19,73 @@
 
 package org.apache.cayenne.access.translator.batch;
 
-import java.util.List;
-
+import org.apache.cayenne.access.sqlbuilder.InsertBuilder;
+import org.apache.cayenne.access.sqlbuilder.SQLBuilder;
 import org.apache.cayenne.access.translator.DbAttributeBinding;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.query.BatchQueryRow;
 import org.apache.cayenne.query.InsertBatchQuery;
 
 /**
- * Translator of InsertBatchQueries.
+ * @since 4.2
  */
-public class InsertBatchTranslator extends DefaultBatchTranslator {
+public class InsertBatchTranslator extends BaseBatchTranslator<InsertBatchQuery> implements BatchTranslator {
 
     public InsertBatchTranslator(InsertBatchQuery query, DbAdapter adapter) {
-        // no trimming is needed here, so passing hardcoded NULL for trim
-        // function
-        super(query, adapter, null);
+        super(query, adapter);
     }
 
     @Override
-    protected String createSql() {
-
-        List<DbAttribute> dbAttributes = query.getDbAttributes();
-        QuotingStrategy strategy = adapter.getQuotingStrategy();
-
-        StringBuilder buffer = new StringBuilder("INSERT INTO ");
-        buffer.append(strategy.quotedFullyQualifiedName(query.getDbEntity()));
-        buffer.append(" (");
-
-        int columnCount = 0;
-        for (DbAttribute attribute : dbAttributes) {
-
-            // attribute inclusion rule - one of the rules below must be true:
-            // (1) attribute not generated
-            // (2) attribute is generated and PK and adapter does not support
-            // generated
-            // keys
-
-            if (includeInBatch(attribute)) {
-
-                if (columnCount > 0) {
-                    buffer.append(", ");
-                }
-                buffer.append(strategy.quotedName(attribute));
-                columnCount++;
+    public String getSql() {
+        InsertBatchQuery query = context.getQuery();
+        InsertBuilder insertBuilder = SQLBuilder.insert(context.getRootDbEntity().getFullyQualifiedName());
+
+        for(DbAttribute attribute : query.getDbAttributes()) {
+            // skip generated attributes, if needed
+            if(excludeInBatch(attribute)) {
+                continue;
             }
+            insertBuilder
+                    .column(SQLBuilder.column(attribute.getName()).attribute(attribute))
+                    // We can use here any non-null value, to create attribute binding,
+                    // actual value and ExtendedType will be set at updateBindings() call.
+                    .value(SQLBuilder.value(1).attribute(attribute));
         }
 
-        buffer.append(") VALUES (");
-
-        for (int i = 0; i < columnCount; i++) {
-            if (i > 0) {
-                buffer.append(", ");
-            }
-
-            buffer.append('?');
-        }
-        buffer.append(')');
-        return buffer.toString();
+        return doTranslate(insertBuilder);
     }
 
     @Override
-    protected DbAttributeBinding[] createBindings() {
-        List<DbAttribute> attributes = query.getDbAttributes();
-        int len = attributes.size();
-
-        DbAttributeBinding[] bindings = new DbAttributeBinding[len];
-
-        for (int i = 0; i < len; i++) {
-            DbAttribute a = attributes.get(i);
-
-            bindings[i] = new DbAttributeBinding(a);
-
-            // include/exclude state depends on DbAttribute only and can be
-            // precompiled here
-            if (includeInBatch(a)) {
-                // setting fake position here... all we care about is that it is
-                // > -1
-                bindings[i].include(1, null, null);
-            } else {
-                bindings[i].exclude();
+    public DbAttributeBinding[] updateBindings(BatchQueryRow row) {
+        InsertBatchQuery query = context.getQuery();
+        int i=0;
+        int j=0;
+        for(DbAttribute attribute : query.getDbAttributes()) {
+            if(excludeInBatch(attribute)) {
+                i++;
+                continue;
             }
-        }
-
-        return bindings;
-    }
-
-    @Override
-    protected DbAttributeBinding[] doUpdateBindings(BatchQueryRow row) {
-        int len = bindings.length;
 
-        for (int i = 0, j = 1; i < len; i++) {
-
-            DbAttributeBinding b = bindings[i];
-
-            // exclusions are permanent
-            if (!b.isExcluded()) {
-                Object value = row.getValue(i);
-                ExtendedType extendedType = value != null
-                        ? adapter.getExtendedTypes().getRegisteredType(value.getClass())
-                        : adapter.getExtendedTypes().getDefaultType();
-
-                b.include(j++, value, extendedType);
-            }
+            Object value = row.getValue(i++);
+            ExtendedType<?> extendedType = value != null
+                    ? context.getAdapter().getExtendedTypes().getRegisteredType(value.getClass())
+                    : context.getAdapter().getExtendedTypes().getDefaultType();
+            bindings[j].include(++j, value, extendedType);
         }
-
         return bindings;
     }
 
-    /**
-     * Returns true if an attribute should be included in the batch.
-     * 
-     * @since 1.2
-     */
-    protected boolean includeInBatch(DbAttribute attribute) {
+    protected boolean excludeInBatch(DbAttribute attribute) {
         // attribute inclusion rule - one of the rules below must be true:
-        // (1) attribute not generated
-        // (2) attribute is generated and PK and adapter does not support
-        // generated
-        // keys
+        //  (1) attribute not generated
+        //  (2) attribute is generated and PK and adapter does not support generated keys
+        return attribute.isGenerated() && (!attribute.isPrimaryKey() || context.getAdapter().supportsGeneratedKeys());
+    }
 
-        return !attribute.isGenerated() || (attribute.isPrimaryKey() && !adapter.supportsGeneratedKeys());
+    @Override
+    protected boolean isNullAttribute(DbAttribute attribute) {
+        return false;
     }
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java
index 12dfe8d..5c1bc2a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java
@@ -1,105 +1,75 @@
-/*****************************************************************
- *   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
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.access.translator.batch;
-
-import org.apache.cayenne.access.translator.DbAttributeBinding;
-import org.apache.cayenne.access.types.ExtendedType;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.dba.TypesMapping;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.query.BatchQueryRow;
-import org.apache.cayenne.query.DeleteBatchQuery;
-
-/**
- * Implementation of {@link DeleteBatchTranslator}, which uses 'soft' delete
- * (runs UPDATE and sets 'deleted' field to true instead-of running SQL DELETE)
- */
-public class SoftDeleteBatchTranslator extends DeleteBatchTranslator {
-
-    private String deletedFieldName;
-
-    public SoftDeleteBatchTranslator(DeleteBatchQuery query, DbAdapter adapter, String trimFunction,
-            String deletedFieldName) {
-        super(query, adapter, trimFunction);
-        this.deletedFieldName = deletedFieldName;
-    }
-
-    @Override
-    protected String createSql() {
-
-        QuotingStrategy strategy = adapter.getQuotingStrategy();
-
-        StringBuilder buffer = new StringBuilder("UPDATE ");
-        buffer.append(strategy.quotedFullyQualifiedName(query.getDbEntity()));
-        buffer.append(" SET ").append(strategy.quotedIdentifier(query.getDbEntity(), deletedFieldName)).append(" = ?");
-
-        applyQualifier(buffer);
-
-        return buffer.toString();
-    }
-
-    @Override
-    protected DbAttributeBinding[] createBindings() {
-
-        DbAttributeBinding[] superBindings = super.createBindings();
-
-        int slen = superBindings.length;
-
-        DbAttributeBinding[] bindings = new DbAttributeBinding[slen + 1];
-
-        DbAttribute deleteAttribute = query.getDbEntity().getAttribute(deletedFieldName);
-        String typeName = TypesMapping.getJavaBySqlType(deleteAttribute.getType());
-        ExtendedType extendedType = adapter.getExtendedTypes().getRegisteredType(typeName);
-
-        bindings[0] = new DbAttributeBinding(deleteAttribute);
-        bindings[0].include(1, true, extendedType);
-        
-        System.arraycopy(superBindings, 0, bindings, 1, slen);
-
-        return bindings;
-    }
-
-    @Override
-    protected DbAttributeBinding[] doUpdateBindings(BatchQueryRow row) {
-        int len = bindings.length;
-
-        DeleteBatchQuery deleteBatch = (DeleteBatchQuery) query;
-
-        // skip position 0... Otherwise follow super algorithm
-        for (int i = 1, j = 2; i < len; i++) {
-
-            DbAttributeBinding b = bindings[i];
-
-            // skip null attributes... they are translated as "IS NULL"
-            if (deleteBatch.isNull(b.getAttribute())) {
-                b.exclude();
-            } else {
-                Object value = row.getValue(i - 1);
-                ExtendedType extendedType = value != null
-                        ? adapter.getExtendedTypes().getRegisteredType(value.getClass())
-                        : adapter.getExtendedTypes().getDefaultType();
-
-                b.include(j++, value, extendedType);
-            }
-        }
-
-        return bindings;
-    }
-}
+/*****************************************************************
+ *   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
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.translator.batch;
+
+import org.apache.cayenne.access.sqlbuilder.SQLBuilder;
+import org.apache.cayenne.access.sqlbuilder.UpdateBuilder;
+import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.types.ExtendedType;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.query.BatchQueryRow;
+import org.apache.cayenne.query.DeleteBatchQuery;
+
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.*;
+
+/**
+ * @since 4.2
+ */
+public class SoftDeleteBatchTranslator extends DeleteBatchTranslator {
+
+    private final String deletedFieldName;
+
+    public SoftDeleteBatchTranslator(DeleteBatchQuery query, DbAdapter adapter, String deletedFieldName) {
+        super(query, adapter);
+        this.deletedFieldName = deletedFieldName;
+    }
+
+    @Override
+    public String getSql() {
+        DeleteBatchQuery query = context.getQuery();
+        DbAttribute deleteAttribute = query.getDbEntity().getAttribute(deletedFieldName);
+
+        UpdateBuilder updateBuilder = update(context.getRootDbEntity().getFullyQualifiedName())
+                .set(column(deletedFieldName).attribute(deleteAttribute)
+                        .eq(SQLBuilder.value(true).attribute(deleteAttribute)))
+                .where(buildQualifier(query.getDbAttributes()));
+
+        String sql = doTranslate(updateBuilder);
+
+        String typeName = TypesMapping.getJavaBySqlType(deleteAttribute.getType());
+        ExtendedType<?> extendedType = context.getAdapter().getExtendedTypes().getRegisteredType(typeName);
+        bindings[0].include(1, true, extendedType);
+
+        return sql;
+    }
+
+    @Override
+    public DbAttributeBinding[] updateBindings(BatchQueryRow row) {
+        DeleteBatchQuery deleteBatch = context.getQuery();
+
+        for(int i=0, position=1; i<deleteBatch.getDbAttributes().size(); i++) {
+            position = updateBinding(row.getValue(i), position);
+        }
+
+        return bindings;
+    }
+}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteTranslatorFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteTranslatorFactory.java
index 10054ba..c0eec5a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteTranslatorFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteTranslatorFactory.java
@@ -1,69 +1,69 @@
-/*****************************************************************
- *   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
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.access.translator.batch;
-
-import java.sql.Types;
-
-import org.apache.cayenne.access.translator.batch.BatchTranslator;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.query.DeleteBatchQuery;
-
-/**
- * Implementation of {link #BatchTranslator}, which uses 'soft' delete
- * (runs UPDATE and sets 'deleted' field to true instead-of running SQL DELETE)
- * 
- * @since 4.0
- */
-public class SoftDeleteTranslatorFactory extends DefaultBatchTranslatorFactory {
-    /**
-     * Default name of 'deleted' field
-     */
-    public static final String DEFAULT_DELETED_FIELD_NAME = "DELETED";
-
-    /**
-     * Name of 'deleted' field
-     */
-    private String deletedFieldName;
-
-    public SoftDeleteTranslatorFactory() {
-        this(DEFAULT_DELETED_FIELD_NAME);
-    }
-
-    public SoftDeleteTranslatorFactory(String deletedFieldName) {
-        this.deletedFieldName = deletedFieldName;
-    }
-
-    @Override
-    protected BatchTranslator deleteTranslator(DeleteBatchQuery query, DbAdapter adapter, String trimFunction) {
-
-        DbAttribute attr = query.getDbEntity().getAttribute(deletedFieldName);
-        boolean needsSoftDelete = attr != null && attr.getType() == Types.BOOLEAN;
-
-        return needsSoftDelete ? new SoftDeleteBatchTranslator(query, adapter, trimFunction, deletedFieldName) : super
-                .deleteTranslator(query, adapter, trimFunction);
-    }
-
-    /**
-     * @return name of 'deleted' field
-     */
-    public String getDeletedFieldName() {
-        return deletedFieldName;
-    }
-}
+/*****************************************************************
+ *   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
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.access.translator.batch;
+
+import java.sql.Types;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.query.DeleteBatchQuery;
+
+/**
+ * Implementation of {link #BatchTranslator}, which uses 'soft' delete
+ * (runs UPDATE and sets 'deleted' field to true instead-of running SQL DELETE)
+ * 
+ * @since 4.2
+ */
+public class SoftDeleteTranslatorFactory extends DefaultBatchTranslatorFactory {
+    /**
+     * Default name of 'deleted' field
+     */
+    public static final String DEFAULT_DELETED_FIELD_NAME = "DELETED";
+
+    /**
+     * Name of 'deleted' field
+     */
+    private String deletedFieldName;
+
+    public SoftDeleteTranslatorFactory() {
+        this(DEFAULT_DELETED_FIELD_NAME);
+    }
+
+    public SoftDeleteTranslatorFactory(String deletedFieldName) {
+        this.deletedFieldName = deletedFieldName;
+    }
+
+    @Override
+    protected BatchTranslator deleteTranslator(DeleteBatchQuery query, DbAdapter adapter) {
+
+        DbAttribute attr = query.getDbEntity().getAttribute(deletedFieldName);
+        boolean needsSoftDelete = attr != null && attr.getType() == Types.BOOLEAN;
+
+        return needsSoftDelete
+                ? new SoftDeleteBatchTranslator(query, adapter, deletedFieldName)
+                : super.deleteTranslator(query, adapter);
+    }
+
+    /**
+     * @return name of 'deleted' field
+     */
+    public String getDeletedFieldName() {
+        return deletedFieldName;
+    }
+}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java
index 8b1c558..0f4e385 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java
@@ -19,128 +19,69 @@
 
 package org.apache.cayenne.access.translator.batch;
 
-import java.util.Iterator;
-import java.util.List;
-
+import org.apache.cayenne.access.sqlbuilder.SQLBuilder;
+import org.apache.cayenne.access.sqlbuilder.UpdateBuilder;
 import org.apache.cayenne.access.translator.DbAttributeBinding;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.query.BatchQueryRow;
 import org.apache.cayenne.query.UpdateBatchQuery;
 
 /**
- * A translator for UpdateBatchQueries that produces parameterized SQL.
+ * @since 4.2
  */
-public class UpdateBatchTranslator extends DefaultBatchTranslator {
+public class UpdateBatchTranslator extends BaseBatchTranslator<UpdateBatchQuery> implements BatchTranslator {
 
-    public UpdateBatchTranslator(UpdateBatchQuery query, DbAdapter adapter, String trimFunction) {
-        super(query, adapter, trimFunction);
+    public UpdateBatchTranslator(UpdateBatchQuery query, DbAdapter adapter) {
+        super(query, adapter);
     }
 
     @Override
-    protected String createSql() {
-        UpdateBatchQuery updateBatch = (UpdateBatchQuery) query;
-
-        QuotingStrategy strategy = adapter.getQuotingStrategy();
-
-        List<DbAttribute> qualifierAttributes = updateBatch.getQualifierAttributes();
-        List<DbAttribute> updatedDbAttributes = updateBatch.getUpdatedAttributes();
-
-        StringBuilder buffer = new StringBuilder("UPDATE ");
-        buffer.append(strategy.quotedFullyQualifiedName(query.getDbEntity()));
-        buffer.append(" SET ");
-
-        int len = updatedDbAttributes.size();
-        for (int i = 0; i < len; i++) {
-            if (i > 0) {
-                buffer.append(", ");
-            }
-
-            DbAttribute attribute = updatedDbAttributes.get(i);
-            buffer.append(strategy.quotedName(attribute));
-            buffer.append(" = ?");
-        }
-
-        buffer.append(" WHERE ");
-
-        Iterator<DbAttribute> i = qualifierAttributes.iterator();
-        while (i.hasNext()) {
-            DbAttribute attribute = i.next();
-            appendDbAttribute(buffer, attribute);
-            buffer.append(updateBatch.isNull(attribute) ? " IS NULL" : " = ?");
-
-            if (i.hasNext()) {
-                buffer.append(" AND ");
-            }
+    public String getSql() {
+        UpdateBatchQuery query = context.getQuery();
+
+        UpdateBuilder updateBuilder = SQLBuilder.update(context.getRootDbEntity().getFullyQualifiedName());
+        for (DbAttribute attr : query.getUpdatedAttributes()) {
+            updateBuilder.set(SQLBuilder
+                    .column(attr.getName()).attribute(attr)
+                    .eq(SQLBuilder.value(1).attribute(attr))
+            );
         }
+        updateBuilder.where(buildQualifier(query.getQualifierAttributes()));
 
-        return buffer.toString();
+        return doTranslate(updateBuilder);
     }
 
     @Override
-    protected DbAttributeBinding[] createBindings() {
-        UpdateBatchQuery updateBatch = (UpdateBatchQuery) query;
-
-        List<DbAttribute> updatedDbAttributes = updateBatch.getUpdatedAttributes();
-        List<DbAttribute> qualifierAttributes = updateBatch.getQualifierAttributes();
-
-        int ul = updatedDbAttributes.size();
-        int ql = qualifierAttributes.size();
-
-        DbAttributeBinding[] bindings = new DbAttributeBinding[ul + ql];
-
-        for (int i = 0; i < ul; i++) {
-            bindings[i] = new DbAttributeBinding(updatedDbAttributes.get(i));
-        }
-
-        for (int i = 0; i < ql; i++) {
-            bindings[ul + i] = new DbAttributeBinding(qualifierAttributes.get(i));
-        }
-
-        return bindings;
+    protected boolean isNullAttribute(DbAttribute attribute) {
+        return context.getQuery().isNull(attribute);
     }
 
     @Override
-    protected DbAttributeBinding[] doUpdateBindings(BatchQueryRow row) {
-
-        UpdateBatchQuery updateBatch = (UpdateBatchQuery) query;
-
-        List<DbAttribute> updatedDbAttributes = updateBatch.getUpdatedAttributes();
-        List<DbAttribute> qualifierAttributes = updateBatch.getQualifierAttributes();
-
-        int ul = updatedDbAttributes.size();
-        int ql = qualifierAttributes.size();
+    public DbAttributeBinding[] updateBindings(BatchQueryRow row) {
+        UpdateBatchQuery updateBatch = context.getQuery();
 
-        int j = 1;
-
-        for (int i = 0; i < ul; i++) {
+        int i = 0;
+        int j = 0;
+        for(; i < updateBatch.getUpdatedAttributes().size(); i++) {
             Object value = row.getValue(i);
-            ExtendedType extendedType = value != null
-                    ? adapter.getExtendedTypes().getRegisteredType(value.getClass())
-                    : adapter.getExtendedTypes().getDefaultType();
-
-            bindings[i].include(j++, value, extendedType);
+            ExtendedType<?> extendedType = value == null
+                ? context.getAdapter().getExtendedTypes().getDefaultType()
+                : context.getAdapter().getExtendedTypes().getRegisteredType(value.getClass());
+            bindings[j].include(++j, value, extendedType);
         }
 
-        for (int i = 0; i < ql; i++) {
-
-            DbAttribute a = qualifierAttributes.get(i);
-
-            // skip null attributes... they are translated as "IS NULL"
-            if (updateBatch.isNull(a)) {
+        for(DbAttribute attribute : updateBatch.getQualifierAttributes()) {
+            if(updateBatch.isNull(attribute)) {
+                i++;
                 continue;
             }
-
-            Object value = row.getValue(ul + i);
-            ExtendedType extendedType = value != null
-                    ? adapter.getExtendedTypes().getRegisteredType(value.getClass())
-                    : adapter.getExtendedTypes().getDefaultType();
-
-            bindings[ul + i].include(j++, value, extendedType);
+            Object value = row.getValue(i);
+            ExtendedType<?> extendedType = context.getAdapter().getExtendedTypes().getRegisteredType(value.getClass());
+            bindings[j].include(++j, value, extendedType);
+            i++;
         }
-
         return bindings;
     }
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslator.java
similarity index 95%
rename from cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslator.java
rename to cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslator.java
index 79e749c..381db10 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslator.java
@@ -16,11 +16,12 @@
  *  specific language governing permissions and limitations
  *  under the License.
  ****************************************************************/
-package org.apache.cayenne.access.translator.batch;
+package org.apache.cayenne.access.translator.batch.legacy;
 
 import java.sql.Types;
 
 import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.batch.BatchTranslator;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.QuotingStrategy;
 import org.apache.cayenne.map.DbAttribute;
@@ -31,7 +32,9 @@ import org.apache.cayenne.query.BatchQueryRow;
  * Superclass of batch query translators.
  * 
  * @since 4.0
+ * @deprecated since 4.2
  */
+@Deprecated
 public abstract class DefaultBatchTranslator implements BatchTranslator {
 
     protected BatchQuery query;
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslatorFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslatorFactory.java
similarity index 91%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslatorFactory.java
copy to cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslatorFactory.java
index 639bded..d246fe3 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslatorFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslatorFactory.java
@@ -16,9 +16,11 @@
  *  specific language governing permissions and limitations
  *  under the License.
  ****************************************************************/
-package org.apache.cayenne.access.translator.batch;
+package org.apache.cayenne.access.translator.batch.legacy;
 
 import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.access.translator.batch.BatchTranslator;
+import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.query.BatchQuery;
 import org.apache.cayenne.query.DeleteBatchQuery;
@@ -29,7 +31,9 @@ import org.apache.cayenne.query.UpdateBatchQuery;
  * Default implementation of {@link BatchTranslatorFactory}.
  * 
  * @since 4.0
+ * @deprecated since 4.2
  */
+@Deprecated
 public class DefaultBatchTranslatorFactory implements BatchTranslatorFactory {
 
     @Override
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DeleteBatchTranslator.java
similarity index 97%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java
copy to cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DeleteBatchTranslator.java
index e5915a3..afe7196 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DeleteBatchTranslator.java
@@ -17,7 +17,7 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.access.translator.batch;
+package org.apache.cayenne.access.translator.batch.legacy;
 
 import java.util.Iterator;
 import java.util.List;
@@ -33,7 +33,9 @@ import org.apache.cayenne.query.DeleteBatchQuery;
 /**
  * Translator for delete BatchQueries. Creates parameterized DELETE SQL
  * statements.
+ * @deprecated since 4.2
  */
+@Deprecated
 public class DeleteBatchTranslator extends DefaultBatchTranslator {
 
     public DeleteBatchTranslator(DeleteBatchQuery query, DbAdapter adapter, String trimFunction) {
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/InsertBatchTranslator.java
similarity index 98%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java
copy to cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/InsertBatchTranslator.java
index 8c70532..3bc5927 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/InsertBatchTranslator.java
@@ -17,7 +17,7 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.access.translator.batch;
+package org.apache.cayenne.access.translator.batch.legacy;
 
 import java.util.List;
 
@@ -31,7 +31,9 @@ import org.apache.cayenne.query.InsertBatchQuery;
 
 /**
  * Translator of InsertBatchQueries.
+ * @deprecated since 4.2
  */
+@Deprecated
 public class InsertBatchTranslator extends DefaultBatchTranslator {
 
     public InsertBatchTranslator(InsertBatchQuery query, DbAdapter adapter) {
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/SoftDeleteBatchTranslator.java
similarity index 92%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java
copy to cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/SoftDeleteBatchTranslator.java
index 12dfe8d..b68017f 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/SoftDeleteBatchTranslator.java
@@ -1,105 +1,110 @@
-/*****************************************************************
- *   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
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.access.translator.batch;
-
-import org.apache.cayenne.access.translator.DbAttributeBinding;
-import org.apache.cayenne.access.types.ExtendedType;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.dba.TypesMapping;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.query.BatchQueryRow;
-import org.apache.cayenne.query.DeleteBatchQuery;
-
-/**
- * Implementation of {@link DeleteBatchTranslator}, which uses 'soft' delete
- * (runs UPDATE and sets 'deleted' field to true instead-of running SQL DELETE)
- */
-public class SoftDeleteBatchTranslator extends DeleteBatchTranslator {
-
-    private String deletedFieldName;
-
-    public SoftDeleteBatchTranslator(DeleteBatchQuery query, DbAdapter adapter, String trimFunction,
-            String deletedFieldName) {
-        super(query, adapter, trimFunction);
-        this.deletedFieldName = deletedFieldName;
-    }
-
-    @Override
-    protected String createSql() {
-
-        QuotingStrategy strategy = adapter.getQuotingStrategy();
-
-        StringBuilder buffer = new StringBuilder("UPDATE ");
-        buffer.append(strategy.quotedFullyQualifiedName(query.getDbEntity()));
-        buffer.append(" SET ").append(strategy.quotedIdentifier(query.getDbEntity(), deletedFieldName)).append(" = ?");
-
-        applyQualifier(buffer);
-
-        return buffer.toString();
-    }
-
-    @Override
-    protected DbAttributeBinding[] createBindings() {
-
-        DbAttributeBinding[] superBindings = super.createBindings();
-
-        int slen = superBindings.length;
-
-        DbAttributeBinding[] bindings = new DbAttributeBinding[slen + 1];
-
-        DbAttribute deleteAttribute = query.getDbEntity().getAttribute(deletedFieldName);
-        String typeName = TypesMapping.getJavaBySqlType(deleteAttribute.getType());
-        ExtendedType extendedType = adapter.getExtendedTypes().getRegisteredType(typeName);
-
-        bindings[0] = new DbAttributeBinding(deleteAttribute);
-        bindings[0].include(1, true, extendedType);
-        
-        System.arraycopy(superBindings, 0, bindings, 1, slen);
-
-        return bindings;
-    }
-
-    @Override
-    protected DbAttributeBinding[] doUpdateBindings(BatchQueryRow row) {
-        int len = bindings.length;
-
-        DeleteBatchQuery deleteBatch = (DeleteBatchQuery) query;
-
-        // skip position 0... Otherwise follow super algorithm
-        for (int i = 1, j = 2; i < len; i++) {
-
-            DbAttributeBinding b = bindings[i];
-
-            // skip null attributes... they are translated as "IS NULL"
-            if (deleteBatch.isNull(b.getAttribute())) {
-                b.exclude();
-            } else {
-                Object value = row.getValue(i - 1);
-                ExtendedType extendedType = value != null
-                        ? adapter.getExtendedTypes().getRegisteredType(value.getClass())
-                        : adapter.getExtendedTypes().getDefaultType();
-
-                b.include(j++, value, extendedType);
-            }
-        }
-
-        return bindings;
-    }
-}
+/*****************************************************************
+ *   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
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.access.translator.batch.legacy;
+
+import java.util.Objects;
+
+import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.types.ExtendedType;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.query.BatchQueryRow;
+import org.apache.cayenne.query.DeleteBatchQuery;
+
+/**
+ * Implementation of {@link DeleteBatchTranslator}, which uses 'soft' delete
+ * (runs UPDATE and sets 'deleted' field to true instead-of running SQL DELETE)
+ *
+ * @deprecated since 4.2
+ */
+@Deprecated
+public class SoftDeleteBatchTranslator extends DeleteBatchTranslator {
+
+    private String deletedFieldName;
+
+    public SoftDeleteBatchTranslator(DeleteBatchQuery query, DbAdapter adapter, String trimFunction,
+            String deletedFieldName) {
+        super(query, adapter, trimFunction);
+        this.deletedFieldName = Objects.requireNonNull(deletedFieldName);
+    }
+
+    @Override
+    protected String createSql() {
+
+        QuotingStrategy strategy = adapter.getQuotingStrategy();
+
+        StringBuilder buffer = new StringBuilder("UPDATE ");
+        buffer.append(strategy.quotedFullyQualifiedName(query.getDbEntity()));
+        buffer.append(" SET ").append(strategy.quotedIdentifier(query.getDbEntity(), deletedFieldName)).append(" = ?");
+
+        applyQualifier(buffer);
+
+        return buffer.toString();
+    }
+
+    @Override
+    protected DbAttributeBinding[] createBindings() {
+
+        DbAttributeBinding[] superBindings = super.createBindings();
+
+        int slen = superBindings.length;
+
+        DbAttributeBinding[] bindings = new DbAttributeBinding[slen + 1];
+
+        DbAttribute deleteAttribute = Objects.requireNonNull(query.getDbEntity().getAttribute(deletedFieldName));
+        String typeName = TypesMapping.getJavaBySqlType(deleteAttribute.getType());
+        ExtendedType extendedType = adapter.getExtendedTypes().getRegisteredType(typeName);
+
+        bindings[0] = new DbAttributeBinding(deleteAttribute);
+        bindings[0].include(1, true, extendedType);
+        
+        System.arraycopy(superBindings, 0, bindings, 1, slen);
+
+        return bindings;
+    }
+
+    @Override
+    protected DbAttributeBinding[] doUpdateBindings(BatchQueryRow row) {
+        int len = bindings.length;
+
+        DeleteBatchQuery deleteBatch = (DeleteBatchQuery) query;
+
+        // skip position 0... Otherwise follow super algorithm
+        for (int i = 1, j = 2; i < len; i++) {
+
+            DbAttributeBinding b = bindings[i];
+
+            // skip null attributes... they are translated as "IS NULL"
+            if (deleteBatch.isNull(b.getAttribute())) {
+                b.exclude();
+            } else {
+                Object value = row.getValue(i - 1);
+                ExtendedType extendedType = value != null
+                        ? adapter.getExtendedTypes().getRegisteredType(value.getClass())
+                        : adapter.getExtendedTypes().getDefaultType();
+
+                b.include(j++, value, extendedType);
+            }
+        }
+
+        return bindings;
+    }
+}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteTranslatorFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/SoftDeleteTranslatorFactory.java
similarity index 85%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteTranslatorFactory.java
copy to cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/SoftDeleteTranslatorFactory.java
index 10054ba..1fa838e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteTranslatorFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/SoftDeleteTranslatorFactory.java
@@ -1,69 +1,73 @@
-/*****************************************************************
- *   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
- *
- *    https://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.access.translator.batch;
-
-import java.sql.Types;
-
-import org.apache.cayenne.access.translator.batch.BatchTranslator;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.query.DeleteBatchQuery;
-
-/**
- * Implementation of {link #BatchTranslator}, which uses 'soft' delete
- * (runs UPDATE and sets 'deleted' field to true instead-of running SQL DELETE)
- * 
- * @since 4.0
- */
-public class SoftDeleteTranslatorFactory extends DefaultBatchTranslatorFactory {
-    /**
-     * Default name of 'deleted' field
-     */
-    public static final String DEFAULT_DELETED_FIELD_NAME = "DELETED";
-
-    /**
-     * Name of 'deleted' field
-     */
-    private String deletedFieldName;
-
-    public SoftDeleteTranslatorFactory() {
-        this(DEFAULT_DELETED_FIELD_NAME);
-    }
-
-    public SoftDeleteTranslatorFactory(String deletedFieldName) {
-        this.deletedFieldName = deletedFieldName;
-    }
-
-    @Override
-    protected BatchTranslator deleteTranslator(DeleteBatchQuery query, DbAdapter adapter, String trimFunction) {
-
-        DbAttribute attr = query.getDbEntity().getAttribute(deletedFieldName);
-        boolean needsSoftDelete = attr != null && attr.getType() == Types.BOOLEAN;
-
-        return needsSoftDelete ? new SoftDeleteBatchTranslator(query, adapter, trimFunction, deletedFieldName) : super
-                .deleteTranslator(query, adapter, trimFunction);
-    }
-
-    /**
-     * @return name of 'deleted' field
-     */
-    public String getDeletedFieldName() {
-        return deletedFieldName;
-    }
-}
+/*****************************************************************
+ *   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
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.access.translator.batch.legacy;
+
+import java.sql.Types;
+import java.util.Objects;
+
+import org.apache.cayenne.access.translator.batch.BatchTranslator;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.query.DeleteBatchQuery;
+
+/**
+ * Implementation of {link #BatchTranslator}, which uses 'soft' delete
+ * (runs UPDATE and sets 'deleted' field to true instead-of running SQL DELETE)
+ * 
+ * @since 4.0
+ * @deprecated since 4.2
+ */
+@Deprecated
+public class SoftDeleteTranslatorFactory extends DefaultBatchTranslatorFactory {
+    /**
+     * Default name of 'deleted' field
+     */
+    public static final String DEFAULT_DELETED_FIELD_NAME = "DELETED";
+
+    /**
+     * Name of 'deleted' field
+     */
+    private String deletedFieldName;
+
+    public SoftDeleteTranslatorFactory() {
+        this(DEFAULT_DELETED_FIELD_NAME);
+    }
+
+    public SoftDeleteTranslatorFactory(String deletedFieldName) {
+        this.deletedFieldName = Objects.requireNonNull(deletedFieldName);
+    }
+
+    @Override
+    protected BatchTranslator deleteTranslator(DeleteBatchQuery query, DbAdapter adapter, String trimFunction) {
+
+        DbAttribute attr = query.getDbEntity().getAttribute(deletedFieldName);
+        boolean needsSoftDelete = attr != null && attr.getType() == Types.BOOLEAN;
+
+        return needsSoftDelete
+                ? new SoftDeleteBatchTranslator(query, adapter, trimFunction, deletedFieldName)
+                : super.deleteTranslator(query, adapter, trimFunction);
+    }
+
+    /**
+     * @return name of 'deleted' field
+     */
+    public String getDeletedFieldName() {
+        return deletedFieldName;
+    }
+}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/UpdateBatchTranslator.java
similarity index 98%
copy from cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java
copy to cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/UpdateBatchTranslator.java
index 8b1c558..36e851b 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/batch/legacy/UpdateBatchTranslator.java
@@ -17,7 +17,7 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.access.translator.batch;
+package org.apache.cayenne.access.translator.batch.legacy;
 
 import java.util.Iterator;
 import java.util.List;
@@ -32,7 +32,9 @@ import org.apache.cayenne.query.UpdateBatchQuery;
 
 /**
  * A translator for UpdateBatchQueries that produces parameterized SQL.
+ * @deprecated since 4.2
  */
+@Deprecated
 public class UpdateBatchTranslator extends DefaultBatchTranslator {
 
     public UpdateBatchTranslator(UpdateBatchQuery query, DbAdapter adapter, String trimFunction) {
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultQuotingAppendable.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultQuotingAppendable.java
index 6d35c64..134fd36 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultQuotingAppendable.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultQuotingAppendable.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.translator.select;
 
 import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
+import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.apache.cayenne.access.sqlbuilder.StringBuilderAppendable;
 
 /**
@@ -27,9 +28,9 @@ import org.apache.cayenne.access.sqlbuilder.StringBuilderAppendable;
  */
 public class DefaultQuotingAppendable extends StringBuilderAppendable {
 
-    private final TranslatorContext context;
+    private final SQLGenerationContext context;
 
-    public DefaultQuotingAppendable(TranslatorContext context) {
+    public DefaultQuotingAppendable(SQLGenerationContext context) {
         super();
         this.context = context;
     }
@@ -41,7 +42,7 @@ public class DefaultQuotingAppendable extends StringBuilderAppendable {
     }
 
     @Override
-    public TranslatorContext getContext() {
+    public SQLGenerationContext getContext() {
         return context;
     }
 }
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java
index 2928257..702bc3a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java
@@ -188,7 +188,7 @@ public class TranslatorContext implements SQLGenerationContext {
         return adapter;
     }
 
-    DbEntity getRootDbEntity() {
+    public DbEntity getRootDbEntity() {
         return metadata.getDbEntity();
     }
 
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBBatchTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBBatchTranslator.java
index 822dde7..eb58e71 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBBatchTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBBatchTranslator.java
@@ -25,7 +25,7 @@ import java.util.List;
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.translator.DbAttributeBinding;
-import org.apache.cayenne.access.translator.batch.DefaultBatchTranslator;
+import org.apache.cayenne.access.translator.batch.legacy.DefaultBatchTranslator;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.QuotingStrategy;
@@ -36,7 +36,7 @@ import org.apache.cayenne.query.BatchQueryRow;
 
 /**
  * Superclass of query builders for the DML operations involving LOBs.
- * 
+ * TODO: update to the new batch translation logic
  */
 abstract class Oracle8LOBBatchTranslator extends DefaultBatchTranslator {
 
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/BatchActionLockingIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/BatchActionLockingIT.java
index 7a7bdda..be1314d 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/BatchActionLockingIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/jdbc/BatchActionLockingIT.java
@@ -79,13 +79,13 @@ public class BatchActionLockingIT extends ServerCase {
 		Collection<String> nullAttributeNames = Collections.singleton("NAME");
 
 		Map<String, Object> qualifierSnapshot = new HashMap<>();
-		qualifierSnapshot.put("LOCKING_TEST_ID", new Integer(1));
+		qualifierSnapshot.put("LOCKING_TEST_ID", 1);
 
 		DeleteBatchQuery batchQuery = new DeleteBatchQuery(dbEntity, qualifierAttributes, nullAttributeNames, 5);
 		batchQuery.setUsingOptimisticLocking(true);
 		batchQuery.add(qualifierSnapshot);
 
-		DeleteBatchTranslator batchQueryBuilder = new DeleteBatchTranslator(batchQuery, adapter, null);
+		DeleteBatchTranslator batchQueryBuilder = new DeleteBatchTranslator(batchQuery, adapter);
 
 		MockConnection mockConnection = new MockConnection();
 		PreparedStatementResultSetHandler preparedStatementResultSetHandler = mockConnection
@@ -127,7 +127,7 @@ public class BatchActionLockingIT extends ServerCase {
 		batchQuery.setUsingOptimisticLocking(true);
 		batchQuery.add(qualifierSnapshot);
 
-		DeleteBatchTranslator batchQueryBuilder = new DeleteBatchTranslator(batchQuery, adapter, null);
+		DeleteBatchTranslator batchQueryBuilder = new DeleteBatchTranslator(batchQuery, adapter);
 
 		MockConnection mockConnection = new MockConnection();
 		PreparedStatementResultSetHandler preparedStatementResultSetHandler = mockConnection
@@ -145,7 +145,7 @@ public class BatchActionLockingIT extends ServerCase {
 		try {
 			action.runAsIndividualQueries(mockConnection, batchQueryBuilder, new MockOperationObserver(), generatesKeys);
 			fail("No OptimisticLockingFailureException thrown.");
-		} catch (OptimisticLockException e) {
+		} catch (OptimisticLockException ignore) {
 		}
 		assertEquals(0, mockConnection.getNumberCommits());
 		assertEquals(0, mockConnection.getNumberRollbacks());
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/DeleteBuilderTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/DeleteBuilderTest.java
new file mode 100644
index 0000000..be6aaba
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/DeleteBuilderTest.java
@@ -0,0 +1,57 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.sqlbuilder;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.DeleteNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.junit.Test;
+
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.*;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.*;
+
+public class DeleteBuilderTest {
+
+    @Test
+    public void testDelete() {
+        DeleteBuilder builder = new DeleteBuilder("test");
+        Node node = builder.build();
+        assertThat(node, instanceOf(DeleteNode.class));
+        assertSQL("DELETE FROM test", node);
+    }
+
+    @Test
+    public void testDeleteWithQualifier() {
+        DeleteBuilder builder = new DeleteBuilder("test");
+        Node node = builder.where(
+                column("col1").eq(value(1))
+                        .and(column("col2").eq(value("test")))
+                        .and(column("col3").eq(value(null)))
+        ).build();
+        assertThat(node, instanceOf(DeleteNode.class));
+        assertSQL("DELETE FROM test WHERE ( ( col1 = 1 ) AND ( col2 = 'test' ) ) AND ( col3 IS NULL )", node);
+    }
+
+    private void assertSQL(String expected, Node node) {
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new StringBuilderAppendable());
+        node.visit(visitor);
+        assertEquals(expected, visitor.getSQLString());
+    }
+}
\ No newline at end of file
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/InsertBuilderTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/InsertBuilderTest.java
new file mode 100644
index 0000000..229d6a1
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/InsertBuilderTest.java
@@ -0,0 +1,87 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.sqlbuilder;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.InsertNode;
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.junit.Test;
+
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.*;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.*;
+
+public class InsertBuilderTest {
+
+    @Test
+    public void testInsert() {
+        InsertBuilder builder = new InsertBuilder("test");
+        Node node = builder.build();
+        assertThat(node, instanceOf(InsertNode.class));
+        assertSQL("INSERT INTO test", node);
+    }
+
+    @Test
+    public void testInsertWithColumns() {
+        InsertBuilder builder = new InsertBuilder("test");
+        builder
+                .column(column("col1"))
+                .column(column("col2"))
+                .column(column("col3"));
+        Node node = builder.build();
+
+        assertThat(node, instanceOf(InsertNode.class));
+        assertSQL("INSERT INTO test( col1, col2, col3)", node);
+    }
+
+    @Test
+    public void testInsertWithValues() {
+        InsertBuilder builder = new InsertBuilder("test");
+        builder
+                .value(value(1))
+                .value(value("test"))
+                .value(value(null));
+        Node node = builder.build();
+
+        assertThat(node, instanceOf(InsertNode.class));
+        assertSQL("INSERT INTO test VALUES( 1, 'test', NULL)", node);
+    }
+
+    @Test
+    public void testInsertWithColumnsAndValues() {
+        InsertBuilder builder = new InsertBuilder("test");
+        builder
+                .column(column("col1"))
+                .value(value(1))
+                .column(column("col2"))
+                .value(value("test"))
+                .column(column("col3"))
+                .value(value(null));
+        Node node = builder.build();
+
+        assertThat(node, instanceOf(InsertNode.class));
+        assertSQL("INSERT INTO test( col1, col2, col3) VALUES( 1, 'test', NULL)", node);
+    }
+
+    private void assertSQL(String expected, Node node) {
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new StringBuilderAppendable());
+        node.visit(visitor);
+        assertEquals(expected, visitor.getSQLString());
+    }
+}
\ No newline at end of file
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/UpdateBuilderTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/UpdateBuilderTest.java
new file mode 100644
index 0000000..d0c1133
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/sqlbuilder/UpdateBuilderTest.java
@@ -0,0 +1,73 @@
+/*****************************************************************
+ *   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
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.access.sqlbuilder;
+
+import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
+import org.apache.cayenne.access.sqlbuilder.sqltree.UpdateNode;
+import org.junit.Test;
+
+import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.*;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.*;
+
+/**
+ * @since 4.2
+ */
+public class UpdateBuilderTest {
+
+    @Test
+    public void testUpdate() {
+        UpdateBuilder builder = new UpdateBuilder("test");
+        Node node = builder.build();
+        assertThat(node, instanceOf(UpdateNode.class));
+        assertSQL("UPDATE test", node);
+    }
+
+    @Test
+    public void testUpdateWithFields() {
+        UpdateBuilder builder = new UpdateBuilder("test");
+        builder
+                .set(column("col1").eq(value(1)))
+                .set(column("col2").eq(value("test")))
+                .set(column("col3").eq(value(null)));
+        Node node = builder.build();
+
+        assertThat(node, instanceOf(UpdateNode.class));
+        assertSQL("UPDATE test SET col1 = 1, col2 = 'test', col3 = NULL", node);
+    }
+
+    @Test
+    public void testUpdateWithWhere() {
+        UpdateBuilder builder = new UpdateBuilder("test");
+        builder
+                .set(column("col1").eq(value(1)))
+                .where(column("id").eq(value(123L)));
+        Node node = builder.build();
+
+        assertThat(node, instanceOf(UpdateNode.class));
+        assertSQL("UPDATE test SET col1 = 1 WHERE id = 123", node);
+    }
+
+    private void assertSQL(String expected, Node node) {
+        SQLGenerationVisitor visitor = new SQLGenerationVisitor(new StringBuilderAppendable());
+        node.visit(visitor);
+        assertEquals(expected, visitor.getSQLString());
+    }
+}
\ No newline at end of file
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslatorIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslatorIT.java
index 262c4a6..a921a04 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslatorIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslatorIT.java
@@ -19,6 +19,11 @@
 
 package org.apache.cayenne.access.translator.batch;
 
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.JdbcAdapter;
@@ -34,11 +39,6 @@ import org.apache.cayenne.unit.di.server.ServerCase;
 import org.apache.cayenne.unit.di.server.UseServerRuntime;
 import org.junit.Test;
 
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
@@ -60,32 +60,34 @@ public class DeleteBatchTranslatorIT extends ServerCase {
     private AdhocObjectFactory objectFactory;
 
     @Test
-    public void testConstructor() throws Exception {
+    public void testConstructor() {
         DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, JdbcAdapter.class.getName());
 
-        DeleteBatchTranslator builder = new DeleteBatchTranslator(mock(DeleteBatchQuery.class), adapter, null);
+        DeleteBatchQuery query = mock(DeleteBatchQuery.class);
+        DeleteBatchTranslator builder = new DeleteBatchTranslator(query, adapter);
 
-        assertSame(adapter, builder.adapter);
+        assertSame(adapter, builder.context.getAdapter());
+        assertSame(query, builder.context.getQuery());
     }
 
     @Test
-    public void testCreateSqlString() throws Exception {
+    public void testCreateSqlString() {
         DbEntity entity = runtime.getDataDomain().getEntityResolver().getObjEntity(SimpleLockingTestEntity.class)
                 .getDbEntity();
 
         List<DbAttribute> idAttributes = Collections.singletonList(entity.getAttribute("LOCKING_TEST_ID"));
 
-        DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, idAttributes, Collections.<String> emptySet(), 1);
+        DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, idAttributes, Collections.emptySet(), 1);
 
         DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, JdbcAdapter.class.getName());
-        DeleteBatchTranslator builder = new DeleteBatchTranslator(deleteQuery, adapter, null);
+        DeleteBatchTranslator builder = new DeleteBatchTranslator(deleteQuery, adapter);
         String generatedSql = builder.getSql();
         assertNotNull(generatedSql);
         assertEquals("DELETE FROM " + entity.getName() + " WHERE LOCKING_TEST_ID = ?", generatedSql);
     }
 
     @Test
-    public void testCreateSqlStringWithNulls() throws Exception {
+    public void testCreateSqlStringWithNulls() {
         DbEntity entity = runtime.getDataDomain().getEntityResolver().getObjEntity(SimpleLockingTestEntity.class)
                 .getDbEntity();
 
@@ -97,14 +99,14 @@ public class DeleteBatchTranslatorIT extends ServerCase {
         DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, idAttributes, nullAttributes, 1);
 
         DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, JdbcAdapter.class.getName());
-        DeleteBatchTranslator builder = new DeleteBatchTranslator(deleteQuery, adapter, null);
+        DeleteBatchTranslator builder = new DeleteBatchTranslator(deleteQuery, adapter);
         String generatedSql = builder.getSql();
         assertNotNull(generatedSql);
-        assertEquals("DELETE FROM " + entity.getName() + " WHERE LOCKING_TEST_ID = ? AND NAME IS NULL", generatedSql);
+        assertEquals("DELETE FROM " + entity.getName() + " WHERE ( LOCKING_TEST_ID = ? ) AND ( NAME IS NULL )", generatedSql);
     }
 
     @Test
-    public void testCreateSqlStringWithIdentifiersQuote() throws Exception {
+    public void testCreateSqlStringWithIdentifiersQuote() {
         DbEntity entity = runtime.getDataDomain().getEntityResolver().getObjEntity(SimpleLockingTestEntity.class)
                 .getDbEntity();
         try {
@@ -112,9 +114,9 @@ public class DeleteBatchTranslatorIT extends ServerCase {
             entity.getDataMap().setQuotingSQLIdentifiers(true);
             List<DbAttribute> idAttributes = Collections.singletonList(entity.getAttribute("LOCKING_TEST_ID"));
 
-            DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, idAttributes, Collections.<String> emptySet(), 1);
+            DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, idAttributes, Collections.emptySet(), 1);
             JdbcAdapter adapter = (JdbcAdapter) this.adapter;
-            DeleteBatchTranslator builder = new DeleteBatchTranslator(deleteQuery, adapter, null);
+            DeleteBatchTranslator builder = new DeleteBatchTranslator(deleteQuery, adapter);
             String generatedSql = builder.getSql();
 
             String charStart = unitAdapter.getIdentifiersStartQuote();
@@ -130,7 +132,7 @@ public class DeleteBatchTranslatorIT extends ServerCase {
     }
 
     @Test
-    public void testCreateSqlStringWithNullsWithIdentifiersQuote() throws Exception {
+    public void testCreateSqlStringWithNullsWithIdentifiersQuote() {
         DbEntity entity = runtime.getDataDomain().getEntityResolver().getObjEntity(SimpleLockingTestEntity.class)
                 .getDbEntity();
         try {
@@ -146,15 +148,15 @@ public class DeleteBatchTranslatorIT extends ServerCase {
 
             JdbcAdapter adapter = (JdbcAdapter) this.adapter;
 
-            DeleteBatchTranslator builder = new DeleteBatchTranslator(deleteQuery, adapter, null);
+            DeleteBatchTranslator builder = new DeleteBatchTranslator(deleteQuery, adapter);
             String generatedSql = builder.getSql();
 
             String charStart = unitAdapter.getIdentifiersStartQuote();
             String charEnd = unitAdapter.getIdentifiersEndQuote();
             assertNotNull(generatedSql);
 
-            assertEquals("DELETE FROM " + charStart + entity.getName() + charEnd + " WHERE " + charStart
-                    + "LOCKING_TEST_ID" + charEnd + " = ? AND " + charStart + "NAME" + charEnd + " IS NULL",
+            assertEquals("DELETE FROM " + charStart + entity.getName() + charEnd + " WHERE ( " + charStart
+                    + "LOCKING_TEST_ID" + charEnd + " = ? ) AND ( " + charStart + "NAME" + charEnd + " IS NULL )",
                     generatedSql);
         } finally {
             entity.getDataMap().setQuotingSQLIdentifiers(false);
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslatorIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslatorIT.java
index e130199..bf29cda 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslatorIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslatorIT.java
@@ -53,17 +53,20 @@ public class InsertBatchTranslatorIT extends ServerCase {
     private AdhocObjectFactory objectFactory;
 
     @Test
-    public void testConstructor() throws Exception {
+    public void testConstructor() {
         DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, JdbcAdapter.class.getName());
 
-        InsertBatchTranslator builder = new InsertBatchTranslator(mock(InsertBatchQuery.class), adapter);
+        InsertBatchQuery query = mock(InsertBatchQuery.class);
+        InsertBatchTranslator builder = new InsertBatchTranslator(query, adapter);
 
-        assertSame(adapter, builder.adapter);
+        assertSame(adapter, builder.context.getAdapter());
+        assertSame(query, builder.context.getQuery());
     }
 
     @Test
-    public void testCreateSqlString() throws Exception {
-        DbEntity entity = runtime.getDataDomain().getEntityResolver().getObjEntity(SimpleLockingTestEntity.class)
+    public void testCreateSqlString() {
+        DbEntity entity = runtime.getDataDomain().getEntityResolver()
+                .getObjEntity(SimpleLockingTestEntity.class)
                 .getDbEntity();
 
         DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, JdbcAdapter.class.getName());
@@ -71,12 +74,13 @@ public class InsertBatchTranslatorIT extends ServerCase {
         InsertBatchTranslator builder = new InsertBatchTranslator(insertQuery, adapter);
         String generatedSql = builder.getSql();
         assertNotNull(generatedSql);
-        assertEquals("INSERT INTO " + entity.getName() + " (DESCRIPTION, INT_COLUMN_NOTNULL, INT_COLUMN_NULL, LOCKING_TEST_ID, NAME) VALUES (?, ?, ?, ?, ?)",
+        assertEquals("INSERT INTO " + entity.getName() + "( DESCRIPTION, INT_COLUMN_NOTNULL, INT_COLUMN_NULL, LOCKING_TEST_ID, NAME) " +
+                        "VALUES( ?, ?, ?, ?, ?)",
                 generatedSql);
     }
 
     @Test
-    public void testCreateSqlStringWithIdentifiersQuote() throws Exception {
+    public void testCreateSqlStringWithIdentifiersQuote() {
         DbEntity entity = runtime.getDataDomain().getEntityResolver().getObjEntity(SimpleLockingTestEntity.class)
                 .getDbEntity();
         try {
@@ -92,11 +96,11 @@ public class InsertBatchTranslatorIT extends ServerCase {
             String charEnd = unitAdapter.getIdentifiersEndQuote();
             assertNotNull(generatedSql);
             assertEquals("INSERT INTO " + charStart + entity.getName() + charEnd
-                    + " (" + charStart + "DESCRIPTION" + charEnd + ", "
+                    + "( " + charStart + "DESCRIPTION" + charEnd + ", "
                     + charStart + "INT_COLUMN_NOTNULL" + charEnd + ", "
                     + charStart + "INT_COLUMN_NULL" + charEnd + ", "
                     + charStart + "LOCKING_TEST_ID" + charEnd + ", "
-                    + charStart + "NAME" + charEnd + ") VALUES (?, ?, ?, ?, ?)", generatedSql);
+                    + charStart + "NAME" + charEnd + ") VALUES( ?, ?, ?, ?, ?)", generatedSql);
         } finally {
             entity.getDataMap().setQuotingSQLIdentifiers(false);
         }
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslatorIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslatorIT.java
index d32b849..e66fb58 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslatorIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslatorIT.java
@@ -18,6 +18,11 @@
  ****************************************************************/
 package org.apache.cayenne.access.translator.batch;
 
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.PersistenceState;
 import org.apache.cayenne.access.DataNode;
@@ -40,11 +45,6 @@ import org.apache.cayenne.unit.di.server.ServerCase;
 import org.apache.cayenne.unit.di.server.UseServerRuntime;
 import org.junit.Test;
 
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
@@ -100,7 +100,7 @@ public class SoftDeleteBatchTranslatorIT extends ServerCase {
         DeleteBatchTranslator builder = createTranslator(deleteQuery);
         String generatedSql = builder.getSql();
         assertNotNull(generatedSql);
-        assertEquals("UPDATE " + entity.getName() + " SET DELETED = ? WHERE ID = ? AND NAME IS NULL", generatedSql);
+        assertEquals("UPDATE " + entity.getName() + " SET DELETED = ? WHERE ( ID = ? ) AND ( NAME IS NULL )", generatedSql);
     }
 
     @Test
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslatorIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslatorIT.java
index e604c77..03719c5 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslatorIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslatorIT.java
@@ -19,11 +19,17 @@
 
 package org.apache.cayenne.access.translator.batch;
 
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.JdbcAdapter;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.query.UpdateBatchQuery;
 import org.apache.cayenne.testdo.locking.SimpleLockingTestEntity;
@@ -33,11 +39,6 @@ import org.apache.cayenne.unit.di.server.ServerCase;
 import org.apache.cayenne.unit.di.server.UseServerRuntime;
 import org.junit.Test;
 
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
@@ -59,67 +60,70 @@ public class UpdateBatchTranslatorIT extends ServerCase {
     private AdhocObjectFactory objectFactory;
 
     @Test
-    public void testConstructor() throws Exception {
+    public void testConstructor() {
         DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, JdbcAdapter.class.getName());
-        UpdateBatchTranslator builder = new UpdateBatchTranslator(mock(UpdateBatchQuery.class), adapter, null);
-        assertSame(adapter, builder.adapter);
+        UpdateBatchQuery query = mock(UpdateBatchQuery.class);
+        UpdateBatchTranslator builder = new UpdateBatchTranslator(query, adapter);
+
+        assertSame(adapter, builder.context.getAdapter());
+        assertSame(query, builder.context.getQuery());
     }
 
     @Test
-    public void testCreateSqlString() throws Exception {
+    public void testCreateSqlString() {
         DbEntity entity = runtime.getDataDomain().getEntityResolver().getObjEntity(SimpleLockingTestEntity.class)
                 .getDbEntity();
 
-        List idAttributes = Collections.singletonList(entity.getAttribute("LOCKING_TEST_ID"));
-        List updatedAttributes = Collections.singletonList(entity.getAttribute("DESCRIPTION"));
+        List<DbAttribute> idAttributes = Collections.singletonList(entity.getAttribute("LOCKING_TEST_ID"));
+        List<DbAttribute> updatedAttributes = Collections.singletonList(entity.getAttribute("DESCRIPTION"));
 
         UpdateBatchQuery updateQuery = new UpdateBatchQuery(entity, idAttributes, updatedAttributes,
-                Collections.<String> emptySet(), 1);
+                Collections.emptySet(), 1);
 
         DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, JdbcAdapter.class.getName());
-        UpdateBatchTranslator builder = new UpdateBatchTranslator(updateQuery, adapter, null);
+        UpdateBatchTranslator builder = new UpdateBatchTranslator(updateQuery, adapter);
         String generatedSql = builder.getSql();
         assertNotNull(generatedSql);
         assertEquals("UPDATE " + entity.getName() + " SET DESCRIPTION = ? WHERE LOCKING_TEST_ID = ?", generatedSql);
     }
 
     @Test
-    public void testCreateSqlStringWithNulls() throws Exception {
+    public void testCreateSqlStringWithNulls() {
         DbEntity entity = runtime.getDataDomain().getEntityResolver().getObjEntity(SimpleLockingTestEntity.class)
                 .getDbEntity();
 
-        List idAttributes = Arrays.asList(entity.getAttribute("LOCKING_TEST_ID"), entity.getAttribute("NAME"));
+        List<DbAttribute> idAttributes = Arrays.asList(entity.getAttribute("LOCKING_TEST_ID"), entity.getAttribute("NAME"));
 
-        List updatedAttributes = Collections.singletonList(entity.getAttribute("DESCRIPTION"));
+        List<DbAttribute> updatedAttributes = Collections.singletonList(entity.getAttribute("DESCRIPTION"));
 
-        Collection nullAttributes = Collections.singleton("NAME");
+        Collection<String> nullAttributes = Collections.singleton("NAME");
 
         UpdateBatchQuery updateQuery = new UpdateBatchQuery(entity, idAttributes, updatedAttributes, nullAttributes, 1);
 
         DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, JdbcAdapter.class.getName());
-        UpdateBatchTranslator builder = new UpdateBatchTranslator(updateQuery, adapter, null);
+        UpdateBatchTranslator builder = new UpdateBatchTranslator(updateQuery, adapter);
         String generatedSql = builder.getSql();
         assertNotNull(generatedSql);
 
-        assertEquals("UPDATE " + entity.getName() + " SET DESCRIPTION = ? WHERE LOCKING_TEST_ID = ? AND NAME IS NULL",
+        assertEquals("UPDATE " + entity.getName() + " SET DESCRIPTION = ? WHERE ( LOCKING_TEST_ID = ? ) AND ( NAME IS NULL )",
                 generatedSql);
     }
 
     @Test
-    public void testCreateSqlStringWithIdentifiersQuote() throws Exception {
+    public void testCreateSqlStringWithIdentifiersQuote() {
         DbEntity entity = runtime.getDataDomain().getEntityResolver().getObjEntity(SimpleLockingTestEntity.class)
                 .getDbEntity();
         try {
 
             entity.getDataMap().setQuotingSQLIdentifiers(true);
-            List idAttributes = Collections.singletonList(entity.getAttribute("LOCKING_TEST_ID"));
-            List updatedAttributes = Collections.singletonList(entity.getAttribute("DESCRIPTION"));
+            List<DbAttribute> idAttributes = Collections.singletonList(entity.getAttribute("LOCKING_TEST_ID"));
+            List<DbAttribute> updatedAttributes = Collections.singletonList(entity.getAttribute("DESCRIPTION"));
 
             UpdateBatchQuery updateQuery = new UpdateBatchQuery(entity, idAttributes, updatedAttributes,
-                    Collections.<String> emptySet(), 1);
+                    Collections.emptySet(), 1);
             JdbcAdapter adapter = (JdbcAdapter) this.adapter;
 
-            UpdateBatchTranslator builder = new UpdateBatchTranslator(updateQuery, adapter, null);
+            UpdateBatchTranslator builder = new UpdateBatchTranslator(updateQuery, adapter);
             String generatedSql = builder.getSql();
 
             String charStart = unitAdapter.getIdentifiersStartQuote();
@@ -135,31 +139,31 @@ public class UpdateBatchTranslatorIT extends ServerCase {
     }
 
     @Test
-    public void testCreateSqlStringWithNullsWithIdentifiersQuote() throws Exception {
+    public void testCreateSqlStringWithNullsWithIdentifiersQuote() {
         DbEntity entity = runtime.getDataDomain().getEntityResolver().getObjEntity(SimpleLockingTestEntity.class)
                 .getDbEntity();
         try {
 
             entity.getDataMap().setQuotingSQLIdentifiers(true);
-            List idAttributes = Arrays.asList(entity.getAttribute("LOCKING_TEST_ID"), entity.getAttribute("NAME"));
+            List<DbAttribute> idAttributes = Arrays.asList(entity.getAttribute("LOCKING_TEST_ID"), entity.getAttribute("NAME"));
 
-            List updatedAttributes = Collections.singletonList(entity.getAttribute("DESCRIPTION"));
+            List<DbAttribute> updatedAttributes = Collections.singletonList(entity.getAttribute("DESCRIPTION"));
 
-            Collection nullAttributes = Collections.singleton("NAME");
+            Collection<String> nullAttributes = Collections.singleton("NAME");
 
             UpdateBatchQuery updateQuery = new UpdateBatchQuery(entity, idAttributes, updatedAttributes,
                     nullAttributes, 1);
             JdbcAdapter adapter = (JdbcAdapter) this.adapter;
 
-            UpdateBatchTranslator builder = new UpdateBatchTranslator(updateQuery, adapter, null);
+            UpdateBatchTranslator builder = new UpdateBatchTranslator(updateQuery, adapter);
             String generatedSql = builder.getSql();
             assertNotNull(generatedSql);
 
             String charStart = unitAdapter.getIdentifiersStartQuote();
             String charEnd = unitAdapter.getIdentifiersEndQuote();
             assertEquals("UPDATE " + charStart + entity.getName() + charEnd + " SET " + charStart + "DESCRIPTION"
-                    + charEnd + " = ? WHERE " + charStart + "LOCKING_TEST_ID" + charEnd + " = ? AND " + charStart
-                    + "NAME" + charEnd + " IS NULL", generatedSql);
+                    + charEnd + " = ? WHERE ( " + charStart + "LOCKING_TEST_ID" + charEnd + " = ? ) AND ( " + charStart
+                    + "NAME" + charEnd + " IS NULL )", generatedSql);
 
         } finally {
             entity.getDataMap().setQuotingSQLIdentifiers(false);
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslatorIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslatorIT.java
similarity index 98%
rename from cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslatorIT.java
rename to cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslatorIT.java
index 581ebfc..f200872 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslatorIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslatorIT.java
@@ -17,7 +17,7 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.access.translator.batch;
+package org.apache.cayenne.access.translator.batch.legacy;
 
 import org.apache.cayenne.access.translator.DbAttributeBinding;
 import org.apache.cayenne.dba.DbAdapter;
@@ -39,6 +39,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertSame;
 import static org.mockito.Mockito.mock;
 
+@Deprecated
 @UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
 public class DefaultBatchTranslatorIT extends ServerCase {
 
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslatorIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/legacy/DeleteBatchTranslatorIT.java
similarity index 98%
copy from cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslatorIT.java
copy to cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/legacy/DeleteBatchTranslatorIT.java
index 262c4a6..2af52db 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslatorIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/legacy/DeleteBatchTranslatorIT.java
@@ -17,7 +17,7 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.access.translator.batch;
+package org.apache.cayenne.access.translator.batch.legacy;
 
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.dba.DbAdapter;
@@ -44,6 +44,7 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
 import static org.mockito.Mockito.mock;
 
+@Deprecated
 @UseServerRuntime(CayenneProjects.LOCKING_PROJECT)
 public class DeleteBatchTranslatorIT extends ServerCase {
 
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslatorIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/legacy/InsertBatchTranslatorIT.java
similarity index 98%
copy from cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslatorIT.java
copy to cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/legacy/InsertBatchTranslatorIT.java
index e130199..dad5bd4 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslatorIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/legacy/InsertBatchTranslatorIT.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  ****************************************************************/
-package org.apache.cayenne.access.translator.batch;
+package org.apache.cayenne.access.translator.batch.legacy;
 
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.dba.DbAdapter;
@@ -37,6 +37,7 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
 import static org.mockito.Mockito.mock;
 
+@Deprecated
 @UseServerRuntime(CayenneProjects.LOCKING_PROJECT)
 public class InsertBatchTranslatorIT extends ServerCase {
 
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslatorIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/legacy/SoftDeleteBatchTranslatorIT.java
similarity index 98%
copy from cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslatorIT.java
copy to cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/legacy/SoftDeleteBatchTranslatorIT.java
index d32b849..36efd4c 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslatorIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/legacy/SoftDeleteBatchTranslatorIT.java
@@ -16,11 +16,12 @@
  *  specific language governing permissions and limitations
  *  under the License.
  ****************************************************************/
-package org.apache.cayenne.access.translator.batch;
+package org.apache.cayenne.access.translator.batch.legacy;
 
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.PersistenceState;
 import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.JdbcAdapter;
 import org.apache.cayenne.di.AdhocObjectFactory;
@@ -48,6 +49,7 @@ import java.util.List;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
+@Deprecated
 @UseServerRuntime(CayenneProjects.SOFT_DELETE_PROJECT)
 public class SoftDeleteBatchTranslatorIT extends ServerCase {
 
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslatorIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/legacy/UpdateBatchTranslatorIT.java
similarity index 99%
copy from cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslatorIT.java
copy to cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/legacy/UpdateBatchTranslatorIT.java
index e604c77..1a10261 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslatorIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/translator/batch/legacy/UpdateBatchTranslatorIT.java
@@ -17,7 +17,7 @@
  *  under the License.
  ****************************************************************/
 
-package org.apache.cayenne.access.translator.batch;
+package org.apache.cayenne.access.translator.batch.legacy;
 
 import org.apache.cayenne.configuration.server.ServerRuntime;
 import org.apache.cayenne.dba.DbAdapter;
@@ -43,6 +43,7 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
 import static org.mockito.Mockito.mock;
 
+@Deprecated
 @UseServerRuntime(CayenneProjects.LOCKING_PROJECT)
 public class UpdateBatchTranslatorIT extends ServerCase {
 


Mime
View raw message