]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Don't apply a length to reflected TEXT / NTEXT
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 12 Aug 2026 19:30:42 +0000 (15:30 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 12 Aug 2026 19:32:35 +0000 (15:32 -0400)
Fixed issue in SQL Server reflection where ``TEXT`` and ``NTEXT``
columns would be reflected with a spurious length of 16 and 8,
respectively.  These are unlengthed LOB datatypes; the value
originates from the ``sys.columns.max_length`` column, which reports
the size of the in-row LOB pointer rather than a character length for
these types.  The reflected ``TEXT`` and ``NTEXT`` types now have a
``length`` of ``None``, so that a reflected table emits valid DDL when
re-created, which previously failed with "Cannot specify a column
width on data type text".

Fixes: #13451
Change-Id: I8456688fc8d21fe25f326c6bf0f7b13aa1fc838c
(cherry picked from commit 325f71701a8a994c982c781f59bf34f42add88e7)

doc/build/changelog/unreleased_20/13451.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/mssql/base.py
test/dialect/mssql/test_reflection.py

diff --git a/doc/build/changelog/unreleased_20/13451.rst b/doc/build/changelog/unreleased_20/13451.rst
new file mode 100644 (file)
index 0000000..fa0502d
--- /dev/null
@@ -0,0 +1,13 @@
+.. change::
+    :tags: bug, mssql, reflection
+    :tickets: 13451
+
+    Fixed issue in SQL Server reflection where ``TEXT`` and ``NTEXT`` columns
+    would be reflected with a spurious length of 16 and 8, respectively.  These
+    are unlengthed LOB datatypes; the value originates from the
+    ``sys.columns.max_length`` column, which reports the size of the in-row LOB
+    pointer rather than a character length for these types.  The reflected
+    :class:`_mssql.TEXT` and :class:`_mssql.NTEXT` types now have a ``length``
+    of ``None``, so that a reflected table emits valid DDL when re-created,
+    which previously failed with "Cannot specify a column width on data type
+    text".
index c3d77d6c18dbd707dd1b9f88365461bbb0b84cce..be7b95d06c1f410fd3e43fb90394b8dda0b0cd59 100644 (file)
@@ -3780,7 +3780,6 @@ order by
             elif coltype in (
                 MSString,
                 MSChar,
-                MSText,
             ):
                 kwargs["length"] = maxlen if maxlen != -1 else None
                 if collation:
@@ -3788,11 +3787,20 @@ order by
             elif coltype in (
                 MSNVarchar,
                 MSNChar,
-                MSNText,
             ):
                 kwargs["length"] = maxlen // 2 if maxlen != -1 else None
                 if collation:
                     kwargs["collation"] = collation
+            elif coltype in (
+                MSText,
+                MSNText,
+            ):
+                # TEXT / NTEXT are unlengthed LOB types.
+                # sys.columns.max_length reports 16 for these, which is the
+                # size of the in-row LOB pointer and not a character length,
+                # so no length is applied.
+                if collation:
+                    kwargs["collation"] = collation
 
             if coltype is None:
                 if base_type is not None and base_type != type_:
index 0c9f1485a07eaeeff327ba16def8e01f812ff964..06a8a4285b5b21c59b12f2529c86eaeb5577ff73 100644 (file)
@@ -115,6 +115,37 @@ class ReflectionTest(fixtures.TestBase, ComparesTables, AssertsCompiledSQL):
             "CREATE TABLE type_test (col1 %s NULL)" % ddl,
         )
 
+    def test_lob_types_no_length(self, metadata, connection):
+        """TEXT / NTEXT / IMAGE are unlengthed, and a reflected version of
+        such a table must remain creatable.
+
+        issue #13451
+
+        """
+        Table(
+            "lob_type_test",
+            metadata,
+            Column("id", types.Integer, primary_key=True),
+            Column("t", mssql.TEXT),
+            Column("nt", mssql.NTEXT),
+            Column("img", mssql.IMAGE),
+        )
+        metadata.create_all(connection)
+
+        m2 = MetaData()
+        table2 = Table("lob_type_test", m2, autoload_with=connection)
+        eq_(
+            {c.name: c.type.length for c in table2.c if c.name != "id"},
+            {"t": None, "nt": None, "img": None},
+        )
+
+        # the reflected types round trip back into valid DDL; a length
+        # here would be rejected with "Cannot specify a column width on
+        # data type text"
+        Table(
+            "lob_type_test_2", metadata, *[c._copy() for c in table2.c]
+        ).create(connection)
+
     def test_identity(self, metadata, connection):
         table = Table(
             "identity_test",