mixing selectin load on top of inline

Issue #4026 resolved
Mike Bayer repo owner created an issue

pushing the new feature beyond what was planned see how hard this would be

from sqlalchemy import Table, Column, Integer, String, ForeignKey, create_engine
from sqlalchemy.orm import Session
from sqlalchemy.ext.declarative import declarative_base


Base = declarative_base()


class Person(Base):
    __tablename__ = 'person'

    id = Column(Integer, primary_key=True)
    type = Column(String(50), nullable=False)
    name = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity': 'person',
        'polymorphic_on': type
    }


class Manager(Person):
    __tablename__ = 'manager'

    id = Column(ForeignKey('person.id'), primary_key=True)
    manager_name = Column(String(30))

    __mapper_args__ = {
        'polymorphic_identity': 'manager',
        'polymorphic_load': 'selectin'
    }


class EngineerBase(Person):
    __tablename__ = 'engineer'

    id = Column(ForeignKey('person.id'), primary_key=True)
    engineer_name = Column(String(30))

    __mapper_args__ = {
        'polymorphic_load': 'selectin'
    }


class EngineerType1(EngineerBase):
    __mapper_args__ = {
        'polymorphic_identity': 'engineer_t1',
        'polymorphic_load': 'inline'
    }


class EngineerType2(EngineerBase):
    __mapper_args__ = {
        'polymorphic_identity': 'engineer_t2',
        'polymorphic_load': 'inline'
    }


engine = create_engine('sqlite://', echo=True)
Base.metadata.create_all(engine)

engine.echo = True

session = Session(engine)

eng1 = EngineerType1()
eng2 = EngineerType2()

session.add_all([eng1, eng2])
session.commit()

session.query(Person).all()

Comments (3)

  1. Mike Bayer reporter

    i think we can do this, here's a patch that doesn't work yet because there's an endless loop I have to figure out how to break, but this is just a matter of identifying the right states / paths. the post-load mechanism is a lot simpler than other loader mechanisms.

    diff --git a/lib/sqlalchemy/orm/loading.py b/lib/sqlalchemy/orm/loading.py
    index 48c0db8..9a4cc91 100644
    --- a/lib/sqlalchemy/orm/loading.py
    +++ b/lib/sqlalchemy/orm/loading.py
    @@ -360,20 +360,25 @@ def _instance_processor(
             if (
                     key in context.attributes and
                     context.attributes[key].strategy ==
    -                (('selectinload_polymorphic', True), ) and
    -                mapper in context.attributes[key].local_opts['mappers']
    -        ) or mapper.polymorphic_load == 'selectin':
    +                (('selectinload_polymorphic', True), )
    +        ):
    +            selectin_load_via = mapper._should_selectin_load(
    +                context.attributes[key].local_opts['mappers'])
    +        else:
    +            selectin_load_via = mapper._should_selectin_load(
    +                [_polymorphic_from])
    
    +        if selectin_load_via:
                 # only_load_props goes w/ refresh_state only, and in a refresh
                 # we are a single row query for the exact entity; polymorphic
                 # loading does not apply
                 assert only_load_props is None
    
    -            callable_ = _load_subclass_via_in(context, path, mapper)
    +            callable_ = _load_subclass_via_in(context, path, selectin_load_via)
    
                 PostLoad.callable_for_path(
    -                context, load_path, mapper,
    -                callable_, mapper)
    +                context, load_path, selectin_load_via,
    +                callable_, selectin_load_via)
    
         post_load = PostLoad.for_context(context, load_path, only_load_props)
    
    diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
    index 1042442..56961c4 100644
    --- a/lib/sqlalchemy/orm/mapper.py
    +++ b/lib/sqlalchemy/orm/mapper.py
    @@ -2706,6 +2706,22 @@ class Mapper(InspectionAttr):
                 cols.extend(props[key].columns)
             return sql.select(cols, cond, use_labels=True)
    
    +    def _should_selectin_load(self, against):
    +        for mapper in against:
    +            if self.isa(mapper):
    +                prev = self
    +                for m in self.iterate_to_root():
    +                    if m.polymorphic_load == 'selectin':
    +                        return m
    +
    +                    if m is not prev and prev not in m._with_polymorphic_mappers:
    +                        break
    +
    +                    prev = m
    +                    if m is mapper:
    +                        break
    +        return None
    +
         @_memoized_configured_property
         @util.dependencies(
             "sqlalchemy.ext.baked",
    
  2. Log in to comment