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 a6bcedd5a DbAdapter refactoring - NativeColumnType with details about 
specific native column types
a6bcedd5a is described below

commit a6bcedd5adf74f3349bac3324ec69f06264dc0ed
Author: Andrus Adamchik <[email protected]>
AuthorDate: Sat Jun 13 12:36:43 2026 -0400

    DbAdapter refactoring - NativeColumnType with details about specific native 
column types
---
 .../merge/factory/IngresMergerTokenFactory.java    |   4 +-
 .../java/org/apache/cayenne/dba/AutoAdapter.java   |   6 +
 .../java/org/apache/cayenne/dba/DbAdapter.java     |  11 ++
 .../java/org/apache/cayenne/dba/JdbcAdapter.java   | 121 +++++++++++++--------
 .../org/apache/cayenne/dba/NativeColumnType.java   |  44 ++++++++
 .../org/apache/cayenne/dba/db2/DB2Adapter.java     |  73 ++++++-------
 .../org/apache/cayenne/dba/derby/DerbyAdapter.java |  73 ++++++-------
 .../cayenne/dba/firebird/FirebirdAdapter.java      |  73 ++++++-------
 .../cayenne/dba/frontbase/FrontBaseAdapter.java    |  68 ++++++------
 .../java/org/apache/cayenne/dba/h2/H2Adapter.java  |  75 +++++++------
 .../apache/cayenne/dba/hsqldb/HSQLDBAdapter.java   |  73 ++++++-------
 .../apache/cayenne/dba/ingres/IngresAdapter.java   |  71 ++++++------
 .../org/apache/cayenne/dba/mysql/MySQLAdapter.java |  63 ++++++-----
 .../apache/cayenne/dba/oracle/OracleAdapter.java   |  65 ++++++-----
 .../cayenne/dba/postgres/PostgresAdapter.java      | 103 ++++++++++--------
 .../apache/cayenne/dba/sqlite/SQLiteAdapter.java   |  75 +++++++------
 .../cayenne/dba/sqlserver/SQLServerAdapter.java    |  65 ++++++-----
 .../apache/cayenne/dba/sybase/SybaseAdapter.java   |  63 ++++++-----
 .../java/org/apache/cayenne/dba/JdbcAdapterIT.java |  11 +-
 .../apache/cayenne/dba/NativeColumnTypeTest.java   |  52 +++++++++
 .../apache/cayenne/dba/oracle/OracleAdapterIT.java |   5 +-
 .../cayenne/dba/postgres/PostgresAdapterIT.java    |  38 +++++++
 22 files changed, 708 insertions(+), 524 deletions(-)

diff --git 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/IngresMergerTokenFactory.java
 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/IngresMergerTokenFactory.java
index 74c929dab..e8e2b3d78 100644
--- 
a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/IngresMergerTokenFactory.java
+++ 
b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/IngresMergerTokenFactory.java
@@ -150,7 +150,7 @@ public class IngresMergerTokenFactory extends 
DefaultMergerTokenFactory {
                 sqlBuffer.append(" ALTER COLUMN ");
                 sqlBuffer.append(context.quotedName(getColumn()));
                 sqlBuffer.append(" ");
-                
sqlBuffer.append(adapter.externalTypesForJdbcType(getColumn().getType())[0]);
+                
sqlBuffer.append(adapter.nativeColumnTypes(getColumn().getType())[0].nativeType());
 
                 if (adapter.typeSupportsLength(getColumn().getType()) && 
getColumn().getMaxLength() > 0) {
                     sqlBuffer.append("(");
@@ -179,7 +179,7 @@ public class IngresMergerTokenFactory extends 
DefaultMergerTokenFactory {
                 sqlBuffer.append(" ALTER COLUMN ");
                 sqlBuffer.append(context.quotedName(getColumn()));
                 sqlBuffer.append(" ");
-                
sqlBuffer.append(adapter.externalTypesForJdbcType(getColumn().getType())[0]);
+                
sqlBuffer.append(adapter.nativeColumnTypes(getColumn().getType())[0].nativeType());
 
                 if (adapter.typeSupportsLength(getColumn().getType()) && 
getColumn().getMaxLength() > 0) {
                     sqlBuffer.append("(");
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 1f9f14e3b..9f270bd9c 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
@@ -190,11 +190,17 @@ public class AutoAdapter implements DbAdapter {
                return getAdapter().createFkConstraint(rel);
        }
 
+       @Deprecated(since = "5.0", forRemoval = true)
        @Override
        public String[] externalTypesForJdbcType(int type) {
                return getAdapter().externalTypesForJdbcType(type);
        }
 
+       @Override
+       public NativeColumnType[] nativeColumnTypes(int type) {
+               return getAdapter().nativeColumnTypes(type);
+       }
+
        @Override
        public ExtendedTypeMap getExtendedTypes() {
                return getAdapter().getExtendedTypes();
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 c76a336ea..3e7503d18 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/DbAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/DbAdapter.java
@@ -162,9 +162,20 @@ public interface DbAdapter {
        /**
         * Returns an array of RDBMS types that can be used with JDBC
         * <code>type</code>. Valid JDBC types are defined in java.sql.Types.
+        *
+        * @deprecated use {@link #nativeColumnTypes(int)}
         */
+       @Deprecated(since = "5.0", forRemoval = true)
        String[] externalTypesForJdbcType(int type);
 
+       /**
+        * Returns the database-native types that the given JDBC 
<code>type</code> (see {@link java.sql.Types}) maps to,
+        * or null if the type is not supported. The first variant is used for 
column DDL.
+        *
+        * @since 5.0
+        */
+       NativeColumnType[] nativeColumnTypes(int type);
+
        /**
         * Returns a map of ExtendedTypes that is used to translate values 
between
         * Java and JDBC layer.
diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
index 691d6d94e..c4cd15ba5 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
@@ -50,6 +50,7 @@ import org.apache.cayenne.query.SQLAction;
 
 import java.sql.PreparedStatement;
 import java.sql.Types;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -67,7 +68,7 @@ public class JdbcAdapter implements DbAdapter {
     private PkGenerator pkGenerator;
     protected QuotingStrategy quotingStrategy;
 
-    protected Map<Integer, String[]> externalTypes;
+    protected Map<Integer, NativeColumnType[]> nativeColumnTypes;
     protected ExtendedTypeMap extendedTypes;
     protected boolean supportsBatchUpdates;
     protected boolean supportsUniqueConstraints;
@@ -96,7 +97,7 @@ public class JdbcAdapter implements DbAdapter {
         this.quotingStrategy = createQuotingStrategy();
 
         this.ejbqlTranslator = createEJBQLTranslator();
-        this.externalTypes = createExternalTypes();
+        this.nativeColumnTypes = indexBySqlType(createExternalTypes());
         this.extendedTypes = new ExtendedTypeMap();
         initExtendedTypes(defaultExtendedTypes, userExtendedTypes, 
extendedTypeFactories, valueObjectTypeRegistry);
     }
@@ -119,50 +120,58 @@ public class JdbcAdapter implements DbAdapter {
     }
 
     /**
-     * Creates the mapping of JDBC types to RDBMS type names used for DDL 
generation. The first name in each
-     * array is used for column DDL; some adapters use subsequent names for 
special cases (e.g. PostgreSQL
-     * serial types for generated columns). Subclasses override this method, 
mapping every type supported by
-     * the target database explicitly, without calling the superclass.
+     * Returns the database-native types supported by this adapter.
      *
      * @since 5.0
      */
-    protected Map<Integer, String[]> createExternalTypes() {
-        Map<Integer, String[]> types = new HashMap<>();
-        types.put(Types.ARRAY, new String[]{"ARRAY"});
-        types.put(Types.BIGINT, new String[]{"BIGINT"});
-        types.put(Types.ROWID, new String[]{"ROWID"});
-        types.put(Types.BINARY, new String[]{"BINARY"});
-        types.put(Types.BIT, new String[]{"BIT"});
-        types.put(Types.BLOB, new String[]{"BLOB"});
-        types.put(Types.BOOLEAN, new String[]{"BOOLEAN"});
-        types.put(Types.CHAR, new String[]{"CHAR"});
-        types.put(Types.NCHAR, new String[]{"NCHAR"});
-        types.put(Types.CLOB, new String[]{"CLOB"});
-        types.put(Types.NCLOB, new String[]{"NCLOB"});
-        types.put(Types.DATALINK, new String[]{"DATALINK"});
-        types.put(Types.DATE, new String[]{"DATE"});
-        types.put(Types.DECIMAL, new String[]{"DECIMAL"});
-        types.put(Types.DOUBLE, new String[]{"DOUBLE"});
-        types.put(Types.FLOAT, new String[]{"FLOAT"});
-        types.put(Types.INTEGER, new String[]{"INTEGER"});
-        types.put(Types.JAVA_OBJECT, new String[]{"JAVA_OBJECT"});
-        types.put(Types.LONGVARBINARY, new String[]{"LONGVARBINARY"});
-        types.put(Types.LONGVARCHAR, new String[]{"LONGVARCHAR"});
-        types.put(Types.LONGNVARCHAR, new String[]{"LONGNVARCHAR"});
-        types.put(Types.NUMERIC, new String[]{"NUMERIC"});
-        types.put(Types.OTHER, new String[]{"OTHER"});
-        types.put(Types.REAL, new String[]{"REAL"});
-        types.put(Types.REF, new String[]{"REF"});
-        types.put(Types.SMALLINT, new String[]{"SMALLINT"});
-        types.put(Types.STRUCT, new String[]{"STRUCT"});
-        types.put(Types.TIME, new String[]{"TIME"});
-        types.put(Types.TIMESTAMP, new String[]{"TIMESTAMP"});
-        types.put(Types.TINYINT, new String[]{"TINYINT"});
-        types.put(Types.VARBINARY, new String[]{"VARBINARY"});
-        types.put(Types.VARCHAR, new String[]{"VARCHAR"});
-        types.put(Types.NVARCHAR, new String[]{"NVARCHAR"});
-        types.put(Types.SQLXML, new String[]{"SQLXML"});
-        return types;
+    protected NativeColumnType[] createExternalTypes() {
+        return new NativeColumnType[]{
+            NativeColumnType.of(Types.ARRAY, "ARRAY"),
+            NativeColumnType.of(Types.BIGINT, "BIGINT"),
+            NativeColumnType.of(Types.ROWID, "ROWID"),
+            NativeColumnType.of(Types.BINARY, "BINARY"),
+            NativeColumnType.of(Types.BIT, "BIT"),
+            NativeColumnType.of(Types.BLOB, "BLOB"),
+            NativeColumnType.of(Types.BOOLEAN, "BOOLEAN"),
+            NativeColumnType.of(Types.CHAR, "CHAR"),
+            NativeColumnType.of(Types.NCHAR, "NCHAR"),
+            NativeColumnType.of(Types.CLOB, "CLOB"),
+            NativeColumnType.of(Types.NCLOB, "NCLOB"),
+            NativeColumnType.of(Types.DATALINK, "DATALINK"),
+            NativeColumnType.of(Types.DATE, "DATE"),
+            NativeColumnType.of(Types.DECIMAL, "DECIMAL"),
+            NativeColumnType.of(Types.DOUBLE, "DOUBLE"),
+            NativeColumnType.of(Types.FLOAT, "FLOAT"),
+            NativeColumnType.of(Types.INTEGER, "INTEGER"),
+            NativeColumnType.of(Types.JAVA_OBJECT, "JAVA_OBJECT"),
+            NativeColumnType.of(Types.LONGVARBINARY, "LONGVARBINARY"),
+            NativeColumnType.of(Types.LONGVARCHAR, "LONGVARCHAR"),
+            NativeColumnType.of(Types.LONGNVARCHAR, "LONGNVARCHAR"),
+            NativeColumnType.of(Types.NUMERIC, "NUMERIC"),
+            NativeColumnType.of(Types.OTHER, "OTHER"),
+            NativeColumnType.of(Types.REAL, "REAL"),
+            NativeColumnType.of(Types.REF, "REF"),
+            NativeColumnType.of(Types.SMALLINT, "SMALLINT"),
+            NativeColumnType.of(Types.STRUCT, "STRUCT"),
+            NativeColumnType.of(Types.TIME, "TIME"),
+            NativeColumnType.of(Types.TIMESTAMP, "TIMESTAMP"),
+            NativeColumnType.of(Types.TINYINT, "TINYINT"),
+            NativeColumnType.of(Types.VARBINARY, "VARBINARY"),
+            NativeColumnType.of(Types.VARCHAR, "VARCHAR"),
+            NativeColumnType.of(Types.NVARCHAR, "NVARCHAR"),
+            NativeColumnType.of(Types.SQLXML, "SQLXML"),
+        };
+    }
+
+    private static Map<Integer, NativeColumnType[]> 
indexBySqlType(NativeColumnType[] types) {
+        Map<Integer, List<NativeColumnType>> grouped = new HashMap<>();
+        for (NativeColumnType type : types) {
+            grouped.computeIfAbsent(type.jdbcType(), key -> new 
ArrayList<>()).add(type);
+        }
+
+        Map<Integer, NativeColumnType[]> indexed = new HashMap<>();
+        grouped.forEach((sqlType, variants) -> indexed.put(sqlType, 
variants.toArray(new NativeColumnType[0])));
+        return indexed;
     }
 
     /**
@@ -398,7 +407,7 @@ public class JdbcAdapter implements DbAdapter {
             return "OTHER";
         }
 
-        String[] types = adapter.externalTypesForJdbcType(columnType);
+        NativeColumnType[] types = adapter.nativeColumnTypes(columnType);
         if (types == null || types.length == 0) {
             String entityName = column.getEntity() != null
                     ? column.getEntity().getFullyQualifiedName()
@@ -406,7 +415,7 @@ public class JdbcAdapter implements DbAdapter {
             throw new CayenneRuntimeException("Undefined type for attribute 
'%s.%s': %s."
                     , entityName, column.getName(), column.getType());
         }
-        return types[0];
+        return types[0].nativeType();
     }
 
     /**
@@ -487,9 +496,29 @@ public class JdbcAdapter implements DbAdapter {
         return buf.toString();
     }
 
+    /**
+     * @deprecated use {@link #nativeColumnTypes(int)}
+     */
+    @Deprecated(since = "5.0", forRemoval = true)
     @Override
     public String[] externalTypesForJdbcType(int type) {
-        return externalTypes.get(type);
+        NativeColumnType[] variants = nativeColumnTypes.get(type);
+        if (variants == null) {
+            return null;
+        }
+        String[] names = new String[variants.length];
+        for (int i = 0; i < variants.length; i++) {
+            names[i] = variants[i].nativeType();
+        }
+        return names;
+    }
+
+    /**
+     * @since 5.0
+     */
+    @Override
+    public NativeColumnType[] nativeColumnTypes(int type) {
+        return nativeColumnTypes.get(type);
     }
 
     @Override
diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/NativeColumnType.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/NativeColumnType.java
new file mode 100644
index 000000000..3d0c347f3
--- /dev/null
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/NativeColumnType.java
@@ -0,0 +1,44 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    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.dba;
+
+/**
+ * Describes a single database-native (external) SQL type that a JDBC type 
(see {@link java.sql.Types}) maps to.
+ * A JDBC type may map to more than one external type, e.g. a primary name 
plus the auto-increment variant used
+ * for generated columns (PostgreSQL "serial").
+ *
+ * @since 5.0
+ */
+public record NativeColumnType(int jdbcType, String nativeType, boolean 
autoIncrement) {
+
+    /**
+     * Creates a plain external type.
+     */
+    public static NativeColumnType of(int jdbcType, String dbType) {
+        return new NativeColumnType(jdbcType, dbType, false);
+    }
+
+    /**
+     * Returns a copy of this type flagged as the auto-increment variant, e.g. 
PostgreSQL "serial" for
+     * {@link java.sql.Types#INTEGER}. Used to render generated columns.
+     */
+    public NativeColumnType asAutoIncrement() {
+        return new NativeColumnType(jdbcType, nativeType, true);
+    }
+}
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 76bc02a39..7270d8b16 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
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.dba.db2;
 
+import org.apache.cayenne.dba.NativeColumnType;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
 import org.apache.cayenne.access.translator.ParameterBinding;
@@ -42,9 +43,7 @@ import org.apache.cayenne.query.SQLAction;
 
 import java.sql.PreparedStatement;
 import java.sql.Types;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * DbAdapter implementation for the DB2 RDBMS.
@@ -65,41 +64,41 @@ public class DB2Adapter extends JdbcAdapter {
     }
 
     @Override
-    protected Map<Integer, String[]> createExternalTypes() {
-        Map<Integer, String[]> types = new HashMap<>();
-        types.put(Types.ARRAY, new String[]{"ARRAY"});
-        types.put(Types.BIGINT, new String[]{"BIGINT"});
-        types.put(Types.BINARY, new String[]{"CHAR FOR BIT DATA"});
-        types.put(Types.BIT, new String[]{"SMALLINT"});
-        types.put(Types.BLOB, new String[]{"BLOB"});
-        types.put(Types.BOOLEAN, new String[]{"SMALLINT"});
-        types.put(Types.CHAR, new String[]{"CHAR"});
-        types.put(Types.CLOB, new String[]{"CLOB"});
-        types.put(Types.DATALINK, new String[]{"DATALINK"});
-        types.put(Types.DATE, new String[]{"DATE"});
-        types.put(Types.DECIMAL, new String[]{"DECIMAL"});
-        types.put(Types.DOUBLE, new String[]{"DOUBLE"});
-        types.put(Types.FLOAT, new String[]{"FLOAT"});
-        types.put(Types.INTEGER, new String[]{"INTEGER"});
-        types.put(Types.JAVA_OBJECT, new String[]{"JAVA_OBJECT"});
-        types.put(Types.LONGNVARCHAR, new String[]{"DBCLOB"});
-        types.put(Types.LONGVARBINARY, new String[]{"BLOB"});
-        types.put(Types.LONGVARCHAR, new String[]{"CLOB"});
-        types.put(Types.NCHAR, new String[]{"GRAPHIC"});
-        types.put(Types.NCLOB, new String[]{"NCLOB"});
-        types.put(Types.NUMERIC, new String[]{"DECIMAL"});
-        types.put(Types.NVARCHAR, new String[]{"VARGRAPHIC"});
-        types.put(Types.OTHER, new String[]{"OTHER"});
-        types.put(Types.REAL, new String[]{"REAL"});
-        types.put(Types.REF, new String[]{"REF"});
-        types.put(Types.SMALLINT, new String[]{"SMALLINT"});
-        types.put(Types.STRUCT, new String[]{"STRUCT"});
-        types.put(Types.TIME, new String[]{"TIME"});
-        types.put(Types.TIMESTAMP, new String[]{"TIMESTAMP"});
-        types.put(Types.TINYINT, new String[]{"SMALLINT"});
-        types.put(Types.VARBINARY, new String[]{"VARCHAR FOR BIT DATA"});
-        types.put(Types.VARCHAR, new String[]{"VARCHAR"});
-        return types;
+    protected NativeColumnType[] createExternalTypes() {
+        return new NativeColumnType[]{
+            NativeColumnType.of(Types.ARRAY, "ARRAY"),
+            NativeColumnType.of(Types.BIGINT, "BIGINT"),
+            NativeColumnType.of(Types.BINARY, "CHAR FOR BIT DATA"),
+            NativeColumnType.of(Types.BIT, "SMALLINT"),
+            NativeColumnType.of(Types.BLOB, "BLOB"),
+            NativeColumnType.of(Types.BOOLEAN, "SMALLINT"),
+            NativeColumnType.of(Types.CHAR, "CHAR"),
+            NativeColumnType.of(Types.CLOB, "CLOB"),
+            NativeColumnType.of(Types.DATALINK, "DATALINK"),
+            NativeColumnType.of(Types.DATE, "DATE"),
+            NativeColumnType.of(Types.DECIMAL, "DECIMAL"),
+            NativeColumnType.of(Types.DOUBLE, "DOUBLE"),
+            NativeColumnType.of(Types.FLOAT, "FLOAT"),
+            NativeColumnType.of(Types.INTEGER, "INTEGER"),
+            NativeColumnType.of(Types.JAVA_OBJECT, "JAVA_OBJECT"),
+            NativeColumnType.of(Types.LONGNVARCHAR, "DBCLOB"),
+            NativeColumnType.of(Types.LONGVARBINARY, "BLOB"),
+            NativeColumnType.of(Types.LONGVARCHAR, "CLOB"),
+            NativeColumnType.of(Types.NCHAR, "GRAPHIC"),
+            NativeColumnType.of(Types.NCLOB, "NCLOB"),
+            NativeColumnType.of(Types.NUMERIC, "DECIMAL"),
+            NativeColumnType.of(Types.NVARCHAR, "VARGRAPHIC"),
+            NativeColumnType.of(Types.OTHER, "OTHER"),
+            NativeColumnType.of(Types.REAL, "REAL"),
+            NativeColumnType.of(Types.REF, "REF"),
+            NativeColumnType.of(Types.SMALLINT, "SMALLINT"),
+            NativeColumnType.of(Types.STRUCT, "STRUCT"),
+            NativeColumnType.of(Types.TIME, "TIME"),
+            NativeColumnType.of(Types.TIMESTAMP, "TIMESTAMP"),
+            NativeColumnType.of(Types.TINYINT, "SMALLINT"),
+            NativeColumnType.of(Types.VARBINARY, "VARCHAR FOR BIT DATA"),
+            NativeColumnType.of(Types.VARCHAR, "VARCHAR"),
+        };
     }
 
     @Override
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 86766fdb3..f6713f4d1 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
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.dba.derby;
 
+import org.apache.cayenne.dba.NativeColumnType;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
 import org.apache.cayenne.access.translator.ParameterBinding;
@@ -43,9 +44,7 @@ import org.apache.cayenne.query.SQLAction;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
 import java.sql.Types;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * DbAdapter implementation for the Derby RDBMS
@@ -71,41 +70,41 @@ public class DerbyAdapter extends JdbcAdapter {
     }
 
     @Override
-    protected Map<Integer, String[]> createExternalTypes() {
-        Map<Integer, String[]> types = new HashMap<>();
-        types.put(Types.ARRAY, new String[]{"ARRAY"});
-        types.put(Types.BIGINT, new String[]{"BIGINT"});
-        types.put(Types.BINARY, new String[]{"CHAR FOR BIT DATA"});
-        types.put(Types.BIT, new String[]{"SMALLINT"});
-        types.put(Types.BLOB, new String[]{"BLOB"});
-        types.put(Types.BOOLEAN, new String[]{"BOOLEAN"});
-        types.put(Types.CHAR, new String[]{"CHAR"});
-        types.put(Types.CLOB, new String[]{"CLOB"});
-        types.put(Types.DATALINK, new String[]{"DATALINK"});
-        types.put(Types.DATE, new String[]{"DATE"});
-        types.put(Types.DECIMAL, new String[]{"DECIMAL"});
-        types.put(Types.DOUBLE, new String[]{"DOUBLE PRECISION"});
-        types.put(Types.FLOAT, new String[]{"DOUBLE PRECISION"});
-        types.put(Types.INTEGER, new String[]{"INTEGER"});
-        types.put(Types.JAVA_OBJECT, new String[]{"JAVA_OBJECT"});
-        types.put(Types.LONGNVARCHAR, new String[]{"LONG VARCHAR"});
-        types.put(Types.LONGVARBINARY, new String[]{"LONG VARCHAR FOR BIT 
DATA"});
-        types.put(Types.LONGVARCHAR, new String[]{"LONG VARCHAR"});
-        types.put(Types.NCHAR, new String[]{"CHAR"});
-        types.put(Types.NCLOB, new String[]{"CLOB"});
-        types.put(Types.NUMERIC, new String[]{"DECIMAL"});
-        types.put(Types.NVARCHAR, new String[]{"VARCHAR"});
-        types.put(Types.OTHER, new String[]{"OTHER"});
-        types.put(Types.REAL, new String[]{"REAL"});
-        types.put(Types.REF, new String[]{"REF"});
-        types.put(Types.SMALLINT, new String[]{"SMALLINT"});
-        types.put(Types.STRUCT, new String[]{"STRUCT"});
-        types.put(Types.TIME, new String[]{"TIME"});
-        types.put(Types.TIMESTAMP, new String[]{"TIMESTAMP"});
-        types.put(Types.TINYINT, new String[]{"SMALLINT"});
-        types.put(Types.VARBINARY, new String[]{"VARCHAR FOR BIT DATA"});
-        types.put(Types.VARCHAR, new String[]{"VARCHAR"});
-        return types;
+    protected NativeColumnType[] createExternalTypes() {
+        return new NativeColumnType[]{
+            NativeColumnType.of(Types.ARRAY, "ARRAY"),
+            NativeColumnType.of(Types.BIGINT, "BIGINT"),
+            NativeColumnType.of(Types.BINARY, "CHAR FOR BIT DATA"),
+            NativeColumnType.of(Types.BIT, "SMALLINT"),
+            NativeColumnType.of(Types.BLOB, "BLOB"),
+            NativeColumnType.of(Types.BOOLEAN, "BOOLEAN"),
+            NativeColumnType.of(Types.CHAR, "CHAR"),
+            NativeColumnType.of(Types.CLOB, "CLOB"),
+            NativeColumnType.of(Types.DATALINK, "DATALINK"),
+            NativeColumnType.of(Types.DATE, "DATE"),
+            NativeColumnType.of(Types.DECIMAL, "DECIMAL"),
+            NativeColumnType.of(Types.DOUBLE, "DOUBLE PRECISION"),
+            NativeColumnType.of(Types.FLOAT, "DOUBLE PRECISION"),
+            NativeColumnType.of(Types.INTEGER, "INTEGER"),
+            NativeColumnType.of(Types.JAVA_OBJECT, "JAVA_OBJECT"),
+            NativeColumnType.of(Types.LONGNVARCHAR, "LONG VARCHAR"),
+            NativeColumnType.of(Types.LONGVARBINARY, "LONG VARCHAR FOR BIT 
DATA"),
+            NativeColumnType.of(Types.LONGVARCHAR, "LONG VARCHAR"),
+            NativeColumnType.of(Types.NCHAR, "CHAR"),
+            NativeColumnType.of(Types.NCLOB, "CLOB"),
+            NativeColumnType.of(Types.NUMERIC, "DECIMAL"),
+            NativeColumnType.of(Types.NVARCHAR, "VARCHAR"),
+            NativeColumnType.of(Types.OTHER, "OTHER"),
+            NativeColumnType.of(Types.REAL, "REAL"),
+            NativeColumnType.of(Types.REF, "REF"),
+            NativeColumnType.of(Types.SMALLINT, "SMALLINT"),
+            NativeColumnType.of(Types.STRUCT, "STRUCT"),
+            NativeColumnType.of(Types.TIME, "TIME"),
+            NativeColumnType.of(Types.TIMESTAMP, "TIMESTAMP"),
+            NativeColumnType.of(Types.TINYINT, "SMALLINT"),
+            NativeColumnType.of(Types.VARBINARY, "VARCHAR FOR BIT DATA"),
+            NativeColumnType.of(Types.VARCHAR, "VARCHAR"),
+        };
     }
 
     /**
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java
index 61611922d..466d0112d 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.dba.firebird;
 
+import org.apache.cayenne.dba.NativeColumnType;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
 import org.apache.cayenne.access.translator.ejbql.EJBQLTranslator;
@@ -37,9 +38,7 @@ import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
 
 import java.sql.Types;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * DbAdapter implementation for the FirebirdSQL RDBMS
@@ -63,41 +62,41 @@ public class FirebirdAdapter extends JdbcAdapter {
     }
 
     @Override
-    protected Map<Integer, String[]> createExternalTypes() {
-        Map<Integer, String[]> types = new HashMap<>();
-        types.put(Types.ARRAY, new String[]{"BLOB"});
-        types.put(Types.BIGINT, new String[]{"BIGINT"});
-        types.put(Types.BINARY, new String[]{"BLOB"});
-        types.put(Types.BIT, new String[]{"SMALLINT"});
-        types.put(Types.BLOB, new String[]{"BLOB"});
-        types.put(Types.BOOLEAN, new String[]{"SMALLINT"});
-        types.put(Types.CHAR, new String[]{"VARCHAR"});
-        types.put(Types.CLOB, new String[]{"BLOB SUB_TYPE TEXT"});
-        types.put(Types.DATALINK, new String[]{"BLOB"});
-        types.put(Types.DATE, new String[]{"DATE"});
-        types.put(Types.DECIMAL, new String[]{"DECIMAL"});
-        types.put(Types.DOUBLE, new String[]{"DOUBLE PRECISION"});
-        types.put(Types.FLOAT, new String[]{"DOUBLE PRECISION"});
-        types.put(Types.INTEGER, new String[]{"INTEGER"});
-        types.put(Types.JAVA_OBJECT, new String[]{"BLOB"});
-        types.put(Types.LONGNVARCHAR, new String[]{"BLOB SUB_TYPE TEXT"});
-        types.put(Types.LONGVARBINARY, new String[]{"BLOB"});
-        types.put(Types.LONGVARCHAR, new String[]{"BLOB SUB_TYPE TEXT"});
-        types.put(Types.NCHAR, new String[]{"CHAR CHARACTER SET UNICODE_FSS"});
-        types.put(Types.NCLOB, new String[]{"BLOB SUB_TYPE TEXT"});
-        types.put(Types.NUMERIC, new String[]{"DECIMAL"});
-        types.put(Types.NVARCHAR, new String[]{"VARCHAR CHARACTER SET 
UNICODE_FSS"});
-        types.put(Types.OTHER, new String[]{"BLOB"});
-        types.put(Types.REAL, new String[]{"REAL"});
-        types.put(Types.REF, new String[]{"BLOB"});
-        types.put(Types.SMALLINT, new String[]{"SMALLINT"});
-        types.put(Types.STRUCT, new String[]{"BLOB"});
-        types.put(Types.TIME, new String[]{"TIME"});
-        types.put(Types.TIMESTAMP, new String[]{"TIMESTAMP"});
-        types.put(Types.TINYINT, new String[]{"SMALLINT"});
-        types.put(Types.VARBINARY, new String[]{"BLOB"});
-        types.put(Types.VARCHAR, new String[]{"VARCHAR"});
-        return types;
+    protected NativeColumnType[] createExternalTypes() {
+        return new NativeColumnType[]{
+            NativeColumnType.of(Types.ARRAY, "BLOB"),
+            NativeColumnType.of(Types.BIGINT, "BIGINT"),
+            NativeColumnType.of(Types.BINARY, "BLOB"),
+            NativeColumnType.of(Types.BIT, "SMALLINT"),
+            NativeColumnType.of(Types.BLOB, "BLOB"),
+            NativeColumnType.of(Types.BOOLEAN, "SMALLINT"),
+            NativeColumnType.of(Types.CHAR, "VARCHAR"),
+            NativeColumnType.of(Types.CLOB, "BLOB SUB_TYPE TEXT"),
+            NativeColumnType.of(Types.DATALINK, "BLOB"),
+            NativeColumnType.of(Types.DATE, "DATE"),
+            NativeColumnType.of(Types.DECIMAL, "DECIMAL"),
+            NativeColumnType.of(Types.DOUBLE, "DOUBLE PRECISION"),
+            NativeColumnType.of(Types.FLOAT, "DOUBLE PRECISION"),
+            NativeColumnType.of(Types.INTEGER, "INTEGER"),
+            NativeColumnType.of(Types.JAVA_OBJECT, "BLOB"),
+            NativeColumnType.of(Types.LONGNVARCHAR, "BLOB SUB_TYPE TEXT"),
+            NativeColumnType.of(Types.LONGVARBINARY, "BLOB"),
+            NativeColumnType.of(Types.LONGVARCHAR, "BLOB SUB_TYPE TEXT"),
+            NativeColumnType.of(Types.NCHAR, "CHAR CHARACTER SET UNICODE_FSS"),
+            NativeColumnType.of(Types.NCLOB, "BLOB SUB_TYPE TEXT"),
+            NativeColumnType.of(Types.NUMERIC, "DECIMAL"),
+            NativeColumnType.of(Types.NVARCHAR, "VARCHAR CHARACTER SET 
UNICODE_FSS"),
+            NativeColumnType.of(Types.OTHER, "BLOB"),
+            NativeColumnType.of(Types.REAL, "REAL"),
+            NativeColumnType.of(Types.REF, "BLOB"),
+            NativeColumnType.of(Types.SMALLINT, "SMALLINT"),
+            NativeColumnType.of(Types.STRUCT, "BLOB"),
+            NativeColumnType.of(Types.TIME, "TIME"),
+            NativeColumnType.of(Types.TIMESTAMP, "TIMESTAMP"),
+            NativeColumnType.of(Types.TINYINT, "SMALLINT"),
+            NativeColumnType.of(Types.VARBINARY, "BLOB"),
+            NativeColumnType.of(Types.VARCHAR, "VARCHAR"),
+        };
     }
     
     protected void configureExtendedTypes(ExtendedTypeMap map) {
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
index e60cc6ff6..b44daf5a8 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.dba.frontbase;
 
+import org.apache.cayenne.dba.NativeColumnType;
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
@@ -40,10 +41,8 @@ import org.apache.cayenne.query.SQLAction;
 import java.sql.Types;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 
 /**
  * DbAdapter implementation for FrontBase RDBMS
@@ -69,36 +68,36 @@ public class FrontBaseAdapter extends JdbcAdapter {
     }
 
     @Override
-    protected Map<Integer, String[]> createExternalTypes() {
-        Map<Integer, String[]> types = new HashMap<>();
-        types.put(Types.BIGINT, new String[]{"LONGINT"});
-        types.put(Types.BINARY, new String[]{"BIT"});
-        types.put(Types.BIT, new String[]{"BIT"});
-        types.put(Types.BLOB, new String[]{"BLOB"});
-        types.put(Types.BOOLEAN, new String[]{"BOOLEAN"});
-        types.put(Types.CHAR, new String[]{"CHAR"});
-        types.put(Types.CLOB, new String[]{"CLOB"});
-        types.put(Types.DATE, new String[]{"DATE"});
-        types.put(Types.DECIMAL, new String[]{"DECIMAL"});
-        types.put(Types.DOUBLE, new String[]{"DOUBLE PRECISION"});
-        types.put(Types.FLOAT, new String[]{"FLOAT"});
-        types.put(Types.INTEGER, new String[]{"INTEGER"});
-        types.put(Types.LONGNVARCHAR, new String[]{"CHAR VARYING"});
-        types.put(Types.LONGVARBINARY, new String[]{"BLOB"});
-        types.put(Types.LONGVARCHAR, new String[]{"CHAR VARYING"});
-        types.put(Types.NCHAR, new String[]{"NCHAR"});
-        types.put(Types.NCLOB, new String[]{"CLOB"});
-        types.put(Types.NUMERIC, new String[]{"NUMERIC"});
-        types.put(Types.NVARCHAR, new String[]{"CHAR VARYING"});
-        types.put(Types.OTHER, new String[]{"INTERVAL"});
-        types.put(Types.REAL, new String[]{"REAL"});
-        types.put(Types.SMALLINT, new String[]{"SMALLINT"});
-        types.put(Types.TIME, new String[]{"TIME"});
-        types.put(Types.TIMESTAMP, new String[]{"TIMESTAMP"});
-        types.put(Types.TINYINT, new String[]{"SMALLINT"});
-        types.put(Types.VARBINARY, new String[]{"BIT VARYING"});
-        types.put(Types.VARCHAR, new String[]{"CHAR VARYING"});
-        return types;
+    protected NativeColumnType[] createExternalTypes() {
+        return new NativeColumnType[]{
+            NativeColumnType.of(Types.BIGINT, "LONGINT"),
+            NativeColumnType.of(Types.BINARY, "BIT"),
+            NativeColumnType.of(Types.BIT, "BIT"),
+            NativeColumnType.of(Types.BLOB, "BLOB"),
+            NativeColumnType.of(Types.BOOLEAN, "BOOLEAN"),
+            NativeColumnType.of(Types.CHAR, "CHAR"),
+            NativeColumnType.of(Types.CLOB, "CLOB"),
+            NativeColumnType.of(Types.DATE, "DATE"),
+            NativeColumnType.of(Types.DECIMAL, "DECIMAL"),
+            NativeColumnType.of(Types.DOUBLE, "DOUBLE PRECISION"),
+            NativeColumnType.of(Types.FLOAT, "FLOAT"),
+            NativeColumnType.of(Types.INTEGER, "INTEGER"),
+            NativeColumnType.of(Types.LONGNVARCHAR, "CHAR VARYING"),
+            NativeColumnType.of(Types.LONGVARBINARY, "BLOB"),
+            NativeColumnType.of(Types.LONGVARCHAR, "CHAR VARYING"),
+            NativeColumnType.of(Types.NCHAR, "NCHAR"),
+            NativeColumnType.of(Types.NCLOB, "CLOB"),
+            NativeColumnType.of(Types.NUMERIC, "NUMERIC"),
+            NativeColumnType.of(Types.NVARCHAR, "CHAR VARYING"),
+            NativeColumnType.of(Types.OTHER, "INTERVAL"),
+            NativeColumnType.of(Types.REAL, "REAL"),
+            NativeColumnType.of(Types.SMALLINT, "SMALLINT"),
+            NativeColumnType.of(Types.TIME, "TIME"),
+            NativeColumnType.of(Types.TIMESTAMP, "TIMESTAMP"),
+            NativeColumnType.of(Types.TINYINT, "SMALLINT"),
+            NativeColumnType.of(Types.VARBINARY, "BIT VARYING"),
+            NativeColumnType.of(Types.VARCHAR, "CHAR VARYING"),
+        };
     }
 
     /**
@@ -152,14 +151,13 @@ public class FrontBaseAdapter extends JdbcAdapter {
                         , ent.getFullyQualifiedName(), at.getName());
             }
 
-            String[] types = externalTypesForJdbcType(at.getType());
+            NativeColumnType[] types = nativeColumnTypes(at.getType());
             if (types == null || types.length == 0) {
                 throw new CayenneRuntimeException("Undefined type for 
attribute '%s.%s': %s"
                         , ent.getFullyQualifiedName(), at.getName(), 
at.getType());
             }
 
-            String type = types[0];
-            buf.append(context.quotedName(at)).append(' ').append(type);
+            buf.append(context.quotedName(at)).append(' 
').append(types[0].nativeType());
 
             // Mapping LONGVARCHAR without length creates a column with length
             // "1" which
diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
index 7e1d5decd..3e3ace416 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.dba.h2;
 
+import org.apache.cayenne.dba.NativeColumnType;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
 import org.apache.cayenne.access.translator.ejbql.EJBQLTranslator;
@@ -37,9 +38,7 @@ import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
 
 import java.sql.Types;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * DbAdapter implementation for H2 RDBMS.
@@ -57,42 +56,42 @@ public class H2Adapter extends JdbcAdapter {
     }
 
     @Override
-    protected Map<Integer, String[]> createExternalTypes() {
-        Map<Integer, String[]> types = new HashMap<>();
-        types.put(Types.ARRAY, new String[]{"ARRAY"});
-        types.put(Types.BIGINT, new String[]{"BIGINT"});
-        types.put(Types.BINARY, new String[]{"BINARY"});
-        types.put(Types.BIT, new String[]{"BIT"});
-        types.put(Types.BLOB, new String[]{"BLOB"});
-        types.put(Types.BOOLEAN, new String[]{"BOOLEAN"});
-        types.put(Types.CHAR, new String[]{"CHAR"});
-        types.put(Types.CLOB, new String[]{"CLOB"});
-        types.put(Types.DATALINK, new String[]{"DATALINK"});
-        types.put(Types.DATE, new String[]{"DATE"});
-        types.put(Types.DECIMAL, new String[]{"DECIMAL"});
-        types.put(Types.DOUBLE, new String[]{"DOUBLE"});
-        types.put(Types.FLOAT, new String[]{"FLOAT"});
-        types.put(Types.INTEGER, new String[]{"INTEGER"});
-        types.put(Types.JAVA_OBJECT, new String[]{"JAVA_OBJECT"});
-        types.put(Types.LONGNVARCHAR, new String[]{"NCLOB"});
-        types.put(Types.LONGVARBINARY, new String[]{"LONGVARBINARY"});
-        types.put(Types.LONGVARCHAR, new String[]{"CLOB"});
-        types.put(Types.NCHAR, new String[]{"NCHAR"});
-        types.put(Types.NCLOB, new String[]{"NCLOB"});
-        types.put(Types.NUMERIC, new String[]{"NUMERIC"});
-        types.put(Types.NVARCHAR, new String[]{"NVARCHAR"});
-        types.put(Types.OTHER, new String[]{"OTHER"});
-        types.put(Types.REAL, new String[]{"REAL"});
-        types.put(Types.REF, new String[]{"REF"});
-        types.put(Types.SMALLINT, new String[]{"SMALLINT"});
-        types.put(Types.SQLXML, new String[]{"NCLOB"});
-        types.put(Types.STRUCT, new String[]{"STRUCT"});
-        types.put(Types.TIME, new String[]{"TIME"});
-        types.put(Types.TIMESTAMP, new String[]{"TIMESTAMP"});
-        types.put(Types.TINYINT, new String[]{"TINYINT"});
-        types.put(Types.VARBINARY, new String[]{"VARBINARY"});
-        types.put(Types.VARCHAR, new String[]{"VARCHAR"});
-        return types;
+    protected NativeColumnType[] createExternalTypes() {
+        return new NativeColumnType[]{
+            NativeColumnType.of(Types.ARRAY, "ARRAY"),
+            NativeColumnType.of(Types.BIGINT, "BIGINT"),
+            NativeColumnType.of(Types.BINARY, "BINARY"),
+            NativeColumnType.of(Types.BIT, "BIT"),
+            NativeColumnType.of(Types.BLOB, "BLOB"),
+            NativeColumnType.of(Types.BOOLEAN, "BOOLEAN"),
+            NativeColumnType.of(Types.CHAR, "CHAR"),
+            NativeColumnType.of(Types.CLOB, "CLOB"),
+            NativeColumnType.of(Types.DATALINK, "DATALINK"),
+            NativeColumnType.of(Types.DATE, "DATE"),
+            NativeColumnType.of(Types.DECIMAL, "DECIMAL"),
+            NativeColumnType.of(Types.DOUBLE, "DOUBLE"),
+            NativeColumnType.of(Types.FLOAT, "FLOAT"),
+            NativeColumnType.of(Types.INTEGER, "INTEGER"),
+            NativeColumnType.of(Types.JAVA_OBJECT, "JAVA_OBJECT"),
+            NativeColumnType.of(Types.LONGNVARCHAR, "NCLOB"),
+            NativeColumnType.of(Types.LONGVARBINARY, "LONGVARBINARY"),
+            NativeColumnType.of(Types.LONGVARCHAR, "CLOB"),
+            NativeColumnType.of(Types.NCHAR, "NCHAR"),
+            NativeColumnType.of(Types.NCLOB, "NCLOB"),
+            NativeColumnType.of(Types.NUMERIC, "NUMERIC"),
+            NativeColumnType.of(Types.NVARCHAR, "NVARCHAR"),
+            NativeColumnType.of(Types.OTHER, "OTHER"),
+            NativeColumnType.of(Types.REAL, "REAL"),
+            NativeColumnType.of(Types.REF, "REF"),
+            NativeColumnType.of(Types.SMALLINT, "SMALLINT"),
+            NativeColumnType.of(Types.SQLXML, "NCLOB"),
+            NativeColumnType.of(Types.STRUCT, "STRUCT"),
+            NativeColumnType.of(Types.TIME, "TIME"),
+            NativeColumnType.of(Types.TIMESTAMP, "TIMESTAMP"),
+            NativeColumnType.of(Types.TINYINT, "TINYINT"),
+            NativeColumnType.of(Types.VARBINARY, "VARBINARY"),
+            NativeColumnType.of(Types.VARCHAR, "VARCHAR"),
+        };
     }
 
     @Override
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
index cd1ab162b..103517e8c 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.dba.hsqldb;
 
+import org.apache.cayenne.dba.NativeColumnType;
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
@@ -46,10 +47,8 @@ import org.apache.cayenne.query.SQLAction;
 
 import java.sql.Types;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 
 /**
  * DbAdapter implementation for the HSQLDB RDBMS.
@@ -69,41 +68,41 @@ public class HSQLDBAdapter extends JdbcAdapter {
     }
 
     @Override
-    protected Map<Integer, String[]> createExternalTypes() {
-        Map<Integer, String[]> types = new HashMap<>();
-        types.put(Types.ARRAY, new String[]{"ARRAY"});
-        types.put(Types.BIGINT, new String[]{"BIGINT"});
-        types.put(Types.BINARY, new String[]{"BINARY"});
-        types.put(Types.BIT, new String[]{"BIT"});
-        types.put(Types.BLOB, new String[]{"LONGVARBINARY"});
-        types.put(Types.BOOLEAN, new String[]{"BOOLEAN"});
-        types.put(Types.CHAR, new String[]{"CHAR"});
-        types.put(Types.CLOB, new String[]{"LONGVARCHAR"});
-        types.put(Types.DATALINK, new String[]{"DATALINK"});
-        types.put(Types.DATE, new String[]{"DATE"});
-        types.put(Types.DECIMAL, new String[]{"DECIMAL"});
-        types.put(Types.DOUBLE, new String[]{"DOUBLE"});
-        types.put(Types.FLOAT, new String[]{"FLOAT"});
-        types.put(Types.INTEGER, new String[]{"INTEGER"});
-        types.put(Types.JAVA_OBJECT, new String[]{"JAVA_OBJECT"});
-        types.put(Types.LONGNVARCHAR, new String[]{"LONGVARCHAR"});
-        types.put(Types.LONGVARBINARY, new String[]{"LONGVARBINARY"});
-        types.put(Types.LONGVARCHAR, new String[]{"LONGVARCHAR"});
-        types.put(Types.NCHAR, new String[]{"CHAR"});
-        types.put(Types.NCLOB, new String[]{"LONGVARCHAR"});
-        types.put(Types.NUMERIC, new String[]{"NUMERIC"});
-        types.put(Types.NVARCHAR, new String[]{"VARCHAR"});
-        types.put(Types.OTHER, new String[]{"OTHER"});
-        types.put(Types.REAL, new String[]{"REAL"});
-        types.put(Types.REF, new String[]{"REF"});
-        types.put(Types.SMALLINT, new String[]{"SMALLINT"});
-        types.put(Types.STRUCT, new String[]{"STRUCT"});
-        types.put(Types.TIME, new String[]{"TIME"});
-        types.put(Types.TIMESTAMP, new String[]{"TIMESTAMP"});
-        types.put(Types.TINYINT, new String[]{"TINYINT"});
-        types.put(Types.VARBINARY, new String[]{"VARBINARY"});
-        types.put(Types.VARCHAR, new String[]{"VARCHAR"});
-        return types;
+    protected NativeColumnType[] createExternalTypes() {
+        return new NativeColumnType[]{
+            NativeColumnType.of(Types.ARRAY, "ARRAY"),
+            NativeColumnType.of(Types.BIGINT, "BIGINT"),
+            NativeColumnType.of(Types.BINARY, "BINARY"),
+            NativeColumnType.of(Types.BIT, "BIT"),
+            NativeColumnType.of(Types.BLOB, "LONGVARBINARY"),
+            NativeColumnType.of(Types.BOOLEAN, "BOOLEAN"),
+            NativeColumnType.of(Types.CHAR, "CHAR"),
+            NativeColumnType.of(Types.CLOB, "LONGVARCHAR"),
+            NativeColumnType.of(Types.DATALINK, "DATALINK"),
+            NativeColumnType.of(Types.DATE, "DATE"),
+            NativeColumnType.of(Types.DECIMAL, "DECIMAL"),
+            NativeColumnType.of(Types.DOUBLE, "DOUBLE"),
+            NativeColumnType.of(Types.FLOAT, "FLOAT"),
+            NativeColumnType.of(Types.INTEGER, "INTEGER"),
+            NativeColumnType.of(Types.JAVA_OBJECT, "JAVA_OBJECT"),
+            NativeColumnType.of(Types.LONGNVARCHAR, "LONGVARCHAR"),
+            NativeColumnType.of(Types.LONGVARBINARY, "LONGVARBINARY"),
+            NativeColumnType.of(Types.LONGVARCHAR, "LONGVARCHAR"),
+            NativeColumnType.of(Types.NCHAR, "CHAR"),
+            NativeColumnType.of(Types.NCLOB, "LONGVARCHAR"),
+            NativeColumnType.of(Types.NUMERIC, "NUMERIC"),
+            NativeColumnType.of(Types.NVARCHAR, "VARCHAR"),
+            NativeColumnType.of(Types.OTHER, "OTHER"),
+            NativeColumnType.of(Types.REAL, "REAL"),
+            NativeColumnType.of(Types.REF, "REF"),
+            NativeColumnType.of(Types.SMALLINT, "SMALLINT"),
+            NativeColumnType.of(Types.STRUCT, "STRUCT"),
+            NativeColumnType.of(Types.TIME, "TIME"),
+            NativeColumnType.of(Types.TIMESTAMP, "TIMESTAMP"),
+            NativeColumnType.of(Types.TINYINT, "TINYINT"),
+            NativeColumnType.of(Types.VARBINARY, "VARBINARY"),
+            NativeColumnType.of(Types.VARCHAR, "VARCHAR"),
+        };
     }
 
     /**
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
index fd15c1bd3..3de597ef8 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.dba.ingres;
 
+import org.apache.cayenne.dba.NativeColumnType;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
 import org.apache.cayenne.access.translator.ParameterBinding;
@@ -37,9 +38,7 @@ import org.apache.cayenne.query.SQLAction;
 
 import java.sql.PreparedStatement;
 import java.sql.Types;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * DbAdapter implementation for Ingres RDBMS.
@@ -57,40 +56,40 @@ public class IngresAdapter extends JdbcAdapter {
     }
 
     @Override
-    protected Map<Integer, String[]> createExternalTypes() {
-        Map<Integer, String[]> types = new HashMap<>();
-        types.put(Types.ARRAY, new String[]{"ARRAY"});
-        types.put(Types.BIGINT, new String[]{"BIGINT"});
-        types.put(Types.BINARY, new String[]{"BYTE"});
-        types.put(Types.BIT, new String[]{"TINYINT"});
-        types.put(Types.BLOB, new String[]{"BLOB"});
-        types.put(Types.BOOLEAN, new String[]{"BOOLEAN"});
-        types.put(Types.CHAR, new String[]{"CHAR"});
-        types.put(Types.CLOB, new String[]{"CLOB"});
-        types.put(Types.DATALINK, new String[]{"DATALINK"});
-        types.put(Types.DATE, new String[]{"DATE"});
-        types.put(Types.DECIMAL, new String[]{"DECIMAL"});
-        types.put(Types.DOUBLE, new String[]{"FLOAT"});
-        types.put(Types.FLOAT, new String[]{"FLOAT"});
-        types.put(Types.INTEGER, new String[]{"INTEGER"});
-        types.put(Types.JAVA_OBJECT, new String[]{"JAVA_OBJECT"});
-        types.put(Types.LONGNVARCHAR, new String[]{"LONG NVARCHAR"});
-        types.put(Types.LONGVARBINARY, new String[]{"LONG BYTE"});
-        types.put(Types.LONGVARCHAR, new String[]{"LONG VARCHAR"});
-        types.put(Types.NCHAR, new String[]{"NCHAR"});
-        types.put(Types.NCLOB, new String[]{"LONG NVARCHAR"});
-        types.put(Types.NUMERIC, new String[]{"NUMERIC"});
-        types.put(Types.NVARCHAR, new String[]{"NVARCHAR"});
-        types.put(Types.OTHER, new String[]{"OTHER"});
-        types.put(Types.REAL, new String[]{"REAL"});
-        types.put(Types.REF, new String[]{"REF"});
-        types.put(Types.SMALLINT, new String[]{"SMALLINT"});
-        types.put(Types.TIME, new String[]{"TIME"});
-        types.put(Types.TIMESTAMP, new String[]{"TIMESTAMP"});
-        types.put(Types.TINYINT, new String[]{"TINYINT"});
-        types.put(Types.VARBINARY, new String[]{"BYTE VARYING"});
-        types.put(Types.VARCHAR, new String[]{"VARCHAR"});
-        return types;
+    protected NativeColumnType[] createExternalTypes() {
+        return new NativeColumnType[]{
+            NativeColumnType.of(Types.ARRAY, "ARRAY"),
+            NativeColumnType.of(Types.BIGINT, "BIGINT"),
+            NativeColumnType.of(Types.BINARY, "BYTE"),
+            NativeColumnType.of(Types.BIT, "TINYINT"),
+            NativeColumnType.of(Types.BLOB, "BLOB"),
+            NativeColumnType.of(Types.BOOLEAN, "BOOLEAN"),
+            NativeColumnType.of(Types.CHAR, "CHAR"),
+            NativeColumnType.of(Types.CLOB, "CLOB"),
+            NativeColumnType.of(Types.DATALINK, "DATALINK"),
+            NativeColumnType.of(Types.DATE, "DATE"),
+            NativeColumnType.of(Types.DECIMAL, "DECIMAL"),
+            NativeColumnType.of(Types.DOUBLE, "FLOAT"),
+            NativeColumnType.of(Types.FLOAT, "FLOAT"),
+            NativeColumnType.of(Types.INTEGER, "INTEGER"),
+            NativeColumnType.of(Types.JAVA_OBJECT, "JAVA_OBJECT"),
+            NativeColumnType.of(Types.LONGNVARCHAR, "LONG NVARCHAR"),
+            NativeColumnType.of(Types.LONGVARBINARY, "LONG BYTE"),
+            NativeColumnType.of(Types.LONGVARCHAR, "LONG VARCHAR"),
+            NativeColumnType.of(Types.NCHAR, "NCHAR"),
+            NativeColumnType.of(Types.NCLOB, "LONG NVARCHAR"),
+            NativeColumnType.of(Types.NUMERIC, "NUMERIC"),
+            NativeColumnType.of(Types.NVARCHAR, "NVARCHAR"),
+            NativeColumnType.of(Types.OTHER, "OTHER"),
+            NativeColumnType.of(Types.REAL, "REAL"),
+            NativeColumnType.of(Types.REF, "REF"),
+            NativeColumnType.of(Types.SMALLINT, "SMALLINT"),
+            NativeColumnType.of(Types.TIME, "TIME"),
+            NativeColumnType.of(Types.TIMESTAMP, "TIMESTAMP"),
+            NativeColumnType.of(Types.TINYINT, "TINYINT"),
+            NativeColumnType.of(Types.VARBINARY, "BYTE VARYING"),
+            NativeColumnType.of(Types.VARCHAR, "VARCHAR"),
+        };
     }
 
     /**
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 6cfde192b..32b8ef853 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
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.dba.mysql;
 
+import org.apache.cayenne.dba.NativeColumnType;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
 import org.apache.cayenne.access.translator.ejbql.EJBQLTranslator;
@@ -53,10 +54,8 @@ import java.sql.Types;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 
 /**
  * DbAdapter implementation for MySQL RDBMS.
@@ -84,36 +83,36 @@ public class MySQLAdapter extends JdbcAdapter {
     }
 
     @Override
-    protected Map<Integer, String[]> createExternalTypes() {
-        Map<Integer, String[]> types = new HashMap<>();
-        types.put(Types.BIGINT, new String[]{"BIGINT", "INT UNSIGNED", 
"INTEGER UNSIGNED", "MEDIUMINT UNSIGNED"});
-        types.put(Types.BINARY, new String[]{"BINARY"});
-        types.put(Types.BIT, new String[]{"BIT"});
-        types.put(Types.BLOB, new String[]{"LONGBLOB"});
-        types.put(Types.BOOLEAN, new String[]{"BOOL"});
-        types.put(Types.CHAR, new String[]{"CHAR"});
-        types.put(Types.CLOB, new String[]{"LONGTEXT"});
-        types.put(Types.DATE, new String[]{"DATE"});
-        types.put(Types.DECIMAL, new String[]{"DECIMAL"});
-        types.put(Types.DOUBLE, new String[]{"DOUBLE"});
-        types.put(Types.FLOAT, new String[]{"FLOAT"});
-        types.put(Types.INTEGER, new String[]{"INT", "INTEGER"});
-        types.put(Types.LONGNVARCHAR, new String[]{"LONGTEXT"});
-        types.put(Types.LONGVARBINARY, new String[]{"LONGBLOB"});
-        types.put(Types.LONGVARCHAR, new String[]{"LONGTEXT"});
-        types.put(Types.NCHAR, new String[]{"CHAR"});
-        types.put(Types.NCLOB, new String[]{"LONGTEXT"});
-        types.put(Types.NUMERIC, new String[]{"DECIMAL", "NUMERIC"});
-        types.put(Types.NVARCHAR, new String[]{"VARCHAR"});
-        types.put(Types.REAL, new String[]{"DOUBLE", "REAL"});
-        types.put(Types.SMALLINT, new String[]{"SMALLINT"});
-        types.put(Types.SQLXML, new String[]{"LONGTEXT"});
-        types.put(Types.TIME, new String[]{"TIME"});
-        types.put(Types.TIMESTAMP, new String[]{"DATETIME", "TIMESTAMP"});
-        types.put(Types.TINYINT, new String[]{"TINYINT"});
-        types.put(Types.VARBINARY, new String[]{"VARBINARY"});
-        types.put(Types.VARCHAR, new String[]{"VARCHAR"});
-        return types;
+    protected NativeColumnType[] createExternalTypes() {
+        return new NativeColumnType[]{
+            NativeColumnType.of(Types.BIGINT, "BIGINT"),
+            NativeColumnType.of(Types.BINARY, "BINARY"),
+            NativeColumnType.of(Types.BIT, "BIT"),
+            NativeColumnType.of(Types.BLOB, "LONGBLOB"),
+            NativeColumnType.of(Types.BOOLEAN, "BOOL"),
+            NativeColumnType.of(Types.CHAR, "CHAR"),
+            NativeColumnType.of(Types.CLOB, "LONGTEXT"),
+            NativeColumnType.of(Types.DATE, "DATE"),
+            NativeColumnType.of(Types.DECIMAL, "DECIMAL"),
+            NativeColumnType.of(Types.DOUBLE, "DOUBLE"),
+            NativeColumnType.of(Types.FLOAT, "FLOAT"),
+            NativeColumnType.of(Types.INTEGER, "INT"),
+            NativeColumnType.of(Types.LONGNVARCHAR, "LONGTEXT"),
+            NativeColumnType.of(Types.LONGVARBINARY, "LONGBLOB"),
+            NativeColumnType.of(Types.LONGVARCHAR, "LONGTEXT"),
+            NativeColumnType.of(Types.NCHAR, "CHAR"),
+            NativeColumnType.of(Types.NCLOB, "LONGTEXT"),
+            NativeColumnType.of(Types.NUMERIC, "DECIMAL"),
+            NativeColumnType.of(Types.NVARCHAR, "VARCHAR"),
+            NativeColumnType.of(Types.REAL, "DOUBLE"),
+            NativeColumnType.of(Types.SMALLINT, "SMALLINT"),
+            NativeColumnType.of(Types.SQLXML, "LONGTEXT"),
+            NativeColumnType.of(Types.TIME, "TIME"),
+            NativeColumnType.of(Types.TIMESTAMP, "DATETIME"),
+            NativeColumnType.of(Types.TINYINT, "TINYINT"),
+            NativeColumnType.of(Types.VARBINARY, "VARBINARY"),
+            NativeColumnType.of(Types.VARCHAR, "VARCHAR"),
+        };
     }
 
     @Override
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
index 4bf4c6aeb..f904e6ed0 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.dba.oracle;
 
+import org.apache.cayenne.dba.NativeColumnType;
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
@@ -50,9 +51,7 @@ import java.sql.ResultSet;
 import java.sql.Types;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * DbAdapter implementation for Oracle RDBMS
@@ -166,37 +165,37 @@ public class OracleAdapter extends JdbcAdapter {
     }
 
     @Override
-    protected Map<Integer, String[]> createExternalTypes() {
-        Map<Integer, String[]> types = new HashMap<>();
-        types.put(Types.BIGINT, new String[]{"NUMBER"});
-        types.put(Types.BINARY, new String[]{"RAW"});
-        types.put(Types.BIT, new String[]{"INTEGER"});
-        types.put(Types.BLOB, new String[]{"BLOB"});
-        types.put(Types.BOOLEAN, new String[]{"INTEGER"});
-        types.put(Types.CHAR, new String[]{"CHAR"});
-        types.put(Types.CLOB, new String[]{"CLOB"});
-        types.put(Types.DATE, new String[]{"DATE"});
-        types.put(Types.DECIMAL, new String[]{"DECIMAL"});
-        types.put(Types.DOUBLE, new String[]{"NUMBER"});
-        types.put(Types.FLOAT, new String[]{"FLOAT"});
-        types.put(Types.INTEGER, new String[]{"INTEGER"});
-        types.put(Types.LONGNVARCHAR, new String[]{"NCLOB"});
-        types.put(Types.LONGVARBINARY, new String[]{"LONG RAW"});
-        types.put(Types.LONGVARCHAR, new String[]{"LONG VARCHAR"});
-        types.put(Types.NCHAR, new String[]{"NCHAR"});
-        types.put(Types.NCLOB, new String[]{"NCLOB"});
-        types.put(Types.NUMERIC, new String[]{"NUMBER"});
-        types.put(Types.NVARCHAR, new String[]{"NVARCHAR2"});
-        types.put(Types.REAL, new String[]{"NUMBER"});
-        types.put(Types.ROWID, new String[]{"ROWID"});
-        types.put(Types.SMALLINT, new String[]{"SMALLINT"});
-        types.put(Types.SQLXML, new String[]{"XMLType"});
-        types.put(Types.TIME, new String[]{"DATE"});
-        types.put(Types.TIMESTAMP, new String[]{"TIMESTAMP"});
-        types.put(Types.TINYINT, new String[]{"SMALLINT"});
-        types.put(Types.VARBINARY, new String[]{"RAW"});
-        types.put(Types.VARCHAR, new String[]{"VARCHAR2"});
-        return types;
+    protected NativeColumnType[] createExternalTypes() {
+        return new NativeColumnType[]{
+            NativeColumnType.of(Types.BIGINT, "NUMBER"),
+            NativeColumnType.of(Types.BINARY, "RAW"),
+            NativeColumnType.of(Types.BIT, "INTEGER"),
+            NativeColumnType.of(Types.BLOB, "BLOB"),
+            NativeColumnType.of(Types.BOOLEAN, "INTEGER"),
+            NativeColumnType.of(Types.CHAR, "CHAR"),
+            NativeColumnType.of(Types.CLOB, "CLOB"),
+            NativeColumnType.of(Types.DATE, "DATE"),
+            NativeColumnType.of(Types.DECIMAL, "DECIMAL"),
+            NativeColumnType.of(Types.DOUBLE, "NUMBER"),
+            NativeColumnType.of(Types.FLOAT, "FLOAT"),
+            NativeColumnType.of(Types.INTEGER, "INTEGER"),
+            NativeColumnType.of(Types.LONGNVARCHAR, "NCLOB"),
+            NativeColumnType.of(Types.LONGVARBINARY, "LONG RAW"),
+            NativeColumnType.of(Types.LONGVARCHAR, "LONG VARCHAR"),
+            NativeColumnType.of(Types.NCHAR, "NCHAR"),
+            NativeColumnType.of(Types.NCLOB, "NCLOB"),
+            NativeColumnType.of(Types.NUMERIC, "NUMBER"),
+            NativeColumnType.of(Types.NVARCHAR, "NVARCHAR2"),
+            NativeColumnType.of(Types.REAL, "NUMBER"),
+            NativeColumnType.of(Types.ROWID, "ROWID"),
+            NativeColumnType.of(Types.SMALLINT, "SMALLINT"),
+            NativeColumnType.of(Types.SQLXML, "XMLType"),
+            NativeColumnType.of(Types.TIME, "DATE"),
+            NativeColumnType.of(Types.TIMESTAMP, "TIMESTAMP"),
+            NativeColumnType.of(Types.TINYINT, "SMALLINT"),
+            NativeColumnType.of(Types.VARBINARY, "RAW"),
+            NativeColumnType.of(Types.VARCHAR, "VARCHAR2"),
+        };
     }
 
     /**
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 e18db6d23..732807965 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
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.dba.postgres;
 
+import org.apache.cayenne.dba.NativeColumnType;
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
@@ -45,10 +46,8 @@ import org.apache.cayenne.query.SQLAction;
 import java.sql.Types;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 
 /**
  * DbAdapter implementation for PostgreSQL RDBMS
@@ -70,37 +69,40 @@ public class PostgresAdapter extends JdbcAdapter {
     }
 
     @Override
-    protected Map<Integer, String[]> createExternalTypes() {
-        Map<Integer, String[]> types = new HashMap<>();
-        types.put(Types.BIGINT, new String[]{"bigint", "bigserial"});
-        types.put(Types.BINARY, new String[]{"bytea"});
-        types.put(Types.BIT, new String[]{"boolean"});
-        types.put(Types.BLOB, new String[]{"oid"});
-        types.put(Types.BOOLEAN, new String[]{"boolean"});
-        types.put(Types.CHAR, new String[]{"character"});
-        types.put(Types.CLOB, new String[]{"text"});
-        types.put(Types.DATE, new String[]{"date"});
-        types.put(Types.DECIMAL, new String[]{"decimal"});
-        types.put(Types.DOUBLE, new String[]{"double precision"});
-        types.put(Types.FLOAT, new String[]{"float"});
-        types.put(Types.INTEGER, new String[]{"integer", "serial"});
-        types.put(Types.LONGNVARCHAR, new String[]{"text"});
-        types.put(Types.LONGVARBINARY, new String[]{"bytea"});
-        types.put(Types.LONGVARCHAR, new String[]{"text"});
-        types.put(Types.NCHAR, new String[]{"character"});
-        types.put(Types.NCLOB, new String[]{"text"});
-        types.put(Types.NUMERIC, new String[]{"numeric"});
-        types.put(Types.NVARCHAR, new String[]{"varchar"});
-        types.put(Types.OTHER, new String[]{"json"});
-        types.put(Types.REAL, new String[]{"real"});
-        types.put(Types.SMALLINT, new String[]{"smallint", "smallserial"});
-        types.put(Types.SQLXML, new String[]{"xml"});
-        types.put(Types.TIME, new String[]{"time"});
-        types.put(Types.TIMESTAMP, new String[]{"timestamp with time zone"});
-        types.put(Types.TINYINT, new String[]{"smallint"});
-        types.put(Types.VARBINARY, new String[]{"bytea"});
-        types.put(Types.VARCHAR, new String[]{"varchar"});
-        return types;
+    protected NativeColumnType[] createExternalTypes() {
+        return new NativeColumnType[]{
+                NativeColumnType.of(Types.BIGINT, "bigint"),
+                NativeColumnType.of(Types.BIGINT, 
"bigserial").asAutoIncrement(),
+                NativeColumnType.of(Types.BINARY, "bytea"),
+                NativeColumnType.of(Types.BIT, "boolean"),
+                NativeColumnType.of(Types.BLOB, "oid"),
+                NativeColumnType.of(Types.BOOLEAN, "boolean"),
+                NativeColumnType.of(Types.CHAR, "character"),
+                NativeColumnType.of(Types.CLOB, "text"),
+                NativeColumnType.of(Types.DATE, "date"),
+                NativeColumnType.of(Types.DECIMAL, "decimal"),
+                NativeColumnType.of(Types.DOUBLE, "double precision"),
+                NativeColumnType.of(Types.FLOAT, "float"),
+                NativeColumnType.of(Types.INTEGER, "integer"),
+                NativeColumnType.of(Types.INTEGER, "serial").asAutoIncrement(),
+                NativeColumnType.of(Types.LONGNVARCHAR, "text"),
+                NativeColumnType.of(Types.LONGVARBINARY, "bytea"),
+                NativeColumnType.of(Types.LONGVARCHAR, "text"),
+                NativeColumnType.of(Types.NCHAR, "character"),
+                NativeColumnType.of(Types.NCLOB, "text"),
+                NativeColumnType.of(Types.NUMERIC, "numeric"),
+                NativeColumnType.of(Types.NVARCHAR, "varchar"),
+                NativeColumnType.of(Types.OTHER, "json"),
+                NativeColumnType.of(Types.REAL, "real"),
+                NativeColumnType.of(Types.SMALLINT, "smallint"),
+                NativeColumnType.of(Types.SMALLINT, 
"smallserial").asAutoIncrement(),
+                NativeColumnType.of(Types.SQLXML, "xml"),
+                NativeColumnType.of(Types.TIME, "time"),
+                NativeColumnType.of(Types.TIMESTAMP, "timestamp with time 
zone"),
+                NativeColumnType.of(Types.TINYINT, "smallint"),
+                NativeColumnType.of(Types.VARBINARY, "bytea"),
+                NativeColumnType.of(Types.VARCHAR, "varchar"),
+        };
     }
 
     /**
@@ -232,18 +234,29 @@ public class PostgresAdapter extends JdbcAdapter {
                     , ent.getFullyQualifiedName(), at.getName());
         }
 
-        String[] types = externalTypesForJdbcType(at.getType());
+        NativeColumnType[] types = nativeColumnTypes(at.getType());
         if (types == null || types.length == 0) {
-            throw new CayenneRuntimeException("Undefined type for attribute 
'%s.%s': %s"
-                    , ent.getFullyQualifiedName(), at.getName(), at.getType());
+            throw new CayenneRuntimeException(
+                    "Undefined type for attribute '%s.%s': %s", 
ent.getFullyQualifiedName(), at.getName(), at.getType());
         }
 
-        // Checking that attribute is generated and we have alternative types 
in the external types mapping.
-        // If so, use those autoincremented types. For example serial, 
bigserial, smallserial.
-        String type = (at.isGenerated() && types.length > 1) ? types[1] : 
types[0];
+        // For a generated column use the auto-increment variant if there is 
one (serial, bigserial, smallserial).
+        NativeColumnType selected = types[0];
+        if (at.isGenerated()) {
+            for (NativeColumnType candidate : types) {
+                if (candidate.autoIncrement()) {
+                    selected = candidate;
+                    break;
+                }
+            }
+        }
 
-        buf.append(context.quotedName(at)).append(' 
').append(type).append(sizeAndPrecision(this, at))
-                .append(at.isMandatory() ? " NOT" : "").append(" NULL");
+        buf.append(context.quotedName(at))
+                .append(' ')
+                .append(selected.nativeType())
+                .append(sizeAndPrecision(this, at))
+                .append(at.isMandatory() ? " NOT" : "")
+                .append(" NULL");
     }
 
     @Override
@@ -252,10 +265,10 @@ public class PostgresAdapter extends JdbcAdapter {
         if (Types.DOUBLE == type || Types.REAL == type) {
             return false;
         }
-        String[] externalTypes = externalTypesForJdbcType(type);
-        if (externalTypes != null && externalTypes.length > 0) {
-            for (String externalType : externalTypes) {
-                if (BYTEA.equalsIgnoreCase(externalType)) {
+        NativeColumnType[] externalTypes = nativeColumnTypes(type);
+        if (externalTypes != null) {
+            for (NativeColumnType externalType : externalTypes) {
+                if (BYTEA.equalsIgnoreCase(externalType.nativeType())) {
                     return false;
                 }
             }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
index ef3e3e37c..70b193d92 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/sqlite/SQLiteAdapter.java
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.dba.sqlite;
 
+import org.apache.cayenne.dba.NativeColumnType;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
 import org.apache.cayenne.access.types.ExtendedType;
@@ -38,9 +39,7 @@ import java.sql.Types;
 import java.util.Calendar;
 import java.util.Collection;
 import java.util.GregorianCalendar;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * A SQLite database adapter.
@@ -66,42 +65,42 @@ public class SQLiteAdapter extends JdbcAdapter {
     }
 
     @Override
-    protected Map<Integer, String[]> createExternalTypes() {
-        Map<Integer, String[]> types = new HashMap<>();
-        types.put(Types.ARRAY, new String[]{"ARRAY"});
-        types.put(Types.BIGINT, new String[]{"BIGINT"});
-        types.put(Types.BINARY, new String[]{"BINARY"});
-        types.put(Types.BIT, new String[]{"INTEGER"});
-        types.put(Types.BLOB, new String[]{"BLOB"});
-        types.put(Types.BOOLEAN, new String[]{"INTEGER"});
-        types.put(Types.CHAR, new String[]{"CHAR"});
-        types.put(Types.CLOB, new String[]{"CLOB"});
-        types.put(Types.DATALINK, new String[]{"DATALINK"});
-        types.put(Types.DATE, new String[]{"DATE"});
-        types.put(Types.DECIMAL, new String[]{"DECIMAL"});
-        types.put(Types.DOUBLE, new String[]{"DOUBLE"});
-        types.put(Types.FLOAT, new String[]{"REAL"});
-        types.put(Types.INTEGER, new String[]{"INTEGER"});
-        types.put(Types.JAVA_OBJECT, new String[]{"JAVA_OBJECT"});
-        types.put(Types.LONGNVARCHAR, new String[]{"LONGNVARCHAR"});
-        types.put(Types.LONGVARBINARY, new String[]{"LONGVARBINARY"});
-        types.put(Types.LONGVARCHAR, new String[]{"LONGVARCHAR"});
-        types.put(Types.NCHAR, new String[]{"NCHAR"});
-        types.put(Types.NCLOB, new String[]{"TEXT"});
-        types.put(Types.NUMERIC, new String[]{"NUMERIC"});
-        types.put(Types.NVARCHAR, new String[]{"NVARCHAR"});
-        types.put(Types.OTHER, new String[]{"OTHER"});
-        types.put(Types.REAL, new String[]{"REAL"});
-        types.put(Types.REF, new String[]{"REF"});
-        types.put(Types.SMALLINT, new String[]{"SMALLINT"});
-        types.put(Types.SQLXML, new String[]{"TEXT"});
-        types.put(Types.STRUCT, new String[]{"STRUCT"});
-        types.put(Types.TIME, new String[]{"TIME"});
-        types.put(Types.TIMESTAMP, new String[]{"TIMESTAMP"});
-        types.put(Types.TINYINT, new String[]{"TINYINT"});
-        types.put(Types.VARBINARY, new String[]{"VARBINARY"});
-        types.put(Types.VARCHAR, new String[]{"VARCHAR"});
-        return types;
+    protected NativeColumnType[] createExternalTypes() {
+        return new NativeColumnType[]{
+            NativeColumnType.of(Types.ARRAY, "ARRAY"),
+            NativeColumnType.of(Types.BIGINT, "BIGINT"),
+            NativeColumnType.of(Types.BINARY, "BINARY"),
+            NativeColumnType.of(Types.BIT, "INTEGER"),
+            NativeColumnType.of(Types.BLOB, "BLOB"),
+            NativeColumnType.of(Types.BOOLEAN, "INTEGER"),
+            NativeColumnType.of(Types.CHAR, "CHAR"),
+            NativeColumnType.of(Types.CLOB, "CLOB"),
+            NativeColumnType.of(Types.DATALINK, "DATALINK"),
+            NativeColumnType.of(Types.DATE, "DATE"),
+            NativeColumnType.of(Types.DECIMAL, "DECIMAL"),
+            NativeColumnType.of(Types.DOUBLE, "DOUBLE"),
+            NativeColumnType.of(Types.FLOAT, "REAL"),
+            NativeColumnType.of(Types.INTEGER, "INTEGER"),
+            NativeColumnType.of(Types.JAVA_OBJECT, "JAVA_OBJECT"),
+            NativeColumnType.of(Types.LONGNVARCHAR, "LONGNVARCHAR"),
+            NativeColumnType.of(Types.LONGVARBINARY, "LONGVARBINARY"),
+            NativeColumnType.of(Types.LONGVARCHAR, "LONGVARCHAR"),
+            NativeColumnType.of(Types.NCHAR, "NCHAR"),
+            NativeColumnType.of(Types.NCLOB, "TEXT"),
+            NativeColumnType.of(Types.NUMERIC, "NUMERIC"),
+            NativeColumnType.of(Types.NVARCHAR, "NVARCHAR"),
+            NativeColumnType.of(Types.OTHER, "OTHER"),
+            NativeColumnType.of(Types.REAL, "REAL"),
+            NativeColumnType.of(Types.REF, "REF"),
+            NativeColumnType.of(Types.SMALLINT, "SMALLINT"),
+            NativeColumnType.of(Types.SQLXML, "TEXT"),
+            NativeColumnType.of(Types.STRUCT, "STRUCT"),
+            NativeColumnType.of(Types.TIME, "TIME"),
+            NativeColumnType.of(Types.TIMESTAMP, "TIMESTAMP"),
+            NativeColumnType.of(Types.TINYINT, "TINYINT"),
+            NativeColumnType.of(Types.VARBINARY, "VARBINARY"),
+            NativeColumnType.of(Types.VARCHAR, "VARCHAR"),
+        };
     }
 
     @Override
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 82acb6874..41556779c 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
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.dba.sqlserver;
 
+import org.apache.cayenne.dba.NativeColumnType;
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
@@ -47,9 +48,7 @@ import org.apache.cayenne.query.SQLAction;
 import java.sql.PreparedStatement;
 import java.sql.Types;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
@@ -83,37 +82,37 @@ public class SQLServerAdapter extends JdbcAdapter {
     }
 
     @Override
-    protected Map<Integer, String[]> createExternalTypes() {
-        Map<Integer, String[]> types = new HashMap<>();
-        types.put(Types.BIGINT, new String[]{"bigint"});
-        types.put(Types.BINARY, new String[]{"binary"});
-        types.put(Types.BIT, new String[]{"bit"});
-        types.put(Types.BLOB, new String[]{"image"});
-        types.put(Types.BOOLEAN, new String[]{"bit"});
-        types.put(Types.CHAR, new String[]{"char"});
-        types.put(Types.CLOB, new String[]{"text"});
-        types.put(Types.DATE, new String[]{"date"});
-        types.put(Types.DECIMAL, new String[]{"decimal"});
-        types.put(Types.DOUBLE, new String[]{"double precision"});
-        types.put(Types.FLOAT, new String[]{"float"});
-        types.put(Types.INTEGER, new String[]{"int"});
-        types.put(Types.LONGNVARCHAR, new String[]{"ntext"});
-        types.put(Types.LONGVARBINARY, new String[]{"image"});
-        types.put(Types.LONGVARCHAR, new String[]{"text"});
-        types.put(Types.NCHAR, new String[]{"nchar"});
-        types.put(Types.NCLOB, new String[]{"ntext"});
-        types.put(Types.NUMERIC, new String[]{"numeric"});
-        types.put(Types.NVARCHAR, new String[]{"nvarchar"});
-        types.put(Types.REAL, new String[]{"real"});
-        types.put(Types.ROWID, new String[]{"ROWID"});
-        types.put(Types.SMALLINT, new String[]{"smallint"});
-        types.put(Types.SQLXML, new String[]{"xml"});
-        types.put(Types.TIME, new String[]{"time"});
-        types.put(Types.TIMESTAMP, new String[]{"datetime"});
-        types.put(Types.TINYINT, new String[]{"tinyint"});
-        types.put(Types.VARBINARY, new String[]{"varbinary"});
-        types.put(Types.VARCHAR, new String[]{"varchar"});
-        return types;
+    protected NativeColumnType[] createExternalTypes() {
+        return new NativeColumnType[]{
+            NativeColumnType.of(Types.BIGINT, "bigint"),
+            NativeColumnType.of(Types.BINARY, "binary"),
+            NativeColumnType.of(Types.BIT, "bit"),
+            NativeColumnType.of(Types.BLOB, "image"),
+            NativeColumnType.of(Types.BOOLEAN, "bit"),
+            NativeColumnType.of(Types.CHAR, "char"),
+            NativeColumnType.of(Types.CLOB, "text"),
+            NativeColumnType.of(Types.DATE, "date"),
+            NativeColumnType.of(Types.DECIMAL, "decimal"),
+            NativeColumnType.of(Types.DOUBLE, "double precision"),
+            NativeColumnType.of(Types.FLOAT, "float"),
+            NativeColumnType.of(Types.INTEGER, "int"),
+            NativeColumnType.of(Types.LONGNVARCHAR, "ntext"),
+            NativeColumnType.of(Types.LONGVARBINARY, "image"),
+            NativeColumnType.of(Types.LONGVARCHAR, "text"),
+            NativeColumnType.of(Types.NCHAR, "nchar"),
+            NativeColumnType.of(Types.NCLOB, "ntext"),
+            NativeColumnType.of(Types.NUMERIC, "numeric"),
+            NativeColumnType.of(Types.NVARCHAR, "nvarchar"),
+            NativeColumnType.of(Types.REAL, "real"),
+            NativeColumnType.of(Types.ROWID, "ROWID"),
+            NativeColumnType.of(Types.SMALLINT, "smallint"),
+            NativeColumnType.of(Types.SQLXML, "xml"),
+            NativeColumnType.of(Types.TIME, "time"),
+            NativeColumnType.of(Types.TIMESTAMP, "datetime"),
+            NativeColumnType.of(Types.TINYINT, "tinyint"),
+            NativeColumnType.of(Types.VARBINARY, "varbinary"),
+            NativeColumnType.of(Types.VARCHAR, "varchar"),
+        };
     }
 
     @Override
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 2b86f054e..29b49abb1 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,6 +19,7 @@
 
 package org.apache.cayenne.dba.sybase;
 
+import org.apache.cayenne.dba.NativeColumnType;
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.translator.ejbql.EJBQLTranslator;
@@ -40,9 +41,7 @@ import org.apache.cayenne.map.DbAttribute;
 
 import java.sql.PreparedStatement;
 import java.sql.Types;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * DbAdapter implementation for Sybase RDBMS.
@@ -60,36 +59,36 @@ public class SybaseAdapter extends JdbcAdapter {
     }
 
     @Override
-    protected Map<Integer, String[]> createExternalTypes() {
-        Map<Integer, String[]> types = new HashMap<>();
-        types.put(Types.BIGINT, new String[]{"decimal(19,0)"});
-        types.put(Types.BINARY, new String[]{"binary"});
-        types.put(Types.BIT, new String[]{"bit"});
-        types.put(Types.BLOB, new String[]{"image"});
-        types.put(Types.BOOLEAN, new String[]{"bit"});
-        types.put(Types.CHAR, new String[]{"char"});
-        types.put(Types.CLOB, new String[]{"text"});
-        types.put(Types.DATE, new String[]{"datetime"});
-        types.put(Types.DECIMAL, new String[]{"decimal"});
-        types.put(Types.DOUBLE, new String[]{"double precision"});
-        types.put(Types.FLOAT, new String[]{"float"});
-        types.put(Types.INTEGER, new String[]{"int"});
-        types.put(Types.LONGNVARCHAR, new String[]{"text"});
-        types.put(Types.LONGVARBINARY, new String[]{"image"});
-        types.put(Types.LONGVARCHAR, new String[]{"text"});
-        types.put(Types.NCHAR, new String[]{"nchar"});
-        types.put(Types.NCLOB, new String[]{"text"});
-        types.put(Types.NUMERIC, new String[]{"numeric"});
-        types.put(Types.NVARCHAR, new String[]{"nvarchar"});
-        types.put(Types.REAL, new String[]{"real"});
-        types.put(Types.SMALLINT, new String[]{"smallint"});
-        types.put(Types.SQLXML, new String[]{"text"});
-        types.put(Types.TIME, new String[]{"datetime"});
-        types.put(Types.TIMESTAMP, new String[]{"datetime"});
-        types.put(Types.TINYINT, new String[]{"tinyint"});
-        types.put(Types.VARBINARY, new String[]{"varbinary"});
-        types.put(Types.VARCHAR, new String[]{"varchar"});
-        return types;
+    protected NativeColumnType[] createExternalTypes() {
+        return new NativeColumnType[]{
+            NativeColumnType.of(Types.BIGINT, "decimal(19,0)"),
+            NativeColumnType.of(Types.BINARY, "binary"),
+            NativeColumnType.of(Types.BIT, "bit"),
+            NativeColumnType.of(Types.BLOB, "image"),
+            NativeColumnType.of(Types.BOOLEAN, "bit"),
+            NativeColumnType.of(Types.CHAR, "char"),
+            NativeColumnType.of(Types.CLOB, "text"),
+            NativeColumnType.of(Types.DATE, "datetime"),
+            NativeColumnType.of(Types.DECIMAL, "decimal"),
+            NativeColumnType.of(Types.DOUBLE, "double precision"),
+            NativeColumnType.of(Types.FLOAT, "float"),
+            NativeColumnType.of(Types.INTEGER, "int"),
+            NativeColumnType.of(Types.LONGNVARCHAR, "text"),
+            NativeColumnType.of(Types.LONGVARBINARY, "image"),
+            NativeColumnType.of(Types.LONGVARCHAR, "text"),
+            NativeColumnType.of(Types.NCHAR, "nchar"),
+            NativeColumnType.of(Types.NCLOB, "text"),
+            NativeColumnType.of(Types.NUMERIC, "numeric"),
+            NativeColumnType.of(Types.NVARCHAR, "nvarchar"),
+            NativeColumnType.of(Types.REAL, "real"),
+            NativeColumnType.of(Types.SMALLINT, "smallint"),
+            NativeColumnType.of(Types.SQLXML, "text"),
+            NativeColumnType.of(Types.TIME, "datetime"),
+            NativeColumnType.of(Types.TIMESTAMP, "datetime"),
+            NativeColumnType.of(Types.TINYINT, "tinyint"),
+            NativeColumnType.of(Types.VARBINARY, "varbinary"),
+            NativeColumnType.of(Types.VARCHAR, "varchar"),
+        };
     }
 
     @Override
diff --git a/cayenne/src/test/java/org/apache/cayenne/dba/JdbcAdapterIT.java 
b/cayenne/src/test/java/org/apache/cayenne/dba/JdbcAdapterIT.java
index 6395afb6e..e25f92a76 100644
--- a/cayenne/src/test/java/org/apache/cayenne/dba/JdbcAdapterIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/dba/JdbcAdapterIT.java
@@ -32,6 +32,7 @@ import org.junit.jupiter.api.extension.RegisterExtension;
 import java.sql.Types;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 public class JdbcAdapterIT {
@@ -40,7 +41,7 @@ public class JdbcAdapterIT {
     static final CayenneTestsEnv env = 
CayenneTestsEnv.forProject(CayenneProjects.TESTMAP_PROJECT);
 
     @Test
-    public void externalTypesForJdbcType() throws Exception {
+    public void nativeColumnTypes() throws Exception {
         // check a few types
         checkType(Types.BLOB);
         checkType(Types.ARRAY);
@@ -50,13 +51,15 @@ public class JdbcAdapterIT {
 
     private void checkType(int type) throws java.lang.Exception {
         JdbcAdapter adapter = env.adhocObjectFactory().newInstance(
-                JdbcAdapter.class, 
+                JdbcAdapter.class,
                 JdbcAdapter.class.getName());
 
-        String[] types = adapter.externalTypesForJdbcType(type);
+        NativeColumnType[] types = adapter.nativeColumnTypes(type);
         assertNotNull(types);
         assertEquals(1, types.length);
-        assertEquals(TypesMapping.getSqlNameByType(type), types[0]);
+        assertEquals(type, types[0].jdbcType());
+        assertEquals(TypesMapping.getSqlNameByType(type), 
types[0].nativeType());
+        assertFalse(types[0].autoIncrement());
     }
 
     @Test
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/dba/NativeColumnTypeTest.java 
b/cayenne/src/test/java/org/apache/cayenne/dba/NativeColumnTypeTest.java
new file mode 100644
index 000000000..7999d5cde
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/dba/NativeColumnTypeTest.java
@@ -0,0 +1,52 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    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.dba;
+
+import java.sql.Types;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class NativeColumnTypeTest {
+
+    @Test
+    public void of() {
+        NativeColumnType type = NativeColumnType.of(Types.VARCHAR, "varchar");
+        assertEquals(Types.VARCHAR, type.jdbcType());
+        assertEquals("varchar", type.nativeType());
+        assertFalse(type.autoIncrement());
+    }
+
+    @Test
+    public void asAutoIncrement() {
+        NativeColumnType base = NativeColumnType.of(Types.INTEGER, "serial");
+        NativeColumnType auto = base.asAutoIncrement();
+
+        // only the flag flips; everything else is preserved
+        assertTrue(auto.autoIncrement());
+        assertEquals(base.jdbcType(), auto.jdbcType());
+        assertEquals(base.nativeType(), auto.nativeType());
+
+        // the original is unchanged
+        assertFalse(base.autoIncrement());
+    }
+}
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/dba/oracle/OracleAdapterIT.java 
b/cayenne/src/test/java/org/apache/cayenne/dba/oracle/OracleAdapterIT.java
index 845695c79..7f240469d 100644
--- a/cayenne/src/test/java/org/apache/cayenne/dba/oracle/OracleAdapterIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/dba/oracle/OracleAdapterIT.java
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.dba.oracle;
 
+import org.apache.cayenne.dba.NativeColumnType;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.query.InsertBatchQuery;
 import org.apache.cayenne.unit.CayenneProjects;
@@ -56,9 +57,9 @@ public class OracleAdapterIT {
                 OracleAdapter.class, 
                 OracleAdapter.class.getName());
 
-        String[] types = adapter.externalTypesForJdbcType(Types.TIMESTAMP);
+        NativeColumnType[] types = adapter.nativeColumnTypes(Types.TIMESTAMP);
         assertNotNull(types);
         assertEquals(1, types.length);
-        assertEquals("TIMESTAMP", types[0]);
+        assertEquals("TIMESTAMP", types[0].nativeType());
     }
 }
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/dba/postgres/PostgresAdapterIT.java 
b/cayenne/src/test/java/org/apache/cayenne/dba/postgres/PostgresAdapterIT.java
index c2becc627..7b147bdef 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/dba/postgres/PostgresAdapterIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/dba/postgres/PostgresAdapterIT.java
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.dba.postgres;
 
+import org.apache.cayenne.dba.NativeColumnType;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.unit.CayenneProjects;
@@ -27,7 +28,9 @@ import org.junit.jupiter.api.extension.RegisterExtension;
 
 import java.sql.Types;
 
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class PostgresAdapterIT {
@@ -56,4 +59,39 @@ public class PostgresAdapterIT {
         assertEquals("CREATE TABLE Test (dbl1 float(22) NULL)", sql);
     }
 
+    @Test
+    public void createTableWithGeneratedColumnRendersSerial() {
+        PostgresAdapter adapter = env.adhocObjectFactory().newInstance(
+                PostgresAdapter.class,
+                PostgresAdapter.class.getName());
+        DbEntity e = new DbEntity("Test");
+        DbAttribute id = new DbAttribute("id");
+        id.setType(Types.INTEGER);
+        id.setGenerated(true);
+        id.setMandatory(true);
+        e.addAttribute(id);
+
+        String sql = adapter.createTable(e);
+
+        // the auto-increment variant is selected for generated columns
+        assertTrue(sql.contains("id serial"), sql);
+    }
+
+    @Test
+    public void externalColumnTypesAndLegacyArray() {
+        PostgresAdapter adapter = env.adhocObjectFactory().newInstance(
+                PostgresAdapter.class,
+                PostgresAdapter.class.getName());
+
+        NativeColumnType[] variants = adapter.nativeColumnTypes(Types.INTEGER);
+        assertEquals(2, variants.length);
+        assertEquals("integer", variants[0].nativeType());
+        assertFalse(variants[0].autoIncrement());
+        assertEquals("serial", variants[1].nativeType());
+        assertTrue(variants[1].autoIncrement());
+
+        // the deprecated array view still reproduces the historical contents
+        assertArrayEquals(new String[]{"integer", "serial"}, 
adapter.externalTypesForJdbcType(Types.INTEGER));
+    }
+
 }


Reply via email to