]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Add secondary selectable to FROM clauses for correlated exists
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 14 Nov 2018 16:02:40 +0000 (11:02 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 14 Nov 2018 22:37:02 +0000 (17:37 -0500)
In continuing with a similar theme as that of very recent :ticket:`4349`,
repaired issue with :meth:`.RelationshipProperty.Comparator.any` and
:meth:`.RelationshipProperty.Comparator.has` where the "secondary"
selectable needs to be explicitly part of the FROM clause in the
EXISTS subquery to suit the case where this "secondary" is a :class:`.Join`
object.

Fixes: #4366
Change-Id: Icd0d0c3871bbd0059f0c9256e2b980edc2c90551

doc/build/changelog/unreleased_12/4366.rst [new file with mode: 0644]
lib/sqlalchemy/orm/relationships.py
test/orm/test_query.py

diff --git a/doc/build/changelog/unreleased_12/4366.rst b/doc/build/changelog/unreleased_12/4366.rst
new file mode 100644 (file)
index 0000000..d333263
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: bug, orm
+    :tickets: 4366
+
+    In continuing with a similar theme as that of very recent :ticket:`4349`,
+    repaired issue with :meth:`.RelationshipProperty.Comparator.any` and
+    :meth:`.RelationshipProperty.Comparator.has` where the "secondary"
+    selectable needs to be explicitly part of the FROM clause in the
+    EXISTS subquery to suit the case where this "secondary" is a :class:`.Join`
+    object.
\ No newline at end of file
index adcaa592717ff2fc1a8f1e316189a0723127c2bd..e7896c42344d27a07011c6c462b8618048ed1aa3 100644 (file)
@@ -1110,9 +1110,12 @@ class RelationshipProperty(StrategizedProperty):
 
             crit = j & sql.True_._ifnone(criterion)
 
-            ex = sql.exists([1], crit, from_obj=dest).correlate_except(dest)
             if secondary is not None:
-                ex = ex.correlate_except(secondary)
+                ex = sql.exists([1], crit, from_obj=[dest, secondary]).\
+                    correlate_except(dest, secondary)
+            else:
+                ex = sql.exists([1], crit, from_obj=dest).\
+                    correlate_except(dest)
             return ex
 
         def any(self, criterion=None, **kwargs):
index 0cd0b1a7e5f48d6f9dcfb80ff5a9b325b1fb945d..f2873236042f3f37dcc817c947006490dc9e3ad1 100644 (file)
@@ -2308,6 +2308,8 @@ class FilterTest(QueryTest, AssertsCompiledSQL):
         )
 
     def test_any(self):
+        # see also HasAnyTest, a newer suite which tests these at the level of
+        # SQL compilation
         User, Address = self.classes.User, self.classes.Address
 
         sess = create_session()
@@ -2344,6 +2346,8 @@ class FilterTest(QueryTest, AssertsCompiledSQL):
             filter(~User.addresses.any()).all()
 
     def test_any_doesnt_overcorrelate(self):
+        # see also HasAnyTest, a newer suite which tests these at the level of
+        # SQL compilation
         User, Address = self.classes.User, self.classes.Address
 
         sess = create_session()
@@ -2356,6 +2360,8 @@ class FilterTest(QueryTest, AssertsCompiledSQL):
                     Address.email_address == 'fred@fred.com')).all()
 
     def test_has(self):
+        # see also HasAnyTest, a newer suite which tests these at the level of
+        # SQL compilation
         Dingaling, User, Address = (
             self.classes.Dingaling, self.classes.User, self.classes.Address)
 
@@ -2607,6 +2613,23 @@ class HasAnyTest(
             id = Column(Integer, primary_key=True)
             b_id = Column(ForeignKey(B.id))
 
+            d = relationship(
+                'D',
+                secondary="join(B, C)",
+                primaryjoin="A.b_id == B.id",
+                secondaryjoin="C.d_id == D.id",
+                uselist=False)
+
+    def test_has_composite_secondary(self):
+        A, D = self.classes("A", "D")
+        s = Session()
+        self.assert_compile(
+            s.query(A).filter(A.d.has(D.id == 1)),
+            "SELECT a.id AS a_id, a.b_id AS a_b_id FROM a WHERE EXISTS "
+            "(SELECT 1 FROM d, b JOIN c ON c.id = b.c_id "
+            "WHERE a.b_id = b.id AND c.d_id = d.id AND d.id = :id_1)"
+        )
+
     def test_has_many_to_one(self):
         B, C = self.classes("B", "C")
         s = Session()