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

borinquenkid pushed a commit to branch 8.0.x
in repository https://gitbox.apache.org/repos/asf/grails-core.git


The following commit(s) were added to refs/heads/8.0.x by this push:
     new 54ea20836a fix(grails-data-hibernate7): make type: 'text' produce an 
unbounded column (#16020)
54ea20836a is described below

commit 54ea20836a9725b4bcc76acc07987e27a7bc89a2
Author: Walter B Duque de Estrada <[email protected]>
AuthorDate: Sun Aug 16 10:50:59 2026 -0500

    fix(grails-data-hibernate7): make type: 'text' produce an unbounded column 
(#16020)
    
    * fix(grails-data-hibernate7): make type: 'text' produce an unbounded column
    
    property type: 'text' resolved through Hibernate's legacy named-type
    lookup to StandardBasicTypes.TEXT, whose JDBC type code is the legacy
    java.sql.Types.LONGVARCHAR. Dialects (e.g. Postgres) don't render that
    legacy code as their native unbounded text/CLOB type, falling back to a
    bounded VARCHAR at Hibernate's generic Length.LONG default (32600) once
    no explicit column length is set. On schema update, altering an existing
    column down to that bound fails once any row already holds more text.
    
    Bind the modern SqlTypes.LONG32VARCHAR JDBC type directly for this case
    instead of going through the ambiguous legacy type name, restoring the
    "CLOB or TEXT depending on dialect" behavior the mapping DSL docs already
    promise for type: 'text'.
    
    Fixes #16010
    
    Co-Authored-By: Claude Sonnet 5 <[email protected]>
    
    * fix(grails-data-hibernate7): resolve type 'text' length via 
dialect-neutral Length.LONG32
    
    Address PR review feedback on the type: 'text' unbounded-column fix: 
instead of
    overriding the JDBC type descriptor with SqlTypes.LONG32VARCHAR, set the 
column's
    length to Hibernate 6+'s documented Length.LONG32 sentinel and let each 
dialect's
    own capacity-dependent DDL type registry resolve the native unbounded type 
(text,
    longtext, CLOB). This composes correctly with maxSize/inList/explicit column
    length instead of racing them, and keeps SimpleValueBinder as a pure 
orchestrator -
    the length decision now lives in StringColumnConstraintsBinder, which 
already owns
    string column length for maxSize/inList.
    
    Extends test coverage to close the "Postgres-only" gap: adds an H2-based 
spec that
    runs without Docker so container-less CI still exercises this path, and 
extends the
    Testcontainers spec to MySQL and MariaDB (Oracle excluded, matching the 
flaky-in-CI
    precedent already established in RLikeHibernate7Spec). Reverting the fix 
locally
    confirmed MySQL/MariaDB were independently affected (TEXT capped at 65535), 
not
    just Postgres.
    
    Co-Authored-By: Claude Sonnet 5 <[email protected]>
    
    * docs: clarify type: 'text' resolves to the dialect's own unbounded column
    
    Addresses review feedback on #16020 asking to document that GORM
    computes the concrete SQL type (text/longtext/CLOB) per dialect rather
    than emitting a literal "text" type, and that every Hibernate-shipped
    Dialect defines this mapping.
    
    Co-Authored-By: Claude Sonnet 5 <[email protected]>
    
    ---------
    
    Co-authored-by: Claude Sonnet 5 <[email protected]>
---
 .../cfg/domainbinding/binder/ColumnBinder.java     |   6 +-
 .../domainbinding/binder/SimpleValueBinder.java    |   5 +-
 .../binder/StringColumnConstraintsBinder.java      |  20 +++-
 .../GormTextTypeColumnIntegrationSpec.groovy       | 105 +++++++++++++++++++++
 .../hibernate/GormTextTypeColumnLengthSpec.groovy  |  99 +++++++++++++++++++
 .../cfg/domainbinding/ColumnBinderSpec.groovy      |  34 +++----
 .../cfg/domainbinding/SimpleValueBinderSpec.groovy |  64 +++++++++++++
 .../StringColumnConstraintsBinderSpec.groovy       |  78 +++++++++++++--
 grails-doc/src/en/ref/Database Mapping/type.adoc   |   4 +-
 9 files changed, 384 insertions(+), 31 deletions(-)

diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ColumnBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ColumnBinder.java
index a4e6f1ffb0..2c93ddb616 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ColumnBinder.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/ColumnBinder.java
@@ -84,6 +84,7 @@ public class ColumnBinder {
      * @param column The column to bind
      * @param path the path
      * @param table The table name
+     * @param typeName the property's resolved Hibernate type name
      */
     public void bindColumn(
             HibernatePersistentProperty property,
@@ -91,7 +92,8 @@ public class ColumnBinder {
             Column column,
             ColumnConfig cc,
             String path,
-            Table table) {
+            Table table,
+            String typeName) {
 
         if (cc != null) {
             column.setComment(cc.getComment());
@@ -116,7 +118,7 @@ public class ColumnBinder {
             Class<?> type = property.getType();
             if (type != null && (String.class.isAssignableFrom(type) || 
byte[].class.isAssignableFrom(type))) {
                 PropertyConfig mappedForm = property.getHibernateMappedForm();
-                
stringColumnConstraintsBinder.bindStringColumnConstraints(column, mappedForm);
+                
stringColumnConstraintsBinder.bindStringColumnConstraints(column, mappedForm, 
typeName);
             } else if (type != null && Number.class.isAssignableFrom(type)) {
                 PropertyConfig mappedForm = property.getHibernateMappedForm();
                 
numericColumnConstraintsBinder.bindNumericColumnConstraints(column, cc, 
mappedForm, type);
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SimpleValueBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SimpleValueBinder.java
index 13f53fb859..576f2f874e 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SimpleValueBinder.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SimpleValueBinder.java
@@ -84,7 +84,8 @@ public class SimpleValueBinder {
             String path) {
 
         PropertyConfig propertyConfig = property.getHibernateMappedForm();
-        simpleValue.setTypeName(property.getTypeName(simpleValue));
+        String typeName = property.getTypeName(simpleValue);
+        simpleValue.setTypeName(typeName);
         simpleValue.setTypeParameters(property.getTypeParameters(simpleValue));
 
         if (propertyConfig.isDerived() && !(property instanceof TenantId)) {
@@ -100,7 +101,7 @@ public class SimpleValueBinder {
                     .forEach(cc -> {
                         Column column = new Column();
                         
columnConfigToColumnBinder.bindColumnConfigToColumn(column, cc, propertyConfig);
-                        columnBinder.bindColumn(property, parentProperty, 
column, cc, path, table);
+                        columnBinder.bindColumn(property, parentProperty, 
column, cc, path, table, typeName);
                         if (simpleValue instanceof DependantValue) {
                             column.setNullable(true);
                         }
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/StringColumnConstraintsBinder.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/StringColumnConstraintsBinder.java
index 6e8d3e1e50..00c8d4a214 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/StringColumnConstraintsBinder.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/StringColumnConstraintsBinder.java
@@ -21,21 +21,39 @@ package org.grails.orm.hibernate.cfg.domainbinding.binder;
 import java.util.Objects;
 import java.util.Optional;
 
+import org.hibernate.Length;
 import org.hibernate.mapping.Column;
 
 import org.grails.datastore.mapping.config.Property;
 
 public class StringColumnConstraintsBinder {
 
-    public void bindStringColumnConstraints(Column column, Property 
mappedForm) {
+    /**
+     * Binds a String/byte[] column's length from the property's {@code 
maxSize}/{@code inList}
+     * constraints. When neither is present and the resolved Hibernate type 
name is {@code text},
+     * the column is left unbounded via Hibernate's capacity-dependent DDL 
type mechanism -
+     * {@code Length.LONG32} is the documented way to obtain each dialect's 
native unbounded string
+     * type (text/longtext/varchar(max)/clob) instead of a bounded VARCHAR - 
see GH-16010.
+     *
+     * @param column the column to bind the length onto
+     * @param mappedForm the property's constraints (maxSize/inList)
+     * @param typeName the resolved Hibernate type name, or {@code null} if 
not relevant
+     */
+    public void bindStringColumnConstraints(Column column, Property 
mappedForm, String typeName) {
         Integer number = Optional.ofNullable(mappedForm.getMaxSize())
                 .map(Number::intValue)
                 .orElse(getMax(mappedForm).orElse(0));
         if (number > 0) {
             column.setLength(number);
+        } else if (isUnboundedTextType(typeName)) {
+            column.setLength(Length.LONG32);
         }
     }
 
+    private static boolean isUnboundedTextType(String typeName) {
+        return "text".equalsIgnoreCase(typeName);
+    }
+
     private Optional<Integer> getMax(Property mappedForm) {
         return Optional.ofNullable(mappedForm.getInList()).flatMap(list -> 
list.stream()
                 .map(this::parseInt)
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/GormTextTypeColumnIntegrationSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/GormTextTypeColumnIntegrationSpec.groovy
new file mode 100644
index 0000000000..7dc24346a6
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/GormTextTypeColumnIntegrationSpec.groovy
@@ -0,0 +1,105 @@
+/*
+ *  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.grails.orm.hibernate
+
+import grails.gorm.annotation.Entity
+import grails.gorm.hibernate.HibernateEntity
+import grails.gorm.tests.HibernateGormDatastoreSpec
+import org.testcontainers.mariadb.MariaDBContainer
+import org.testcontainers.mysql.MySQLContainer
+import org.testcontainers.postgresql.PostgreSQLContainer
+import org.testcontainers.spock.Testcontainers
+import spock.lang.Requires
+import spock.lang.Shared
+
+/**
+ * Reproduces https://github.com/apache/grails-core/issues/16010 across every 
externally-run
+ * dialect this module tests against (see {@link 
grails.gorm.tests.RLikeHibernate7Spec} for the
+ * same H2/Postgres/MySQL/MariaDB precedent): a property mapped with {@code 
type: 'text'} must
+ * produce a genuinely unbounded column, not a bounded {@code 
varchar(n)}/{@code character
+ * varying(n)} that can fail to accommodate existing data on schema update. 
Oracle is
+ * intentionally excluded - its Testcontainers image is too flaky in CI to 
gate this spec on.
+ * H2 coverage lives separately in {@link GormTextTypeColumnLengthSpec}, which 
needs no container
+ * and so still runs when Docker (and therefore this whole spec) is 
unavailable.
+ */
+@Testcontainers
+@Requires({ isDockerAvailable() })
+class GormTextTypeColumnIntegrationSpec extends HibernateGormDatastoreSpec {
+
+    @Shared PostgreSQLContainer postgres = new 
PostgreSQLContainer("postgres:16")
+    @Shared MySQLContainer mysql = new MySQLContainer("mysql:8.0")
+    @Shared MariaDBContainer mariadb = new MariaDBContainer("mariadb:10.11")
+
+    void setupSpec() {
+        manager.registerDomainClasses(TextTypeMessage)
+    }
+
+    void "a property mapped with type 'text' produces an unbounded column on 
#db"() {
+        given:
+        if (!container.isRunning()) {
+            container.start()
+        }
+        // Ensure a completely fresh datastore per dialect, as in 
RLikeHibernate7Spec.
+        manager.destroy()
+        manager.grailsConfig = [
+            'dataSource.url'             : container.jdbcUrl,
+            'dataSource.driverClassName' : container.driverClassName,
+            'dataSource.username'        : container.username,
+            'dataSource.password'        : container.password,
+            'dataSource.dbCreate'        : 'create-drop',
+            'hibernate.hbm2ddl.auto'     : 'create',
+        ]
+        // 'hibernate.dialect' is intentionally omitted - Hibernate 7 
auto-detects it from
+        // JDBC metadata, avoiding a hardcoded dialect string per database.
+        manager.setup(this.class)
+
+        when:
+        Map<String, Object> column
+        datastore.dataSource.connection.withCloseable { conn ->
+            conn.createStatement().withCloseable { stmt ->
+                stmt.executeQuery('''
+                    select character_maximum_length
+                    from information_schema.columns
+                    where upper(table_name) = 'TEXT_TYPE_MESSAGE' and 
upper(column_name) = 'BODY'
+                '''.stripIndent()).with { rs ->
+                    rs.next()
+                    column = [maxLength: 
rs.getObject('character_maximum_length')]
+                }
+            }
+        }
+
+        then: 'no small bounded length is reported - a regression would report 
32600 (Length.LONG)'
+        column.maxLength == null || (column.maxLength as long) > 1_000_000L
+
+        where:
+        db          | container
+        "Postgres"  | postgres
+        "MySQL"     | mysql
+        "MariaDB"   | mariadb
+    }
+}
+
+@Entity
+class TextTypeMessage implements HibernateEntity<TextTypeMessage> {
+    String body
+
+    static mapping = {
+        body type: 'text'
+    }
+}
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/GormTextTypeColumnLengthSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/GormTextTypeColumnLengthSpec.groovy
new file mode 100644
index 0000000000..5c09a6f634
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/GormTextTypeColumnLengthSpec.groovy
@@ -0,0 +1,99 @@
+/*
+ *  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.grails.orm.hibernate
+
+import grails.gorm.annotation.Entity
+import grails.gorm.hibernate.HibernateEntity
+import grails.gorm.tests.HibernateGormDatastoreSpec
+import org.hibernate.Length
+import org.hibernate.mapping.PersistentClass
+
+/**
+ * Covers https://github.com/apache/grails-core/issues/16010 against the 
default H2 datastore
+ * used by the rest of the test suite, so the {@code type: 'text'} 
column-length behaviour is
+ * exercised even when Docker (and so {@link 
GormTextTypeColumnIntegrationSpec}'s Postgres/MySQL/
+ * MariaDB Testcontainers) is unavailable.
+ */
+class GormTextTypeColumnLengthSpec extends HibernateGormDatastoreSpec {
+
+    void setupSpec() {
+        manager.registerDomainClasses(UnboundedTextTypeMessage, 
BoundedTextTypeMessage)
+    }
+
+    void "a property mapped with type 'text' and no explicit length is bound 
to Length.LONG32"() {
+        when:
+        PersistentClass persistentClass = 
datastore.getMetadata().getEntityBinding(UnboundedTextTypeMessage.name)
+        def column = persistentClass.getProperty('body').getColumns().first()
+
+        then:
+        column.getLength() == Length.LONG32 as Long
+    }
+
+    void "a property mapped with type 'text' and an explicit maxSize keeps the 
bounded length"() {
+        when:
+        PersistentClass persistentClass = 
datastore.getMetadata().getEntityBinding(BoundedTextTypeMessage.name)
+        def column = persistentClass.getProperty('body').getColumns().first()
+
+        then:
+        column.getLength() == 500L
+    }
+
+    void "a property mapped with type 'text' and no explicit length produces 
an unbounded H2 CLOB column"() {
+        when:
+        Map<String, Object> column
+        datastore.dataSource.connection.withCloseable { conn ->
+            conn.createStatement().withCloseable { stmt ->
+                stmt.executeQuery('''
+                    select data_type, character_maximum_length
+                    from information_schema.columns
+                    where table_name = 'UNBOUNDED_TEXT_TYPE_MESSAGE' and 
column_name = 'BODY'
+                '''.stripIndent()).with { rs ->
+                    rs.next()
+                    column = [dataType: rs.getString('data_type'), maxLength: 
rs.getObject('character_maximum_length')]
+                }
+            }
+        }
+
+        then:
+        column.dataType == 'CHARACTER LARGE OBJECT'
+        column.maxLength == Long.MAX_VALUE
+    }
+}
+
+@Entity
+class UnboundedTextTypeMessage implements 
HibernateEntity<UnboundedTextTypeMessage> {
+    String body
+
+    static mapping = {
+        body type: 'text'
+    }
+}
+
+@Entity
+class BoundedTextTypeMessage implements 
HibernateEntity<BoundedTextTypeMessage> {
+    String body
+
+    static constraints = {
+        body maxSize: 500
+    }
+
+    static mapping = {
+        body type: 'text'
+    }
+}
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ColumnBinderSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ColumnBinderSpec.groovy
index 096c3f9409..7c4a51cf48 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ColumnBinderSpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/ColumnBinderSpec.groovy
@@ -60,7 +60,7 @@ class ColumnBinderSpec extends HibernateGormDatastoreSpec {
         columnNameFetcher.getColumnNameForPropertyAndPath(prop, null, null) >> 
"mtm_fk"
 
         when:
-        binder.bindColumn(prop, null, column, null, null, table)
+        binder.bindColumn(prop, null, column, null, null, table, null)
 
         then:
         column.getName() == "mtm_fk"
@@ -97,7 +97,7 @@ class ColumnBinderSpec extends HibernateGormDatastoreSpec {
         parentProp.isNullable() >> true // should make column initially 
nullable
 
         when:
-        binder.bindColumn(prop, parentProp, column, cc, "p", table)
+        binder.bindColumn(prop, parentProp, column, cc, "p", table, null)
 
         then:
         column.getName() == "num_col"
@@ -138,7 +138,7 @@ class ColumnBinderSpec extends HibernateGormDatastoreSpec {
         columnNameFetcher.getColumnNameForPropertyAndPath(prop, null, null) >> 
"fetched_col"
 
         when:
-        binder.bindColumn(prop, null, column, null, null, table)
+        binder.bindColumn(prop, null, column, null, null, table, null)
 
         then:
         column.getName() == "pre_existing" // should not overwrite existing 
name
@@ -171,12 +171,12 @@ class ColumnBinderSpec extends HibernateGormDatastoreSpec 
{
         columnNameFetcher.getColumnNameForPropertyAndPath(prop, null, null) >> 
"str_col"
 
         when:
-        binder.bindColumn(prop, null, column, null, null, table)
+        binder.bindColumn(prop, null, column, null, null, table, "text")
 
         then:
         column.getName() == "str_col"
         column.isNullable() == false
-        1 * stringBinder.bindStringColumnConstraints(column, _)
+        1 * stringBinder.bindStringColumnConstraints(column, _, "text")
         1 * keyCreator.createKeyForProps(prop, null, table, "str_col")
         1 * indexBinder.bindIndex("str_col", column, null, table)
     }
@@ -206,7 +206,7 @@ class ColumnBinderSpec extends HibernateGormDatastoreSpec {
         columnNameFetcher.getColumnNameForPropertyAndPath(prop, null, null) >> 
"one_to_one_fk"
 
         when:
-        binder.bindColumn(prop, null, column, null, null, table)
+        binder.bindColumn(prop, null, column, null, null, table, null)
 
         then:
         column.getName() == "one_to_one_fk"
@@ -239,7 +239,7 @@ class ColumnBinderSpec extends HibernateGormDatastoreSpec {
         columnNameFetcher.getColumnNameForPropertyAndPath(prop, null, null) >> 
"to_one_fk"
 
         when:
-        binder.bindColumn(prop, null, column, null, null, table)
+        binder.bindColumn(prop, null, column, null, null, table, null)
 
         then:
         column.getName() == "to_one_fk"
@@ -272,7 +272,7 @@ class ColumnBinderSpec extends HibernateGormDatastoreSpec {
         columnNameFetcher.getColumnNameForPropertyAndPath(prop, null, null) >> 
"assoc_fk"
 
         when:
-        binder.bindColumn(prop, null, column, null, null, table)
+        binder.bindColumn(prop, null, column, null, null, table, null)
 
         then:
         column.getName() == "assoc_fk"
@@ -307,7 +307,7 @@ class ColumnBinderSpec extends HibernateGormDatastoreSpec {
         parentProp.isNullable() >> false
 
         when:
-        binder.bindColumn(prop, parentProp, column, null, null, table)
+        binder.bindColumn(prop, parentProp, column, null, null, table, null)
 
         then:
         column.getName() == "na_col"
@@ -342,7 +342,7 @@ class ColumnBinderSpec extends HibernateGormDatastoreSpec {
         parentProp.isNullable() >> true
 
         when:
-        binder.bindColumn(prop, parentProp, column, null, null, table)
+        binder.bindColumn(prop, parentProp, column, null, null, table, null)
 
         then:
         column.getName() == "na_col2"
@@ -377,7 +377,7 @@ class ColumnBinderSpec extends HibernateGormDatastoreSpec {
         parentProp.isNullable() >> false
 
         when:
-        binder.bindColumn(prop, parentProp, column, null, null, table)
+        binder.bindColumn(prop, parentProp, column, null, null, table, null)
 
         then:
         column.getName() == "na_col3"
@@ -413,14 +413,14 @@ class ColumnBinderSpec extends HibernateGormDatastoreSpec 
{
         columnNameFetcher.getColumnNameForPropertyAndPath(propNotUnique, null, 
null) >> "nu_col"
 
         when:
-        binder.bindColumn(propUnique, null, column, null, null, table)
+        binder.bindColumn(propUnique, null, column, null, null, table, null)
 
         then:
         column.isUnique()
 
         when:
         def column2 = new Column("test2")
-        binder.bindColumn(propNotUnique, null, column2, null, null, table)
+        binder.bindColumn(propNotUnique, null, column2, null, null, table, 
null)
 
         then:
         !column2.isUnique()
@@ -450,7 +450,7 @@ class ColumnBinderSpec extends HibernateGormDatastoreSpec {
         columnNameFetcher.getColumnNameForPropertyAndPath(prop, null, null) >> 
"sub_col"
 
         when:
-        binder.bindColumn(prop, null, column, null, null, table)
+        binder.bindColumn(prop, null, column, null, null, table, null)
 
         then:
         column.getName() == "sub_col"
@@ -483,10 +483,10 @@ class ColumnBinderSpec extends HibernateGormDatastoreSpec 
{
         columnNameFetcher.getColumnNameForPropertyAndPath(prop, null, null) >> 
"data_col"
 
         when:
-        binder.bindColumn(prop, null, column, null, null, table)
+        binder.bindColumn(prop, null, column, null, null, table, null)
 
         then:
-        1 * stringBinder.bindStringColumnConstraints(column, _)
+        1 * stringBinder.bindStringColumnConstraints(column, _, null)
     }
 
     def "uniqueness is false when uniqueWithinGroup is true"() {
@@ -513,7 +513,7 @@ class ColumnBinderSpec extends HibernateGormDatastoreSpec {
         columnNameFetcher.getColumnNameForPropertyAndPath(prop, null, null) >> 
"g_col"
 
         when:
-        binder.bindColumn(prop, null, column, null, null, table)
+        binder.bindColumn(prop, null, column, null, null, table, null)
 
         then:
         !column.isUnique()
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/SimpleValueBinderSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/SimpleValueBinderSpec.groovy
index 68772241dc..4da1d223b1 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/SimpleValueBinderSpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/SimpleValueBinderSpec.groovy
@@ -29,6 +29,7 @@ import org.grails.orm.hibernate.cfg.Mapping
 import org.grails.orm.hibernate.cfg.PersistentEntityNamingStrategy
 import org.grails.orm.hibernate.cfg.PropertyConfig
 
+import org.hibernate.Length
 import org.hibernate.mapping.Column
 import org.hibernate.mapping.SimpleValue
 import spock.lang.Specification
@@ -241,6 +242,69 @@ class SimpleValueBinderSpec extends Specification {
         2 * sv.addColumn(_ as Column)
     }
 
+    def "unbounded text type defaults column length to Length.LONG32 when no 
explicit length is configured"() {
+        given:
+        def prop = Mock(HibernatePersistentProperty)
+        def owner = Mock(GrailsHibernatePersistentEntity)
+        def mapping = Mock(Mapping)
+        def pc = Mock(PropertyConfig)
+        def sv = Mock(SimpleValue)
+        sv.getTable() >> null
+
+        prop.getMappedForm() >> pc
+        prop.getHibernateMappedForm() >> pc
+        prop.getOwner() >> owner
+        prop.getHibernateOwner() >> owner
+        owner.getMappedForm() >> mapping
+        owner.getHibernateMappedForm() >> mapping
+        _ * prop.getHibernateMappedForm() >> pc
+        _ * owner.getHibernateMappedForm() >> mapping
+        prop.getTypeName(sv) >> 'text'
+        pc.isDerived() >> false
+        pc.getColumns() >> null
+        prop.getType() >> String
+        prop.isNullable() >> true
+        namingStrategy.resolveColumnName(_) >> 'body'
+
+        when:
+        binder.bindSimpleValue(prop, null, sv, 'path')
+
+        then:
+        1 * sv.addColumn({ Column column -> column.getLength() == 
Length.LONG32 as Long })
+    }
+
+    def "unbounded text type keeps an explicit maxSize bound instead of 
defaulting to Length.LONG32"() {
+        given:
+        def prop = Mock(HibernatePersistentProperty)
+        def owner = Mock(GrailsHibernatePersistentEntity)
+        def mapping = Mock(Mapping)
+        def pc = Mock(PropertyConfig)
+        def sv = Mock(SimpleValue)
+        sv.getTable() >> null
+
+        prop.getMappedForm() >> pc
+        prop.getHibernateMappedForm() >> pc
+        prop.getOwner() >> owner
+        prop.getHibernateOwner() >> owner
+        owner.getMappedForm() >> mapping
+        owner.getHibernateMappedForm() >> mapping
+        _ * prop.getHibernateMappedForm() >> pc
+        _ * owner.getHibernateMappedForm() >> mapping
+        prop.getTypeName(sv) >> 'text'
+        pc.isDerived() >> false
+        pc.getColumns() >> null
+        pc.getMaxSize() >> 500
+        prop.getType() >> String
+        prop.isNullable() >> true
+        namingStrategy.resolveColumnName(_) >> 'body'
+
+        when:
+        binder.bindSimpleValue(prop, null, sv, 'path')
+
+        then:
+        1 * sv.addColumn({ Column column -> column.getLength() == 500L })
+    }
+
     def "bindSimpleValue creates and returns BasicValue"() {
         given:
         def prop = Mock(HibernatePersistentProperty)
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/StringColumnConstraintsBinderSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/StringColumnConstraintsBinderSpec.groovy
index de372d077b..8006ced8ba 100644
--- 
a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/StringColumnConstraintsBinderSpec.groovy
+++ 
b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/cfg/domainbinding/StringColumnConstraintsBinderSpec.groovy
@@ -19,6 +19,7 @@
 
 package org.grails.orm.hibernate.cfg.domainbinding
 
+import org.hibernate.Length
 import org.hibernate.mapping.Column
 import org.grails.datastore.mapping.config.Property
 import spock.lang.Specification
@@ -44,7 +45,7 @@ class StringColumnConstraintsBinderSpec extends Specification 
{
         def originalLength = column.length
 
         when:
-        binder.bindStringColumnConstraints(column, mappedForm)
+        binder.bindStringColumnConstraints(column, mappedForm, null)
 
         then:
         column.length == originalLength
@@ -57,7 +58,7 @@ class StringColumnConstraintsBinderSpec extends Specification 
{
         def originalLength = column.length
 
         when:
-        binder.bindStringColumnConstraints(column, mappedForm)
+        binder.bindStringColumnConstraints(column, mappedForm, null)
 
         then:
         column.length == originalLength
@@ -69,7 +70,7 @@ class StringColumnConstraintsBinderSpec extends Specification 
{
         mappedForm.getInList() >> null
 
         when:
-        binder.bindStringColumnConstraints(column, mappedForm)
+        binder.bindStringColumnConstraints(column, mappedForm, null)
 
         then:
         column.length == 255
@@ -81,7 +82,7 @@ class StringColumnConstraintsBinderSpec extends Specification 
{
         mappedForm.getInList() >> ["1","2","3","4"]
 
         when:
-        binder.bindStringColumnConstraints(column, mappedForm)
+        binder.bindStringColumnConstraints(column, mappedForm, null)
 
         then:
         column.length == 4 // length of "very long string" - preserving 
original expectation
@@ -93,7 +94,7 @@ class StringColumnConstraintsBinderSpec extends Specification 
{
         mappedForm.getInList() >> ["4","string",Long.MAX_VALUE.toString(), 
null]
 
         when:
-        binder.bindStringColumnConstraints(column, mappedForm)
+        binder.bindStringColumnConstraints(column, mappedForm, null)
 
         then:
         column.length == 4 // length of "very long string" - preserving 
original expectation
@@ -106,7 +107,7 @@ class StringColumnConstraintsBinderSpec extends 
Specification {
         mappedForm.getInList() >> ["3"]
 
         when:
-        binder.bindStringColumnConstraints(column, mappedForm)
+        binder.bindStringColumnConstraints(column, mappedForm, null)
 
         then:
         column.length == 1
@@ -119,7 +120,7 @@ class StringColumnConstraintsBinderSpec extends 
Specification {
         def originalLength = column.length
 
         when:
-        binder.bindStringColumnConstraints(column, mappedForm)
+        binder.bindStringColumnConstraints(column, mappedForm, null)
 
         then:
         column.length == originalLength
@@ -132,9 +133,70 @@ class StringColumnConstraintsBinderSpec extends 
Specification {
         mappedForm.getInList() >> null
 
         when:
-        binder.bindStringColumnConstraints(column, mappedForm)
+        binder.bindStringColumnConstraints(column, mappedForm, null)
 
         then:
         column.length == 50
     }
+
+    def "should default column length to Length.LONG32 for an unbounded text 
type when neither maxSize nor inList is provided"() {
+        given:
+        mappedForm.getMaxSize() >> null
+        mappedForm.getInList() >> null
+
+        when:
+        binder.bindStringColumnConstraints(column, mappedForm, 'text')
+
+        then:
+        column.length == Length.LONG32
+    }
+
+    def "should match the text type name case-insensitively"() {
+        given:
+        mappedForm.getMaxSize() >> null
+        mappedForm.getInList() >> null
+
+        when:
+        binder.bindStringColumnConstraints(column, mappedForm, 'TEXT')
+
+        then:
+        column.length == Length.LONG32
+    }
+
+    def "should prioritize an explicit maxSize over defaulting a text type to 
Length.LONG32"() {
+        given:
+        mappedForm.getMaxSize() >> 500
+        mappedForm.getInList() >> null
+
+        when:
+        binder.bindStringColumnConstraints(column, mappedForm, 'text')
+
+        then:
+        column.length == 500
+    }
+
+    def "should prioritize inList over defaulting a text type to 
Length.LONG32"() {
+        given:
+        mappedForm.getMaxSize() >> null
+        mappedForm.getInList() >> ["1", "22", "333"]
+
+        when:
+        binder.bindStringColumnConstraints(column, mappedForm, 'text')
+
+        then:
+        column.length == 333
+    }
+
+    def "should not default column length when typeName is not text"() {
+        given:
+        mappedForm.getMaxSize() >> null
+        mappedForm.getInList() >> null
+        def originalLength = column.length
+
+        when:
+        binder.bindStringColumnConstraints(column, mappedForm, 'custom.Type')
+
+        then:
+        column.length == originalLength
+    }
 }
\ No newline at end of file
diff --git a/grails-doc/src/en/ref/Database Mapping/type.adoc 
b/grails-doc/src/en/ref/Database Mapping/type.adoc
index 7264a31025..0e21df0a55 100644
--- a/grails-doc/src/en/ref/Database Mapping/type.adoc  
+++ b/grails-doc/src/en/ref/Database Mapping/type.adoc  
@@ -31,7 +31,7 @@ Configures the Hibernate type for a particular property.
 === Examples
 
 
-Changing to a text type (CLOB or TEXT depending on database dialect):
+Changing to an unbounded text type (e.g. `TEXT`, `LONGTEXT` or `CLOB` 
depending on database dialect):
 
 [source,groovy]
 ----
@@ -77,6 +77,8 @@ static mapping = {
 }
 ----
 
+NOTE: `type: "text"` does not map to a literal SQL type named `text`. GORM 
asks Hibernate to resolve the dialect's own unbounded large-character type, 
which is `text` on PostgreSQL and H2, `longtext` on MySQL and MariaDB, and 
`CLOB` on Oracle. Every `Dialect` shipped with Hibernate defines this mapping, 
so the resolved column is always unbounded regardless of which of these 
databases you use — you don't need to (and can't) choose the literal SQL type 
name yourself.
+
 Hibernate also has the concept of custom `UserType` implementations. In this 
case you specify the `UserType` class. If the `UserType` maps to multiple 
columns you may need to specify a mapping for each column:
 
 [source,groovy]

Reply via email to