]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
TypeDecorator passes "outer" flag to itself for set_parent accounting
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 29 Mar 2021 21:57:20 +0000 (17:57 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 30 Mar 2021 14:00:58 +0000 (10:00 -0400)
Fixed bug first introduced in as some combination of :ticket:`2892`,
:ticket:`2919` nnd :ticket:`3832` where the attachment events for a
:class:`_types.TypeDecorator` would be doubled up against the "impl" class,
if the "impl" were also a :class:`_types.SchemaType`. The real-world case
is any :class:`_types.TypeDecorator` against :class:`_types.Enum` or
:class:`_types.Boolean` would get a doubled
:class:`_schema.CheckConstraint` when the ``create_constraint=True`` flag
is set.

Fixes: #6152
Change-Id: I3218b7081297270c132421f6765b5c3673d10a5c

doc/build/changelog/unreleased_13/6152.rst [new file with mode: 0644]
lib/sqlalchemy/sql/sqltypes.py
lib/sqlalchemy/sql/type_api.py
test/sql/test_metadata.py
test/sql/test_types.py

diff --git a/doc/build/changelog/unreleased_13/6152.rst b/doc/build/changelog/unreleased_13/6152.rst
new file mode 100644 (file)
index 0000000..b6dae22
--- /dev/null
@@ -0,0 +1,13 @@
+.. change::
+    :tags: bug, schema
+    :tickets: 6152
+
+    Fixed bug first introduced in as some combination of :ticket:`2892`,
+    :ticket:`2919` nnd :ticket:`3832` where the attachment events for a
+    :class:`_types.TypeDecorator` would be doubled up against the "impl" class,
+    if the "impl" were also a :class:`_types.SchemaType`. The real-world case
+    is any :class:`_types.TypeDecorator` against :class:`_types.Enum` or
+    :class:`_types.Boolean` would get a doubled
+    :class:`_schema.CheckConstraint` when the ``create_constraint=True`` flag
+    is set.
+
index a73c611476c45ab991560bd13f2fcc41cf76a211..367b2e203788516fb7e2fc7d1eac4771cb0bb64b 100644 (file)
@@ -1092,7 +1092,7 @@ class SchemaType(SchemaEventTarget):
                 util.portable_instancemethod(self._on_metadata_drop),
             )
 
-    def _set_parent(self, column):
+    def _set_parent(self, column, **kw):
         column._on_table_attach(util.portable_instancemethod(self._set_table))
 
     def _variant_mapping_for_set_table(self, column):
@@ -2857,11 +2857,11 @@ class ARRAY(SchemaEventTarget, Indexable, Concatenable, TypeEngine):
     def compare_values(self, x, y):
         return x == y
 
-    def _set_parent(self, column):
+    def _set_parent(self, column, **kw):
         """Support SchemaEventTarget"""
 
         if isinstance(self.item_type, SchemaEventTarget):
-            self.item_type._set_parent(column)
+            self.item_type._set_parent(column, **kw)
 
     def _set_parent_with_dispatch(self, parent):
         """Support SchemaEventTarget"""
index 9752750c5582f628fba4d4adcff297a7147e4688..bfce00cb53ba1a80f3061857183aef4792c62816 100644 (file)
@@ -1063,18 +1063,20 @@ class TypeDecorator(SchemaEventTarget, TypeEngine):
         """
         return self.impl._type_affinity
 
-    def _set_parent(self, column):
+    def _set_parent(self, column, outer=False, **kw):
         """Support SchemaEventTarget"""
 
         super(TypeDecorator, self)._set_parent(column)
 
-        if isinstance(self.impl, SchemaEventTarget):
-            self.impl._set_parent(column)
+        if not outer and isinstance(self.impl, SchemaEventTarget):
+            self.impl._set_parent(column, outer=False, **kw)
 
     def _set_parent_with_dispatch(self, parent):
         """Support SchemaEventTarget"""
 
-        super(TypeDecorator, self)._set_parent_with_dispatch(parent)
+        super(TypeDecorator, self)._set_parent_with_dispatch(
+            parent, outer=True
+        )
 
         if isinstance(self.impl, SchemaEventTarget):
             self.impl._set_parent_with_dispatch(parent)
@@ -1495,14 +1497,14 @@ class Variant(TypeDecorator):
         else:
             return self.impl
 
-    def _set_parent(self, column):
+    def _set_parent(self, column, **kw):
         """Support SchemaEventTarget"""
 
         if isinstance(self.impl, SchemaEventTarget):
-            self.impl._set_parent(column)
+            self.impl._set_parent(column, **kw)
         for impl in self.mapping.values():
             if isinstance(impl, SchemaEventTarget):
-                impl._set_parent(column)
+                impl._set_parent(column, **kw)
 
     def _set_parent_with_dispatch(self, parent):
         """Support SchemaEventTarget"""
index 3e5f7b916bc773ffcf3a2841d2dc8d76f6d12de7..eb5e305a24ff5e231cdaa041abd7d128663d2028 100644 (file)
@@ -2090,6 +2090,7 @@ class SchemaTypeTest(fixtures.TestBase):
     def test_before_parent_attach_typedec_enclosing_schematype(self):
         # additional test for [ticket:2919] as part of test for
         # [ticket:3832]
+        # this also serves as the test for [ticket:6152]
 
         class MySchemaType(sqltypes.TypeEngine, sqltypes.SchemaType):
             pass
@@ -2100,7 +2101,7 @@ class SchemaTypeTest(fixtures.TestBase):
             impl = target_typ
 
         typ = MyType()
-        self._test_before_parent_attach(typ, target_typ, double=True)
+        self._test_before_parent_attach(typ, target_typ)
 
     def test_before_parent_attach_array_enclosing_schematype(self):
         # test for [ticket:4141] which is the same idea as [ticket:3832]
@@ -2124,7 +2125,7 @@ class SchemaTypeTest(fixtures.TestBase):
         typ = MyType()
         self._test_before_parent_attach(typ)
 
-    def _test_before_parent_attach(self, typ, evt_target=None, double=False):
+    def _test_before_parent_attach(self, typ, evt_target=None):
         canary = mock.Mock()
 
         if evt_target is None:
@@ -2133,8 +2134,8 @@ class SchemaTypeTest(fixtures.TestBase):
         orig_set_parent = evt_target._set_parent
         orig_set_parent_w_dispatch = evt_target._set_parent_with_dispatch
 
-        def _set_parent(parent):
-            orig_set_parent(parent)
+        def _set_parent(parent, **kw):
+            orig_set_parent(parent, **kw)
             canary._set_parent(parent)
 
         def _set_parent_w_dispatch(parent):
@@ -2149,27 +2150,14 @@ class SchemaTypeTest(fixtures.TestBase):
 
                 c = Column("q", typ)
 
-        if double:
-            # no clean way yet to fix this, inner schema type is called
-            # twice, but this is a very unusual use case.
-            eq_(
-                canary.mock_calls,
-                [
-                    mock.call._set_parent(c),
-                    mock.call.go(evt_target, c),
-                    mock.call._set_parent(c),
-                    mock.call._set_parent_with_dispatch(c),
-                ],
-            )
-        else:
-            eq_(
-                canary.mock_calls,
-                [
-                    mock.call.go(evt_target, c),
-                    mock.call._set_parent(c),
-                    mock.call._set_parent_with_dispatch(c),
-                ],
-            )
+        eq_(
+            canary.mock_calls,
+            [
+                mock.call.go(evt_target, c),
+                mock.call._set_parent(c),
+                mock.call._set_parent_with_dispatch(c),
+            ],
+        )
 
     def test_independent_schema(self):
         m = MetaData()
index 9a5b9d274aa486f79543025bd10e69b28ce70eae..08966b38e6dc8ff7083c97b8283eb021b9635e8e 100644 (file)
@@ -833,6 +833,17 @@ class UserDefinedTest(
             Float().dialect_impl(pg).__class__,
         )
 
+    @testing.combinations((Boolean,), (Enum,))
+    def test_typedecorator_schematype_constraint(self, typ):
+        class B(TypeDecorator):
+            impl = typ
+
+        t1 = Table("t1", MetaData(), Column("q", B(create_constraint=True)))
+        eq_(
+            len([c for c in t1.constraints if isinstance(c, CheckConstraint)]),
+            1,
+        )
+
     def test_type_decorator_repr(self):
         class MyType(TypeDecorator):
             impl = VARCHAR