on compatibility concerns, see :doc:`/changelog/migration_10`.
+ .. change::
+ :tags: bug, orm
+ :tickets: 3177
+
+ Changed the approach by which the "single inheritance criterion"
+ is applied, when using :meth:`.Query.from_self`, or its common
+ user :meth:`.Query.count`. The criteria to limit rows to those
+ with a certain type is now indicated on the inside subquery,
+ not the outside one, so that even if the "type" column is not
+ available in the columns clause, we can filter on it on the "inner"
+ query.
+
+ .. seealso::
+
+ :ref:`migration_3177`
+
.. change::
:tags: change, orm
:ticket:`2963`
+.. _migration_3177:
+
+Change to single-table-inheritance criteria when using from_self(), count()
+---------------------------------------------------------------------------
+
+Given a single-table inheritance mapping, such as::
+
+ class Widget(Base):
+ __table__ = 'widget_table'
+
+ class FooWidget(Widget):
+ pass
+
+Using :meth:`.Query.from_self` or :meth:`.Query.count` against a subclass
+would produce a subquery, but then add the "WHERE" criteria for subtypes
+to the outside::
+
+ sess.query(FooWidget).from_self().all()
+
+rendering::
+
+ SELECT
+ anon_1.widgets_id AS anon_1_widgets_id,
+ anon_1.widgets_type AS anon_1_widgets_type
+ FROM (SELECT widgets.id AS widgets_id, widgets.type AS widgets_type,
+ FROM widgets) AS anon_1
+ WHERE anon_1.widgets_type IN (?)
+
+The issue with this is that if the inner query does not specify all
+columns, then we can't add the WHERE clause on the outside (it actually tries,
+and produces a bad query). This decision
+apparently goes way back to 0.6.5 with the note "may need to make more
+adjustments to this". Well, those adjustments have arrived! So now the
+above query will render::
+
+ SELECT
+ anon_1.widgets_id AS anon_1_widgets_id,
+ anon_1.widgets_type AS anon_1_widgets_type
+ FROM (SELECT widgets.id AS widgets_id, widgets.type AS widgets_type,
+ FROM widgets
+ WHERE widgets.type IN (?)) AS anon_1
+
+So that queries that don't include "type" will still work!::
+
+ sess.query(FooWidget.id).count()
+
+Renders::
+
+ SELECT count(*) AS count_1
+ FROM (SELECT widgets.id AS widgets_id
+ FROM widgets
+ WHERE widgets.type IN (?)) AS anon_1
+
+
+:ticket:`3177`
+
+
Dialect Changes
===============
"""
fromclause = self.with_labels().enable_eagerloads(False).\
- _set_enable_single_crit(False).\
statement.correlate(None)
q = self._from_selectable(fromclause)
+ q._enable_single_crit = False
if entities:
q._set_entities(entities)
return q
'employees_manager_data, '
'employees.engineer_info AS '
'employees_engineer_info, employees.type '
- 'AS employees_type FROM employees) AS '
- 'anon_1 WHERE anon_1.employees_type IN '
- '(:type_1, :type_2)',
+ 'AS employees_type FROM employees WHERE '
+ 'employees.type IN (:type_1, :type_2)) AS '
+ 'anon_1',
use_default_dialect=True)
+ def test_from_self_count(self):
+ Engineer = self.classes.Engineer
+
+ sess = create_session()
+ col = func.count(literal_column('*'))
+ self.assert_compile(
+ sess.query(Engineer.employee_id).from_self(col),
+ "SELECT count(*) AS count_1 "
+ "FROM (SELECT employees.employee_id AS employees_employee_id "
+ "FROM employees "
+ "WHERE employees.type IN (?, ?)) AS anon_1"
+ )
+
def test_select_from(self):
Manager, JuniorEngineer, employees, Engineer = (self.classes.Manager,
self.classes.JuniorEngineer,