.. changelog::
:version: 1.2.0b1
+ .. change:: 3988
+ :tags: bug, orm
+ :tickets: 3988
+
+ Fixed bug where combining a "with_polymorphic" load in conjunction
+ with subclass-linked relationships that specify joinedload with
+ innerjoin=True, would fail to demote those "innerjoins" to
+ "outerjoins" to suit the other polymorphic classes that don't
+ support that relationship. This applies to both a single and a
+ joined inheritance polymorphic load.
+
.. change:: 3984
:tags: bug, orm
:tickets: 3984
is_mapper = True
"""Part of the inspection API."""
+ represents_outer_join = False
+
@property
def mapper(self):
"""Part of the inspection API.
self.path, adapter, context.primary_columns,
with_polymorphic=self._with_polymorphic,
only_load_props=query._only_load_props,
- polymorphic_discriminator=self._polymorphic_discriminator)
+ polymorphic_discriminator=self._polymorphic_discriminator
+ )
def __str__(self):
return str(self.mapper)
attach_on_outside = (
not chained_from_outerjoin or
- not innerjoin or innerjoin == 'unnested')
+ not innerjoin or innerjoin == 'unnested' or
+ entity.entity_zero.represents_outer_join
+ )
if attach_on_outside:
# this is the "classic" eager join case.
towrap,
clauses.aliased_class,
onclause,
- isouter=not innerjoin or (
+ isouter=not innerjoin or
+ entity.entity_zero.represents_outer_join or
+ (
chained_from_outerjoin and isinstance(towrap, sql.Join)
), _left_memo=self.parent, _right_memo=self.mapper
)
with_polymorphic_mappers=(),
with_polymorphic_discriminator=None,
base_alias=None,
- use_mapper_path=False):
+ use_mapper_path=False,
+ represents_outer_join=False):
mapper = _class_to_mapper(cls)
if alias is None:
alias = mapper._with_polymorphic_selectable.alias(
else mapper.polymorphic_on,
base_alias,
use_mapper_path,
- adapt_on_names
+ adapt_on_names,
+ represents_outer_join
)
self.__name__ = 'AliasedClass_%s' % mapper.class_.__name__
def __init__(self, entity, mapper, selectable, name,
with_polymorphic_mappers, polymorphic_on,
- _base_alias, _use_mapper_path, adapt_on_names):
+ _base_alias, _use_mapper_path, adapt_on_names,
+ represents_outer_join):
self.entity = entity
self.mapper = mapper
self.selectable = selectable
self.polymorphic_on = polymorphic_on
self._base_alias = _base_alias or self
self._use_mapper_path = _use_mapper_path
+ self.represents_outer_join = represents_outer_join
self._adapter = sql_util.ColumnAdapter(
selectable, equivalents=mapper._equivalent_columns,
'with_polymorphic_discriminator':
self.polymorphic_on,
'base_alias': self._base_alias,
- 'use_mapper_path': self._use_mapper_path
+ 'use_mapper_path': self._use_mapper_path,
+ 'represents_outer_join': self.represents_outer_join
}
def __setstate__(self, state):
state['with_polymorphic_discriminator'],
state['base_alias'],
state['use_mapper_path'],
- state['adapt_on_names']
+ state['adapt_on_names'],
+ state['represents_outer_join']
)
def _adapt_element(self, elem):
selectable,
with_polymorphic_mappers=mappers,
with_polymorphic_discriminator=polymorphic_on,
- use_mapper_path=_use_mapper_path)
+ use_mapper_path=_use_mapper_path,
+ represents_outer_join=not innerjoin)
def _orm_annotate(element, exclude=None):
"LEFT OUTER JOIN link AS link_2 ON parent_1.id = link_2.parent_id"
)
+ def test_local_wpoly_innerjoins(self):
+ # test for issue #3988
+ Sub1 = self._fixture_from_subclass()
+ Parent = self.classes.Parent
+ Link = self.classes.Link
+
+ poly = with_polymorphic(Parent, [Sub1])
+
+ session = Session()
+ q = session.query(poly).options(
+ joinedload(poly.Sub1.links, innerjoin=True).
+ joinedload(Link.child.of_type(Sub1), innerjoin=True).
+ joinedload(poly.Sub1.links, innerjoin=True)
+ )
+ self.assert_compile(
+ q,
+ "SELECT parent.id AS parent_id, parent.type AS parent_type, "
+ "link_1.parent_id AS link_1_parent_id, "
+ "link_1.child_id AS link_1_child_id, "
+ "parent_1.id AS parent_1_id, parent_1.type AS parent_1_type, "
+ "link_2.parent_id AS link_2_parent_id, "
+ "link_2.child_id AS link_2_child_id FROM parent "
+ "LEFT OUTER JOIN link AS link_1 ON parent.id = link_1.parent_id "
+ "LEFT OUTER JOIN parent AS parent_1 "
+ "ON link_1.child_id = parent_1.id "
+ "LEFT OUTER JOIN link AS link_2 ON parent_1.id = link_2.parent_id"
+ )
+
+ def test_local_wpoly_innerjoins_roundtrip(self):
+ # test for issue #3988
+ Sub1 = self._fixture_from_subclass()
+ Parent = self.classes.Parent
+ Link = self.classes.Link
+
+ session = Session()
+ session.add_all([
+ Parent(),
+ Parent()
+ ])
+
+ # represents "Parent" and "Sub1" rows
+ poly = with_polymorphic(Parent, [Sub1])
+
+ # innerjoin for Sub1 only, but this needs
+ # to be cancelled because the Parent rows
+ # would be omitted
+ q = session.query(poly).options(
+ joinedload(poly.Sub1.links, innerjoin=True).
+ joinedload(Link.child.of_type(Sub1), innerjoin=True)
+ )
+ eq_(len(q.all()), 2)
+
class JoinAcrossJoinedInhMultiPath(fixtures.DeclarativeMappedTest,
testing.AssertsCompiledSQL):