From: Mike Bayer Date: Fri, 18 Nov 2016 16:49:00 +0000 (-0500) Subject: Disable single-inheritance critera on the outside of UNION X-Git-Tag: rel_1_1_5~34 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7de0d1785335961ce0f723877ca7a8fd85b2c0ca;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Disable single-inheritance critera on the outside of UNION 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 --- diff --git a/doc/build/changelog/changelog_11.rst b/doc/build/changelog/changelog_11.rst index f6334203d7..53c80eebad 100644 --- a/doc/build/changelog/changelog_11.rst +++ b/doc/build/changelog/changelog_11.rst @@ -21,6 +21,18 @@ .. 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 diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 139b61afbb..340e71d25e 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -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 diff --git a/test/orm/inheritance/test_single.py b/test/orm/inheritance/test_single.py index 0d102c0656..d12d969772 100644 --- a/test/orm/inheritance/test_single.py +++ b/test/orm/inheritance/test_single.py @@ -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