]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
accommodate for cloned bindparams w/ maintain_key
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 29 Jul 2021 14:10:28 +0000 (10:10 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 29 Jul 2021 14:12:04 +0000 (10:12 -0400)
Fixed issue where a bound parameter object that was "cloned" would cause a
name conflict in the compiler, if more than one clone of this parameter
were used at the same time in a single statement. This could occur in
particular with things like ORM single table inheritance queries that
indicated the same "discriminator" value multiple times in one query.

Fixes: #6824
Change-Id: Iba7a786fc5a2341ff7d07fc666d24ed790ad4fe8

doc/build/changelog/unreleased_14/6824.rst [new file with mode: 0644]
lib/sqlalchemy/sql/compiler.py
test/orm/inheritance/test_single.py
test/sql/test_compiler.py

diff --git a/doc/build/changelog/unreleased_14/6824.rst b/doc/build/changelog/unreleased_14/6824.rst
new file mode 100644 (file)
index 0000000..48086e0
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: bug, orm, sql
+    :tickets: 6824
+
+    Fixed issue where a bound parameter object that was "cloned" would cause a
+    name conflict in the compiler, if more than one clone of this parameter
+    were used at the same time in a single statement. This could occur in
+    particular with things like ORM single table inheritance queries that
+    indicated the same "discriminator" value multiple times in one query.
+
index 29ef8047ef72afd785816cafd88ce03083b7768b..a81507acb94415f7f67f9b3656aece679e0ac875 100644 (file)
@@ -2311,8 +2311,14 @@ class SQLCompiler(Compiled):
             existing = self.binds[name]
             if existing is not bindparam:
                 if (
-                    existing.unique or bindparam.unique
-                ) and not existing.proxy_set.intersection(bindparam.proxy_set):
+                    (existing.unique or bindparam.unique)
+                    and not existing.proxy_set.intersection(
+                        bindparam.proxy_set
+                    )
+                    and not existing._cloned_set.intersection(
+                        bindparam._cloned_set
+                    )
+                ):
                     raise exc.CompileError(
                         "Bind parameter '%s' conflicts with "
                         "unique bind parameter of the same name" % name
index 873b808ec91c0646aad0389029ea423c205753ac..32cc57020441d2063de81438caffa0e2b1a439dc 100644 (file)
@@ -162,6 +162,20 @@ class SingleInheritanceTest(testing.AssertsCompiledSQL, fixtures.MappedTest):
         assert row.name == "Kurt"
         assert row.employee_id == e1.employee_id
 
+    def test_discrim_bound_param_cloned_ok(self):
+        """Test #6824"""
+        Manager = self.classes.Manager
+
+        subq1 = select(Manager.employee_id).label("foo")
+        subq2 = select(Manager.employee_id).label("bar")
+        self.assert_compile(
+            select(subq1, subq2),
+            "SELECT (SELECT employees.employee_id FROM employees "
+            "WHERE employees.type IN ([POSTCOMPILE_type_1])) AS foo, "
+            "(SELECT employees.employee_id FROM employees "
+            "WHERE employees.type IN ([POSTCOMPILE_type_1])) AS bar",
+        )
+
     def test_multi_qualification(self):
         Manager, Engineer = (self.classes.Manager, self.classes.Engineer)
 
index 40faab486791dd17bae037bcdd6caa5dbfb88d2c..d270218b24346f8fbf9d6fba815415914ae9a278 100644 (file)
@@ -3589,6 +3589,22 @@ class BindParameterTest(AssertsCompiledSQL, fixtures.TestBase):
             s,
         )
 
+    def test_unique_binds_no_clone_collision(self):
+        """test #6824"""
+        bp = bindparam("foo", unique=True)
+
+        bpc1 = bp._clone(maintain_key=True)
+        bpc2 = bp._clone(maintain_key=True)
+
+        stmt1 = select(bp, bpc1, bpc2)
+
+        # OK, still strange that the double-dedupe logic is still *duping*
+        # the label name, but that's a different issue
+        self.assert_compile(
+            stmt1,
+            "SELECT :foo_1 AS anon_1, :foo_1 AS anon__1, :foo_1 AS anon__1",
+        )
+
     def _test_binds_no_hash_collision(self):
         """test that construct_params doesn't corrupt dict
         due to hash collisions"""