--- /dev/null
+.. change::
+ :tags: usecase, postgresql
+ :tickets: 6511
+
+ Added support for reflection of collation in types for PostgreSQL.
+ The ``collation`` will be set only if different from the default
+ one for the type.
+ Pull request courtesy Denis Laxalde.
.scalar_subquery()
.label("default")
)
+
+ # get the name of the collate when it's different from the default one
+ collate = sql.case(
+ (
+ sql.and_(
+ pg_catalog.pg_attribute.c.attcollation != 0,
+ select(pg_catalog.pg_type.c.typcollation)
+ .where(
+ pg_catalog.pg_type.c.oid
+ == pg_catalog.pg_attribute.c.atttypid,
+ )
+ .correlate(pg_catalog.pg_attribute)
+ .scalar_subquery()
+ != pg_catalog.pg_attribute.c.attcollation,
+ ),
+ select(pg_catalog.pg_collation.c.collname)
+ .where(
+ pg_catalog.pg_collation.c.oid
+ == pg_catalog.pg_attribute.c.attcollation
+ )
+ .correlate(pg_catalog.pg_attribute)
+ .scalar_subquery(),
+ ),
+ else_=sql.null(),
+ ).label("collation")
+
relkinds = self._kind_to_relkinds(kind)
query = (
select(
pg_catalog.pg_description.c.description.label("comment"),
generated,
identity,
+ collate,
)
.select_from(pg_catalog.pg_class)
# NOTE: postgresql support table with no user column, meaning
domains: Dict[str, ReflectedDomain],
enums: Dict[str, ReflectedEnum],
type_description: str,
+ collation: Optional[str],
) -> sqltypes.TypeEngine[Any]:
"""
Attempts to reconstruct a column type defined in ischema_names based
domains,
enums,
type_description="DOMAIN '%s'" % domain["name"],
+ collation=domain["collation"],
)
args = (domain["name"], data_type)
)
return sqltypes.NULLTYPE
+ if collation is not None:
+ kwargs["collation"] = collation
+
data_type = schema_type(*args, **kwargs)
if array_dim >= 1:
# postgres does not preserve dimensionality or size of array types.
continue
table_cols = columns[(schema, row_dict["table_name"])]
+ collation = row_dict["collation"]
+
coltype = self._reflect_type(
row_dict["format_type"],
domains,
enums,
type_description="column '%s'" % row_dict["name"],
+ collation=collation,
)
default = row_dict["default"]
and their reflection"""
return exclusions.closed()
+ @property
+ def column_collation_reflection(self):
+ """Indicates if the database support column collation reflection.
+
+ This requirement also uses ``get_order_by_collation`` to get
+ an available collation.
+ """
+ return exclusions.closed()
+
@property
def view_column_reflection(self):
"""target database must support retrieval of the columns in a view,
from ...testing import ComparesIndexes
from ...testing import ComparesTables
from ...testing import is_false
+from ...testing import is_none
from ...testing import is_true
from ...testing import mock
c = insp.get_columns("unicode_comments")[0]
eq_({c["name"]: c["comment"]}, {"emoji": "🐍🧙🝝🧙♂️🧙♀️"})
+ @testing.requires.column_collation_reflection
+ @testing.requires.order_by_collation
+ def test_column_collation_reflection(self, connection, metadata):
+ collation = testing.requires.get_order_by_collation(config)
+ Table(
+ "t",
+ metadata,
+ Column("collated", sa.String(collation=collation)),
+ Column("not_collated", sa.String()),
+ )
+ metadata.create_all(connection)
+
+ m2 = MetaData()
+ t2 = Table("t", m2, autoload_with=connection)
+
+ eq_(t2.c.collated.type.collation, collation)
+ is_none(t2.c.not_collated.type.collation)
+
+ insp = inspect(connection)
+ collated, not_collated = insp.get_columns("t")
+ eq_(collated["type"].collation, collation)
+ is_none(not_collated["type"].collation)
+
class TableNoColumnsTest(fixtures.TestBase):
__requires__ = ("reflect_tables_no_columns",)
class CustomTypeReflectionTest(fixtures.TestBase):
class CustomType:
- def __init__(self, arg1=None, arg2=None):
+ def __init__(self, arg1=None, arg2=None, collation=None):
self.arg1 = arg1
self.arg2 = arg2
+ self.collation = collation
ischema_names = None
"format_type": sch,
"default": None,
"not_null": False,
+ "collation": "cc" if sch == "my_custom_type()" else None,
"comment": None,
"generated": "",
"identity_options": None,
assert isinstance(column_info["type"], self.CustomType)
eq_(column_info["type"].arg1, args[0])
eq_(column_info["type"].arg2, args[1])
+ if sch == "my_custom_type()":
+ eq_(column_info["type"].collation, "cc")
+ else:
+ eq_(column_info["type"].collation, None)
def test_clslevel(self):
postgresql.PGDialect.ischema_names["my_custom_type"] = self.CustomType
"format_type": None,
"default": None,
"not_null": False,
+ "collation": None,
"comment": None,
"generated": "",
"identity_options": None,
def constraint_comment_reflection(self):
return only_on(["postgresql"])
+ @property
+ def column_collation_reflection(self):
+ return only_on(["postgresql"])
+
@property
def unbounded_varchar(self):
"""Target database must support VARCHAR with no length"""