From 22cbc7dcb48c946dda66704797665289965eb22e Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 29 Aug 2024 10:04:47 -0400 Subject: [PATCH] pass to_metadata argument to Enum.copy() Fixed bug where the ``metadata`` element of an ``Enum`` datatype would not be transferred to the new :class:`.MetaData` object when the type had been copied via a :meth:`.Table.to_metadata` operation, leading to inconsistent behaviors within create/drop sequences. Fixes: #11802 Change-Id: Ibbc93aa31bdfde0d67a9530f41a08e826c17d58e --- doc/build/changelog/unreleased_20/11802.rst | 8 ++++ lib/sqlalchemy/sql/schema.py | 4 +- lib/sqlalchemy/sql/sqltypes.py | 12 ++++++ test/sql/test_metadata.py | 45 ++++++++++++++++++--- 4 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 doc/build/changelog/unreleased_20/11802.rst diff --git a/doc/build/changelog/unreleased_20/11802.rst b/doc/build/changelog/unreleased_20/11802.rst new file mode 100644 index 0000000000..f6e7847ee2 --- /dev/null +++ b/doc/build/changelog/unreleased_20/11802.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: bug, schema + :tickets: 11802 + + Fixed bug where the ``metadata`` element of an ``Enum`` datatype would not + be transferred to the new :class:`.MetaData` object when the type had been + copied via a :meth:`.Table.to_metadata` operation, leading to inconsistent + behaviors within create/drop sequences. diff --git a/lib/sqlalchemy/sql/schema.py b/lib/sqlalchemy/sql/schema.py index 1ecb680e44..21c44d8170 100644 --- a/lib/sqlalchemy/sql/schema.py +++ b/lib/sqlalchemy/sql/schema.py @@ -1435,7 +1435,7 @@ class Table( args = [] for col in self.columns: - args.append(col._copy(schema=actual_schema)) + args.append(col._copy(schema=actual_schema, _to_metadata=metadata)) table = Table( name, metadata, @@ -2477,6 +2477,8 @@ class Column(DialectKWArgs, SchemaItem, ColumnClause[_T]): server_onupdate = self.server_onupdate if isinstance(server_default, (Computed, Identity)): # TODO: likely should be copied in all cases + # TODO: if a Sequence, we would need to transfer the Sequence + # .metadata as well args.append(server_default._copy(**kw)) server_default = server_onupdate = None diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py index 0a411ce349..145fce2fb4 100644 --- a/lib/sqlalchemy/sql/sqltypes.py +++ b/lib/sqlalchemy/sql/sqltypes.py @@ -1086,6 +1086,11 @@ class SchemaType(SchemaEventTarget, TypeEngineMixin): return self.adapt( cast("Type[TypeEngine[Any]]", self.__class__), _create_events=True, + metadata=( + kw.get("_to_metadata", self.metadata) + if self.metadata is not None + else None + ), ) @overload @@ -1909,6 +1914,13 @@ class Boolean(SchemaType, Emulated, TypeEngine[bool]): if _adapted_from: self.dispatch = self.dispatch._join(_adapted_from.dispatch) + def copy(self, **kw): + # override SchemaType.copy() to not include to_metadata logic + return self.adapt( + cast("Type[TypeEngine[Any]]", self.__class__), + _create_events=True, + ) + def _should_create_constraint(self, compiler, **kw): if not self._is_impl_for_variant(compiler.dialect, kw): return False diff --git a/test/sql/test_metadata.py b/test/sql/test_metadata.py index 97c2f08645..1b068c02f7 100644 --- a/test/sql/test_metadata.py +++ b/test/sql/test_metadata.py @@ -2395,17 +2395,27 @@ class SchemaTypeTest(fixtures.TestBase): t1 = Table("x", m, Column("y", type_), schema="z") eq_(t1.c.y.type.schema, "z") - def test_to_metadata_copy_type(self): + @testing.variation("assign_metadata", [True, False]) + def test_to_metadata_copy_type(self, assign_metadata): m1 = MetaData() - type_ = self.MyType() + if assign_metadata: + type_ = self.MyType(metadata=m1) + else: + type_ = self.MyType() + t1 = Table("x", m1, Column("y", type_)) m2 = MetaData() t2 = t1.to_metadata(m2) - # metadata isn't set - is_(t2.c.y.type.metadata, None) + if assign_metadata: + # metadata was transferred + # issue #11802 + is_(t2.c.y.type.metadata, m2) + else: + # metadata isn't set + is_(t2.c.y.type.metadata, None) # our test type sets table, though is_(t2.c.y.type.table, t2) @@ -2435,11 +2445,34 @@ class SchemaTypeTest(fixtures.TestBase): eq_(t2.c.y.type.schema, None) - def test_to_metadata_inherit_schema(self): + @testing.combinations( + ("name", "foobar", "name"), + ("schema", "someschema", "schema"), + ("inherit_schema", True, "inherit_schema"), + ("metadata", MetaData(), "metadata"), + ) + def test_copy_args(self, argname, value, attrname): + kw = {argname: value} + e1 = self.MyType(**kw) + + e1_copy = e1.copy() + + eq_(getattr(e1_copy, attrname), value) + + @testing.variation("already_has_a_schema", [True, False]) + def test_to_metadata_inherit_schema(self, already_has_a_schema): m1 = MetaData() - type_ = self.MyType(inherit_schema=True) + if already_has_a_schema: + type_ = self.MyType(schema="foo", inherit_schema=True) + eq_(type_.schema, "foo") + else: + type_ = self.MyType(inherit_schema=True) + t1 = Table("x", m1, Column("y", type_)) + # note that inherit_schema means the schema mutates to be that + # of the table + is_(type_.schema, None) m2 = MetaData() t2 = t1.to_metadata(m2, schema="bar") -- 2.47.2