some issues may be moved to later milestones in order to allow
for a timely release.
- Document last updated: Feburary 9, 2016
+ Document last updated: Feburary 25, 2016
Introduction
============
:ticket:`3601`
+.. _change_3662:
+
+Improvements to the Query.correlate method with polymoprhic entities
+--------------------------------------------------------------------
+
+In recent SQLAlchemy versions, the SQL generated by many forms of
+"polymorphic" queries has a more "flat" form than it used to, where
+a JOIN of several tables is no longer bundled into a subquery unconditionally.
+To accommodate this, the :meth:`.Query.correlate` method now extracts the
+individual tables from such a polymorphic selectable and ensures that all
+are part of the "correlate" for the subquery. Assuming the
+``Person/Manager/Engineer->Company`` setup from the mapping documentation,
+using with_polymorphic::
+
+ sess.query(Person.name)
+ .filter(
+ sess.query(Company.name).
+ filter(Company.company_id == Person.company_id).
+ correlate(Person).as_scalar() == "Elbonia, Inc.")
+
+The above query now produces::
+
+ SELECT people.name AS people_name
+ FROM people
+ LEFT OUTER JOIN engineers ON people.person_id = engineers.person_id
+ LEFT OUTER JOIN managers ON people.person_id = managers.person_id
+ WHERE (SELECT companies.name
+ FROM companies
+ WHERE companies.company_id = people.company_id) = ?
+
+Before the fix, the call to ``correlate(Person)`` would inadvertently
+attempt to correlate to the join of ``Person``, ``Engineer`` and ``Manager``
+as a single unit, so ``Person`` wouldn't be correlated::
+
+ -- old, incorrect query
+ SELECT people.name AS people_name
+ FROM people
+ LEFT OUTER JOIN engineers ON people.person_id = engineers.person_id
+ LEFT OUTER JOIN managers ON people.person_id = managers.person_id
+ WHERE (SELECT companies.name
+ FROM companies, people
+ WHERE companies.company_id = people.company_id) = ?
+
+Using correlated subqueries against polymorphic mappings still has some
+unpolished edges. If for example ``Person`` is polymorphically linked
+to a so-called "concrete polymorphic union" query, the above subquery
+may not correctly refer to this subquery. In all cases, a way to refer
+to the "polyorphic" entity fully is to create an :func:`.aliased` object
+from it first::
+
+ # works with all SQLAlchemy versions and all types of polymorphic
+ # aliasing.
+
+ paliased = aliased(Person)
+ sess.query(paliased.name)
+ .filter(
+ sess.query(Company.name).
+ filter(Company.company_id == paliased.company_id).
+ correlate(paliased).as_scalar() == "Elbonia, Inc.")
+
+The :func:`.aliased` construct guarantees that the "polymorphic selectable"
+is wrapped in a subquery. By referring to it explicitly in the correlated
+subquery, the polymorphic form is correctly used.
+
+:ticket:`3662`
+
.. _change_3081:
Stringify of Query will consult the Session for the correct dialect
from sqlalchemy import func, desc
from sqlalchemy.orm import interfaces, create_session, joinedload, joinedload_all, \
subqueryload, subqueryload_all, aliased,\
- class_mapper
+ class_mapper, with_polymorphic
from sqlalchemy import exc as sa_exc
from sqlalchemy import testing
assert row.name == 'dilbert'
assert row.primary_language == 'java'
+ def test_correlation_one(self):
+ sess = create_session()
+
+ # unfortunately this pattern can't yet work for PolymorphicAliased
+ # and PolymorphicUnions, because the subquery does not compile
+ # out including the polymorphic selectable; only if Person is in
+ # the query() list does that happen.
+ eq_(sess.query(Person.name)
+ .filter(
+ sess.query(Company.name).
+ filter(Company.company_id == Person.company_id).
+ correlate(Person).as_scalar() == "Elbonia, Inc.").all(),
+ [(e3.name, )])
+
+ def test_correlation_two(self):
+ sess = create_session()
+
+ paliased = aliased(Person)
+
+ eq_(sess.query(paliased.name)
+ .filter(
+ sess.query(Company.name).
+ filter(Company.company_id == paliased.company_id).
+ correlate(paliased).as_scalar() == "Elbonia, Inc.").all(),
+ [(e3.name, )])
+
+ def test_correlation_three(self):
+ sess = create_session()
+
+ paliased = aliased(Person, flat=True)
+
+ eq_(sess.query(paliased.name)
+ .filter(
+ sess.query(Company.name).
+ filter(Company.company_id == paliased.company_id).
+ correlate(paliased).as_scalar() == "Elbonia, Inc.").all(),
+ [(e3.name, )])
+
class PolymorphicTest(_PolymorphicTestBase, _Polymorphic):
def test_join_to_subclass_four(self):
sess = create_session()
.filter(Machine.name.ilike("%ibm%")).all(),
[e1, e3])
+ def test_correlation_w_polymorphic(self):
+
+ sess = create_session()
+
+ p_poly = with_polymorphic(Person, '*')
+
+ eq_(sess.query(p_poly.name)
+ .filter(
+ sess.query(Company.name).
+ filter(Company.company_id == p_poly.company_id).
+ correlate(p_poly).as_scalar() == "Elbonia, Inc.").all(),
+ [(e3.name, )])
+
+ def test_correlation_w_polymorphic_flat(self):
+
+ sess = create_session()
+
+ p_poly = with_polymorphic(Person, '*', flat=True)
+
+ eq_(sess.query(p_poly.name)
+ .filter(
+ sess.query(Company.name).
+ filter(Company.company_id == p_poly.company_id).
+ correlate(p_poly).as_scalar() == "Elbonia, Inc.").all(),
+ [(e3.name, )])
def test_join_to_subclass_ten(self):
pass
class PolymorphicUnionsTest(_PolymorphicTestBase, _PolymorphicUnions):
- pass
+
+ @testing.fails()
+ def test_correlation_one(self):
+ super(PolymorphicUnionsTest, self).test_correlation_one()
+
class PolymorphicAliasedJoinsTest(_PolymorphicTestBase, _PolymorphicAliasedJoins):
- pass
+ @testing.fails()
+ def test_correlation_one(self):
+ super(PolymorphicAliasedJoinsTest, self).test_correlation_one()
class PolymorphicJoinsTest(_PolymorphicTestBase, _PolymorphicJoins):
pass