series as well. For changes that are specific to 1.0 with an emphasis
on compatibility concerns, see :doc:`/changelog/migration_10`.
+ .. change::
+ :tags: bug, orm
+ :tickets: 3256
+
+ The :meth:`.PropComparator.of_type` modifier has been
+ improved in conjunction with loader directives such as
+ :func:`.joinedload` and :func:`.contains_eager` such that if
+ two :meth:`.PropComparator.of_type` modifiers of the same
+ base type/path are encountered, they will be joined together
+ into a single "polymorphic" entity, rather than replacing
+ the entity of type A with the one of type B. E.g.
+ a joinedload of ``A.b.of_type(BSub1)->BSub1.c`` combined with
+ joinedload of ``A.b.of_type(BSub2)->BSub2.c`` will create a
+ single joinedload of ``A.b.of_type((BSub1, BSub2)) -> BSub1.c, BSub2.c``,
+ without the need for the ``with_polymorphic`` to be explicit
+ in the query.
+
+ .. seealso::
+
+ :ref:`eagerloading_polymorphic_subtypes` - contains an updated
+ example illustrating the new format.
+
.. change::
:tags: bug, sql
:tickets: 3245
:func:`.orm.aliased` and :func:`.orm.with_polymorphic` constructs in conjunction
with :meth:`.Query.join`, ``any()`` and ``has()``.
+.. _eagerloading_polymorphic_subtypes:
+
Eager Loading of Specific or Polymorphic Subtypes
++++++++++++++++++++++++++++++++++++++++++++++++++
)
)
-As is the case with :meth:`.Query.join`, :func:`~sqlalchemy.orm.interfaces.PropComparator.of_type`
+As is the case with :meth:`.Query.join`, :meth:`~PropComparator.of_type`
also can be used with eager loading and :func:`.orm.with_polymorphic`
at the same time, so that all sub-attributes of all referenced subtypes
can be loaded::
:func:`~sqlalchemy.orm.interfaces.PropComparator.of_type`, supporting
single target types as well as :func:`.orm.with_polymorphic` targets.
+Another option for the above query is to state the two subtypes separately;
+the :func:`.joinedload` directive should detect this and create the
+above ``with_polymorphic`` construct automatically::
+
+ session.query(Company).\
+ options(
+ joinedload(Company.employees.of_type(Manager)),
+ joinedload(Company.employees.of_type(Engineer)),
+ )
+ )
+
+.. versionadded:: 1.0
+ Eager loaders such as :func:`.joinedload` will create a polymorphic
+ entity when multiple overlapping :meth:`~PropComparator.of_type`
+ directives are encountered.
+
+
Single Table Inheritance
------------------------
ext_info = inspect(ac)
path_element = ext_info.mapper
+ existing = path.entity_path[prop].get(
+ self.context, "path_with_polymorphic")
if not ext_info.is_aliased_class:
ac = orm_util.with_polymorphic(
ext_info.mapper.base_mapper,
ext_info.mapper, aliased=True,
- _use_mapper_path=True)
+ _use_mapper_path=True,
+ _existing_alias=existing)
path.entity_path[prop].set(
self.context, "path_with_polymorphic", inspect(ac))
path = path[prop][path_element]
mapper, self)
def __repr__(self):
- return '<AliasedInsp at 0x%x; %s>' % (
- id(self), self.class_.__name__)
+ if self.with_polymorphic_mappers:
+ with_poly = "(%s)" % ", ".join(
+ mp.class_.__name__ for mp in self.with_polymorphic_mappers)
+ else:
+ with_poly = ""
+ return '<AliasedInsp at 0x%x; %s%s>' % (
+ id(self), self.class_.__name__, with_poly)
inspection._inspects(AliasedClass)(lambda target: target._aliased_insp)
def with_polymorphic(base, classes, selectable=False,
flat=False,
polymorphic_on=None, aliased=False,
- innerjoin=False, _use_mapper_path=False):
+ innerjoin=False, _use_mapper_path=False,
+ _existing_alias=None):
"""Produce an :class:`.AliasedClass` construct which specifies
columns for descendant mappers of the given base.
only be specified if querying for one specific subtype only
"""
primary_mapper = _class_to_mapper(base)
+ if _existing_alias:
+ assert _existing_alias.mapper is primary_mapper
+ classes = util.to_set(classes)
+ new_classes = set([
+ mp.class_ for mp in
+ _existing_alias.with_polymorphic_mappers])
+ if classes == new_classes:
+ return _existing_alias
+ else:
+ classes = classes.union(new_classes)
mappers, selectable = primary_mapper.\
_with_polymorphic_args(classes, selectable,
innerjoin=innerjoin)
from __future__ import absolute_import
import weakref
import operator
-from .compat import threading, itertools_filterfalse
+from .compat import threading, itertools_filterfalse, string_types
from . import py2k
import types
+import collections
EMPTY_SET = frozenset()
def to_list(x, default=None):
if x is None:
return default
- if not isinstance(x, (list, tuple)):
+ if not isinstance(x, collections.Iterable) or isinstance(x, string_types):
return [x]
- else:
+ elif isinstance(x, list):
return x
+ else:
+ return list(x)
def to_set(x):
from sqlalchemy.sql import column
from sqlalchemy.util import langhelpers
+
class _KeyedTupleTest(object):
def _fixture(self, values, labels):
eq_(val[0], 21)
+class ToListTest(fixtures.TestBase):
+ def test_from_string(self):
+ eq_(
+ util.to_list("xyz"),
+ ["xyz"]
+ )
+
+ def test_from_set(self):
+ spec = util.to_list(set([1, 2, 3]))
+ assert isinstance(spec, list)
+ eq_(
+ sorted(spec),
+ [1, 2, 3]
+ )
+
+ def test_from_dict(self):
+ spec = util.to_list({1: "a", 2: "b", 3: "c"})
+ assert isinstance(spec, list)
+ eq_(
+ sorted(spec),
+ [1, 2, 3]
+ )
+
+ def test_from_tuple(self):
+ eq_(
+ util.to_list((1, 2, 3)),
+ [1, 2, 3]
+ )
+
class ColumnCollectionTest(fixtures.TestBase):
def test_in(self):
_PolymorphicPolymorphic, _PolymorphicUnions, _PolymorphicJoins,\
_PolymorphicAliasedJoins
+
class _PolymorphicTestBase(object):
__dialect__ = 'default'
)
self.assert_sql_count(testing.db, go, 3)
+ def test_joinedload_stacked_of_type(self):
+ sess = Session()
+
+ def go():
+ eq_(
+ sess.query(Company).
+ filter_by(company_id=1).
+ options(
+ joinedload(Company.employees.of_type(Manager)),
+ joinedload(Company.employees.of_type(Engineer))
+ ).all(),
+ [self._company_with_emps_fixture()[0]]
+ )
+ self.assert_sql_count(testing.db, go, 2)
+
class PolymorphicPolymorphicTest(_PolymorphicTestBase, _PolymorphicPolymorphic):
def _polymorphic_join_target(self, cls):