This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch past-M2
in repository https://gitbox.apache.org/repos/asf/cayenne.git


The following commit(s) were added to refs/heads/past-M2 by this push:
     new 2d1989e5b Unifying ParameterBinding hierarchy into a single class and 
reducing its mutability
2d1989e5b is described below

commit 2d1989e5bca7a76a68bfecdcb80eab52c5b554df
Author: Andrus Adamchik <[email protected]>
AuthorDate: Sun May 31 10:56:09 2026 -0400

    Unifying ParameterBinding hierarchy into a single class and reducing its 
mutability
---
 .../CryptoBatchTranslatorFactoryDecorator.java     |  8 +--
 .../crypto/transformer/BindingsTransformer.java    |  4 +-
 .../transformer/DefaultBindingsTransformer.java    |  9 ++-
 .../transformer/DefaultTransformerFactory.java     |  6 +-
 .../crypto/transformer/TransformerFactory.java     |  4 +-
 .../merge/token/DefaultValueForNullProvider.java   |  7 ++-
 .../cayenne/dbsync/merge/token/ValueForNullIT.java |  2 +-
 .../org/apache/cayenne/velocity/BindDirective.java | 35 +++++++++--
 .../cayenne/velocity/BindEqualDirective.java       |  7 ++-
 .../cayenne/velocity/BindNotEqualDirective.java    |  7 ++-
 .../cayenne/velocity/BindObjectEqualDirective.java |  9 +--
 .../velocity/BindObjectNotEqualDirective.java      |  7 ++-
 .../velocity/VelocitySQLTemplateTranslator.java    | 13 ++--
 .../VelocitySQLTemplateTranslatorTest.java         | 43 ++++++++------
 .../VelocitySQLTemplateTranslator_ChainTest.java   | 37 +++++++-----
 .../VelocitySQLTemplateTranslator_SelectTest.java  | 19 ++++--
 .../apache/cayenne/access/jdbc/BatchAction.java    | 10 ++--
 .../cayenne/access/jdbc/SQLTemplateAction.java     | 19 +-----
 .../apache/cayenne/access/jdbc/SelectAction.java   |  6 +-
 .../access/sqlbuilder/SQLGenerationContext.java    |  4 +-
 .../access/sqlbuilder/sqltree/ValueNode.java       | 18 ++++--
 .../access/translator/DbAttributeBinding.java      | 49 ---------------
 .../access/translator/ParameterBinding.java        | 69 ++++++++++++----------
 .../translator/ProcedureParameterBinding.java      | 49 ---------------
 .../translator/batch/BaseBatchTranslator.java      |  8 +--
 .../access/translator/batch/BatchTranslator.java   |  6 +-
 .../translator/batch/BatchTranslatorContext.java   |  6 +-
 .../translator/batch/DeleteBatchTranslator.java    |  4 +-
 .../translator/batch/InsertBatchTranslator.java    |  4 +-
 .../batch/SoftDeleteBatchTranslator.java           |  4 +-
 .../translator/batch/UpdateBatchTranslator.java    |  4 +-
 .../batch/legacy/DefaultBatchTranslator.java       | 12 ++--
 .../translator/procedure/ProcedureTranslator.java  | 12 ++--
 .../access/translator/select/TranslatedSelect.java |  4 +-
 .../translator/select/TranslatorContext.java       |  8 +--
 .../access/translator/sqltemplate/Context.java     | 27 ++++++++-
 .../sqltemplate/DefaultSQLTemplateTranslator.java  |  9 +--
 .../sqltemplate/DefaultTemplateContextFactory.java |  5 +-
 .../sqltemplate/SQLTemplateTranslator.java         | 13 ++--
 .../sqltemplate/TemplateContextFactory.java        | 14 ++++-
 .../translator/sqltemplate/directive/Bind.java     |  6 +-
 .../sqltemplate/directive/BindEqual.java           |  6 +-
 .../sqltemplate/directive/BindNotEqual.java        |  6 +-
 .../sqltemplate/directive/BindObjectEqual.java     |  8 +--
 .../sqltemplate/directive/BindObjectNotEqual.java  |  6 +-
 .../java/org/apache/cayenne/dba/AutoAdapter.java   |  8 +++
 .../java/org/apache/cayenne/dba/DbAdapter.java     | 13 ++++
 .../org/apache/cayenne/dba/db2/DB2Adapter.java     | 10 ++--
 .../org/apache/cayenne/dba/derby/DerbyAdapter.java | 11 ++--
 .../org/apache/cayenne/dba/mysql/MySQLAdapter.java | 11 +---
 .../cayenne/dba/oracle/Oracle8LOBBatchAction.java  | 29 ++++-----
 .../dba/oracle/Oracle8LOBBatchTranslator.java      | 23 ++++----
 .../oracle/Oracle8LOBInsertBatchTranslator.java    | 18 ------
 .../oracle/Oracle8LOBUpdateBatchTranslator.java    | 27 ---------
 .../cayenne/dba/postgres/PostgresAdapter.java      | 11 +---
 .../cayenne/dba/sqlserver/SQLServerAdapter.java    | 15 ++---
 .../apache/cayenne/dba/sybase/SybaseAdapter.java   | 24 ++++----
 .../cayenne/log/CompactSlf4jJdbcEventLogger.java   |  9 +--
 .../apache/cayenne/log/Slf4jJdbcEventLogger.java   | 11 ++--
 .../batch/legacy/DefaultBatchTranslatorIT.java     | 26 ++++----
 .../DefaultSQLTemplateTranslatorTest.java          | 41 ++++++++-----
 .../sqltemplate/parser/SQLTemplateParserTest.java  | 51 +++++++++-------
 .../log/CompactSlf4jJdbcEventLoggerTest.java       | 15 +++--
 63 files changed, 466 insertions(+), 500 deletions(-)

diff --git 
a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/batch/CryptoBatchTranslatorFactoryDecorator.java
 
b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/batch/CryptoBatchTranslatorFactoryDecorator.java
index 35f9ca66a..ddc021d6c 100644
--- 
a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/batch/CryptoBatchTranslatorFactoryDecorator.java
+++ 
b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/batch/CryptoBatchTranslatorFactoryDecorator.java
@@ -18,7 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.crypto.batch;
 
-import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.translator.batch.BatchTranslator;
 import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
 import org.apache.cayenne.crypto.transformer.BindingsTransformer;
@@ -65,16 +65,16 @@ public class CryptoBatchTranslatorFactoryDecorator 
implements BatchTranslatorFac
             }
 
             @Override
-            public DbAttributeBinding[] getBindings() {
+            public ParameterBinding[] getBindings() {
                 return delegateTranslator.getBindings();
             }
 
             @Override
-            public DbAttributeBinding[] updateBindings(BatchQueryRow row) {
+            public ParameterBinding[] updateBindings(BatchQueryRow row) {
 
                 ensureEncryptorCompiled();
 
-                DbAttributeBinding[] bindings = 
delegateTranslator.updateBindings(row);
+                ParameterBinding[] bindings = 
delegateTranslator.updateBindings(row);
 
                 if (encryptor != null) {
                     encryptor.transform(bindings);
diff --git 
a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/transformer/BindingsTransformer.java
 
b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/transformer/BindingsTransformer.java
index 35a9e96aa..251d3766e 100644
--- 
a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/transformer/BindingsTransformer.java
+++ 
b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/transformer/BindingsTransformer.java
@@ -18,12 +18,12 @@
  ****************************************************************/
 package org.apache.cayenne.crypto.transformer;
 
-import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 
 /**
  * @since 4.0
  */
 public interface BindingsTransformer {
 
-    void transform(DbAttributeBinding[] bindings);
+    void transform(ParameterBinding[] bindings);
 }
diff --git 
a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/transformer/DefaultBindingsTransformer.java
 
b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/transformer/DefaultBindingsTransformer.java
index 96f0920b8..02d43bda3 100644
--- 
a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/transformer/DefaultBindingsTransformer.java
+++ 
b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/transformer/DefaultBindingsTransformer.java
@@ -18,7 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.crypto.transformer;
 
-import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
 import org.apache.cayenne.crypto.transformer.bytes.BytesEncryptor;
@@ -45,20 +45,19 @@ public class DefaultBindingsTransformer implements 
BindingsTransformer {
     }
 
     @Override
-    public void transform(DbAttributeBinding[] bindings) {
+    public void transform(ParameterBinding[] bindings) {
 
         int len = positions.length;
 
         for (int i = 0; i < len; i++) {
-            DbAttributeBinding b = bindings[positions[i]];
+            ParameterBinding b = bindings[positions[i]];
             Object transformed = transformers[i].encrypt(encryptor, 
b.getValue());
-            b.setValue(transformed);
 
             ExtendedType extendedType = transformed != null
                     ? extendedTypeMap.getRegisteredType(transformed.getClass())
                     : extendedTypeMap.getDefaultType();
 
-            b.setExtendedType(extendedType);
+            b.include(b.getStatementPosition(), transformed, extendedType);
         }
     }
 
diff --git 
a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/transformer/DefaultTransformerFactory.java
 
b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/transformer/DefaultTransformerFactory.java
index 439bd4dc7..958cc013c 100644
--- 
a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/transformer/DefaultTransformerFactory.java
+++ 
b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/transformer/DefaultTransformerFactory.java
@@ -19,7 +19,7 @@
 package org.apache.cayenne.crypto.transformer;
 
 import org.apache.cayenne.access.jdbc.ColumnDescriptor;
-import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
 import org.apache.cayenne.crypto.map.ColumnMapper;
 import org.apache.cayenne.crypto.transformer.bytes.BytesTransformerFactory;
@@ -91,7 +91,7 @@ public class DefaultTransformerFactory implements 
TransformerFactory {
     }
 
     @Override
-    public BindingsTransformer encryptor(DbAttributeBinding[] bindings, 
ExtendedTypeMap extendedTypeMap) {
+    public BindingsTransformer encryptor(ParameterBinding[] bindings, 
ExtendedTypeMap extendedTypeMap) {
         int len = bindings.length;
         List<Integer> cryptoColumns = null;
 
@@ -116,7 +116,7 @@ public class DefaultTransformerFactory implements 
TransformerFactory {
 
             for (int i = 0; i < dlen; i++) {
                 int pos = cryptoColumns.get(i);
-                DbAttributeBinding b = bindings[pos];
+                ParameterBinding b = bindings[pos];
                 positions[i] = pos;
                 transformers[i] = 
transformerFactory.encryptor(b.getAttribute());
             }
diff --git 
a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/transformer/TransformerFactory.java
 
b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/transformer/TransformerFactory.java
index 96c9f9ec0..a2eade242 100644
--- 
a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/transformer/TransformerFactory.java
+++ 
b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/transformer/TransformerFactory.java
@@ -19,7 +19,7 @@
 package org.apache.cayenne.crypto.transformer;
 
 import org.apache.cayenne.access.jdbc.ColumnDescriptor;
-import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
 
 /**
@@ -30,7 +30,7 @@ import org.apache.cayenne.access.types.ExtendedTypeMap;
  */
 public interface TransformerFactory {
 
-    BindingsTransformer encryptor(DbAttributeBinding[] bindings, 
ExtendedTypeMap extendedTypeMap);
+    BindingsTransformer encryptor(ParameterBinding[] bindings, ExtendedTypeMap 
extendedTypeMap);
 
     MapTransformer decryptor(ColumnDescriptor[] columns, Object sampleRow);
 }
diff --git 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DefaultValueForNullProvider.java
 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DefaultValueForNullProvider.java
index 63753c0c6..28d8564cd 100644
--- 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DefaultValueForNullProvider.java
+++ 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DefaultValueForNullProvider.java
@@ -33,8 +33,11 @@ public class DefaultValueForNullProvider implements 
ValueForNullProvider {
     private Map<String, ParameterBinding> values = new HashMap<>();
 
     public void set(DbEntity entity, DbAttribute column, Object value, int 
type) {
-        values.put(createKey(entity, column), new ParameterBinding(value, 
type, column
-                .getAttributePrecision()));
+        // the binding is only ever read for its value (inlined into the 
UPDATE below), never bound to a
+        // statement, so the position is irrelevant here
+        ParameterBinding binding = new ParameterBinding(type, 
column.getAttributePrecision())
+                .include(1, value, null);
+        values.put(createKey(entity, column), binding);
     }
 
     protected ParameterBinding get(DbEntity entity, DbAttribute column) {
diff --git 
a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/token/ValueForNullIT.java
 
b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/token/ValueForNullIT.java
index 249b4f57e..f85815c94 100644
--- 
a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/token/ValueForNullIT.java
+++ 
b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/token/ValueForNullIT.java
@@ -105,7 +105,7 @@ public class ValueForNullIT extends MergeCase {
                 int type = column.getType();
                 switch (type) {
                     case Types.VARCHAR:
-                        return new ParameterBinding(DEFAULT_VALUE_STRING, 
type, -1);
+                        return new ParameterBinding(type, -1).include(1, 
DEFAULT_VALUE_STRING, null);
                     default:
                         throw new AssertionError("should not get here");
                 }
diff --git 
a/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindDirective.java 
b/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindDirective.java
index dbf8a4370..26f927328 100644
--- 
a/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindDirective.java
+++ 
b/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindDirective.java
@@ -25,6 +25,9 @@ import java.util.Collection;
 import java.util.Iterator;
 
 import org.apache.cayenne.access.translator.ParameterBinding;
+import org.apache.cayenne.access.types.ExtendedType;
+import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.util.ConversionUtil;
 import org.apache.velocity.context.InternalContextAdapter;
@@ -130,12 +133,26 @@ public class BindDirective extends Directive {
                                        + ") at line " + node.getLine() + ", 
column " + node.getColumn());
                }
 
-               render(context, writer, new ParameterBinding(value, jdbcType, 
scale));
+               render(context, writer, new 
ParameterBinding(preferredBindingType(context, jdbcType), scale), value);
        }
 
-       protected void render(InternalContextAdapter context, Writer writer, 
ParameterBinding binding) throws IOException {
+       /**
+        * Resolves the JDBC type the target adapter prefers for binding.
+        *
+        * @since 5.0
+        */
+       protected int preferredBindingType(InternalContextAdapter context, int 
jdbcType) {
+               return adapter(context).preferredBindingType(jdbcType);
+       }
+
+       private static DbAdapter adapter(InternalContextAdapter context) {
+               return (DbAdapter) 
context.getInternalUserContext().get(VelocitySQLTemplateTranslator.ADAPTER_KEY);
+       }
+
+       protected void render(InternalContextAdapter context, Writer writer, 
ParameterBinding binding, Object value)
+                       throws IOException {
 
-               bind(context, binding);
+               bind(context, binding, value);
                writer.write('?');
        }
 
@@ -146,14 +163,24 @@ public class BindDirective extends Directive {
        /**
         * Adds value to the list of bindings in the context.
         */
-       protected void bind(InternalContextAdapter context, ParameterBinding 
binding) {
+       protected void bind(InternalContextAdapter context, ParameterBinding 
binding, Object value) {
 
                @SuppressWarnings("unchecked")
                Collection<ParameterBinding> bindings = 
(Collection<ParameterBinding>)
                                
context.getInternalUserContext().get(VelocitySQLTemplateTranslator.BINDINGS_LIST_KEY);
 
                if (bindings != null) {
+                       // a binding's statement position is its 1-based 
ordinal among the bound parameters; the
+                       // ExtendedType is resolved from the value via the 
adapter
+                       binding.include(bindings.size() + 1, value, 
extendedType(context, value));
                        bindings.add(binding);
                }
        }
+
+       private ExtendedType<?> extendedType(InternalContextAdapter context, 
Object value) {
+               ExtendedTypeMap extendedTypes = 
adapter(context).getExtendedTypes();
+               return value != null
+                               ? 
extendedTypes.getRegisteredType(value.getClass())
+                               : extendedTypes.getDefaultType();
+       }
 }
diff --git 
a/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindEqualDirective.java
 
b/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindEqualDirective.java
index 2c3518691..af8916d47 100644
--- 
a/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindEqualDirective.java
+++ 
b/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindEqualDirective.java
@@ -43,11 +43,12 @@ public class BindEqualDirective extends BindDirective {
     protected void render(
         InternalContextAdapter context,
         Writer writer,
-        ParameterBinding binding)
+        ParameterBinding binding,
+        Object value)
         throws IOException {
 
-        if (binding.getValue() != null) {
-            bind(context, binding);
+        if (value != null) {
+            bind(context, binding, value);
             writer.write("= ?");
         }
         else {
diff --git 
a/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindNotEqualDirective.java
 
b/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindNotEqualDirective.java
index c95af7541..f25bf7992 100644
--- 
a/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindNotEqualDirective.java
+++ 
b/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindNotEqualDirective.java
@@ -43,10 +43,11 @@ public class BindNotEqualDirective extends BindDirective {
     protected void render(
             InternalContextAdapter context,
             Writer writer,
-            ParameterBinding binding) throws IOException {
+            ParameterBinding binding,
+            Object value) throws IOException {
 
-        if (binding.getValue() != null) {
-            bind(context, binding);
+        if (value != null) {
+            bind(context, binding, value);
             writer.write("<> ?");
         }
         else {
diff --git 
a/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindObjectEqualDirective.java
 
b/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindObjectEqualDirective.java
index 179f340a7..9662cb710 100644
--- 
a/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindObjectEqualDirective.java
+++ 
b/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindObjectEqualDirective.java
@@ -94,7 +94,7 @@ public class BindObjectEqualDirective extends BindDirective {
 
             renderColumn(context, writer, sqlColumnsArray[i], i);
             writer.write(' ');
-            render(context, writer, new ParameterBinding(value, jdbcType, -1));
+            render(context, writer, new 
ParameterBinding(preferredBindingType(context, jdbcType), -1), value);
         }
 
         return true;
@@ -151,10 +151,11 @@ public class BindObjectEqualDirective extends 
BindDirective {
     protected void render(
             InternalContextAdapter context,
             Writer writer,
-            ParameterBinding binding) throws IOException {
+            ParameterBinding binding,
+            Object value) throws IOException {
 
-        if (binding.getValue() != null) {
-            bind(context, binding);
+        if (value != null) {
+            bind(context, binding, value);
             writer.write("= ?");
         }
         else {
diff --git 
a/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindObjectNotEqualDirective.java
 
b/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindObjectNotEqualDirective.java
index faac03252..70afc50ff 100644
--- 
a/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindObjectNotEqualDirective.java
+++ 
b/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/BindObjectNotEqualDirective.java
@@ -57,10 +57,11 @@ public class BindObjectNotEqualDirective extends 
BindObjectEqualDirective {
     protected void render(
             InternalContextAdapter context,
             Writer writer,
-            ParameterBinding binding) throws IOException {
+            ParameterBinding binding,
+            Object value) throws IOException {
 
-        if (binding.getValue() != null) {
-            bind(context, binding);
+        if (value != null) {
+            bind(context, binding, value);
             writer.write("<> ?");
         }
         else {
diff --git 
a/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslator.java
 
b/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslator.java
index 704a0d6a0..d88bb39f2 100644
--- 
a/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslator.java
+++ 
b/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslator.java
@@ -24,6 +24,7 @@ import org.apache.cayenne.access.jdbc.ColumnDescriptor;
 import org.apache.cayenne.access.translator.sqltemplate.TranslatedSQL;
 import org.apache.cayenne.access.translator.sqltemplate.SQLTemplateTranslator;
 import org.apache.cayenne.access.translator.ParameterBinding;
+import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.exp.ExpressionException;
 import 
org.apache.cayenne.access.translator.sqltemplate.SQLTemplateRenderingUtils;
 import org.apache.velocity.Template;
@@ -92,6 +93,7 @@ public class VelocitySQLTemplateTranslator implements 
SQLTemplateTranslator {
        static final String BINDINGS_LIST_KEY = "bindings";
        static final String RESULT_COLUMNS_LIST_KEY = "resultColumns";
        static final String HELPER_KEY = "helper";
+       static final String ADAPTER_KEY = "adapter";
 
        protected RuntimeInstance velocityRuntime;
        protected SQLTemplateRenderingUtils renderingUtils;
@@ -123,17 +125,17 @@ public class VelocitySQLTemplateTranslator implements 
SQLTemplateTranslator {
         * as a "helper" variable and SQLStatement object as "statement" 
variable.
         */
        @Override
-       public TranslatedSQL translate(String template, Map<String, ?> 
parameters) {
+       public TranslatedSQL translate(String template, Map<String, ?> 
parameters, DbAdapter adapter) {
                // have to make a copy of parameter map since we are gonna 
modify it..
                Map<String, Object> internalParameters = (parameters != null && 
!parameters.isEmpty()) ? new HashMap<>(
                                parameters) : new HashMap<String, Object>(5);
 
                SimpleNode parsedTemplate = parse(template);
-               return translate(template, parsedTemplate, internalParameters);
+               return translate(template, parsedTemplate, internalParameters, 
adapter);
        }
 
        @Override
-       public TranslatedSQL translate(String template, List<Object> 
positionalParameters) {
+       public TranslatedSQL translate(String template, List<Object> 
positionalParameters, DbAdapter adapter) {
 
                SimpleNode parsedTemplate = parse(template);
 
@@ -143,15 +145,16 @@ public class VelocitySQLTemplateTranslator implements 
SQLTemplateTranslator {
                parsedTemplate.jjtAccept(visitor, null);
                visitor.onFinish();
 
-               return translate(template, parsedTemplate, internalParameters);
+               return translate(template, parsedTemplate, internalParameters, 
adapter);
        }
 
-       TranslatedSQL translate(String template, SimpleNode parsedTemplate, 
Map<String, Object> parameters) {
+       TranslatedSQL translate(String template, SimpleNode parsedTemplate, 
Map<String, Object> parameters, DbAdapter adapter) {
                List<ParameterBinding> bindings = new ArrayList<>();
                List<ColumnDescriptor> results = new ArrayList<>();
                parameters.put(BINDINGS_LIST_KEY, bindings);
                parameters.put(RESULT_COLUMNS_LIST_KEY, results);
                parameters.put(HELPER_KEY, renderingUtils);
+               parameters.put(ADAPTER_KEY, adapter);
 
                String sql;
                try {
diff --git 
a/cayenne-velocity/src/test/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslatorTest.java
 
b/cayenne-velocity/src/test/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslatorTest.java
index f3daaf396..ae96a6e18 100644
--- 
a/cayenne-velocity/src/test/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslatorTest.java
+++ 
b/cayenne-velocity/src/test/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslatorTest.java
@@ -21,6 +21,9 @@ package org.apache.cayenne.velocity;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.sql.Types;
 import java.util.Arrays;
@@ -33,23 +36,29 @@ import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.Persistent;
 import org.apache.cayenne.access.translator.sqltemplate.TranslatedSQL;
 import org.apache.cayenne.access.translator.ParameterBinding;
+import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.dba.DbAdapter;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 public class VelocitySQLTemplateTranslatorTest {
 
        private VelocitySQLTemplateTranslator processor;
+       private DbAdapter adapter;
 
        @BeforeEach
        public void before() {
                processor = new VelocitySQLTemplateTranslator();
+               adapter = mock(DbAdapter.class);
+               when(adapter.preferredBindingType(anyInt())).thenAnswer(i -> 
i.getArgument(0));
+               when(adapter.getExtendedTypes()).thenReturn(new 
ExtendedTypeMap());
        }
 
        @Test
        public void processTemplateUnchanged1() throws Exception {
                String sqlTemplate = "SELECT * FROM ME";
 
-               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap());
+               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap(), adapter);
 
                assertEquals(sqlTemplate, compiled.sql());
                assertEquals(0, compiled.bindings().length);
@@ -59,7 +68,7 @@ public class VelocitySQLTemplateTranslatorTest {
        public void processTemplateUnchanged2() throws Exception {
                String sqlTemplate = "SELECT a.b as XYZ FROM $SYSTEM_TABLE";
 
-               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap());
+               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap(), adapter);
 
                assertEquals(sqlTemplate, compiled.sql());
                assertEquals(0, compiled.bindings().length);
@@ -70,7 +79,7 @@ public class VelocitySQLTemplateTranslatorTest {
                String sqlTemplate = "SELECT * FROM ME WHERE $a";
 
                Map<String, Object> map = Collections.<String, Object> 
singletonMap("a", "VALUE_OF_A");
-               TranslatedSQL compiled = processor.translate(sqlTemplate, map);
+               TranslatedSQL compiled = processor.translate(sqlTemplate, map, 
adapter);
 
                assertEquals("SELECT * FROM ME WHERE VALUE_OF_A", 
compiled.sql());
 
@@ -83,7 +92,7 @@ public class VelocitySQLTemplateTranslatorTest {
                String sqlTemplate = "SELECT * FROM ME WHERE "
                                + "COLUMN1 = #bind($a 'VARCHAR') AND COLUMN2 = 
#bind($b 'INTEGER')";
                Map<String, Object> map = Collections.<String, Object> 
singletonMap("a", "VALUE_OF_A");
-               TranslatedSQL compiled = processor.translate(sqlTemplate, map);
+               TranslatedSQL compiled = processor.translate(sqlTemplate, map, 
adapter);
 
                assertEquals("SELECT * FROM ME WHERE COLUMN1 = ? AND COLUMN2 = 
?", compiled.sql());
                assertEquals(2, compiled.bindings().length);
@@ -96,7 +105,7 @@ public class VelocitySQLTemplateTranslatorTest {
                String sqlTemplate = "SELECT * FROM ME WHERE COLUMN1 = 
#bind($a)";
                Map<String, Object> map = Collections.<String, Object> 
singletonMap("a", "VALUE_OF_A");
 
-               TranslatedSQL compiled = processor.translate(sqlTemplate, map);
+               TranslatedSQL compiled = processor.translate(sqlTemplate, map, 
adapter);
 
                assertEquals(1, compiled.bindings().length);
                assertBindingType(Types.VARCHAR, compiled.bindings()[0]);
@@ -107,7 +116,7 @@ public class VelocitySQLTemplateTranslatorTest {
                String sqlTemplate = "SELECT * FROM ME WHERE COLUMN1 = 
#bind($a)";
                Map<String, Object> map = Collections.<String, Object> 
singletonMap("a", 4);
 
-               TranslatedSQL compiled = processor.translate(sqlTemplate, map);
+               TranslatedSQL compiled = processor.translate(sqlTemplate, map, 
adapter);
 
                assertEquals(1, compiled.bindings().length);
                assertBindingType(Types.INTEGER, compiled.bindings()[0]);
@@ -117,14 +126,14 @@ public class VelocitySQLTemplateTranslatorTest {
        public void processTemplateBindEqual() throws Exception {
                String sqlTemplate = "SELECT * FROM ME WHERE COLUMN 
#bindEqual($a 'VARCHAR')";
 
-               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap());
+               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap(), adapter);
 
                assertEquals("SELECT * FROM ME WHERE COLUMN IS NULL", 
compiled.sql());
                assertEquals(0, compiled.bindings().length);
 
                Map<String, Object> map = Collections.<String, Object> 
singletonMap("a", "VALUE_OF_A");
 
-               compiled = processor.translate(sqlTemplate, map);
+               compiled = processor.translate(sqlTemplate, map, adapter);
 
                assertEquals("SELECT * FROM ME WHERE COLUMN = ?", 
compiled.sql());
                assertEquals(1, compiled.bindings().length);
@@ -135,14 +144,14 @@ public class VelocitySQLTemplateTranslatorTest {
        public void processTemplateBindNotEqual() throws Exception {
                String sqlTemplate = "SELECT * FROM ME WHERE COLUMN 
#bindNotEqual($a 'VARCHAR')";
 
-               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap());
+               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap(), adapter);
 
                assertEquals("SELECT * FROM ME WHERE COLUMN IS NOT NULL", 
compiled.sql());
                assertEquals(0, compiled.bindings().length);
 
                Map<String, Object> map = Collections.<String, Object> 
singletonMap("a", "VALUE_OF_A");
 
-               compiled = processor.translate(sqlTemplate, map);
+               compiled = processor.translate(sqlTemplate, map, adapter);
 
                assertEquals("SELECT * FROM ME WHERE COLUMN <> ?", 
compiled.sql());
                assertEquals(1, compiled.bindings().length);
@@ -158,7 +167,7 @@ public class VelocitySQLTemplateTranslatorTest {
 
                Map<String, Object> map = Collections.<String, Object> 
singletonMap("a", persistent);
 
-               TranslatedSQL compiled = processor.translate(sqlTemplate, map);
+               TranslatedSQL compiled = processor.translate(sqlTemplate, map, 
adapter);
 
                assertEquals("SELECT * FROM ME WHERE COLUMN1 = ?", 
compiled.sql());
                assertEquals(1, compiled.bindings().length);
@@ -180,7 +189,7 @@ public class VelocitySQLTemplateTranslatorTest {
 
                Map<String, Object> map = Collections.<String, Object> 
singletonMap("a", persistent);
 
-               TranslatedSQL compiled = processor.translate(sqlTemplate, map);
+               TranslatedSQL compiled = processor.translate(sqlTemplate, map, 
adapter);
 
                assertEquals("SELECT * FROM ME WHERE COLUMN1 <> ? AND COLUMN2 
<> ?", compiled.sql());
                assertEquals(2, compiled.bindings().length);
@@ -194,13 +203,13 @@ public class VelocitySQLTemplateTranslatorTest {
 
                Map<String, Object> map = Collections.<String, Object> 
singletonMap("a", "VALUE_OF_A");
 
-               TranslatedSQL compiled = processor.translate(sqlTemplate, map);
+               TranslatedSQL compiled = processor.translate(sqlTemplate, map, 
adapter);
 
                assertEquals("SELECT * FROM ME  WHERE COLUMN1 > ?", 
compiled.sql());
                assertEquals(1, compiled.bindings().length);
                assertBindingValue("VALUE_OF_A", compiled.bindings()[0]);
 
-               compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap());
+               compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap(), adapter);
 
                assertEquals("SELECT * FROM ME ", compiled.sql());
                assertEquals(0, compiled.bindings().length);
@@ -211,12 +220,12 @@ public class VelocitySQLTemplateTranslatorTest {
                String sqlTemplate = "SELECT * FROM ME WHERE COLUMN IN 
(#bind($list 'VARCHAR'))";
 
                Map<String, Object> map = Collections.<String, Object> 
singletonMap("list", Arrays.asList("a", "b", "c"));
-               TranslatedSQL compiled = new 
VelocitySQLTemplateTranslator().translate(sqlTemplate, map);
+               TranslatedSQL compiled = new 
VelocitySQLTemplateTranslator().translate(sqlTemplate, map, adapter);
 
                assertEquals("SELECT * FROM ME WHERE COLUMN IN (?,?,?)", 
compiled.sql());
                assertEquals(3, compiled.bindings().length);
 
-               compiled = processor.translate(sqlTemplate, map);
+               compiled = processor.translate(sqlTemplate, map, adapter);
                assertBindingValue("a", compiled.bindings()[0]);
                assertBindingValue("b", compiled.bindings()[1]);
                assertBindingValue("c", compiled.bindings()[2]);
@@ -225,7 +234,7 @@ public class VelocitySQLTemplateTranslatorTest {
        @Test
        public void unknownDirective() throws Exception {
                String sqlTemplate = "SELECT #from(1) FROM a";
-               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.emptyMap());
+               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.emptyMap(), adapter);
         assertEquals("SELECT #from(1) FROM a", compiled.sql());
        }
 
diff --git 
a/cayenne-velocity/src/test/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslator_ChainTest.java
 
b/cayenne-velocity/src/test/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslator_ChainTest.java
index a8b707e64..2a8284ff8 100644
--- 
a/cayenne-velocity/src/test/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslator_ChainTest.java
+++ 
b/cayenne-velocity/src/test/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslator_ChainTest.java
@@ -20,22 +20,31 @@
 package org.apache.cayenne.velocity;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.cayenne.access.translator.sqltemplate.TranslatedSQL;
+import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.dba.DbAdapter;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 public class VelocitySQLTemplateTranslator_ChainTest {
 
        private VelocitySQLTemplateTranslator processor;
+       private DbAdapter adapter;
 
        @BeforeEach
        public void before() {
                processor = new VelocitySQLTemplateTranslator();
+               adapter = mock(DbAdapter.class);
+               when(adapter.preferredBindingType(anyInt())).thenAnswer(i -> 
i.getArgument(0));
+               when(adapter.getExtendedTypes()).thenReturn(new 
ExtendedTypeMap());
        }
 
        @Test
@@ -44,17 +53,17 @@ public class VelocitySQLTemplateTranslator_ChainTest {
                // is no chunks...
 
                TranslatedSQL compiled = processor.translate("#chain(' AND ') 
#end",
-                               Collections.<String, Object> emptyMap());
+                               Collections.<String, Object> emptyMap(), 
adapter);
                assertEquals("", compiled.sql());
 
-               compiled = processor.translate("#chain(' AND ') garbage #end", 
Collections.<String, Object> emptyMap());
+               compiled = processor.translate("#chain(' AND ') garbage #end", 
Collections.<String, Object> emptyMap(), adapter);
                assertEquals("", compiled.sql());
 
-               compiled = processor.translate("#chain(' AND ' 'PREFIX') #end", 
Collections.<String, Object> emptyMap());
+               compiled = processor.translate("#chain(' AND ' 'PREFIX') #end", 
Collections.<String, Object> emptyMap(), adapter);
 
                assertEquals("", compiled.sql());
                compiled = processor.translate("#chain(' AND ' 'PREFIX') 
garbage #end",
-                               Collections.<String, Object> emptyMap());
+                               Collections.<String, Object> emptyMap(), 
adapter);
 
                assertEquals("", compiled.sql());
        }
@@ -68,7 +77,7 @@ public class VelocitySQLTemplateTranslator_ChainTest {
                map.put("b", "[B]");
                map.put("c", "[C]");
 
-               TranslatedSQL compiled = processor.translate(template, map);
+               TranslatedSQL compiled = processor.translate(template, map, 
adapter);
                assertEquals("[A] OR [B] OR [C]", compiled.sql());
        }
 
@@ -82,7 +91,7 @@ public class VelocitySQLTemplateTranslator_ChainTest {
                map.put("b", "[B]");
                map.put("c", "[C]");
 
-               TranslatedSQL compiled = processor.translate(template, map);
+               TranslatedSQL compiled = processor.translate(template, map, 
adapter);
                assertEquals("WHERE [A] OR [B] OR [C]", compiled.sql());
        }
 
@@ -95,7 +104,7 @@ public class VelocitySQLTemplateTranslator_ChainTest {
                map.put("a", "[A]");
                map.put("c", "[C]");
 
-               TranslatedSQL compiled = processor.translate(template, map);
+               TranslatedSQL compiled = processor.translate(template, map, 
adapter);
                assertEquals("WHERE [A] OR [C]", compiled.sql());
        }
 
@@ -108,7 +117,7 @@ public class VelocitySQLTemplateTranslator_ChainTest {
                map.put("b", "[B]");
                map.put("c", "[C]");
 
-               TranslatedSQL compiled = processor.translate(template, map);
+               TranslatedSQL compiled = processor.translate(template, map, 
adapter);
                assertEquals("WHERE [B] OR [C]", compiled.sql());
        }
 
@@ -121,7 +130,7 @@ public class VelocitySQLTemplateTranslator_ChainTest {
                map.put("a", "[A]");
                map.put("b", "[B]");
 
-               TranslatedSQL compiled = processor.translate(template, map);
+               TranslatedSQL compiled = processor.translate(template, map, 
adapter);
                assertEquals("WHERE [A] OR [B]", compiled.sql());
        }
 
@@ -134,7 +143,7 @@ public class VelocitySQLTemplateTranslator_ChainTest {
                map.put("a", "[A]");
                map.put("c", "[C]");
 
-               TranslatedSQL compiled = processor.translate(template, map);
+               TranslatedSQL compiled = processor.translate(template, map, 
adapter);
                assertEquals("WHERE [A] some other stuff OR [C]", 
compiled.sql());
        }
 
@@ -142,7 +151,7 @@ public class VelocitySQLTemplateTranslator_ChainTest {
        public void processTemplateChainUnconditionalChunks() throws Exception {
                String template = "#chain(' OR ' 'WHERE ')" + "#chunk()C1#end" 
+ "#chunk()C2#end" + "#chunk()C3#end" + "#end";
 
-               TranslatedSQL compiled = processor.translate(template, 
Collections.<String, Object> emptyMap());
+               TranslatedSQL compiled = processor.translate(template, 
Collections.<String, Object> emptyMap(), adapter);
                assertEquals("WHERE C1 OR C2 OR C3", compiled.sql());
        }
 
@@ -151,7 +160,7 @@ public class VelocitySQLTemplateTranslator_ChainTest {
                String template = "#chain(' OR ' 'WHERE ')" + 
"#chunk($a)$a#end" + "#chunk($b)$b#end" + "#chunk($c)$c#end"
                                + "#end";
 
-               TranslatedSQL compiled = processor.translate(template, 
Collections.<String, Object> emptyMap());
+               TranslatedSQL compiled = processor.translate(template, 
Collections.<String, Object> emptyMap(), adapter);
                assertEquals("", compiled.sql());
        }
 
@@ -164,7 +173,7 @@ public class VelocitySQLTemplateTranslator_ChainTest {
                map.put("a", false);
                map.put("b", 0);
 
-               TranslatedSQL compiled = processor.translate(template, map);
+               TranslatedSQL compiled = processor.translate(template, map, 
adapter);
                assertEquals("WHERE [A] OR [B]", compiled.sql());
        }
 
@@ -177,7 +186,7 @@ public class VelocitySQLTemplateTranslator_ChainTest {
                map.put("a", false);
                map.put("b", 0);
 
-               TranslatedSQL compiled = processor.translate(template, map);
+               TranslatedSQL compiled = processor.translate(template, map, 
adapter);
                assertEquals("WHERE false OR 0", compiled.sql());
        }
 
diff --git 
a/cayenne-velocity/src/test/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslator_SelectTest.java
 
b/cayenne-velocity/src/test/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslator_SelectTest.java
index cc7369be1..f49d13b92 100644
--- 
a/cayenne-velocity/src/test/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslator_SelectTest.java
+++ 
b/cayenne-velocity/src/test/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslator_SelectTest.java
@@ -21,28 +21,37 @@ package org.apache.cayenne.velocity;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.util.Collections;
 
 import org.apache.cayenne.access.jdbc.ColumnDescriptor;
 import org.apache.cayenne.access.translator.sqltemplate.TranslatedSQL;
+import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.dba.DbAdapter;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 public class VelocitySQLTemplateTranslator_SelectTest {
 
        private VelocitySQLTemplateTranslator processor;
+       private DbAdapter adapter;
 
        @BeforeEach
        public void before() {
                processor = new VelocitySQLTemplateTranslator();
+               adapter = mock(DbAdapter.class);
+               when(adapter.preferredBindingType(anyInt())).thenAnswer(i -> 
i.getArgument(0));
+               when(adapter.getExtendedTypes()).thenReturn(new 
ExtendedTypeMap());
        }
 
        @Test
        public void processTemplateUnchanged() throws Exception {
                String sqlTemplate = "SELECT * FROM ME";
 
-               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap());
+               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap(), adapter);
 
                assertEquals(sqlTemplate, compiled.sql());
                assertEquals(0, compiled.bindings().length);
@@ -53,7 +62,7 @@ public class VelocitySQLTemplateTranslator_SelectTest {
        public void processSelectTemplate1() throws Exception {
                String sqlTemplate = "SELECT #result('A') FROM ME";
 
-               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap());
+               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap(), adapter);
 
                assertEquals("SELECT A FROM ME", compiled.sql());
                assertEquals(0, compiled.bindings().length);
@@ -66,7 +75,7 @@ public class VelocitySQLTemplateTranslator_SelectTest {
        public void processSelectTemplate2() throws Exception {
                String sqlTemplate = "SELECT #result('A' 'String') FROM ME";
 
-               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap());
+               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap(), adapter);
 
                assertEquals("SELECT A FROM ME", compiled.sql());
                assertEquals(0, compiled.bindings().length);
@@ -80,7 +89,7 @@ public class VelocitySQLTemplateTranslator_SelectTest {
        public void processSelectTemplate3() throws Exception {
                String sqlTemplate = "SELECT #result('A' 'String' 'B') FROM ME";
 
-               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap());
+               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap(), adapter);
 
                assertEquals("SELECT A AS B FROM ME", compiled.sql());
                assertEquals(0, compiled.bindings().length);
@@ -96,7 +105,7 @@ public class VelocitySQLTemplateTranslator_SelectTest {
        public void processSelectTemplate4() throws Exception {
                String sqlTemplate = "SELECT #result('A'), #result('B'), 
#result('C') FROM ME";
 
-               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap());
+               TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.<String, Object> emptyMap(), adapter);
 
                assertEquals("SELECT A, B, C FROM ME", compiled.sql());
                assertEquals(0, compiled.bindings().length);
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java 
b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java
index d1043b41f..42d918132 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java
@@ -25,7 +25,7 @@ import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.OperationObserver;
 import org.apache.cayenne.access.OptimisticLockException;
 import org.apache.cayenne.access.jdbc.reader.RowReader;
-import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.translator.batch.BatchTranslator;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.log.JdbcEventLogger;
@@ -55,10 +55,10 @@ public class BatchAction extends BaseSQLAction {
        protected BatchQuery query;
        protected RowDescriptor keyRowDescriptor;
 
-       private static void bind(DbAdapter adapter, PreparedStatement 
statement, DbAttributeBinding[] bindings)
+       private static void bind(DbAdapter adapter, PreparedStatement 
statement, ParameterBinding[] bindings)
                        throws Exception {
 
-               for (DbAttributeBinding b : bindings) {
+               for (ParameterBinding b : bindings) {
                        if (!b.isExcluded()) {
                                adapter.bindParameter(statement, b);
                        }
@@ -130,7 +130,7 @@ public class BatchAction extends BaseSQLAction {
                try (PreparedStatement statement = prepareStatement(con, sql, 
adapter, generatesKeys)) {
                        for (BatchQueryRow row : query.getRows()) {
 
-                               DbAttributeBinding[] bindings = 
translator.updateBindings(row);
+                               ParameterBinding[] bindings = 
translator.updateBindings(row);
                                logger.logQueryParameters("batch bind", 
bindings);
                                bind(adapter, statement, bindings);
 
@@ -189,7 +189,7 @@ public class BatchAction extends BaseSQLAction {
                try (PreparedStatement statement = prepareStatement(connection, 
queryStr, adapter, generatesKeys)) {
                        for (BatchQueryRow row : query.getRows()) {
 
-                               DbAttributeBinding[] bindings = 
translator.updateBindings(row);
+                               ParameterBinding[] bindings = 
translator.updateBindings(row);
                                logger.logQueryParameters("bind", bindings);
 
                                bind(adapter, statement, bindings);
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java 
b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java
index 5976e9f67..c4bd62c6d 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java
@@ -26,7 +26,6 @@ import org.apache.cayenne.access.OperationObserver;
 import org.apache.cayenne.access.jdbc.reader.RowReader;
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.translator.sqltemplate.TranslatedSQL;
-import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.TypesMapping;
@@ -125,25 +124,12 @@ public class SQLTemplateAction implements SQLAction {
         callback.nextBatchCount(query, ints);
     }
 
-    private void bindExtendedTypes(ParameterBinding[] bindings) {
-        int i = 1;
-        for (ParameterBinding binding : bindings) {
-            Object value = binding.getValue();
-            ExtendedType extendedType = value != null
-                    ? 
getAdapter().getExtendedTypes().getRegisteredType(value.getClass())
-                    : getAdapter().getExtendedTypes().getDefaultType();
-            binding.setExtendedType(extendedType);
-            binding.setStatementPosition(i++);
-        }
-    }
-
     private void runWithPositionalParameters(Connection connection, 
OperationObserver callback, String template,
                                              Collection<Number> counts, 
boolean loggable) throws Exception {
 
         TranslatedSQL compiled = 
dataNode.getSqlTemplateTranslator().translate(template,
-                query.getPositionalParams());
+                query.getPositionalParams(), getAdapter());
 
-        bindExtendedTypes(compiled.bindings());
         if (loggable) {
             dataNode.getJdbcEventLogger().logQuery(compiled.sql(), 
compiled.bindings());
         }
@@ -171,8 +157,7 @@ public class SQLTemplateAction implements SQLAction {
 
         for (int i = 0; i < batchSize; i++) {
             Map<String, ?> nextParameters = it.next();
-            TranslatedSQL compiled = 
dataNode.getSqlTemplateTranslator().translate(template, nextParameters);
-            bindExtendedTypes(compiled.bindings());
+            TranslatedSQL compiled = 
dataNode.getSqlTemplateTranslator().translate(template, nextParameters, 
getAdapter());
             if (loggable) {
                 dataNode.getJdbcEventLogger().logQuery(compiled.sql(), 
compiled.bindings());
             }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java 
b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java
index 5094135f5..6318d1798 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java
@@ -23,7 +23,7 @@ import org.apache.cayenne.ResultIterator;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.OperationObserver;
 import org.apache.cayenne.access.jdbc.reader.RowReader;
-import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.translator.select.TranslatedSelect;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.log.JdbcEventLogger;
@@ -44,9 +44,9 @@ import java.util.List;
  */
 public class SelectAction extends BaseSQLAction {
 
-    private static void bind(DbAdapter adapter, PreparedStatement statement, 
DbAttributeBinding[] bindings) throws Exception {
+    private static void bind(DbAdapter adapter, PreparedStatement statement, 
ParameterBinding[] bindings) throws Exception {
 
-        for (DbAttributeBinding b : bindings) {
+        for (ParameterBinding b : bindings) {
 
             if (b.isExcluded()) {
                 continue;
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
index 7e0b7597a..661f352c1 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/SQLGenerationContext.java
@@ -21,7 +21,7 @@ package org.apache.cayenne.access.sqlbuilder;
 
 import java.util.Collection;
 
-import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.QuotingStrategy;
 import org.apache.cayenne.map.DbEntity;
@@ -33,7 +33,7 @@ public interface SQLGenerationContext {
 
     DbAdapter getAdapter();
 
-    Collection<DbAttributeBinding> getBindings();
+    Collection<ParameterBinding> getBindings();
 
     QuotingStrategy getQuotingStrategy();
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
index 220aadcf8..f6ffbf01f 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/sqlbuilder/sqltree/ValueNode.java
@@ -26,9 +26,10 @@ import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.Persistent;
 import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
-import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.sqlbuilder.QuotingAppendable;
 import org.apache.cayenne.access.types.ExtendedType;
+import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.map.DbAttribute;
 
 /**
@@ -143,10 +144,17 @@ public class ValueNode extends Node {
         SQLGenerationContext context = buffer.getContext();
         // allow translation in out-of-context scope, to be able to use as a 
standalone SQL generator
         ExtendedType<?> extendedType = 
context.getAdapter().getExtendedTypes().getRegisteredType(value.getClass());
-        DbAttributeBinding binding = new DbAttributeBinding(attribute);
-        binding.setStatementPosition(context.getBindings().size() + 1);
-        binding.setExtendedType(extendedType);
-        binding.setValue(value);
+
+        // 'attribute' is only a type hint and may be absent (e.g. function 
arguments and other
+        // literals not bound to a column); fall back to deriving the JDBC 
type from the value
+        ParameterBinding binding;
+        if (attribute != null) {
+            binding = new ParameterBinding(attribute, 
context.getAdapter().preferredBindingType(attribute.getType()));
+        } else {
+            int jdbcType = 
context.getAdapter().preferredBindingType(TypesMapping.getSqlTypeByJava(value.getClass()));
+            binding = new ParameterBinding(jdbcType, -1);
+        }
+        binding.include(context.getBindings().size() + 1, value, extendedType);
         context.getBindings().add(binding);
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/DbAttributeBinding.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/DbAttributeBinding.java
deleted file mode 100644
index 0cf44365d..000000000
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/DbAttributeBinding.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    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;
-
-import org.apache.cayenne.map.DbAttribute;
-
-/**
- * Describes a PreparedStatement parameter binding mapped to a DbAttribute.
- * 
- * @since 4.0
- */
-public class DbAttributeBinding extends ParameterBinding {
-
-       private final DbAttribute attribute;
-
-       public DbAttributeBinding(DbAttribute attribute) {
-               this.attribute = attribute;
-       }
-
-       public DbAttribute getAttribute() {
-               return attribute;
-       }
-
-       @Override
-       public Integer getJdbcType() {
-               return super.getJdbcType() != null ? super.getJdbcType() : 
attribute.getType();
-       }
-
-       @Override
-       public int getScale() {
-               return getAttribute().getScale();
-       }
-}
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/ParameterBinding.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/ParameterBinding.java
index 1e6fc103b..96871a958 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/ParameterBinding.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/ParameterBinding.java
@@ -19,29 +19,50 @@
 package org.apache.cayenne.access.translator;
 
 import org.apache.cayenne.access.types.ExtendedType;
+import org.apache.cayenne.map.DbAttribute;
 
 /**
- * Describes a PreparedStatement parameter generic binding.
- * 
+ * Describes a single PreparedStatement parameter binding. A binding carries 
immutable metadata
+ * (the optional source {@link DbAttribute}, the JDBC type and scale) 
established at creation time,
+ * plus a mutable per-iteration state (value, statement position, {@link 
ExtendedType}) updated via
+ * {@link #include(int, Object, ExtendedType)} / {@link #exclude()} as batch 
rows are processed.
+ *
  * @since 4.0
  */
 public class ParameterBinding {
 
        private static final int EXCLUDED_POSITION = -1;
 
+       private final DbAttribute attribute;
+       private final int jdbcType;
+       private final int scale;
+
        private Object value;
        private int statementPosition;
        private ExtendedType<?> extendedType;
-       private Integer jdbcType;
-       private int scale;
 
-       public ParameterBinding(Object value, Integer jdbcType, int scale) {
-               this.value = value;
+       /**
+        * Creates a binding mapped to a {@link DbAttribute}. The scale is 
taken from the attribute, while the JDBC type is
+        * passed explicitly, as it may have been changed from the attribute 
type by the underlying DbAdapter
+        *
+        * @since 5.0
+        */
+       public ParameterBinding(DbAttribute attribute, int jdbcType) {
+               this.attribute = attribute;
                this.jdbcType = jdbcType;
-               this.scale = scale;
+               this.scale = attribute.getScale();
+               this.statementPosition = EXCLUDED_POSITION;
        }
 
-       public ParameterBinding() {
+       /**
+        * Creates a binding not mapped to any {@link DbAttribute}. Like the 
attribute-based constructor, this only
+        * establishes the immutable metadata; the value, position and {@link 
ExtendedType} are set via
+        * {@link #include(int, Object, ExtendedType)}.
+        */
+       public ParameterBinding(int jdbcType, int scale) {
+               this.attribute = null;
+               this.jdbcType = jdbcType;
+               this.scale = scale;
                this.statementPosition = EXCLUDED_POSITION;
        }
 
@@ -49,18 +70,10 @@ public class ParameterBinding {
                return value;
        }
 
-       public void setValue(Object value) {
-               this.value = value;
-       }
-
        public int getStatementPosition() {
                return statementPosition;
        }
 
-       public void setStatementPosition(int statementPosition) {
-               this.statementPosition = statementPosition;
-       }
-
        public boolean isExcluded() {
                return statementPosition == EXCLUDED_POSITION;
        }
@@ -69,10 +82,6 @@ public class ParameterBinding {
                return extendedType;
        }
 
-       public void setExtendedType(ExtendedType<?> extendedType) {
-               this.extendedType = extendedType;
-       }
-
        /**
         * Marks the binding object as excluded for the current iteration.
         */
@@ -83,28 +92,28 @@ public class ParameterBinding {
        }
 
        /**
-        * Sets the value of the binding and initializes statement position var,
-        * thus "including" this binding in the current iteration.
+        * Sets the value, statement position and {@link ExtendedType} of the 
binding, thus "including" it in the current
+        * iteration. Returns this binding for chaining.
         */
-       public void include(int statementPosition, Object value, 
ExtendedType<?> extendedType) {
+       public ParameterBinding include(int statementPosition, Object value, 
ExtendedType<?> extendedType) {
                this.statementPosition = statementPosition;
                this.value = value;
                this.extendedType = extendedType;
+               return this;
        }
 
-       public Integer getJdbcType() {
+       public int getJdbcType() {
                return jdbcType;
        }
 
-       public void setJdbcType(Integer type) {
-               this.jdbcType = type;
-       }
-
        public int getScale() {
                return scale;
        }
 
-       public void setScale(int scale) {
-               this.scale = scale;
+       /**
+        * @since 5.0
+        */
+       public DbAttribute getAttribute() {
+               return attribute;
        }
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/ProcedureParameterBinding.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/ProcedureParameterBinding.java
deleted file mode 100644
index ffca313cc..000000000
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/ProcedureParameterBinding.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- * <p/>
- * https://www.apache.org/licenses/LICENSE-2.0
- * <p/>
- * 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;
-
-import org.apache.cayenne.map.ProcedureParameter;
-
-/**
- * Describes a PreparedStatement parameter binding mapped to a DbAttribute.
- *
- * @since 4.0
- */
-public class ProcedureParameterBinding extends ParameterBinding {
-
-       private final ProcedureParameter parameter;
-
-       public ProcedureParameterBinding(ProcedureParameter procedureParameter) 
{
-               this.parameter = procedureParameter;
-       }
-
-       public ProcedureParameter getParameter() {
-               return parameter;
-       }
-
-       @Override
-       public Integer getJdbcType() {
-               return parameter.getType();
-       }
-
-       @Override
-       public int getScale() {
-               return parameter.getPrecision();
-       }
-}
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java
index 92966ed01..d5ab3fa1b 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java
@@ -26,7 +26,7 @@ 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.ParameterBinding;
 import org.apache.cayenne.access.translator.select.DefaultQuotingAppendable;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.map.DbAttribute;
@@ -40,13 +40,13 @@ public abstract class BaseBatchTranslator<T extends 
BatchQuery> {
 
     protected final BatchTranslatorContext<T> context;
 
-    protected DbAttributeBinding[] bindings;
+    protected ParameterBinding[] bindings;
 
     public BaseBatchTranslator(T query, DbAdapter adapter) {
         this.context = new BatchTranslatorContext<>(query, adapter);
     }
 
-    public DbAttributeBinding[] getBindings() {
+    public ParameterBinding[] getBindings() {
         return bindings;
     }
 
@@ -65,7 +65,7 @@ public abstract class BaseBatchTranslator<T extends 
BatchQuery> {
         SQLGenerationVisitor visitor = new SQLGenerationVisitor(new 
DefaultQuotingAppendable(context));
         node.visit(visitor);
 
-        bindings = context.getBindings().toArray(new DbAttributeBinding[0]);
+        bindings = context.getBindings().toArray(new ParameterBinding[0]);
         return visitor.getSQLString();
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslator.java
index 4be8497c4..73a75fdbc 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslator.java
@@ -19,7 +19,7 @@
 
 package org.apache.cayenne.access.translator.batch;
 
-import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.query.BatchQueryRow;
 
 /**
@@ -37,7 +37,7 @@ public interface BatchTranslator {
     /**
      * Returns the widest possible array of bindings for this query.
      */
-    DbAttributeBinding[] getBindings();
+    ParameterBinding[] getBindings();
 
     /**
      * Updates internal bindings to be used with a given row, returning updated
@@ -47,5 +47,5 @@ public interface BatchTranslator {
      * parameter). Usually the returned array is actually the same object 
reused
      * for every iteration, only with changed object state.
      */
-    DbAttributeBinding[] updateBindings(BatchQueryRow row);
+    ParameterBinding[] updateBindings(BatchQueryRow row);
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorContext.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorContext.java
index 17352dd56..341d3c8d7 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorContext.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorContext.java
@@ -24,7 +24,7 @@ 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.access.translator.ParameterBinding;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.QuotingStrategy;
 import org.apache.cayenne.map.DbEntity;
@@ -38,7 +38,7 @@ class BatchTranslatorContext<T extends BatchQuery> implements 
SQLGenerationConte
 
     private final T query;
     private final DbAdapter adapter;
-    private final List<DbAttributeBinding> bindings;
+    private final List<ParameterBinding> bindings;
 
     BatchTranslatorContext(T query, DbAdapter adapter) {
         this.query = query;
@@ -52,7 +52,7 @@ class BatchTranslatorContext<T extends BatchQuery> implements 
SQLGenerationConte
     }
 
     @Override
-    public Collection<DbAttributeBinding> getBindings() {
+    public Collection<ParameterBinding> getBindings() {
         return bindings;
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java
index 57675d154..78c87d6ea 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java
@@ -21,7 +21,7 @@ package org.apache.cayenne.access.translator.batch;
 
 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.translator.ParameterBinding;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.map.DbAttribute;
@@ -51,7 +51,7 @@ public class DeleteBatchTranslator extends 
BaseBatchTranslator<DeleteBatchQuery>
     }
 
     @Override
-    public DbAttributeBinding[] updateBindings(BatchQueryRow row) {
+    public ParameterBinding[] updateBindings(BatchQueryRow row) {
         DeleteBatchQuery deleteBatch = context.getQuery();
         for(int i=0, position=0; i<deleteBatch.getDbAttributes().size(); i++) {
             position = updateBinding(row.getValue(i), position);
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java
index 03e6e41a9..bddf13877 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java
@@ -21,7 +21,7 @@ package org.apache.cayenne.access.translator.batch;
 
 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.translator.ParameterBinding;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.map.DbAttribute;
@@ -58,7 +58,7 @@ public class InsertBatchTranslator extends 
BaseBatchTranslator<InsertBatchQuery>
     }
 
     @Override
-    public DbAttributeBinding[] updateBindings(BatchQueryRow row) {
+    public ParameterBinding[] updateBindings(BatchQueryRow row) {
         InsertBatchQuery query = context.getQuery();
         int i=0;
         int j=0;
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java
index 43fad4d30..50e7824df 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java
@@ -21,7 +21,7 @@ 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.translator.ParameterBinding;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.TypesMapping;
@@ -63,7 +63,7 @@ public class SoftDeleteBatchTranslator extends 
DeleteBatchTranslator {
     }
 
     @Override
-    public DbAttributeBinding[] updateBindings(BatchQueryRow row) {
+    public ParameterBinding[] updateBindings(BatchQueryRow row) {
         DeleteBatchQuery deleteBatch = context.getQuery();
 
         for(int i=0, position=1; i<deleteBatch.getDbAttributes().size(); i++) {
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java
index 6b973b5dd..a7c074fef 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java
@@ -21,7 +21,7 @@ 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.translator.ParameterBinding;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.map.DbAttribute;
@@ -59,7 +59,7 @@ public class UpdateBatchTranslator extends 
BaseBatchTranslator<UpdateBatchQuery>
     }
 
     @Override
-    public DbAttributeBinding[] updateBindings(BatchQueryRow row) {
+    public ParameterBinding[] updateBindings(BatchQueryRow row) {
         UpdateBatchQuery updateBatch = context.getQuery();
 
         int i = 0;
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslator.java
index f5a07939d..aa8435253 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslator.java
@@ -20,7 +20,7 @@ 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.ParameterBinding;
 import org.apache.cayenne.access.translator.batch.BatchTranslator;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.QuotingStrategy;
@@ -43,7 +43,7 @@ public abstract class DefaultBatchTranslator implements 
BatchTranslator {
 
     protected boolean translated;
     protected String sql;
-    protected DbAttributeBinding[] bindings;
+    protected ParameterBinding[] bindings;
 
     public DefaultBatchTranslator(BatchQuery query, DbAdapter adapter, String 
trimFunction) {
         this.query = query;
@@ -70,22 +70,22 @@ public abstract class DefaultBatchTranslator implements 
BatchTranslator {
     }
 
     @Override
-    public DbAttributeBinding[] getBindings() {
+    public ParameterBinding[] getBindings() {
         ensureTranslated();
         return bindings;
     }
     
     @Override
-    public DbAttributeBinding[] updateBindings(BatchQueryRow row) {
+    public ParameterBinding[] updateBindings(BatchQueryRow row) {
         ensureTranslated();
         return doUpdateBindings(row);
     }
 
     protected abstract String createSql();
 
-    protected abstract DbAttributeBinding[] createBindings();
+    protected abstract ParameterBinding[] createBindings();
     
-    protected abstract DbAttributeBinding[] doUpdateBindings(BatchQueryRow 
row);
+    protected abstract ParameterBinding[] doUpdateBindings(BatchQueryRow row);
 
     /**
      * Appends the name of the column to the query buffer. Subclasses use this
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/ProcedureTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/ProcedureTranslator.java
index 3639b0f65..8d329fd46 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/ProcedureTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/ProcedureTranslator.java
@@ -27,7 +27,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.cayenne.access.translator.ParameterBinding;
-import org.apache.cayenne.access.translator.ProcedureParameterBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.log.JdbcEventLogger;
@@ -160,8 +160,8 @@ public class ProcedureTranslator {
                                if(value instanceof NotInParam) {
                                        value = value.toString();
                                }
-                               parameterBindings[i] = new 
ParameterBinding(value,
-                                               procedureParameter.getType(), 
procedureParameter.getPrecision());
+                               parameterBindings[i] = new 
ParameterBinding(procedureParameter.getType(), 
procedureParameter.getPrecision())
+                                               .include(i + 1, value, null);
                        }
                        logger.logQuery(sqlStr, parameterBindings);
                }
@@ -231,10 +231,8 @@ public class ProcedureTranslator {
                                ? 
adapter.getExtendedTypes().getRegisteredType(val.getClass())
                                : adapter.getExtendedTypes().getDefaultType();
 
-               ProcedureParameterBinding binding = new 
ProcedureParameterBinding(param);
-               binding.setStatementPosition(pos);
-               binding.setValue(val);
-               binding.setExtendedType(extendedType);
+               ParameterBinding binding = new 
ParameterBinding(adapter.preferredBindingType(param.getType()), 
param.getPrecision())
+                               .include(pos, val, extendedType);
                adapter.bindParameter(stmt, binding);
        }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TranslatedSelect.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TranslatedSelect.java
index ee1ec983f..973dc5793 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TranslatedSelect.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TranslatedSelect.java
@@ -20,7 +20,7 @@
 package org.apache.cayenne.access.translator.select;
 
 import org.apache.cayenne.access.jdbc.ColumnDescriptor;
-import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 
 /**
  * An immutable result of translating a {@link 
org.apache.cayenne.query.Select} query to SQL,
@@ -31,7 +31,7 @@ import 
org.apache.cayenne.access.translator.DbAttributeBinding;
 public record TranslatedSelect(
         String sql,
         ColumnDescriptor[] resultColumns,
-        DbAttributeBinding[] bindings,
+        ParameterBinding[] bindings,
         boolean suppressingDistinct,
         boolean hasJoins) {
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java
index acb9a1ab8..8115192c1 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/TranslatorContext.java
@@ -24,7 +24,7 @@ import org.apache.cayenne.access.sqlbuilder.SQLBuilder;
 import org.apache.cayenne.access.sqlbuilder.SQLGenerationContext;
 import org.apache.cayenne.access.sqlbuilder.SelectBuilder;
 import org.apache.cayenne.access.sqlbuilder.sqltree.Node;
-import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.QuotingStrategy;
 import org.apache.cayenne.exp.parser.ASTAggregateFunctionCall;
@@ -92,7 +92,7 @@ public class TranslatorContext implements 
SQLGenerationContext {
      * - order by expressions
      * - where expression (including qualifiers from all used DbEntities and 
ObjEntities)
      */
-    private final Collection<DbAttributeBinding> bindings;
+    private final Collection<ParameterBinding> bindings;
 
     // Translated query
     private final TranslatableQueryWrapper query;
@@ -161,7 +161,7 @@ public class TranslatorContext implements 
SQLGenerationContext {
         return new TranslatedSelect(
                 getFinalSQL(),
                 getColumnDescriptors().toArray(new ColumnDescriptor[0]),
-                getBindings().toArray(new DbAttributeBinding[0]),
+                getBindings().toArray(new ParameterBinding[0]),
                 isDistinctSuppression(),
                 getTableCount() > 1);
     }
@@ -194,7 +194,7 @@ public class TranslatorContext implements 
SQLGenerationContext {
         return columnDescriptors;
     }
 
-    public Collection<DbAttributeBinding> getBindings() {
+    public Collection<ParameterBinding> getBindings() {
         return bindings;
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/Context.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/Context.java
index d4bc86bca..2821dd0c1 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/Context.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/Context.java
@@ -28,6 +28,8 @@ import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.jdbc.ColumnDescriptor;
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.translator.sqltemplate.directive.Directive;
+import org.apache.cayenne.access.types.ExtendedType;
+import org.apache.cayenne.dba.DbAdapter;
 
 /**
  * @since 4.1
@@ -38,14 +40,17 @@ public class Context {
     private final Map<String, ?> objects;
     private final Map<String, String> parameterAliases;
     private final Map<String, Directive> directives;
+    private final DbAdapter adapter;
 
     private List<ParameterBinding> parameterBindings;
     private List<ColumnDescriptor> columnDescriptors;
     private int counter;
 
-    public Context(Map<String, Directive> directives, Map<String, ?> 
parameters, boolean positionalMode) {
+    public Context(Map<String, Directive> directives, Map<String, ?> 
parameters, boolean positionalMode,
+                   DbAdapter adapter) {
         this.directives = directives;
         this.objects = parameters;
+        this.adapter = adapter;
         this.builder = new StringBuilder();
         if(positionalMode) {
             parameterAliases = new HashMap<>();
@@ -54,6 +59,15 @@ public class Context {
         }
     }
 
+    /**
+     * Returns the JDBC type the target adapter prefers for binding the given 
type.
+     *
+     * @since 5.0
+     */
+    public int preferredBindingType(int jdbcType) {
+        return adapter.preferredBindingType(jdbcType);
+    }
+
     public Directive getDirective(String name) {
         return directives.get(name);
     }
@@ -98,13 +112,22 @@ public class Context {
         return null;
     }
 
-    public void addParameterBinding(ParameterBinding binding) {
+    public void addParameterBinding(ParameterBinding binding, Object value) {
         if(parameterBindings == null) {
             parameterBindings = new ArrayList<>();
         }
+        // a binding's statement position is its 1-based ordinal among the 
bound parameters; the
+        // ExtendedType is resolved from the value via the adapter
+        binding.include(parameterBindings.size() + 1, value, 
extendedType(value));
         parameterBindings.add(binding);
     }
 
+    private ExtendedType<?> extendedType(Object value) {
+        return value != null
+                ? 
adapter.getExtendedTypes().getRegisteredType(value.getClass())
+                : adapter.getExtendedTypes().getDefaultType();
+    }
+
     public void addColumnDescriptor(ColumnDescriptor descriptor) {
         if(columnDescriptors == null) {
             columnDescriptors = new ArrayList<>();
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/DefaultSQLTemplateTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/DefaultSQLTemplateTranslator.java
index 0ddd36a55..a10dc8bf2 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/DefaultSQLTemplateTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/DefaultSQLTemplateTranslator.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.access.translator.sqltemplate;
 
 import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.access.translator.sqltemplate.parser.Node;
 import org.apache.cayenne.access.translator.sqltemplate.parser.ParseException;
@@ -49,19 +50,19 @@ public class DefaultSQLTemplateTranslator implements 
SQLTemplateTranslator {
     }
 
     @Override
-    public TranslatedSQL translate(String template, Map<String, ?> parameters) 
{
-        Context context = contextFactory.createContext(parameters);
+    public TranslatedSQL translate(String template, Map<String, ?> parameters, 
DbAdapter adapter) {
+        Context context = contextFactory.createContext(parameters, adapter);
         return process(template, context);
     }
 
     @Override
-    public TranslatedSQL translate(String template, List<Object> 
positionalParameters) {
+    public TranslatedSQL translate(String template, List<Object> 
positionalParameters, DbAdapter adapter) {
         Map<String, Object> parameters = new HashMap<>();
         int i = 0;
         for (Object param : positionalParameters) {
             parameters.put(String.valueOf(i++), param);
         }
-        Context context = contextFactory.createContext(parameters, true);
+        Context context = contextFactory.createContext(parameters, true, 
adapter);
         return process(template, context);
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/DefaultTemplateContextFactory.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/DefaultTemplateContextFactory.java
index 715be0980..f3b35417f 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/DefaultTemplateContextFactory.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/DefaultTemplateContextFactory.java
@@ -30,6 +30,7 @@ import 
org.apache.cayenne.access.translator.sqltemplate.directive.BindObjectEqua
 import 
org.apache.cayenne.access.translator.sqltemplate.directive.BindObjectNotEqual;
 import org.apache.cayenne.access.translator.sqltemplate.directive.Directive;
 import org.apache.cayenne.access.translator.sqltemplate.directive.Result;
+import org.apache.cayenne.dba.DbAdapter;
 
 /**
  * @since 4.1
@@ -55,10 +56,10 @@ public class DefaultTemplateContextFactory implements 
TemplateContextFactory {
     }
 
     @Override
-    public Context createContext(Map<String, ?> parameters, boolean 
positionalMode) {
+    public Context createContext(Map<String, ?> parameters, boolean 
positionalMode, DbAdapter adapter) {
         Map<String, Object> realParameters = new HashMap<>(parameters.size() + 
1);
         realParameters.putAll(parameters);
         realParameters.put("helper", helper);
-        return new Context(directives, realParameters, positionalMode);
+        return new Context(directives, realParameters, positionalMode, 
adapter);
     }
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/SQLTemplateTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/SQLTemplateTranslator.java
index 70a7df95f..3ee3cd1e4 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/SQLTemplateTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/SQLTemplateTranslator.java
@@ -18,6 +18,8 @@
  ****************************************************************/
 package org.apache.cayenne.access.translator.sqltemplate;
 
+import org.apache.cayenne.dba.DbAdapter;
+
 import java.util.List;
 import java.util.Map;
 
@@ -27,18 +29,17 @@ import java.util.Map;
 public interface SQLTemplateTranslator {
 
        /**
-        * Builds and returns a SQLStatement based on SQL template String and a 
map
-        * of parameters.
+        * Builds and returns a SQLStatement based on SQL template String and a 
map of parameters.
         *
         * @since 5.0
         */
-       TranslatedSQL translate(String template, Map<String, ?> parameters);
+       TranslatedSQL translate(String template, Map<String, ?> parameters, 
DbAdapter adapter);
 
        /**
-        * Builds and returns a SQLStatement based on SQL template String and a 
list
-        * of positional parameters.
+        * Builds and returns a SQLStatement based on SQL template String and a 
list of positional
+        * parameters.
         *
         * @since 5.0
         */
-       TranslatedSQL translate(String template, List<Object> 
positionalParameters);
+       TranslatedSQL translate(String template, List<Object> 
positionalParameters, DbAdapter adapter);
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/TemplateContextFactory.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/TemplateContextFactory.java
index cf1fe981f..8044482ea 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/TemplateContextFactory.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/TemplateContextFactory.java
@@ -21,15 +21,23 @@ package org.apache.cayenne.access.translator.sqltemplate;
 
 import java.util.Map;
 
+import org.apache.cayenne.dba.DbAdapter;
+
 /**
  * @since 4.1
  */
 public interface TemplateContextFactory {
 
-    Context createContext(Map<String, ?> parameters, boolean positionalMode);
+    /**
+     * @since 5.0
+     */
+    Context createContext(Map<String, ?> parameters, boolean positionalMode, 
DbAdapter adapter);
 
-    default Context createContext(Map<String, ?> parameters) {
-        return createContext(parameters, false);
+    /**
+     * @since 5.0
+     */
+    default Context createContext(Map<String, ?> parameters, DbAdapter 
adapter) {
+        return createContext(parameters, false, adapter);
     }
 
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/Bind.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/Bind.java
index 2fc2a8c97..63f1b8e67 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/Bind.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/Bind.java
@@ -67,11 +67,11 @@ public class Bind implements Directive {
             jdbcType = TypesMapping.getSqlTypeByName(TypesMapping.SQL_NULL);
         }
 
-        processBinding(context, new ParameterBinding(value, jdbcType, scale));
+        processBinding(context, new 
ParameterBinding(context.preferredBindingType(jdbcType), scale), value);
     }
 
-    protected void processBinding(Context context, ParameterBinding binding) {
-        context.addParameterBinding(binding);
+    protected void processBinding(Context context, ParameterBinding binding, 
Object value) {
+        context.addParameterBinding(binding, value);
         context.getBuilder().append('?');
     }
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/BindEqual.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/BindEqual.java
index 8a2ca26e3..38ce70428 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/BindEqual.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/BindEqual.java
@@ -30,9 +30,9 @@ public class BindEqual extends Bind {
     public static final BindEqual INSTANCE = new BindEqual();
 
     @Override
-    protected void processBinding(Context context, ParameterBinding binding) {
-        if (binding.getValue() != null) {
-            context.addParameterBinding(binding);
+    protected void processBinding(Context context, ParameterBinding binding, 
Object value) {
+        if (value != null) {
+            context.addParameterBinding(binding, value);
             context.getBuilder().append("= ?");
         } else {
             context.getBuilder().append("IS NULL");
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/BindNotEqual.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/BindNotEqual.java
index 717b94165..723d0436e 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/BindNotEqual.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/BindNotEqual.java
@@ -30,9 +30,9 @@ public class BindNotEqual extends Bind {
     public static final BindNotEqual INSTANCE = new BindNotEqual();
 
     @Override
-    protected void processBinding(Context context, ParameterBinding binding) {
-        if (binding.getValue() != null) {
-            context.addParameterBinding(binding);
+    protected void processBinding(Context context, ParameterBinding binding, 
Object value) {
+        if (value != null) {
+            context.addParameterBinding(binding, value);
             context.getBuilder().append("<> ?");
         } else {
             context.getBuilder().append("IS NOT NULL");
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/BindObjectEqual.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/BindObjectEqual.java
index 67f3b1576..2aaddc190 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/BindObjectEqual.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/BindObjectEqual.java
@@ -81,7 +81,7 @@ public class BindObjectEqual implements Directive {
             int jdbcType = (value != null) ? 
TypesMapping.getSqlTypeByJava(value.getClass()) : Types.INTEGER;
 
             renderColumn(context, sqlColumnsArray[i], i);
-            render(context, new ParameterBinding(value, jdbcType, -1));
+            render(context, new 
ParameterBinding(context.preferredBindingType(jdbcType), -1), value);
         }
     }
 
@@ -93,9 +93,9 @@ public class BindObjectEqual implements Directive {
         context.getBuilder().append(columnName).append(' ');
     }
 
-    protected void render(Context context, ParameterBinding binding) {
-        if (binding.getValue() != null) {
-            context.addParameterBinding(binding);
+    protected void render(Context context, ParameterBinding binding, Object 
value) {
+        if (value != null) {
+            context.addParameterBinding(binding, value);
             context.getBuilder().append("= ?");
         } else {
             context.getBuilder().append("IS NULL");
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/BindObjectNotEqual.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/BindObjectNotEqual.java
index 8ebd66198..fbf3cc847 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/BindObjectNotEqual.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/BindObjectNotEqual.java
@@ -39,9 +39,9 @@ public class BindObjectNotEqual extends BindObjectEqual {
     }
 
     @Override
-    protected void render(Context context, ParameterBinding binding) {
-        if (binding.getValue() != null) {
-            context.addParameterBinding(binding);
+    protected void render(Context context, ParameterBinding binding, Object 
value) {
+        if (value != null) {
+            context.addParameterBinding(binding, value);
             context.getBuilder().append("<> ?");
         } else {
             context.getBuilder().append("IS NOT NULL");
diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
index e9a6464fc..8c2c7980b 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
@@ -218,6 +218,14 @@ public class AutoAdapter implements DbAdapter {
                getAdapter().bindParameter(statement, parameterBinding);
        }
 
+       /**
+        * @since 5.0
+        */
+       @Override
+       public int preferredBindingType(int jdbcType) {
+               return getAdapter().preferredBindingType(jdbcType);
+       }
+
        @Override
        public String tableTypeForTable() {
                return getAdapter().tableTypeForTable();
diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/DbAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/DbAdapter.java
index 2a7f53940..7d3e399de 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/DbAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/DbAdapter.java
@@ -199,6 +199,19 @@ public interface DbAdapter {
         */
        void bindParameter(PreparedStatement statement, ParameterBinding 
parameterBinding) throws Exception;
 
+       /**
+        * Returns the JDBC type that this adapter prefers to bind for a given 
mapped JDBC type. The
+        * default implementation is an identity function; adapters whose 
drivers require a different
+        * type (e.g. remapping the {@code N*} character types to their 
non-national counterparts)
+        * override this. The returned type is resolved into the {@link 
ParameterBinding} at binding
+        * creation time, so it matches the type actually sent to the 
PreparedStatement.
+        *
+        * @since 5.0
+        */
+       default int preferredBindingType(int jdbcType) {
+               return jdbcType;
+       }
+
        /**
         * Returns the name of the table type (as returned by
         * <code>DatabaseMetaData.getTableTypes</code>) for a simple user table.
diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
index 89c139cb3..801e7e35b 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
@@ -164,7 +164,6 @@ public class DB2Adapter extends JdbcAdapter {
         if (binding.getValue() == null && (binding.getJdbcType() == 0 || 
binding.getJdbcType() == Types.BOOLEAN)) {
             statement.setNull(binding.getStatementPosition(), Types.VARCHAR);
         } else {
-            binding.setJdbcType(convertNTypes(binding.getJdbcType()));
             super.bindParameter(statement, binding);
         }
     }
@@ -180,10 +179,11 @@ public class DB2Adapter extends JdbcAdapter {
     }
 
     /**
-     * @since 4.0
+     * @since 5.0
      */
-    private int convertNTypes(int sqlType) {
-        switch (sqlType) {
+    @Override
+    public int preferredBindingType(int jdbcType) {
+        switch (jdbcType) {
             case Types.NCHAR:
                 return Types.CHAR;
             case Types.NVARCHAR:
@@ -194,7 +194,7 @@ public class DB2Adapter extends JdbcAdapter {
                 return Types.CLOB;
 
             default:
-                return sqlType;
+                return jdbcType;
         }
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
index 540b50245..a9007e37b 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
@@ -199,13 +199,16 @@ public class DerbyAdapter extends JdbcAdapter {
         if (binding.getValue() == null && binding.getJdbcType() == 0) {
             statement.setNull(binding.getStatementPosition(), Types.VARCHAR);
         } else {
-            binding.setJdbcType(convertNTypes(binding.getJdbcType()));
             super.bindParameter(statement, binding);
         }
     }
 
-    private int convertNTypes(int sqlType) {
-        switch (sqlType) {
+    /**
+     * @since 5.0
+     */
+    @Override
+    public int preferredBindingType(int jdbcType) {
+        switch (jdbcType) {
             case Types.NCHAR:
                 return Types.CHAR;
             case Types.NVARCHAR:
@@ -216,7 +219,7 @@ public class DerbyAdapter extends JdbcAdapter {
                 return Types.CLOB;
 
             default:
-                return sqlType;
+                return jdbcType;
         }
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
index 71f796caa..907e94e6b 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
@@ -227,18 +227,13 @@ public class MySQLAdapter extends JdbcAdapter {
        }
 
        @Override
-       public void bindParameter(PreparedStatement statement, ParameterBinding 
binding) throws Exception {
-               binding.setJdbcType(mapNTypes(binding.getJdbcType()));
-               super.bindParameter(statement, binding);
-       }
-
-       private int mapNTypes(int sqlType) {
-        return switch (sqlType) {
+       public int preferredBindingType(int jdbcType) {
+        return switch (jdbcType) {
             case Types.NCHAR -> Types.CHAR;
             case Types.NCLOB -> Types.CLOB;
             case Types.NVARCHAR -> Types.VARCHAR;
             case Types.LONGNVARCHAR -> Types.LONGVARCHAR;
-            default -> sqlType;
+            default -> jdbcType;
         };
        }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBBatchAction.java
 
b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBBatchAction.java
index f19c35124..2092fb182 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBBatchAction.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBBatchAction.java
@@ -21,7 +21,7 @@ package org.apache.cayenne.dba.oracle;
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.OperationObserver;
-import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.log.JdbcEventLogger;
@@ -53,14 +53,6 @@ class Oracle8LOBBatchAction implements SQLAction {
        private final DbAdapter adapter;
        private final JdbcEventLogger logger;
 
-       private static void bind(DbAdapter adapter, PreparedStatement 
statement, DbAttributeBinding[] bindings) throws Exception {
-
-               for (DbAttributeBinding b : bindings) {
-                       DbAttributeBinding binding = new 
DbAttributeBinding(b.getAttribute());
-                       adapter.bindParameter(statement, binding);
-               }
-       }
-
        Oracle8LOBBatchAction(BatchQuery query, DbAdapter adapter, 
JdbcEventLogger logger) {
                this.adapter = adapter;
                this.query = query;
@@ -103,10 +95,14 @@ class Oracle8LOBBatchAction implements SQLAction {
 
                        try (PreparedStatement statement = 
connection.prepareStatement(updateStr)) {
 
-                               DbAttributeBinding[] bindings = 
translator.updateBindings(row);
+                               ParameterBinding[] bindings = 
translator.updateBindings(row);
                                logger.logQueryParameters("bind", bindings);
 
-                               bind(adapter, statement, bindings);
+                               for (ParameterBinding b : bindings) {
+                                       if (!b.isExcluded()) {
+                                               
adapter.bindParameter(statement, b);
+                                       }
+                               }
 
                                updated = statement.executeUpdate();
                                logger.logUpdateCount(updated);
@@ -138,9 +134,9 @@ class Oracle8LOBBatchAction implements SQLAction {
                String selectStr = 
queryBuilder.createLOBSelectString(lobAttributes, qualifierAttributes);
 
                try (PreparedStatement selectStatement = 
con.prepareStatement(selectStr)) {
-                       DbAttributeBinding[] attributeBindings = null;
+                       ParameterBinding[] attributeBindings = null;
                        if(isLoggable) {
-                               attributeBindings = new 
DbAttributeBinding[parametersSize];
+                               attributeBindings = new 
ParameterBinding[parametersSize];
                        }
                        for (int i = 0; i < parametersSize; i++) {
                                DbAttribute attribute = 
qualifierAttributes.get(i);
@@ -149,10 +145,9 @@ class Oracle8LOBBatchAction implements SQLAction {
                                                ? 
adapter.getExtendedTypes().getRegisteredType(value.getClass())
                                                : 
adapter.getExtendedTypes().getDefaultType();
 
-                               DbAttributeBinding binding = new 
DbAttributeBinding(attribute);
-                               binding.setStatementPosition(i + 1);
-                               binding.setValue(value);
-                               binding.setExtendedType(extendedType);
+                               ParameterBinding binding = new 
ParameterBinding(attribute,
+                                               
adapter.preferredBindingType(attribute.getType()));
+                               binding.include(i + 1, value, extendedType);
                                adapter.bindParameter(selectStatement, binding);
                                if(isLoggable) {
                                        attributeBindings[i] = binding;
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBBatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBBatchTranslator.java
index eb58e71d4..6910d5f14 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBBatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBBatchTranslator.java
@@ -19,12 +19,8 @@
 
 package org.apache.cayenne.dba.oracle;
 
-import java.sql.Types;
-import java.util.Iterator;
-import java.util.List;
-
 import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 import 
org.apache.cayenne.access.translator.batch.legacy.DefaultBatchTranslator;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.dba.DbAdapter;
@@ -34,6 +30,10 @@ import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.query.BatchQuery;
 import org.apache.cayenne.query.BatchQueryRow;
 
+import java.sql.Types;
+import java.util.Iterator;
+import java.util.List;
+
 /**
  * Superclass of query builders for the DML operations involving LOBs.
  * TODO: update to the new batch translation logic
@@ -47,8 +47,6 @@ abstract class Oracle8LOBBatchTranslator extends 
DefaultBatchTranslator {
         super(query, adapter, trimFunction);
     }
 
-    abstract List<Object> getValuesForLOBUpdateParameters(BatchQueryRow row);
-
     abstract String createSql(BatchQueryRow row);
 
     @Override
@@ -111,27 +109,28 @@ abstract class Oracle8LOBBatchTranslator extends 
DefaultBatchTranslator {
     }
     
     @Override
-    protected DbAttributeBinding[] createBindings() {
+    protected ParameterBinding[] createBindings() {
         List<DbAttribute> dbAttributes = query.getDbAttributes();
         int len = dbAttributes.size();
 
-        DbAttributeBinding[] bindings = new DbAttributeBinding[len];
+        ParameterBinding[] bindings = new ParameterBinding[len];
 
         for (int i = 0; i < len; i++) {
-            bindings[i] = new DbAttributeBinding(dbAttributes.get(i));
+            DbAttribute attribute = dbAttributes.get(i);
+            bindings[i] = new ParameterBinding(attribute, 
adapter.preferredBindingType(attribute.getType()));
         }
 
         return bindings;
     }
     
     @Override
-    protected DbAttributeBinding[] doUpdateBindings(BatchQueryRow row) {
+    protected ParameterBinding[] doUpdateBindings(BatchQueryRow row) {
 
         int len = bindings.length;
 
         for (int i = 0, j = 1; i < len; i++) {
 
-            DbAttributeBinding b = bindings[i];
+            ParameterBinding b = bindings[i];
 
             Object value = row.getValue(i);
             DbAttribute attribute = b.getAttribute();
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBInsertBatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBInsertBatchTranslator.java
index e3f695577..2c4b27490 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBInsertBatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBInsertBatchTranslator.java
@@ -25,7 +25,6 @@ import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.query.BatchQueryRow;
 import org.apache.cayenne.query.InsertBatchQuery;
 
-import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
@@ -35,23 +34,6 @@ class Oracle8LOBInsertBatchTranslator extends 
Oracle8LOBBatchTranslator {
         super(query, adapter, trimFunction);
     }
 
-    @Override
-    List<Object> getValuesForLOBUpdateParameters(BatchQueryRow row) {
-        List<DbAttribute> dbAttributes = query.getDbAttributes();
-        int len = dbAttributes.size();
-
-        List<Object> values = new ArrayList<>(len);
-        for (int i = 0; i < len; i++) {
-            Object value = row.getValue(i);
-            DbAttribute attribute = dbAttributes.get(i);
-            if (isUpdateableColumn(value, attribute.getType())) {
-                values.add(value);
-            }
-        }
-
-        return values;
-    }
-
     @Override
     public String createSql(BatchQueryRow row) {
         List<DbAttribute> dbAttributes = query.getDbAttributes();
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBUpdateBatchTranslator.java
 
b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBUpdateBatchTranslator.java
index 3796c4cac..ffa3f9fef 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBUpdateBatchTranslator.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/Oracle8LOBUpdateBatchTranslator.java
@@ -25,7 +25,6 @@ import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.query.BatchQueryRow;
 import org.apache.cayenne.query.UpdateBatchQuery;
 
-import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
@@ -35,32 +34,6 @@ class Oracle8LOBUpdateBatchTranslator extends 
Oracle8LOBBatchTranslator {
         super(query, adapter, trimFunction);
     }
 
-    @Override
-    List<Object> getValuesForLOBUpdateParameters(BatchQueryRow row) {
-        int len = query.getDbAttributes().size();
-        UpdateBatchQuery updateBatch = (UpdateBatchQuery) query;
-
-        List<Object> values = new ArrayList<>(len);
-        List<DbAttribute> qualifierAttributes = 
updateBatch.getQualifierAttributes();
-        List<DbAttribute> updatedDbAttributes = 
updateBatch.getUpdatedAttributes();
-
-        int updatedLen = updatedDbAttributes.size();
-        int qualifierLen = qualifierAttributes.size();
-        for (int i = 0; i < updatedLen; i++) {
-            DbAttribute attribute = updatedDbAttributes.get(i);
-            Object value = row.getValue(i);
-            if (isUpdateableColumn(value, attribute.getType())) {
-                values.add(value);
-            }
-        }
-
-        for (int i = 0; i < qualifierLen; i++) {
-            values.add(row.getValue(updatedLen + i));
-        }
-
-        return values;
-    }
-
     @Override
     public String createSql(BatchQueryRow row) {
         UpdateBatchQuery updateBatch = (UpdateBatchQuery) query;
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
index 9a43ce915..a18f5d68b 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
@@ -135,18 +135,13 @@ public class PostgresAdapter extends JdbcAdapter {
     }
 
     @Override
-    public void bindParameter(PreparedStatement statement, ParameterBinding 
binding) throws Exception {
-        binding.setJdbcType(mapNTypes(binding.getJdbcType()));
-        super.bindParameter(statement, binding);
-    }
-
-    private int mapNTypes(int sqlType) {
-        return switch (sqlType) {
+    public int preferredBindingType(int jdbcType) {
+        return switch (jdbcType) {
             case Types.NCHAR -> Types.CHAR;
             case Types.NCLOB -> Types.CLOB;
             case Types.NVARCHAR -> Types.VARCHAR;
             case Types.LONGNVARCHAR -> Types.LONGVARCHAR;
-            default -> sqlType;
+            default -> jdbcType;
         };
     }
 
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
index 876176049..9873b183d 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
@@ -139,15 +139,12 @@ public class SQLServerAdapter extends JdbcAdapter {
 
         // SQL Server driver doesn't like CLOBs and BLOBs as parameters
         if (binding.getValue() == null) {
-            if (binding.getJdbcType() == Types.CLOB) {
-                binding.setJdbcType(Types.VARCHAR);
-            } else if (binding.getJdbcType() == Types.BLOB) {
-                binding.setJdbcType(Types.VARBINARY);
-            }
-        }
-
-        if (binding.getValue() == null && binding.getJdbcType() == 0) {
-            statement.setNull(binding.getStatementPosition(), Types.VARCHAR);
+            int jdbcType = switch (binding.getJdbcType()) {
+                case Types.CLOB, 0 -> Types.VARCHAR;
+                case Types.BLOB -> Types.VARBINARY;
+                default -> binding.getJdbcType();
+            };
+            statement.setNull(binding.getStatementPosition(), jdbcType);
         } else {
             super.bindParameter(statement, binding);
         }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java
index 459f5e0ad..9ddff72a7 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java
@@ -19,10 +19,6 @@
 
 package org.apache.cayenne.dba.sybase;
 
-import java.sql.PreparedStatement;
-import java.sql.Types;
-import java.util.List;
-
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
@@ -38,12 +34,15 @@ import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.DefaultQuotingStrategy;
 import org.apache.cayenne.dba.JdbcAdapter;
-import org.apache.cayenne.dba.PkGenerator;
 import org.apache.cayenne.dba.QuotingStrategy;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.resource.ResourceLocator;
 
+import java.sql.PreparedStatement;
+import java.sql.Types;
+import java.util.List;
+
 /**
  * DbAdapter implementation for <a href="http://www.sybase.com";>Sybase 
RDBMS</a>.
  */
@@ -116,15 +115,12 @@ public class SybaseAdapter extends JdbcAdapter {
 
         // Sybase driver doesn't like CLOBs and BLOBs as parameters
         if (binding.getValue() == null) {
-            if (binding.getJdbcType() == Types.CLOB) {
-                binding.setJdbcType(Types.VARCHAR);
-            } else if (binding.getJdbcType() == Types.BLOB) {
-                binding.setJdbcType(Types.VARBINARY);
-            }
-        }
-
-        if (binding.getValue() == null && binding.getJdbcType() == 0) {
-            statement.setNull(binding.getStatementPosition(), Types.VARCHAR);
+            int jdbcType = switch (binding.getJdbcType()) {
+                case Types.CLOB, 0 -> Types.VARCHAR;
+                case Types.BLOB -> Types.VARBINARY;
+                default -> binding.getJdbcType();
+            };
+            statement.setNull(binding.getStatementPosition(), jdbcType);
         } else {
             super.bindParameter(statement, binding);
         }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/log/CompactSlf4jJdbcEventLogger.java 
b/cayenne/src/main/java/org/apache/cayenne/log/CompactSlf4jJdbcEventLogger.java
index 1baaa276c..54bea360e 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/log/CompactSlf4jJdbcEventLogger.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/log/CompactSlf4jJdbcEventLogger.java
@@ -26,7 +26,6 @@ import java.util.Map;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
-import org.apache.cayenne.access.translator.DbAttributeBinding;
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.di.Inject;
@@ -117,11 +116,9 @@ public class CompactSlf4jJdbcEventLogger extends 
Slf4jJdbcEventLogger {
                 continue;
             }
 
-            if (b instanceof DbAttributeBinding) {
-                DbAttribute attribute = ((DbAttributeBinding) 
b).getAttribute();
-                if (attribute != null) {
-                    key = attribute.getName();
-                }
+            DbAttribute attribute = b.getAttribute();
+            if (attribute != null) {
+                key = attribute.getName();
             }
 
             if (b.getExtendedType() != null) {
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/log/Slf4jJdbcEventLogger.java 
b/cayenne/src/main/java/org/apache/cayenne/log/Slf4jJdbcEventLogger.java
index f64f9f645..484eab7c7 100644
--- a/cayenne/src/main/java/org/apache/cayenne/log/Slf4jJdbcEventLogger.java
+++ b/cayenne/src/main/java/org/apache/cayenne/log/Slf4jJdbcEventLogger.java
@@ -19,7 +19,6 @@
 package org.apache.cayenne.log;
 
 import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.translator.DbAttributeBinding;
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
@@ -115,12 +114,10 @@ public class Slf4jJdbcEventLogger implements 
JdbcEventLogger {
 
                                buffer.append(j++);
 
-                               if(b instanceof DbAttributeBinding) {
-                                       DbAttribute attribute = 
((DbAttributeBinding) b).getAttribute();
-                                       if (attribute != null) {
-                                               buffer.append("->");
-                                               
buffer.append(attribute.getName());
-                                       }
+                               DbAttribute attribute = b.getAttribute();
+                               if (attribute != null) {
+                                       buffer.append("->");
+                                       buffer.append(attribute.getName());
                                }
 
                                buffer.append(":");
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslatorIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslatorIT.java
index 1e56b5ff9..3db0aa773 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslatorIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslatorIT.java
@@ -19,7 +19,7 @@
 
 package org.apache.cayenne.access.translator.batch.legacy;
 
-import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.JdbcAdapter;
 import org.apache.cayenne.map.DbAttribute;
@@ -52,13 +52,13 @@ public class DefaultBatchTranslatorIT {
             }
 
             @Override
-            protected DbAttributeBinding[] createBindings() {
-                return new DbAttributeBinding[0];
+            protected ParameterBinding[] createBindings() {
+                return new ParameterBinding[0];
             }
 
             @Override
-            protected DbAttributeBinding[] doUpdateBindings(BatchQueryRow row) 
{
-                return new DbAttributeBinding[0];
+            protected ParameterBinding[] doUpdateBindings(BatchQueryRow row) {
+                return new ParameterBinding[0];
             }
         };
 
@@ -78,13 +78,13 @@ public class DefaultBatchTranslatorIT {
             }
 
             @Override
-            protected DbAttributeBinding[] createBindings() {
-                return new DbAttributeBinding[0];
+            protected ParameterBinding[] createBindings() {
+                return new ParameterBinding[0];
             }
 
             @Override
-            protected DbAttributeBinding[] doUpdateBindings(BatchQueryRow row) 
{
-                return new DbAttributeBinding[0];
+            protected ParameterBinding[] doUpdateBindings(BatchQueryRow row) {
+                return new ParameterBinding[0];
             }
         };
 
@@ -113,13 +113,13 @@ public class DefaultBatchTranslatorIT {
             }
 
             @Override
-            protected DbAttributeBinding[] createBindings() {
-                return new DbAttributeBinding[0];
+            protected ParameterBinding[] createBindings() {
+                return new ParameterBinding[0];
             }
 
             @Override
-            protected DbAttributeBinding[] doUpdateBindings(BatchQueryRow row) 
{
-                return new DbAttributeBinding[0];
+            protected ParameterBinding[] doUpdateBindings(BatchQueryRow row) {
+                return new ParameterBinding[0];
             }
         };
 
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/sqltemplate/DefaultSQLTemplateTranslatorTest.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/sqltemplate/DefaultSQLTemplateTranslatorTest.java
index d3f866d81..79e27c3e2 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/sqltemplate/DefaultSQLTemplateTranslatorTest.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/sqltemplate/DefaultSQLTemplateTranslatorTest.java
@@ -29,25 +29,34 @@ import org.apache.cayenne.GenericPersistentObject;
 import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.Persistent;
 import org.apache.cayenne.access.translator.ParameterBinding;
+import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.dba.DbAdapter;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 public class DefaultSQLTemplateTranslatorTest {
 
     private DefaultSQLTemplateTranslator processor;
+    private DbAdapter adapter;
 
     @BeforeEach
     public void before() {
         processor = new DefaultSQLTemplateTranslator(new 
DefaultTemplateContextFactory());
+        adapter = mock(DbAdapter.class);
+        when(adapter.preferredBindingType(anyInt())).thenAnswer(i -> 
i.getArgument(0));
+        when(adapter.getExtendedTypes()).thenReturn(new ExtendedTypeMap());
     }
 
     @Test
     public void processTemplateUnchanged1() throws Exception {
         String sqlTemplate = "SELECT * FROM ME";
 
-        TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.emptyMap());
+        TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.emptyMap(), adapter);
 
         assertEquals(sqlTemplate, compiled.sql());
         assertEquals(0, compiled.bindings().length);
@@ -57,7 +66,7 @@ public class DefaultSQLTemplateTranslatorTest {
     public void processTemplateUnchanged2() throws Exception {
         String sqlTemplate = "SELECT a.b as XYZ FROM $SYSTEM_TABLE";
 
-        TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.emptyMap());
+        TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.emptyMap(), adapter);
 
         assertEquals(sqlTemplate, compiled.sql());
         assertEquals(0, compiled.bindings().length);
@@ -68,7 +77,7 @@ public class DefaultSQLTemplateTranslatorTest {
         String sqlTemplate = "SELECT * FROM ME WHERE $a";
 
         Map<String, Object> map = Collections.singletonMap("a", "VALUE_OF_A");
-        TranslatedSQL compiled = processor.translate(sqlTemplate, map);
+        TranslatedSQL compiled = processor.translate(sqlTemplate, map, 
adapter);
 
         assertEquals("SELECT * FROM ME WHERE VALUE_OF_A", compiled.sql());
 
@@ -81,7 +90,7 @@ public class DefaultSQLTemplateTranslatorTest {
         String sqlTemplate = "SELECT * FROM ME WHERE "
                 + "COLUMN1 = #bind($a 'VARCHAR') AND COLUMN2 = #bind($b 
'INTEGER')";
         Map<String, Object> map = Collections.singletonMap("a", "VALUE_OF_A");
-        TranslatedSQL compiled = processor.translate(sqlTemplate, map);
+        TranslatedSQL compiled = processor.translate(sqlTemplate, map, 
adapter);
 
         assertEquals("SELECT * FROM ME WHERE COLUMN1 = ? AND COLUMN2 = ?", 
compiled.sql());
         assertEquals(2, compiled.bindings().length);
@@ -94,7 +103,7 @@ public class DefaultSQLTemplateTranslatorTest {
         String sqlTemplate = "SELECT * FROM ME WHERE COLUMN1 = #bind($a)";
         Map<String, Object> map = Collections.singletonMap("a", "VALUE_OF_A");
 
-        TranslatedSQL compiled = processor.translate(sqlTemplate, map);
+        TranslatedSQL compiled = processor.translate(sqlTemplate, map, 
adapter);
 
         assertEquals(1, compiled.bindings().length);
         assertBindingType(Types.VARCHAR, compiled.bindings()[0]);
@@ -105,7 +114,7 @@ public class DefaultSQLTemplateTranslatorTest {
         String sqlTemplate = "SELECT * FROM ME WHERE COLUMN1 = #bind($a)";
         Map<String, Object> map = Collections.singletonMap("a", 4);
 
-        TranslatedSQL compiled = processor.translate(sqlTemplate, map);
+        TranslatedSQL compiled = processor.translate(sqlTemplate, map, 
adapter);
 
         assertEquals(1, compiled.bindings().length);
         assertBindingType(Types.INTEGER, compiled.bindings()[0]);
@@ -115,14 +124,14 @@ public class DefaultSQLTemplateTranslatorTest {
     public void processTemplateBindEqual() throws Exception {
         String sqlTemplate = "SELECT * FROM ME WHERE COLUMN #bindEqual($a 
'VARCHAR')";
 
-        TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.emptyMap());
+        TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.emptyMap(), adapter);
 
         assertEquals("SELECT * FROM ME WHERE COLUMN IS NULL", compiled.sql());
         assertEquals(0, compiled.bindings().length);
 
         Map<String, Object> map = Collections.singletonMap("a", "VALUE_OF_A");
 
-        compiled = processor.translate(sqlTemplate, map);
+        compiled = processor.translate(sqlTemplate, map, adapter);
 
         assertEquals("SELECT * FROM ME WHERE COLUMN = ?", compiled.sql());
         assertEquals(1, compiled.bindings().length);
@@ -133,14 +142,14 @@ public class DefaultSQLTemplateTranslatorTest {
     public void processTemplateBindNotEqual() throws Exception {
         String sqlTemplate = "SELECT * FROM ME WHERE COLUMN #bindNotEqual($a 
'VARCHAR')";
 
-        TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.emptyMap());
+        TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.emptyMap(), adapter);
 
         assertEquals("SELECT * FROM ME WHERE COLUMN IS NOT NULL", 
compiled.sql());
         assertEquals(0, compiled.bindings().length);
 
         Map<String, Object> map = Collections.singletonMap("a", "VALUE_OF_A");
 
-        compiled = processor.translate(sqlTemplate, map);
+        compiled = processor.translate(sqlTemplate, map, adapter);
 
         assertEquals("SELECT * FROM ME WHERE COLUMN <> ?", compiled.sql());
         assertEquals(1, compiled.bindings().length);
@@ -156,7 +165,7 @@ public class DefaultSQLTemplateTranslatorTest {
 
         Map<String, Object> map = Collections.singletonMap("a", persistent);
 
-        TranslatedSQL compiled = processor.translate(sqlTemplate, map);
+        TranslatedSQL compiled = processor.translate(sqlTemplate, map, 
adapter);
 
         assertEquals("SELECT * FROM ME WHERE COLUMN1 = ?", compiled.sql());
         assertEquals(1, compiled.bindings().length);
@@ -178,7 +187,7 @@ public class DefaultSQLTemplateTranslatorTest {
 
         Map<String, Object> map = Collections.singletonMap("a", persistent);
 
-        TranslatedSQL compiled = processor.translate(sqlTemplate, map);
+        TranslatedSQL compiled = processor.translate(sqlTemplate, map, 
adapter);
 
         assertEquals("SELECT * FROM ME WHERE COLUMN1 <> ? AND COLUMN2 <> ?", 
compiled.sql());
         assertEquals(2, compiled.bindings().length);
@@ -192,13 +201,13 @@ public class DefaultSQLTemplateTranslatorTest {
 
         Map<String, Object> map = Collections.singletonMap("a", "VALUE_OF_A");
 
-        TranslatedSQL compiled = processor.translate(sqlTemplate, map);
+        TranslatedSQL compiled = processor.translate(sqlTemplate, map, 
adapter);
 
         assertEquals("SELECT * FROM ME  WHERE COLUMN1 > ?", compiled.sql());
         assertEquals(1, compiled.bindings().length);
         assertBindingValue("VALUE_OF_A", compiled.bindings()[0]);
 
-        compiled = processor.translate(sqlTemplate, Collections.emptyMap());
+        compiled = processor.translate(sqlTemplate, Collections.emptyMap(), 
adapter);
 
         assertEquals("SELECT * FROM ME ", compiled.sql());
         assertEquals(0, compiled.bindings().length);
@@ -209,7 +218,7 @@ public class DefaultSQLTemplateTranslatorTest {
         String sqlTemplate = "SELECT * FROM ME WHERE COLUMN IN (#bind($list 
'VARCHAR'))";
 
         Map<String, Object> map = Collections.singletonMap("list", 
Arrays.asList("a", "b", "c"));
-        TranslatedSQL compiled = processor.translate(sqlTemplate, map);
+        TranslatedSQL compiled = processor.translate(sqlTemplate, map, 
adapter);
 
         assertEquals("SELECT * FROM ME WHERE COLUMN IN (?,?,?)", 
compiled.sql());
         assertEquals(3, compiled.bindings().length);
@@ -221,7 +230,7 @@ public class DefaultSQLTemplateTranslatorTest {
     @Test
     public void unknownDirective() throws Exception {
         String sqlTemplate = "SELECT #from(1) FROM a";
-        TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.emptyMap());
+        TranslatedSQL compiled = processor.translate(sqlTemplate, 
Collections.emptyMap(), adapter);
         assertEquals("SELECT  FROM a", compiled.sql());
     }
 
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/sqltemplate/parser/SQLTemplateParserTest.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/sqltemplate/parser/SQLTemplateParserTest.java
index 55d4dba65..8121d768f 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/translator/sqltemplate/parser/SQLTemplateParserTest.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/translator/sqltemplate/parser/SQLTemplateParserTest.java
@@ -27,24 +27,33 @@ import java.util.Map;
 import org.apache.cayenne.access.translator.sqltemplate.Context;
 import 
org.apache.cayenne.access.translator.sqltemplate.DefaultTemplateContextFactory;
 import org.apache.cayenne.access.translator.sqltemplate.TemplateContextFactory;
+import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.dba.DbAdapter;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 public class SQLTemplateParserTest {
 
     private TemplateContextFactory contextFactory;
+    private DbAdapter adapter;
 
     @BeforeEach
     public void setUp() {
         contextFactory = new DefaultTemplateContextFactory();
+        adapter = mock(DbAdapter.class);
+        when(adapter.preferredBindingType(anyInt())).thenAnswer(i -> 
i.getArgument(0));
+        when(adapter.getExtendedTypes()).thenReturn(new ExtendedTypeMap());
     }
 
     @Test
     public void unchangedParse() throws Exception {
-        Context context = contextFactory.createContext(Collections.emptyMap());
+        Context context = contextFactory.createContext(Collections.emptyMap(), 
adapter);
         String template = "SELECT * FROM a";
 
         String sql = parseString(template, context);
@@ -53,7 +62,7 @@ public class SQLTemplateParserTest {
 
     @Test
     public void parameterParse() throws Exception {
-        Context context = 
contextFactory.createContext(Collections.singletonMap("a", true));
+        Context context = 
contextFactory.createContext(Collections.singletonMap("a", true), adapter);
         String template = "SELECT $a FROM a";
 
         String sql = parseString(template, context);
@@ -62,13 +71,13 @@ public class SQLTemplateParserTest {
 
     @Test
     public void ifElseParse() throws Exception {
-        Context context = 
contextFactory.createContext(Collections.singletonMap("a", true));
+        Context context = 
contextFactory.createContext(Collections.singletonMap("a", true), adapter);
         String template = "SELECT #if($a) * #else 1 #end FROM a";
 
         String sql = parseString(template, context);
         assertEquals("SELECT  *  FROM a", sql);
 
-        context = contextFactory.createContext(Collections.singletonMap("a", 
false));
+        context = contextFactory.createContext(Collections.singletonMap("a", 
false), adapter);
         template = "SELECT #if($a) * #else 1 #end FROM a";
 
         sql = parseString(template, context);
@@ -81,7 +90,7 @@ public class SQLTemplateParserTest {
         parameters.put("a", "var");
         parameters.put("b", "bbb");
 
-        Context context = contextFactory.createContext(parameters);
+        Context context = contextFactory.createContext(parameters, adapter);
         String template = "SELECT #if($a) #bind($a, 'INT' ,2) #else #bind($b, 
'CHAR' ,2) #end FROM a";
 
         String sql = parseString(template, context);
@@ -109,7 +118,7 @@ public class SQLTemplateParserTest {
                 "     \tAND \n" +
                 "COLUMN2 #bind($myHelper.cayenneExp($a, 'db:ID_COLUMN2'), 
'VARCHAR')\n" +
                 "#end\n";
-        Context context = contextFactory.createContext(parameters);
+        Context context = contextFactory.createContext(parameters, adapter);
 
         String sql = parseString(template, context);
         assertEquals("SELECT * \n" +
@@ -130,7 +139,7 @@ public class SQLTemplateParserTest {
                 "#result('t0.ID' 'java.lang.Integer' 'ec0_1' 'ec0_1' 4) " +
                 "FROM BIGDECIMAL_ENTITY t0 WHERE {fn ABS( 
t0.BIGDECIMAL_FIELD)} < #bind($id0 'DECIMAL')";
 
-        Context context = 
contextFactory.createContext(Collections.singletonMap("id0", 123));
+        Context context = 
contextFactory.createContext(Collections.singletonMap("id0", 123), adapter);
         String sql = parseString(tpl, context);
 
         assertEquals("SELECT " +
@@ -152,7 +161,7 @@ public class SQLTemplateParserTest {
                 "FROM ARTIST t0 " +
                 "LEFT OUTER JOIN PAINTING t1 ON (t0.ARTIST_ID = t1.ARTIST_ID) 
" +
                 "GROUP BY t0.ARTIST_NAME, t0.DATE_OF_BIRTH, t0.ARTIST_ID ORDER 
BY t0.ARTIST_NAME";
-        Context context = contextFactory.createContext(Collections.emptyMap());
+        Context context = contextFactory.createContext(Collections.emptyMap(), 
adapter);
         String sql = parseString(tpl, context);
         assertEquals(5, context.getColumnDescriptors().length);
         assertEquals("SELECT COUNT(*) AS sc0, t0.ARTIST_NAME AS ec1_0, " +
@@ -165,7 +174,7 @@ public class SQLTemplateParserTest {
     @Test
     public void helperObject() throws Exception {
         String tpl = "($helper.cayenneExp($a, 'field'))";
-        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)));
+        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)), 
adapter);
 
         String sql = parseString(tpl, context);
         assertEquals("(5)", sql);
@@ -174,7 +183,7 @@ public class SQLTemplateParserTest {
     @Test
     public void methodCallArray() throws Exception {
         String tpl = "$a.arrayMethod(['1' '2' '3'])";
-        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)));
+        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)), 
adapter);
 
         String sql = parseString(tpl, context);
         assertEquals("array_3", sql);
@@ -183,7 +192,7 @@ public class SQLTemplateParserTest {
     @Test
     public void methodCallArray2() throws Exception {
         String tpl = "$a.arrayMethod(['1', '2', '3'])";
-        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)));
+        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)), 
adapter);
 
         String sql = parseString(tpl, context);
         assertEquals("array_3", sql);
@@ -192,7 +201,7 @@ public class SQLTemplateParserTest {
     @Test
     public void methodCallInt() throws Exception {
         String tpl = "$a.intMethod(42)";
-        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)));
+        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)), 
adapter);
 
         String sql = parseString(tpl, context);
         assertEquals("int_42", sql);
@@ -201,7 +210,7 @@ public class SQLTemplateParserTest {
     @Test
     public void methodCallString() throws Exception {
         String tpl = "$a.stringMethod(\"abc\")";
-        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)));
+        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)), 
adapter);
 
         String sql = parseString(tpl, context);
         assertEquals("string_abc", sql);
@@ -210,7 +219,7 @@ public class SQLTemplateParserTest {
     @Test
     public void methodCallFloat() throws Exception {
         String tpl = "$a.floatMethod(3.14)";
-        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)));
+        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)), 
adapter);
 
         String sql = parseString(tpl, context);
         assertEquals("float_3.14", sql);
@@ -220,7 +229,7 @@ public class SQLTemplateParserTest {
     @Disabled("Method overload not properly supported, this test can return 
m2_true")
     public void methodCallSelectByArgType1() throws Exception {
         String tpl = "$a.method(123)";
-        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)));
+        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)), 
adapter);
 
         String sql = parseString(tpl, context);
         assertEquals("m1_123", sql);
@@ -229,7 +238,7 @@ public class SQLTemplateParserTest {
     @Test
     public void methodCallSelectByArgType2() throws Exception {
         String tpl = "$a.method(true)";
-        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)));
+        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)), 
adapter);
 
         String sql = parseString(tpl, context);
         assertEquals("m2_true", sql);
@@ -238,7 +247,7 @@ public class SQLTemplateParserTest {
     @Test
     public void propertyAccess() throws Exception {
         String tpl = "$a.field()";
-        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)));
+        Context context = 
contextFactory.createContext(Collections.singletonMap("a", new TestBean(5)), 
adapter);
 
         String sql = parseString(tpl, context);
         assertEquals("5", sql);
@@ -247,7 +256,7 @@ public class SQLTemplateParserTest {
     @Test
     public void nestedBrackets() throws Exception {
         String tpl = "(#bind('A' 'b'))";
-        String sql = parseString(tpl, 
contextFactory.createContext(Collections.emptyMap()));
+        String sql = parseString(tpl, 
contextFactory.createContext(Collections.emptyMap(), adapter));
         assertEquals("(?)", sql);
     }
 
@@ -256,11 +265,11 @@ public class SQLTemplateParserTest {
         String template = "\"$a\"";
         Map<String, Object> parameters = new HashMap<>();
         parameters.put("a", "val");
-        Context context = contextFactory.createContext(parameters);
+        Context context = contextFactory.createContext(parameters, adapter);
         String sql = parseString(template, context);
         assertEquals("\"val\"", sql);
 
-        context = contextFactory.createContext(parameters);
+        context = contextFactory.createContext(parameters, adapter);
         template = "'$a'";
         sql = parseString(template, context);
         assertEquals("'val'", sql);
@@ -271,7 +280,7 @@ public class SQLTemplateParserTest {
         String template = "$a,$a";
         Map<String, Object> parameters = new HashMap<>();
         parameters.put("a", "val");
-        Context context = contextFactory.createContext(parameters);
+        Context context = contextFactory.createContext(parameters, adapter);
         String sql = parseString(template, context);
         assertEquals("val,val", sql);
     }
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/log/CompactSlf4jJdbcEventLoggerTest.java
 
b/cayenne/src/test/java/org/apache/cayenne/log/CompactSlf4jJdbcEventLoggerTest.java
index 32c1f7799..f34d3de85 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/log/CompactSlf4jJdbcEventLoggerTest.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/log/CompactSlf4jJdbcEventLoggerTest.java
@@ -18,7 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.log;
 
-import org.apache.cayenne.access.translator.DbAttributeBinding;
+import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.types.BooleanType;
 import org.apache.cayenne.access.types.CharType;
 import org.apache.cayenne.access.types.ExtendedType;
@@ -98,7 +98,7 @@ public class CompactSlf4jJdbcEventLoggerTest {
     @Test
     public void compactBindings() {
         StringBuilder buffer = new StringBuilder();
-        DbAttributeBinding[] bindings = new DbAttributeBinding[] {
+        ParameterBinding[] bindings = new ParameterBinding[] {
                 createBinding("t0.NAME", 1, "", new CharType(false, false)),
                 createBinding("t0.NAME", 2, 52, new IntegerType()),
                 createBinding("t0.NAME", 3, true, new BooleanType()),
@@ -109,11 +109,10 @@ public class CompactSlf4jJdbcEventLoggerTest {
         assertEquals("[bind: 1->t0.NAME: {'', 52, 'true'}, 2->t0.F_KEY1: 
'true']", buffer.toString());
     }
 
-    private DbAttributeBinding createBinding(String name, int position, Object 
object, ExtendedType type){
-        DbAttributeBinding dbAttributeBinding = new DbAttributeBinding(new 
DbAttribute(name));
-        dbAttributeBinding.setValue(object);
-        dbAttributeBinding.setStatementPosition(position);
-        dbAttributeBinding.setExtendedType(type);
-        return dbAttributeBinding;
+    private ParameterBinding createBinding(String name, int position, Object 
object, ExtendedType type){
+        DbAttribute attribute = new DbAttribute(name);
+        ParameterBinding binding = new ParameterBinding(attribute, 
attribute.getType());
+        binding.include(position, object, type);
+        return binding;
     }
 }


Reply via email to