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

commit 80dea98fd93786a36e67fe7e10d1fea784e4d3f7
Author: Andrus Adamchik <[email protected]>
AuthorDate: Sun Jun 14 16:09:17 2026 -0400

    CAY-2962 Allow unconstrained VARCHAR
---
 RELEASE-NOTES.txt                                  |  1 +
 .../validation/ValidationSaverDelegate.java        |  2 +
 .../project/validation/DbAttributeValidator.java   | 16 +---
 .../cayenne/project/validation/Inspection.java     | 29 +++++++-
 .../validation/ProcedureParameterValidator.java    | 15 ----
 .../validation/ValidationConfigSaveTest.java       | 86 ++++++++++++++++++++++
 .../java/org/apache/cayenne/dba/AutoAdapter.java   |  8 ++
 .../java/org/apache/cayenne/dba/DbAdapter.java     |  9 +++
 .../java/org/apache/cayenne/dba/JdbcAdapter.java   | 69 ++++++++++++++---
 .../org/apache/cayenne/dba/NativeColumnType.java   | 23 ++++--
 .../java/org/apache/cayenne/dba/h2/H2Adapter.java  |  8 +-
 .../apache/cayenne/dba/hsqldb/HSQLDBAdapter.java   |  8 +-
 .../org/apache/cayenne/dba/mysql/MySQLAdapter.java |  4 +
 .../apache/cayenne/dba/oracle/OracleAdapter.java   |  3 +
 .../cayenne/dba/postgres/PostgresAdapter.java      | 32 +++-----
 .../cayenne/dba/sqlserver/SQLServerAdapter.java    |  4 +
 .../java/org/apache/cayenne/dba/JdbcAdapterIT.java | 13 ++++
 .../apache/cayenne/dba/NativeColumnTypeTest.java   | 17 +++++
 .../apache/cayenne/dba/hsqldb/HSQLDBAdapterIT.java | 17 ++++-
 .../apache/cayenne/dba/mysql/MySQLAdapterIT.java   | 14 ++++
 .../apache/cayenne/dba/oracle/OracleAdapterIT.java | 17 +++++
 .../cayenne/dba/postgres/PostgresAdapterIT.java    | 25 +++++++
 .../cayenne/dba/sqlserver/SQLServerAdapterIT.java  | 13 ++++
 .../validation/InspectionCheckBoxTree.java         |  3 +
 24 files changed, 355 insertions(+), 81 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index e381602f4..3d9159ed7 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -16,6 +16,7 @@ Changes/New Features:
 CAY-2954 Don't wrap selecting queries in transactions
 CAY-2956 Get rid of a dedicated adapter for Oracle 8
 CAY-2957 Get rid of adapter for legacy HSQLDB <= 1.8
+CAY-2962 Allow unconstrained VARCHAR
 CAY-2963 Replace TypesHandler / types.xml with hardcoded map
 
 Bug Fixes:
diff --git 
a/cayenne-project/src/main/java/org/apache/cayenne/project/extension/validation/ValidationSaverDelegate.java
 
b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/validation/ValidationSaverDelegate.java
index 8b0db1ebf..0a6ed55e8 100644
--- 
a/cayenne-project/src/main/java/org/apache/cayenne/project/extension/validation/ValidationSaverDelegate.java
+++ 
b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/validation/ValidationSaverDelegate.java
@@ -44,6 +44,8 @@ public class ValidationSaverDelegate extends 
BaseSaverDelegate {
         ValidationConfig validationConfig = 
ValidationConfig.fromMetadata(metaData, dataChannelDescriptor);
         Set<Inspection> disabledInspections = EnumSet.allOf(Inspection.class);
         
disabledInspections.removeAll(validationConfig.getEnabledInspections());
+        // deprecated inspections are no longer performed; drop them on save 
instead of round-tripping them
+        disabledInspections.removeIf(Inspection::isDeprecated);
         if (disabledInspections.isEmpty()) {
             return null;
         }
diff --git 
a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DbAttributeValidator.java
 
b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DbAttributeValidator.java
index f391c951e..946c5e796 100644
--- 
a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DbAttributeValidator.java
+++ 
b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/DbAttributeValidator.java
@@ -40,8 +40,7 @@ class DbAttributeValidator extends 
ConfigurationNodeValidator<DbAttribute> {
         on(node, validationResult)
                 .performIfEnabled(Inspection.DB_ATTRIBUTE_NO_NAME, 
this::checkForName)
                 .performIfEnabled(Inspection.DB_ATTRIBUTE_INVALID_NAME, 
this::validateName)
-                .performIfEnabled(Inspection.DB_ATTRIBUTE_NO_TYPE, 
this::checkForType)
-                .performIfEnabled(Inspection.DB_ATTRIBUTE_NO_LENGTH, 
this::checkForLength);
+                .performIfEnabled(Inspection.DB_ATTRIBUTE_NO_TYPE, 
this::checkForType);
     }
 
     private void checkForName(DbAttribute attribute, ValidationResult 
validationResult) {
@@ -72,17 +71,4 @@ class DbAttributeValidator extends 
ConfigurationNodeValidator<DbAttribute> {
             addFailure(validationResult, attribute, "DbAttribute has no type");
         }
     }
-
-    private void checkForLength(DbAttribute attribute, ValidationResult 
validationResult) {
-        if (attribute.getMaxLength() < 0
-                && (attribute.getType() == java.sql.Types.VARCHAR
-                || attribute.getType() == java.sql.Types.NVARCHAR
-                || attribute.getType() == java.sql.Types.CHAR
-                || attribute.getType() == java.sql.Types.NCHAR)) {
-            // VARCHAR and CHAR attributes must have max length
-
-            addFailure(validationResult, attribute, "Character DbAttribute 
'%s' doesn't have max length",
-                    attribute.getName());
-        }
-    }
 }
diff --git 
a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/Inspection.java
 
b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/Inspection.java
index 136612006..b5b531643 100644
--- 
a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/Inspection.java
+++ 
b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/Inspection.java
@@ -67,7 +67,8 @@ public enum Inspection {
     DB_ATTRIBUTE_NO_NAME(Group.DB_ATTRIBUTE, "Empty db attribute name"),
     DB_ATTRIBUTE_INVALID_NAME(Group.DB_ATTRIBUTE, "Invalid db attribute name"),
     DB_ATTRIBUTE_NO_TYPE(Group.DB_ATTRIBUTE, "Empty db attribute type"),
-    DB_ATTRIBUTE_NO_LENGTH(Group.DB_ATTRIBUTE, "String db attribute has no 
length"),
+    @Deprecated(since = "5.0", forRemoval = true)
+    DB_ATTRIBUTE_NO_LENGTH(Group.DB_ATTRIBUTE, "String db attribute has no 
length", true),
 
     DB_RELATIONSHIP_NO_NAME(Group.DB_RELATIONSHIP, "Empty db relationship 
name"),
     DB_RELATIONSHIP_NAME_DUPLICATE(Group.DB_RELATIONSHIP, "Duplicate of a db 
relationship name"),
@@ -93,7 +94,8 @@ public enum Inspection {
 
     PROCEDURE_PARAMETER_NO_NAME(Group.PROCEDURE_PARAMETER, "Empty procedure 
parameter name"),
     PROCEDURE_PARAMETER_NO_TYPE(Group.PROCEDURE_PARAMETER, "Empty procedure 
parameter type"),
-    PROCEDURE_PARAMETER_NO_LENGTH(Group.PROCEDURE_PARAMETER, "String procedure 
parameter has no length"),
+    @Deprecated(since = "5.0", forRemoval = true)
+    PROCEDURE_PARAMETER_NO_LENGTH(Group.PROCEDURE_PARAMETER, "String procedure 
parameter has no length", true),
     PROCEDURE_PARAMETER_NO_DIRECTION(Group.PROCEDURE_PARAMETER, "Procedure 
parameter has no direction"),
 
     QUERY_NO_NAME(Group.QUERY, "Empty query name"),
@@ -116,15 +118,25 @@ public enum Inspection {
     private final Group group;
     private final String readableName;
     private final String description;
+    private final boolean deprecated;
 
     Inspection(Group group, String name) {
-        this(group, name, name);
+        this(group, name, name, false);
+    }
+
+    Inspection(Group group, String name, boolean deprecated) {
+        this(group, name, name, deprecated);
     }
 
     Inspection(Group group, String name, String description) {
+        this(group, name, description, false);
+    }
+
+    Inspection(Group group, String name, String description, boolean 
deprecated) {
         this.group = group;
         this.readableName = name;
         this.description = description;
+        this.deprecated = deprecated;
     }
 
     public Group group() {
@@ -139,6 +151,17 @@ public enum Inspection {
         return description;
     }
 
+    /**
+     * Returns true if this inspection is no longer performed and is retained 
only so previously saved project
+     * configurations that reference it still load. Deprecated inspections are 
hidden from the UI and stripped
+     * when a project is saved.
+     *
+     * @since 5.0
+     */
+    public boolean isDeprecated() {
+        return deprecated;
+    }
+
     @Override
     public String toString() {
         return readableName();
diff --git 
a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ProcedureParameterValidator.java
 
b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ProcedureParameterValidator.java
index dcf754f5d..06ea68c4e 100644
--- 
a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ProcedureParameterValidator.java
+++ 
b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/ProcedureParameterValidator.java
@@ -18,7 +18,6 @@
  ****************************************************************/
 package org.apache.cayenne.project.validation;
 
-import java.sql.Types;
 import java.util.function.Supplier;
 
 import org.apache.cayenne.dba.TypesMapping;
@@ -41,7 +40,6 @@ class ProcedureParameterValidator extends 
ConfigurationNodeValidator<ProcedurePa
         on(node, validationResult)
                 .performIfEnabled(Inspection.PROCEDURE_PARAMETER_NO_NAME, 
this::checkForName)
                 .performIfEnabled(Inspection.PROCEDURE_PARAMETER_NO_TYPE, 
this::checkForType)
-                .performIfEnabled(Inspection.PROCEDURE_PARAMETER_NO_LENGTH, 
this::checkForLength)
                 .performIfEnabled(Inspection.PROCEDURE_PARAMETER_NO_DIRECTION, 
this::checkForDirection);
     }
 
@@ -59,19 +57,6 @@ class ProcedureParameterValidator extends 
ConfigurationNodeValidator<ProcedurePa
         }
     }
 
-    private void checkForLength(ProcedureParameter parameter, ValidationResult 
validationResult) {
-        // VARCHAR and CHAR attributes must have max length
-        if (parameter.getMaxLength() < 0
-                && (parameter.getType() == Types.VARCHAR
-                    || parameter.getType() == Types.NVARCHAR
-                    || parameter.getType() == Types.CHAR
-                    || parameter.getType() == Types.NCHAR)) {
-
-            addFailure(validationResult, parameter, "Character 
ProcedureParameter '%s' doesn't have max length",
-                    parameter.getName());
-        }
-    }
-
     private void checkForDirection(ProcedureParameter parameter, 
ValidationResult validationResult) {
         // all attributes must have type
         if (parameter.getDirection() <= 0) {
diff --git 
a/cayenne-project/src/test/java/org/apache/cayenne/project/extension/validation/ValidationConfigSaveTest.java
 
b/cayenne-project/src/test/java/org/apache/cayenne/project/extension/validation/ValidationConfigSaveTest.java
new file mode 100644
index 000000000..59778fcc6
--- /dev/null
+++ 
b/cayenne-project/src/test/java/org/apache/cayenne/project/extension/validation/ValidationConfigSaveTest.java
@@ -0,0 +1,86 @@
+/*****************************************************************
+ *   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.project.extension.validation;
+
+import org.apache.cayenne.configuration.ConfigurationNameMapper;
+import org.apache.cayenne.configuration.ConfigurationTree;
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.configuration.DefaultConfigurationNameMapper;
+import org.apache.cayenne.configuration.xml.DataChannelMetaData;
+import org.apache.cayenne.configuration.xml.DefaultDataChannelMetaData;
+import org.apache.cayenne.di.DIBootstrap;
+import org.apache.cayenne.di.Injector;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.project.FileProjectSaver;
+import org.apache.cayenne.project.Project;
+import org.apache.cayenne.project.extension.ProjectExtension;
+import org.apache.cayenne.project.validation.Inspection;
+import org.apache.cayenne.project.validation.ValidationConfig;
+import org.apache.cayenne.resource.URLResource;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.util.EnumSet;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ValidationConfigSaveTest {
+
+    @TempDir
+    public File tempDir;
+
+    @Test
+    @SuppressWarnings("removal") // intentionally references a deprecated 
inspection
+    public void deprecatedInspectionStrippedOnSave() throws Exception {
+        Module testModule = binder -> {
+            
binder.bind(ConfigurationNameMapper.class).to(DefaultConfigurationNameMapper.class);
+            
binder.bind(DataChannelMetaData.class).to(DefaultDataChannelMetaData.class);
+        };
+        Injector injector = DIBootstrap.createInjector(testModule);
+
+        DataChannelMetaData metaData = 
injector.getInstance(DataChannelMetaData.class);
+        ValidationExtension extension = new ValidationExtension();
+        injector.injectMembers(extension);
+
+        FileProjectSaver saver = new 
FileProjectSaver(List.<ProjectExtension>of(extension));
+        injector.injectMembers(saver);
+
+        DataChannelDescriptor descriptor = new DataChannelDescriptor();
+        descriptor.setName("deprecation");
+
+        // disable one deprecated inspection and one regular inspection
+        EnumSet<Inspection> enabled = EnumSet.complementOf(EnumSet.of(
+                Inspection.DB_ATTRIBUTE_NO_LENGTH,
+                Inspection.DATA_NODE_NO_NAME));
+        metaData.add(descriptor, new ValidationConfig(enabled));
+
+        Project project = new Project(new ConfigurationTree<>(descriptor));
+        saver.saveAs(project, new URLResource(tempDir.toURI().toURL()));
+
+        String xml = Files.readString(new File(tempDir, 
"cayenne-deprecation.xml").toPath());
+
+        // the deprecated inspection is dropped, the regular one is 
round-tripped
+        assertFalse(xml.contains("DB_ATTRIBUTE_NO_LENGTH"), xml);
+        assertTrue(xml.contains("DATA_NODE_NO_NAME"), xml);
+    }
+}
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 b67f80022..ad2c21bd0 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
@@ -171,6 +171,14 @@ public class AutoAdapter implements DbAdapter {
                return getAdapter().typeSupportsScale(type);
        }
 
+       /**
+        * @since 5.0
+        */
+       @Override
+       public int defaultCharLength() {
+               return getAdapter().defaultCharLength();
+       }
+
        @Override
        public Collection<String> dropTableStatements(DbEntity table) {
                return getAdapter().dropTableStatements(table);
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 3e7503d18..3e32f9690 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/DbAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/DbAdapter.java
@@ -132,6 +132,15 @@ public interface DbAdapter {
         */
        boolean typeSupportsScale(int type);
 
+       /**
+        * Returns the length to use for an unconstrained character column (a 
CHAR/VARCHAR-family column with no max
+        * length) when this database requires a length. Adapters whose 
database accepts a length-free character type
+        * instead declare an {@link NativeColumnType#asUnconstrained() 
unconstrained} native type.
+        *
+        * @since 5.0
+        */
+       int defaultCharLength();
+
        /**
         * Returns a collection of SQL statements needed to drop a database 
table.
         *
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 c4cd15ba5..1b05fea64 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
@@ -68,6 +68,8 @@ public class JdbcAdapter implements DbAdapter {
     private PkGenerator pkGenerator;
     protected QuotingStrategy quotingStrategy;
 
+    protected int defaultCharLength = 255;
+
     protected Map<Integer, NativeColumnType[]> nativeColumnTypes;
     protected ExtendedTypeMap extendedTypes;
     protected boolean supportsBatchUpdates;
@@ -291,6 +293,14 @@ public class JdbcAdapter implements DbAdapter {
                 || type == Types.TIME || type == Types.TIMESTAMP;
     }
 
+    /**
+     * @since 5.0
+     */
+    @Override
+    public int defaultCharLength() {
+        return defaultCharLength;
+    }
+
     /**
      * @since 3.0
      */
@@ -379,8 +389,49 @@ public class JdbcAdapter implements DbAdapter {
         sqlBuffer.append(column.isMandatory() ? " NOT NULL" : " NULL");
     }
 
+    /**
+     * Selects the native type variant to use for a column: the auto-increment 
variant for a generated column,
+     * the {@link NativeColumnType#unconstrained() unconstrained} variant for 
a character column with no max length,
+     * otherwise the primary variant.
+     *
+     * @since 5.0
+     */
+    public static NativeColumnType selectNativeType(DbAdapter adapter, 
DbAttribute column) {
+        NativeColumnType[] variants = 
adapter.nativeColumnTypes(column.getType());
+        if (variants == null || variants.length == 0) {
+            String entityName = column.getEntity() != null
+                    ? column.getEntity().getFullyQualifiedName()
+                    : "<null>";
+            throw new CayenneRuntimeException("Undefined type for attribute 
'%s.%s': %s."
+                    , entityName, column.getName(), column.getType());
+        }
+
+        if (column.isGenerated()) {
+            for (NativeColumnType variant : variants) {
+                if (variant.autoIncrement()) {
+                    return variant;
+                }
+            }
+        } else if (isLengthBearingCharacter(column.getType()) && 
column.getMaxLength() <= 0) {
+            for (NativeColumnType variant : variants) {
+                if (variant.unconstrained()) {
+                    return variant;
+                }
+            }
+        }
+        return variants[0];
+    }
+
     public static String sizeAndPrecision(DbAdapter adapter, DbAttribute 
column) {
-        if (!adapter.typeSupportsLength(column.getType()) && 
!adapter.typeSupportsScale(column.getType())) {
+        int type = column.getType();
+
+        // an unconstrained character column either uses a length-free native 
type, or falls back to the
+        // adapter's default length for databases that require one
+        if (isLengthBearingCharacter(type) && column.getMaxLength() <= 0) {
+            return selectNativeType(adapter, column).unconstrained() ? "" : 
"(" + adapter.defaultCharLength() + ")";
+        }
+
+        if (!adapter.typeSupportsLength(type) && 
!adapter.typeSupportsScale(type)) {
             return "";
         }
 
@@ -401,21 +452,12 @@ public class JdbcAdapter implements DbAdapter {
     }
 
     public static String getType(DbAdapter adapter, DbAttribute column) {
-        int columnType = column.getType();
-        if(columnType == Types.OTHER) {
+        if (column.getType() == Types.OTHER) {
             // TODO: warn that this is unsupported yet
             return "OTHER";
         }
 
-        NativeColumnType[] types = adapter.nativeColumnTypes(columnType);
-        if (types == null || types.length == 0) {
-            String entityName = column.getEntity() != null
-                    ? column.getEntity().getFullyQualifiedName()
-                    : "<null>";
-            throw new CayenneRuntimeException("Undefined type for attribute 
'%s.%s': %s."
-                    , entityName, column.getName(), column.getType());
-        }
-        return types[0].nativeType();
+        return selectNativeType(adapter, column).nativeType();
     }
 
     /**
@@ -684,4 +726,7 @@ public class JdbcAdapter implements DbAdapter {
         return this;
     }
 
+    private static boolean isLengthBearingCharacter(int type) {
+        return type == Types.CHAR || type == Types.NCHAR || type == 
Types.VARCHAR || type == Types.NVARCHAR;
+    }
 }
diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/NativeColumnType.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/NativeColumnType.java
index 3d0c347f3..56d1545f1 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/NativeColumnType.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/NativeColumnType.java
@@ -20,18 +20,19 @@ 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").
+ * A JDBC type may map to more than one native type, e.g. a primary name plus 
the auto-increment variant used
+ * for generated columns (PostgreSQL "serial"), or a primary name plus an 
unconstrained variant used for a
+ * character column with no max length (PostgreSQL "text").
  *
  * @since 5.0
  */
-public record NativeColumnType(int jdbcType, String nativeType, boolean 
autoIncrement) {
+public record NativeColumnType(int jdbcType, String nativeType, boolean 
autoIncrement, boolean unconstrained) {
 
     /**
-     * Creates a plain external type.
+     * Creates a plain native type.
      */
-    public static NativeColumnType of(int jdbcType, String dbType) {
-        return new NativeColumnType(jdbcType, dbType, false);
+    public static NativeColumnType of(int jdbcType, String nativeType) {
+        return new NativeColumnType(jdbcType, nativeType, false, false);
     }
 
     /**
@@ -39,6 +40,14 @@ public record NativeColumnType(int jdbcType, String 
nativeType, boolean autoIncr
      * {@link java.sql.Types#INTEGER}. Used to render generated columns.
      */
     public NativeColumnType asAutoIncrement() {
-        return new NativeColumnType(jdbcType, nativeType, true);
+        return new NativeColumnType(jdbcType, nativeType, true, unconstrained);
+    }
+
+    /**
+     * Returns a copy of this type flagged as the unconstrained variant, i.e. 
one that may be used for a character
+     * column with no max length and rendered without a length suffix, e.g. 
PostgreSQL "text" or MySQL "longtext".
+     */
+    public NativeColumnType asUnconstrained() {
+        return new NativeColumnType(jdbcType, nativeType, autoIncrement, true);
     }
 }
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 3e3ace416..2f9de43b7 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
@@ -64,7 +64,7 @@ public class H2Adapter extends JdbcAdapter {
             NativeColumnType.of(Types.BIT, "BIT"),
             NativeColumnType.of(Types.BLOB, "BLOB"),
             NativeColumnType.of(Types.BOOLEAN, "BOOLEAN"),
-            NativeColumnType.of(Types.CHAR, "CHAR"),
+            NativeColumnType.of(Types.CHAR, "CHAR").asUnconstrained(),
             NativeColumnType.of(Types.CLOB, "CLOB"),
             NativeColumnType.of(Types.DATALINK, "DATALINK"),
             NativeColumnType.of(Types.DATE, "DATE"),
@@ -76,10 +76,10 @@ public class H2Adapter extends JdbcAdapter {
             NativeColumnType.of(Types.LONGNVARCHAR, "NCLOB"),
             NativeColumnType.of(Types.LONGVARBINARY, "LONGVARBINARY"),
             NativeColumnType.of(Types.LONGVARCHAR, "CLOB"),
-            NativeColumnType.of(Types.NCHAR, "NCHAR"),
+            NativeColumnType.of(Types.NCHAR, "NCHAR").asUnconstrained(),
             NativeColumnType.of(Types.NCLOB, "NCLOB"),
             NativeColumnType.of(Types.NUMERIC, "NUMERIC"),
-            NativeColumnType.of(Types.NVARCHAR, "NVARCHAR"),
+            NativeColumnType.of(Types.NVARCHAR, "NVARCHAR").asUnconstrained(),
             NativeColumnType.of(Types.OTHER, "OTHER"),
             NativeColumnType.of(Types.REAL, "REAL"),
             NativeColumnType.of(Types.REF, "REF"),
@@ -90,7 +90,7 @@ public class H2Adapter extends JdbcAdapter {
             NativeColumnType.of(Types.TIMESTAMP, "TIMESTAMP"),
             NativeColumnType.of(Types.TINYINT, "TINYINT"),
             NativeColumnType.of(Types.VARBINARY, "VARBINARY"),
-            NativeColumnType.of(Types.VARCHAR, "VARCHAR"),
+            NativeColumnType.of(Types.VARCHAR, "VARCHAR").asUnconstrained(),
         };
     }
 
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 103517e8c..0e031871c 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
@@ -76,7 +76,7 @@ public class HSQLDBAdapter extends JdbcAdapter {
             NativeColumnType.of(Types.BIT, "BIT"),
             NativeColumnType.of(Types.BLOB, "LONGVARBINARY"),
             NativeColumnType.of(Types.BOOLEAN, "BOOLEAN"),
-            NativeColumnType.of(Types.CHAR, "CHAR"),
+            NativeColumnType.of(Types.CHAR, "CHAR").asUnconstrained(),
             NativeColumnType.of(Types.CLOB, "LONGVARCHAR"),
             NativeColumnType.of(Types.DATALINK, "DATALINK"),
             NativeColumnType.of(Types.DATE, "DATE"),
@@ -88,10 +88,10 @@ public class HSQLDBAdapter extends JdbcAdapter {
             NativeColumnType.of(Types.LONGNVARCHAR, "LONGVARCHAR"),
             NativeColumnType.of(Types.LONGVARBINARY, "LONGVARBINARY"),
             NativeColumnType.of(Types.LONGVARCHAR, "LONGVARCHAR"),
-            NativeColumnType.of(Types.NCHAR, "CHAR"),
+            NativeColumnType.of(Types.NCHAR, "CHAR").asUnconstrained(),
             NativeColumnType.of(Types.NCLOB, "LONGVARCHAR"),
             NativeColumnType.of(Types.NUMERIC, "NUMERIC"),
-            NativeColumnType.of(Types.NVARCHAR, "VARCHAR"),
+            NativeColumnType.of(Types.NVARCHAR, "VARCHAR").asUnconstrained(),
             NativeColumnType.of(Types.OTHER, "OTHER"),
             NativeColumnType.of(Types.REAL, "REAL"),
             NativeColumnType.of(Types.REF, "REF"),
@@ -101,7 +101,7 @@ public class HSQLDBAdapter extends JdbcAdapter {
             NativeColumnType.of(Types.TIMESTAMP, "TIMESTAMP"),
             NativeColumnType.of(Types.TINYINT, "TINYINT"),
             NativeColumnType.of(Types.VARBINARY, "VARBINARY"),
-            NativeColumnType.of(Types.VARCHAR, "VARCHAR"),
+            NativeColumnType.of(Types.VARCHAR, "VARCHAR").asUnconstrained(),
         };
     }
 
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 32b8ef853..7a5edb83d 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
@@ -91,6 +91,7 @@ public class MySQLAdapter extends JdbcAdapter {
             NativeColumnType.of(Types.BLOB, "LONGBLOB"),
             NativeColumnType.of(Types.BOOLEAN, "BOOL"),
             NativeColumnType.of(Types.CHAR, "CHAR"),
+            NativeColumnType.of(Types.CHAR, "longtext").asUnconstrained(),
             NativeColumnType.of(Types.CLOB, "LONGTEXT"),
             NativeColumnType.of(Types.DATE, "DATE"),
             NativeColumnType.of(Types.DECIMAL, "DECIMAL"),
@@ -101,9 +102,11 @@ public class MySQLAdapter extends JdbcAdapter {
             NativeColumnType.of(Types.LONGVARBINARY, "LONGBLOB"),
             NativeColumnType.of(Types.LONGVARCHAR, "LONGTEXT"),
             NativeColumnType.of(Types.NCHAR, "CHAR"),
+            NativeColumnType.of(Types.NCHAR, "longtext").asUnconstrained(),
             NativeColumnType.of(Types.NCLOB, "LONGTEXT"),
             NativeColumnType.of(Types.NUMERIC, "DECIMAL"),
             NativeColumnType.of(Types.NVARCHAR, "VARCHAR"),
+            NativeColumnType.of(Types.NVARCHAR, "longtext").asUnconstrained(),
             NativeColumnType.of(Types.REAL, "DOUBLE"),
             NativeColumnType.of(Types.SMALLINT, "SMALLINT"),
             NativeColumnType.of(Types.SQLXML, "LONGTEXT"),
@@ -112,6 +115,7 @@ public class MySQLAdapter extends JdbcAdapter {
             NativeColumnType.of(Types.TINYINT, "TINYINT"),
             NativeColumnType.of(Types.VARBINARY, "VARBINARY"),
             NativeColumnType.of(Types.VARCHAR, "VARCHAR"),
+            NativeColumnType.of(Types.VARCHAR, "longtext").asUnconstrained(),
         };
     }
 
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 f904e6ed0..4f7da00be 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
@@ -162,6 +162,9 @@ public class OracleAdapter extends JdbcAdapter {
 
         // enable batch updates by default
         setSupportsBatchUpdates(true);
+
+        // VARCHAR2 standard maximum, used for an unconstrained character 
column
+        this.defaultCharLength = 4000;
     }
 
     @Override
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 732807965..90dc06fdb 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
@@ -78,6 +78,7 @@ public class PostgresAdapter extends JdbcAdapter {
                 NativeColumnType.of(Types.BLOB, "oid"),
                 NativeColumnType.of(Types.BOOLEAN, "boolean"),
                 NativeColumnType.of(Types.CHAR, "character"),
+                NativeColumnType.of(Types.CHAR, "text").asUnconstrained(),
                 NativeColumnType.of(Types.CLOB, "text"),
                 NativeColumnType.of(Types.DATE, "date"),
                 NativeColumnType.of(Types.DECIMAL, "decimal"),
@@ -89,9 +90,11 @@ public class PostgresAdapter extends JdbcAdapter {
                 NativeColumnType.of(Types.LONGVARBINARY, "bytea"),
                 NativeColumnType.of(Types.LONGVARCHAR, "text"),
                 NativeColumnType.of(Types.NCHAR, "character"),
+                NativeColumnType.of(Types.NCHAR, "text").asUnconstrained(),
                 NativeColumnType.of(Types.NCLOB, "text"),
                 NativeColumnType.of(Types.NUMERIC, "numeric"),
                 NativeColumnType.of(Types.NVARCHAR, "varchar"),
+                NativeColumnType.of(Types.NVARCHAR, "text").asUnconstrained(),
                 NativeColumnType.of(Types.OTHER, "json"),
                 NativeColumnType.of(Types.REAL, "real"),
                 NativeColumnType.of(Types.SMALLINT, "smallint"),
@@ -102,6 +105,7 @@ public class PostgresAdapter extends JdbcAdapter {
                 NativeColumnType.of(Types.TINYINT, "smallint"),
                 NativeColumnType.of(Types.VARBINARY, "bytea"),
                 NativeColumnType.of(Types.VARCHAR, "varchar"),
+                NativeColumnType.of(Types.VARCHAR, "text").asUnconstrained(),
         };
     }
 
@@ -149,7 +153,8 @@ public class PostgresAdapter extends JdbcAdapter {
     @Override
     public DbAttribute buildAttribute(String name, String typeName, int type, 
int maxLength, int scale, boolean allowNulls) {
 
-        int amendedType = switch (typeName != null ? typeName.toLowerCase() : 
"") {
+        String lower = typeName != null ? typeName.toLowerCase() : "";
+        int amendedType = switch (lower) {
             case "json" -> Types.OTHER;
             // "bytea" maps to pretty much any binary type, so it is up to us 
to select the most sensible default.
             // And the winner is LONGVARBINARY
@@ -159,7 +164,10 @@ public class PostgresAdapter extends JdbcAdapter {
             default -> type;
         };
 
-        return super.buildAttribute(name, typeName, amendedType, maxLength, 
scale, allowNulls);
+        // "text" is an unconstrained VARCHAR; the driver reports a bogus huge 
length, so treat it as unknown (-1)
+        int amendedLength = "text".equals(lower) ? -1 : maxLength;
+
+        return super.buildAttribute(name, typeName, amendedType, 
amendedLength, scale, allowNulls);
     }
 
     @Override
@@ -234,26 +242,10 @@ public class PostgresAdapter extends JdbcAdapter {
                     , ent.getFullyQualifiedName(), at.getName());
         }
 
-        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());
-        }
-
-        // 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;
-                }
-            }
-        }
-
+        // getType / sizeAndPrecision pick the right variant (auto-increment 
serial, unconstrained "text", ...)
         buf.append(context.quotedName(at))
                 .append(' ')
-                .append(selected.nativeType())
+                .append(getType(this, at))
                 .append(sizeAndPrecision(this, at))
                 .append(at.isMandatory() ? " NOT" : "")
                 .append(" NULL");
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 41556779c..a93235110 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
@@ -90,6 +90,7 @@ public class SQLServerAdapter extends JdbcAdapter {
             NativeColumnType.of(Types.BLOB, "image"),
             NativeColumnType.of(Types.BOOLEAN, "bit"),
             NativeColumnType.of(Types.CHAR, "char"),
+            NativeColumnType.of(Types.CHAR, "varchar(max)").asUnconstrained(),
             NativeColumnType.of(Types.CLOB, "text"),
             NativeColumnType.of(Types.DATE, "date"),
             NativeColumnType.of(Types.DECIMAL, "decimal"),
@@ -100,9 +101,11 @@ public class SQLServerAdapter extends JdbcAdapter {
             NativeColumnType.of(Types.LONGVARBINARY, "image"),
             NativeColumnType.of(Types.LONGVARCHAR, "text"),
             NativeColumnType.of(Types.NCHAR, "nchar"),
+            NativeColumnType.of(Types.NCHAR, 
"nvarchar(max)").asUnconstrained(),
             NativeColumnType.of(Types.NCLOB, "ntext"),
             NativeColumnType.of(Types.NUMERIC, "numeric"),
             NativeColumnType.of(Types.NVARCHAR, "nvarchar"),
+            NativeColumnType.of(Types.NVARCHAR, 
"nvarchar(max)").asUnconstrained(),
             NativeColumnType.of(Types.REAL, "real"),
             NativeColumnType.of(Types.ROWID, "ROWID"),
             NativeColumnType.of(Types.SMALLINT, "smallint"),
@@ -112,6 +115,7 @@ public class SQLServerAdapter extends JdbcAdapter {
             NativeColumnType.of(Types.TINYINT, "tinyint"),
             NativeColumnType.of(Types.VARBINARY, "varbinary"),
             NativeColumnType.of(Types.VARCHAR, "varchar"),
+            NativeColumnType.of(Types.VARCHAR, 
"varchar(max)").asUnconstrained(),
         };
     }
 
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 e25f92a76..ce4850a84 100644
--- a/cayenne/src/test/java/org/apache/cayenne/dba/JdbcAdapterIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/dba/JdbcAdapterIT.java
@@ -62,6 +62,19 @@ public class JdbcAdapterIT {
         assertFalse(types[0].autoIncrement());
     }
 
+    @Test
+    public void createTableUnconstrainedVarcharUsesDefaultLength() {
+        JdbcAdapter adapter = env.adhocObjectFactory().newInstance(
+                JdbcAdapter.class,
+                JdbcAdapter.class.getName());
+        DbEntity e = new DbEntity("Test");
+        DbAttribute c = new DbAttribute("c");
+        c.setType(Types.VARCHAR);   // no max length -> default char length
+        e.addAttribute(c);
+
+        assertEquals("CREATE TABLE Test (c VARCHAR(255) NULL)", 
adapter.createTable(e));
+    }
+
     @Test
     public void createTableQuoteSqlIdentifiers() {
 
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/dba/NativeColumnTypeTest.java 
b/cayenne/src/test/java/org/apache/cayenne/dba/NativeColumnTypeTest.java
index 7999d5cde..d70b9532b 100644
--- a/cayenne/src/test/java/org/apache/cayenne/dba/NativeColumnTypeTest.java
+++ b/cayenne/src/test/java/org/apache/cayenne/dba/NativeColumnTypeTest.java
@@ -34,6 +34,7 @@ public class NativeColumnTypeTest {
         assertEquals(Types.VARCHAR, type.jdbcType());
         assertEquals("varchar", type.nativeType());
         assertFalse(type.autoIncrement());
+        assertFalse(type.unconstrained());
     }
 
     @Test
@@ -43,10 +44,26 @@ public class NativeColumnTypeTest {
 
         // only the flag flips; everything else is preserved
         assertTrue(auto.autoIncrement());
+        assertFalse(auto.unconstrained());
         assertEquals(base.jdbcType(), auto.jdbcType());
         assertEquals(base.nativeType(), auto.nativeType());
 
         // the original is unchanged
         assertFalse(base.autoIncrement());
     }
+
+    @Test
+    public void asUnconstrained() {
+        NativeColumnType base = NativeColumnType.of(Types.VARCHAR, "text");
+        NativeColumnType unconstrained = base.asUnconstrained();
+
+        // only the flag flips; everything else is preserved
+        assertTrue(unconstrained.unconstrained());
+        assertFalse(unconstrained.autoIncrement());
+        assertEquals(base.jdbcType(), unconstrained.jdbcType());
+        assertEquals(base.nativeType(), unconstrained.nativeType());
+
+        // the original is unchanged
+        assertFalse(base.unconstrained());
+    }
 }
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapterIT.java 
b/cayenne/src/test/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapterIT.java
index 8b80f6d58..546a7f1fc 100644
--- a/cayenne/src/test/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapterIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapterIT.java
@@ -66,7 +66,22 @@ public class HSQLDBAdapterIT {
         e.addAttribute(dblPrec);
         
         String sql = adapter.createTable(e);
-        
+
         assertEquals(0,sql.indexOf("CREATE CACHED TABLE"));
     }
+
+    @Test
+    public void createTableUnconstrainedVarcharIsLengthFree() {
+        HSQLDBAdapter adapter = env.adhocObjectFactory().newInstance(
+                HSQLDBAdapter.class,
+                HSQLDBAdapter.class.getName());
+        DbEntity e = new DbEntity("Test");
+        DbAttribute c = new DbAttribute("c");
+        c.setType(Types.VARCHAR);   // no max length -> unconstrained
+
+        e.addAttribute(c);
+
+        // HSQLDB accepts a length-free VARCHAR
+        assertEquals("CREATE CACHED TABLE Test (c VARCHAR NULL)", 
adapter.createTable(e));
+    }
 }
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/dba/mysql/MySQLAdapterIT.java 
b/cayenne/src/test/java/org/apache/cayenne/dba/mysql/MySQLAdapterIT.java
index 7613d54bf..6b13183aa 100644
--- a/cayenne/src/test/java/org/apache/cayenne/dba/mysql/MySQLAdapterIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/dba/mysql/MySQLAdapterIT.java
@@ -115,4 +115,18 @@ public class MySQLAdapterIT {
         assertTrue(sql.indexOf("DATETIME") > 0);
         assertEquals("CREATE TABLE Test (dbl1 DATETIME NULL, dbl2 TIME NULL) 
ENGINE=InnoDB", sql);
     }
+
+    @Test
+    public void createTableUnconstrainedVarcharRendersLongtext() {
+        MySQLAdapter adapter = env.adhocObjectFactory().newInstance(
+                MySQLAdapter.class,
+                MySQLAdapter.class.getName());
+
+        DbEntity e = new DbEntity("Test");
+        DbAttribute c = new DbAttribute("c");
+        c.setType(Types.VARCHAR);   // no max length -> unconstrained
+        e.addAttribute(c);
+
+        assertEquals("CREATE TABLE Test (c longtext NULL) ENGINE=InnoDB", 
adapter.createTable(e));
+    }
 }
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 7f240469d..57258fd1b 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
@@ -21,6 +21,8 @@ package org.apache.cayenne.dba.oracle;
 
 import org.apache.cayenne.dba.NativeColumnType;
 import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.query.InsertBatchQuery;
 import org.apache.cayenne.unit.CayenneProjects;
 import org.apache.cayenne.unit.CayenneTestsEnv;
@@ -62,4 +64,19 @@ public class OracleAdapterIT {
         assertEquals(1, types.length);
         assertEquals("TIMESTAMP", types[0].nativeType());
     }
+
+    @Test
+    public void createTableUnconstrainedVarcharUsesDefaultLength() {
+        OracleAdapter adapter = env.adhocObjectFactory().newInstance(
+                OracleAdapter.class,
+                OracleAdapter.class.getName());
+        DbEntity e = new DbEntity("Test");
+        DbAttribute c = new DbAttribute("c");
+        c.setType(Types.VARCHAR);   // no max length -> Oracle default char 
length
+
+        e.addAttribute(c);
+
+        // Oracle has no length-free character type, so it falls back to 
VARCHAR2(4000)
+        assertEquals("CREATE TABLE Test (c VARCHAR2(4000) NULL)", 
adapter.createTable(e));
+    }
 }
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 7b147bdef..9795c56b4 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
@@ -94,4 +94,29 @@ public class PostgresAdapterIT {
         assertArrayEquals(new String[]{"integer", "serial"}, 
adapter.externalTypesForJdbcType(Types.INTEGER));
     }
 
+    @Test
+    public void textColumnReverseEngineeredAsUnconstrained() {
+        PostgresAdapter adapter = env.adhocObjectFactory().newInstance(
+                PostgresAdapter.class,
+                PostgresAdapter.class.getName());
+
+        // the driver reports a bogus huge length for "text"; it must become 
an unconstrained VARCHAR
+        DbAttribute attribute = adapter.buildAttribute("c", "text", 
Types.VARCHAR, Integer.MAX_VALUE, -1, true);
+        assertEquals(Types.VARCHAR, attribute.getType());
+        assertEquals(-1, attribute.getMaxLength());
+    }
+
+    @Test
+    public void createTableUnconstrainedVarcharRendersText() {
+        PostgresAdapter adapter = env.adhocObjectFactory().newInstance(
+                PostgresAdapter.class,
+                PostgresAdapter.class.getName());
+        DbEntity e = new DbEntity("Test");
+        DbAttribute c = new DbAttribute("c");
+        c.setType(Types.VARCHAR);   // no max length -> unconstrained
+        e.addAttribute(c);
+
+        assertEquals("CREATE TABLE Test (c text NULL)", 
adapter.createTable(e));
+    }
+
 }
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapterIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapterIT.java
index 6843d9582..20245537b 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapterIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapterIT.java
@@ -55,4 +55,17 @@ public class SQLServerAdapterIT {
         assertEquals(-1, sql.indexOf("float(22, 12)"));
         assertEquals("CREATE TABLE Test (dbl1 float(22) NULL)", sql);
     }
+
+    @Test
+    public void createTableUnconstrainedVarcharRendersMax() {
+        SQLServerAdapter adapter = env.adhocObjectFactory().newInstance(
+                SQLServerAdapter.class,
+                SQLServerAdapter.class.getName());
+        DbEntity e = new DbEntity("Test");
+        DbAttribute c = new DbAttribute("c");
+        c.setType(Types.VARCHAR);   // no max length -> unconstrained
+        e.addAttribute(c);
+
+        assertEquals("CREATE TABLE Test (c varchar(max) NULL)", 
adapter.createTable(e));
+    }
 }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datadomain/validation/InspectionCheckBoxTree.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datadomain/validation/InspectionCheckBoxTree.java
index 224413257..30847a2d6 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datadomain/validation/InspectionCheckBoxTree.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datadomain/validation/InspectionCheckBoxTree.java
@@ -68,6 +68,9 @@ class InspectionCheckBoxTree extends CheckBoxTree {
         DefaultMutableTreeNode root = new DefaultMutableTreeNode();
         Map<Inspection.Group, MutableTreeNode> groupNodes = new 
EnumMap<>(Inspection.Group.class);
         for (Inspection inspection : Inspection.values()) {
+            if (inspection.isDeprecated()) {
+                continue;
+            }
             MutableTreeNode groupNode = 
groupNodes.computeIfAbsent(inspection.group(), g -> addChild(root, g));
             DefaultMutableTreeNode inspectionNode = addChild(groupNode, 
inspection);
             inspectionPathCache.put(inspection, new 
TreePath(inspectionNode.getPath()));


Reply via email to