diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-07-30 10:34:36 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-07-30 10:34:36 -0400 |
commit | dd6110eed335154e0ae14b2dba13e44af76c4f2b (patch) | |
tree | 14090663623b65008e49d5eb5d428003e7ea19f5 | |
parent | d8efa2257ec650b345ec6e840984387263a957a6 (diff) | |
download | sqlalchemy-dd6110eed335154e0ae14b2dba13e44af76c4f2b.tar.gz |
- Fixed issue where the SQL Server dialect would reflect a string-
or other variable-length column type with unbounded length
by assigning the token ``"max"`` to the
length attribute of the string. While using the ``"max"`` token
explicitly is supported by the SQL Server dialect, it isn't part
of the normal contract of the base string types, and instead the
length should just be left as None. The dialect now assigns the
length to None on reflection of the type so that the type behaves
normally in other contexts.
fixes #3504
-rw-r--r-- | doc/build/changelog/changelog_11.rst | 17 | ||||
-rw-r--r-- | doc/build/changelog/migration_11.rst | 35 | ||||
-rw-r--r-- | lib/sqlalchemy/dialects/mssql/base.py | 2 | ||||
-rw-r--r-- | test/dialect/mssql/test_reflection.py | 34 |
4 files changed, 83 insertions, 5 deletions
diff --git a/doc/build/changelog/changelog_11.rst b/doc/build/changelog/changelog_11.rst index 1f18d6d16..bb395a826 100644 --- a/doc/build/changelog/changelog_11.rst +++ b/doc/build/changelog/changelog_11.rst @@ -21,3 +21,20 @@ .. changelog:: :version: 1.1.0b1 + .. change:: + :tags: bug, mssql + :tickets: 3504 + + Fixed issue where the SQL Server dialect would reflect a string- + or other variable-length column type with unbounded length + by assigning the token ``"max"`` to the + length attribute of the string. While using the ``"max"`` token + explicitly is supported by the SQL Server dialect, it isn't part + of the normal contract of the base string types, and instead the + length should just be left as None. The dialect now assigns the + length to None on reflection of the type so that the type behaves + normally in other contexts. + + .. seealso:: + + :ref:`change_3504`
\ No newline at end of file diff --git a/doc/build/changelog/migration_11.rst b/doc/build/changelog/migration_11.rst index 9c046ab9e..f5602a8ad 100644 --- a/doc/build/changelog/migration_11.rst +++ b/doc/build/changelog/migration_11.rst @@ -94,6 +94,41 @@ Dialect Improvements and Changes - SQLite Dialect Improvements and Changes - SQL Server ============================================= +.. _change_3504: + +String / varlength types no longer represent "max" explicitly on reflection +--------------------------------------------------------------------------- + +When reflecting a type such as :class:`.String`, :class:`.Text`, etc. +which includes a length, an "un-lengthed" type under SQL Server would +copy the "length" parameter as the value ``"max"``:: + + >>> from sqlalchemy import create_engine, inspect + >>> engine = create_engine('mssql+pyodbc://scott:tiger@ms_2008', echo=True) + >>> engine.execute("create table s (x varchar(max), y varbinary(max))") + >>> insp = inspect(engine) + >>> for col in insp.get_columns("s"): + ... print col['type'].__class__, col['type'].length + ... + <class 'sqlalchemy.sql.sqltypes.VARCHAR'> max + <class 'sqlalchemy.dialects.mssql.base.VARBINARY'> max + +The "length" parameter in the base types is expected to be an integer value +or None only; None indicates unbounded length which the SQL Server dialect +interprets as "max". The fix then is so that these lengths come +out as None, so that the type objects work in non-SQL Server contexts:: + + >>> for col in insp.get_columns("s"): + ... print col['type'].__class__, col['type'].length + ... + <class 'sqlalchemy.sql.sqltypes.VARCHAR'> None + <class 'sqlalchemy.dialects.mssql.base.VARBINARY'> None + +Applications which may have been relying on a direct comparison of the "length" +value to the string "max" should consider the value of ``None`` to mean +the same thing. + +:ticket:`3504` Dialect Improvements and Changes - Oracle ============================================= diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py index bd41c19bf..6670a28bd 100644 --- a/lib/sqlalchemy/dialects/mssql/base.py +++ b/lib/sqlalchemy/dialects/mssql/base.py @@ -1772,7 +1772,7 @@ class MSDialect(default.DefaultDialect): MSNText, MSBinary, MSVarBinary, sqltypes.LargeBinary): if charlen == -1: - charlen = 'max' + charlen = None kwargs['length'] = charlen if collation: kwargs['collation'] = collation diff --git a/test/dialect/mssql/test_reflection.py b/test/dialect/mssql/test_reflection.py index bee441586..daf8af724 100644 --- a/test/dialect/mssql/test_reflection.py +++ b/test/dialect/mssql/test_reflection.py @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -from sqlalchemy.testing import eq_ +from sqlalchemy.testing import eq_, is_, in_ from sqlalchemy import * from sqlalchemy import types, schema, event from sqlalchemy.databases import mssql @@ -24,14 +24,14 @@ class ReflectionTest(fixtures.TestBase, ComparesTables): Column('user_name', types.VARCHAR(20), nullable=False), Column('test1', types.CHAR(5), nullable=False), Column('test2', types.Float(5), nullable=False), - Column('test3', types.Text('max')), + Column('test3', types.Text()), Column('test4', types.Numeric, nullable=False), Column('test5', types.DateTime), Column('parent_user_id', types.Integer, ForeignKey('engine_users.user_id')), Column('test6', types.DateTime, nullable=False), - Column('test7', types.Text('max')), - Column('test8', types.LargeBinary('max')), + Column('test7', types.Text()), + Column('test8', types.LargeBinary()), Column('test_passivedefault2', types.Integer, server_default='5'), Column('test9', types.BINARY(100)), @@ -171,6 +171,32 @@ class ReflectionTest(fixtures.TestBase, ComparesTables): set([t2.c['x col'], t2.c.y]) ) + @testing.provide_metadata + def test_max_ident_in_varchar_not_present(self): + """test [ticket:3504]. + + Here we are testing not just that the "max" token comes back + as None, but also that these types accept "max" as the value + of "length" on construction, which isn't a directly documented + pattern however is likely in common use. + + """ + metadata = self.metadata + + Table( + 't', metadata, + Column('t1', types.String), + Column('t2', types.Text('max')), + Column('t3', types.Text('max')), + Column('t4', types.LargeBinary('max')), + Column('t5', types.VARBINARY('max')), + ) + metadata.create_all() + for col in inspect(testing.db).get_columns('t'): + is_(col['type'].length, None) + in_('max', str(col['type'].compile(dialect=testing.db.dialect))) + + from sqlalchemy.dialects.mssql.information_schema import CoerceUnicode, tables from sqlalchemy.dialects.mssql import base |