]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Disable single-inheritance critera on the outside of UNION
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 18 Nov 2016 16:49:00 +0000 (11:49 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 18 Nov 2016 16:53:33 +0000 (11:53 -0500)
Fixed bug related to :ticket:`3177`, where a UNION or other set operation
emitted by a :class:`.Query` would apply "single-inheritance" criteria
to the outside of the union (also referencing the wrong selectable),
even though this criteria is now expected to
be already present on the inside subqueries.  The single-inheritance
criteria is now omitted once union() or another set operation is
called against :class:`.Query` in the same way as :meth:`.Query.from_self`.

Change-Id: I0fd1331c7ba85a758a1c15e06c271914f2c717f3
Fixes: #3856
doc/build/changelog/changelog_11.rst
lib/sqlalchemy/orm/query.py
test/orm/inheritance/test_single.py

index f6334203d78d12a7c18426f9d2a3f0fbedc5da35..53c80eebad0141552bcd9ddbfea13ebe2dc66d36 100644 (file)
 .. changelog::
     :version: 1.1.5
 
+    .. change:: 3856
+        :tags: bug, orm
+        :tickets: 3856
+
+        Fixed bug related to :ticket:`3177`, where a UNION or other set operation
+        emitted by a :class:`.Query` would apply "single-inheritance" criteria
+        to the outside of the union (also referencing the wrong selectable),
+        even though this criteria is now expected to
+        be already present on the inside subqueries.  The single-inheritance
+        criteria is now omitted once union() or another set operation is
+        called against :class:`.Query` in the same way as :meth:`.Query.from_self`.
+
     .. change:: 3548
         :tag: bug, firebird
         :tickets: 3548
index 139b61afbb865eac2e0c7e97501a5a3c3fb9ce45..340e71d25e660ef6ed9965e667357b5bba0c029f 100644 (file)
@@ -1607,6 +1607,11 @@ class Query(object):
         else:
             self._having = criterion
 
+    def _set_op(self, expr_fn, *q):
+        return self._from_selectable(
+            expr_fn(*([self] + list(q)))
+        )._set_enable_single_crit(False)
+
     def union(self, *q):
         """Produce a UNION of this Query against one or more queries.
 
@@ -1644,9 +1649,7 @@ class Query(object):
         its SELECT statement.
 
         """
-
-        return self._from_selectable(
-            expression.union(*([self] + list(q))))
+        return self._set_op(expression.union, *q)
 
     def union_all(self, *q):
         """Produce a UNION ALL of this Query against one or more queries.
@@ -1655,9 +1658,7 @@ class Query(object):
         that method for usage examples.
 
         """
-        return self._from_selectable(
-            expression.union_all(*([self] + list(q)))
-        )
+        return self._set_op(expression.union_all, *q)
 
     def intersect(self, *q):
         """Produce an INTERSECT of this Query against one or more queries.
@@ -1666,9 +1667,7 @@ class Query(object):
         that method for usage examples.
 
         """
-        return self._from_selectable(
-            expression.intersect(*([self] + list(q)))
-        )
+        return self._set_op(expression.intersect, *q)
 
     def intersect_all(self, *q):
         """Produce an INTERSECT ALL of this Query against one or more queries.
@@ -1677,9 +1676,7 @@ class Query(object):
         that method for usage examples.
 
         """
-        return self._from_selectable(
-            expression.intersect_all(*([self] + list(q)))
-        )
+        return self._set_op(expression.intersect_all, *q)
 
     def except_(self, *q):
         """Produce an EXCEPT of this Query against one or more queries.
@@ -1688,9 +1685,7 @@ class Query(object):
         that method for usage examples.
 
         """
-        return self._from_selectable(
-            expression.except_(*([self] + list(q)))
-        )
+        return self._set_op(expression.except_, *q)
 
     def except_all(self, *q):
         """Produce an EXCEPT ALL of this Query against one or more queries.
@@ -1699,9 +1694,7 @@ class Query(object):
         that method for usage examples.
 
         """
-        return self._from_selectable(
-            expression.except_all(*([self] + list(q)))
-        )
+        return self._set_op(expression.except_all, *q)
 
     def join(self, *props, **kwargs):
         """Create a SQL JOIN against this :class:`.Query` object's criterion
@@ -3447,7 +3440,6 @@ class Query(object):
         subtypes are selected from the total results.
 
         """
-
         for (ext_info, adapter) in set(self._mapper_adapter_map.values()):
             if ext_info in self._join_entities:
                 continue
index 0d102c06562a0dad331a5335e056f4d6a3b083f1..d12d9697722890783d2d85a5277a88543ed718fd 100644 (file)
@@ -156,6 +156,56 @@ class SingleInheritanceTest(testing.AssertsCompiledSQL, fixtures.MappedTest):
                             'anon_1',
                             use_default_dialect=True)
 
+    def test_union_modifiers(self):
+        Engineer, Manager = self.classes("Engineer", "Manager")
+
+        sess = create_session()
+        q1 = sess.query(Engineer).filter(Engineer.engineer_info == 'foo')
+        q2 = sess.query(Manager).filter(Manager.manager_data == 'bar')
+
+        assert_sql = (
+            "SELECT anon_1.employees_employee_id AS "
+            "anon_1_employees_employee_id, "
+            "anon_1.employees_name AS anon_1_employees_name, "
+            "anon_1.employees_manager_data AS anon_1_employees_manager_data, "
+            "anon_1.employees_engineer_info AS anon_1_employees_engineer_info, "
+            "anon_1.employees_type AS anon_1_employees_type "
+            "FROM (SELECT employees.employee_id AS employees_employee_id, "
+            "employees.name AS employees_name, "
+            "employees.manager_data AS employees_manager_data, "
+            "employees.engineer_info AS employees_engineer_info, "
+            "employees.type AS employees_type FROM employees "
+            "WHERE employees.engineer_info = :engineer_info_1 "
+            "AND employees.type IN (:type_1, :type_2) "
+            "%(token)s "
+            "SELECT employees.employee_id AS employees_employee_id, "
+            "employees.name AS employees_name, "
+            "employees.manager_data AS employees_manager_data, "
+            "employees.engineer_info AS employees_engineer_info, "
+            "employees.type AS employees_type FROM employees "
+            "WHERE employees.manager_data = :manager_data_1 "
+            "AND employees.type IN (:type_3)) AS anon_1"
+        )
+
+        for meth, token in [
+            (q1.union, "UNION"),
+            (q1.union_all, "UNION ALL"),
+            (q1.except_, "EXCEPT"),
+            (q1.except_all, "EXCEPT ALL"),
+            (q1.intersect, "INTERSECT"),
+            (q1.intersect_all, "INTERSECT ALL"),
+        ]:
+            self.assert_compile(
+                meth(q2),
+                assert_sql % {"token": token},
+                checkparams={
+                    'manager_data_1': 'bar',
+                    'type_2': 'juniorengineer',
+                    'type_3': 'manager',
+                    'engineer_info_1': 'foo',
+                    'type_1': 'engineer'},
+            )
+
     def test_from_self_count(self):
         Engineer = self.classes.Engineer