diff options
-rw-r--r-- | doc/build/changelog/changelog_10.rst | 16 | ||||
-rw-r--r-- | doc/build/changelog/migration_10.rst | 57 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/query.py | 2 | ||||
-rw-r--r-- | test/orm/inheritance/test_single.py | 19 |
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, |