summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/changelog_10.rst16
-rw-r--r--doc/build/changelog/migration_10.rst57
-rw-r--r--lib/sqlalchemy/orm/query.py2
-rw-r--r--test/orm/inheritance/test_single.py19
4 files changed, 90 insertions, 4 deletions
diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst
index 87f32aff6..13af7f953 100644
--- a/doc/build/changelog/changelog_10.rst
+++ b/doc/build/changelog/changelog_10.rst
@@ -23,6 +23,22 @@
.. 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
The ``proc()`` callable passed to the ``create_row_processor()``
diff --git a/doc/build/changelog/migration_10.rst b/doc/build/changelog/migration_10.rst
index 3dc2871b0..16a790234 100644
--- a/doc/build/changelog/migration_10.rst
+++ b/doc/build/changelog/migration_10.rst
@@ -417,6 +417,63 @@ as remaining ORM constructs such as :func:`.orm.synonym`.
: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
===============
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index b05646719..61fbd1be8 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -945,9 +945,9 @@ class Query(object):
"""
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
diff --git a/test/orm/inheritance/test_single.py b/test/orm/inheritance/test_single.py
index 434642ca1..23b1c4fd2 100644
--- a/test/orm/inheritance/test_single.py
+++ b/test/orm/inheritance/test_single.py
@@ -149,11 +149,24 @@ class SingleInheritanceTest(testing.AssertsCompiledSQL, fixtures.MappedTest):
'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,