From: Mike Bayer Date: Wed, 12 Aug 2026 19:30:42 +0000 (-0400) Subject: Don't apply a length to reflected TEXT / NTEXT X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1730a092684f272c8ba9843f89f76e80b160324f;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Don't apply a length to reflected TEXT / NTEXT 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) --- diff --git a/doc/build/changelog/unreleased_20/13451.rst b/doc/build/changelog/unreleased_20/13451.rst new file mode 100644 index 0000000000..fa0502d221 --- /dev/null +++ b/doc/build/changelog/unreleased_20/13451.rst @@ -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". diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py index c3d77d6c18..be7b95d06c 100644 --- a/lib/sqlalchemy/dialects/mssql/base.py +++ b/lib/sqlalchemy/dialects/mssql/base.py @@ -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_: diff --git a/test/dialect/mssql/test_reflection.py b/test/dialect/mssql/test_reflection.py index 0c9f1485a0..06a8a4285b 100644 --- a/test/dialect/mssql/test_reflection.py +++ b/test/dialect/mssql/test_reflection.py @@ -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",