--- /dev/null
+.. change::
+ :tags: bug, mssql, reflection
+ :tickets: 13181, 13182
+
+ Fixed regression from version 2.0.42 caused by :ticket:`12654` where the
+ updated column reflection query would receive SQL Server "type alias" names
+ for special types such as ``sysname``, whereas previously the base name
+ would be received (e.g. ``nvarchar`` for ``sysname``), leading to warnings
+ that such types could not be reflected and resulting in :class:`.NullType`,
+ rather than the expected :class:`.NVARCHAR` for a type like ``sysname``.
+ The column reflection query now joins ``sys.types`` a second time to look
+ up the base type when the user type name is not present in
+ :attr:`.MSDialect.ischema_names`, and both names are checked in
+ :attr:`.MSDialect.ischema_names` for a match. Pull request courtesy Carlos
+ Serrano.
def get_columns(self, connection, tablename, dbname, owner, schema, **kw):
sys_columns = ischema.sys_columns
sys_types = ischema.sys_types
+ sys_base_types = ischema.sys_types.alias("base_types")
sys_default_constraints = ischema.sys_default_constraints
computed_cols = ischema.computed_columns
identity_cols = ischema.identity_columns
sql.select(
sys_columns.c.name,
sys_types.c.name,
+ sys_base_types.c.name.label("base_type"),
sys_columns.c.is_nullable,
sys_columns.c.max_length,
sys_columns.c.precision,
onclause=sys_columns.c.user_type_id
== sys_types.c.user_type_id,
)
+ .outerjoin(
+ sys_base_types,
+ onclause=sql.and_(
+ sys_types.c.system_type_id
+ == sys_base_types.c.system_type_id,
+ sys_base_types.c.user_type_id
+ == sys_base_types.c.system_type_id,
+ ),
+ )
.outerjoin(
sys_default_constraints,
sql.and_(
for row in c.mappings():
name = row[sys_columns.c.name]
type_ = row[sys_types.c.name]
+ base_type = row["base_type"]
nullable = row[sys_columns.c.is_nullable] == 1
maxlen = row[sys_columns.c.max_length]
numericprec = row[sys_columns.c.precision]
identity_increment = row[identity_cols.c.increment_value]
comment = row[extended_properties.c.value]
+ # Try to resolve the user type first (e.g., "sysname"),
+ # then fall back to the base type (e.g., "nvarchar").
+ # base_type may be None for CLR types (geography, geometry,
+ # hierarchyid) which have no corresponding base type.
coltype = self.ischema_names.get(type_, None)
+ if (
+ coltype is None
+ and base_type is not None
+ and base_type != type_
+ ):
+ coltype = self.ischema_names.get(base_type, None)
kwargs = {}
kwargs["collation"] = collation
if coltype is None:
- util.warn(
- "Did not recognize type '%s' of column '%s'"
- % (type_, name)
- )
+ if base_type is not None and base_type != type_:
+ util.warn(
+ "Did not recognize type '%s' (user type) or '%s' "
+ "(base type) of column '%s'" % (type_, base_type, name)
+ )
+ else:
+ util.warn(
+ "Did not recognize type '%s' of column '%s'"
+ % (type_, name)
+ )
coltype = sqltypes.NULLTYPE
else:
if issubclass(coltype, sqltypes.NumericCommon):
is_true("dialect_options" not in col)
is_true("identity" in col)
eq_(col["identity"], {})
+
+
+class AliasTypeReflectionTest(fixtures.TestBase):
+ """Test reflection of alias and CLR types via base-type fallback.
+
+ issue #13181
+
+ SYSNAME is a built-in alias for NVARCHAR(128); geography, geometry,
+ and hierarchyid are CLR types with no base system type row.
+ """
+
+ __only_on__ = "mssql"
+ __backend__ = True
+
+ def test_sysname_column_reflects_as_nvarchar(self, metadata, connection):
+ connection.exec_driver_sql(
+ "create table sysname_t (id integer primary key, name sysname)"
+ )
+
+ insp = inspect(connection)
+ cols = {c["name"]: c for c in insp.get_columns("sysname_t")}
+ assert isinstance(cols["name"]["type"], mssql.NVARCHAR)
+ assert not isinstance(cols["name"]["type"], sqltypes.NullType)
+
+ @testing.combinations(
+ "geography",
+ "geometry",
+ "hierarchyid",
+ argnames="type_name",
+ )
+ def test_clr_type_reflection(self, metadata, connection, type_name):
+ connection.exec_driver_sql(
+ "create table clr_t (id integer primary key, "
+ "data %s)" % type_name
+ )
+
+ with testing.expect_warnings(
+ "Did not recognize type '%s' of column 'data'" % type_name
+ ):
+ insp = inspect(connection)
+ cols = {c["name"]: c for c in insp.get_columns("clr_t")}
+ assert isinstance(cols["data"]["type"], sqltypes.NullType)