.. 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
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.
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.
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.
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.
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.
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.
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
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
'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