_adjust_for_single_table_inheritance() can't be invoked if the target is an outer join

Issue #2328 resolved
Mike Bayer repo owner created an issue
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import *
from sqlalchemy.orm import *

Base = declarative_base()

class A(Base):
    __tablename__ = 'a'
    id = Column(Integer, primary_key=True)
    disc = Column(Unicode, nullable=False)
    name = Column(Unicode)

    __mapper_args__ = {'polymorphic_on': disc}

class B(A):
    __mapper_args__ = {'polymorphic_identity': 'b'}


class Z(Base):
    __tablename__ = 'z'
    id = Column(Integer, primary_key=True)
    b_id = Column(Integer, ForeignKey(B.id))

    b = relationship(B)

session = Session()
query = session.query(Z, B.name).outerjoin(Z.b).filter(Z.id == 1)
print query

Comments (5)

  1. Mike Bayer reporter

    this patch adds a specific collection of "right mappers joined to" which is possibly a decent candidate to consider as "handled". This may change behavior if someone is joining on explicit criterion and depending on the WHERE clause, though a join to a single-inh should really be handling the discrimination in the ON clause. One option would be to limit the collection just to OUTER joins. But it is tempting to apply a fix for the redundant WHERE overall.

    diff -r 25f77454bdbfbe994eeb92a9824f135a603a0776 lib/sqlalchemy/orm/query.py
    --- a/lib/sqlalchemy/orm/query.py   Mon Nov 14 19:24:08 2011 -0500
    +++ b/lib/sqlalchemy/orm/query.py   Tue Nov 15 23:58:12 2011 -0500
    @@ -89,6 +89,7 @@
         _only_load_props = None
         _refresh_state = None
         _from_obj = ()
    +    _join_entities = ()
         _select_from_entity = None
         _filter_aliases = None
         _from_obj_alias = None
    @@ -1641,6 +1642,9 @@
             left_mapper, left_selectable, left_is_aliased = _entity_info(left)
             right_mapper, right_selectable, right_is_aliased = _entity_info(right)
    
    +        if right_mapper:
    +            self._join_entities += (right_mapper, )
    +
             if right_mapper and prop and \
                     not right_mapper.common_parent(prop.mapper):
                 raise sa_exc.InvalidRequestError(
    @@ -2818,9 +2822,10 @@
             selected from the total results.
    
             """
    -
             for entity, (mapper, adapter, s, i, w) in \
                                 self._mapper_adapter_map.iteritems():
    +            if mapper in self._join_entities:
    +                continue
                 single_crit = mapper._single_table_criterion
                 if single_crit is not None:
                     if adapter:
    
  2. Mike Bayer reporter

    the above patch needs to be adjusted to account for aliases. "mapper" is not specific enough.

  3. Mike Bayer reporter

    here's that:

    diff -r 25f77454bdbfbe994eeb92a9824f135a603a0776 lib/sqlalchemy/orm/query.py
    --- a/lib/sqlalchemy/orm/query.py   Mon Nov 14 19:24:08 2011 -0500
    +++ b/lib/sqlalchemy/orm/query.py   Wed Nov 16 00:11:09 2011 -0500
    @@ -89,6 +89,7 @@
         _only_load_props = None
         _refresh_state = None
         _from_obj = ()
    +    _join_entities = ()
         _select_from_entity = None
         _filter_aliases = None
         _from_obj_alias = None
    @@ -1641,6 +1642,9 @@
             left_mapper, left_selectable, left_is_aliased = _entity_info(left)
             right_mapper, right_selectable, right_is_aliased = _entity_info(right)
    
    +        if right_mapper:
    +            self._join_entities += (right, )
    +
             if right_mapper and prop and \
                     not right_mapper.common_parent(prop.mapper):
                 raise sa_exc.InvalidRequestError(
    @@ -2818,9 +2822,10 @@
             selected from the total results.
    
             """
    -
             for entity, (mapper, adapter, s, i, w) in \
                                 self._mapper_adapter_map.iteritems():
    +            if entity in self._join_entities:
    +                continue
                 single_crit = mapper._single_table_criterion
                 if single_crit is not None:
                     if adapter:
    

    some tests:

    print session.query(Z, B.name).outerjoin(Z.b).filter(Z.id == 1)
    
    ba = aliased(B)
    print session.query(Z, ba.name).outerjoin(ba, Z.b).filter(Z.id == 1)
    
    print session.query(Z, B.name, ba.name).outerjoin(ba, Z.b).filter(Z.id == 1)
    
    print session.query(Z, B.name, ba.name).outerjoin(Z.b).filter(Z.id == 1)
    
  4. Log in to comment