Polymorphic map: Lookup when discriminator is NULL

Issue #1409 resolved
Former user created an issue

Hello again,

this is the second of two tickets I'm going to open regarding the polymorphic map, which I consider a very neat feature of SA. I haven't found much documentation on it though, so I'm not sure whether these are features or bugs - I'd guess the latter.

The polymorphic map, as I understand it, is an alternative to the default polymorphic_identity system and f.e. suitable for setups where you would dynamically decide which class to map to or have more complex rules, such as: - if the discriminator is an instance of int or long, map to class A - if the discriminator is an instance of unicode, map to class B

As I played with the polymorphic map I noticed that when the value of the polymorphic_on column is NULL, the polymorphic map had not been looked up and the "default" object (the object defining polymorphic_on) had been mapped to. I guess you can argue about whether NULL not being a value should be looked up in the map at all. I was expecting it and would rather have it that way, as it may not be a value, but it is a valid scenario you might want to discriminate upon.

I have attached a test to demonstrate this.

Best Regards, Thomas Wiebe

Comments (4)

  1. Mike Bayer repo owner

    theres explicit code checking for None and discarding for this:

                if polymorphic_on:
                    discriminator = row[polymorphic_on](polymorphic_on)
                    if discriminator is not None:
                        _instance = polymorphic_instances[discriminator](discriminator)
                        if _instance:
                            return _instance(row, result)
    

    I can try removing the check to see if any tests were relying upon that somehow, but I prefer #1131 as a solution to an unusual use case such as this one.

  2. Mike Bayer repo owner

    it does break tests right now to make that change since for a blank eagerloaded row we don't check identity until later on. so removing the discriminator check needs to be more intricate than that.

  3. Mike Bayer repo owner

    here we are, "null" identity key using changesets referenced in #2238:

    from sqlalchemy import *
    from sqlalchemy.orm import *
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy import event
    
    engine = create_engine('sqlite:///:memory:', echo=True)
    Base = declarative_base()
    
    class User(Base):
        """ Base user """
        __tablename__ = 'users'
    
        id = Column(Integer, primary_key=True)
        name = Column(String)
        fullname = Column(String)
        password = Column(String)
        status_id = Column(Integer)
    
        __mapper_args__ = {'polymorphic_on': func.coalesce(status_id, "inactive")}
    
    @event.listens_for(User, "init", propagate=True)
    def init(target, args, kwargs):
        identity = target.__mapper__.polymorphic_identity
        if identity == "inactive":
            target.status_id = None
        else:
            target.status_id = identity
    
    class ActiveUser(User):
        """ Active user """
    
        __mapper_args__ = {'polymorphic_identity': 1}
    
    class InactiveUser(User):
        """ Inactive user """
    
        __mapper_args__ = {'polymorphic_identity': "inactive"}
    
    
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    session = Session()
    
    # data already in DB
    session.execute(
        u'INSERT INTO `users` (`name`, `fullname`, `password`, `status_id`)'
        u'VALUES ("hp", "Hans-Peter", "abcdefg", 1)'
    )
    session.execute(
        u'INSERT INTO `users` (`name`, `fullname`, `password`, `status_id`)'
        r'VALUES ("fj", "Franz-Josef", "abcdefg", NULL)'
    )
    
    # test ORM persistence with our init events
    session.add(ActiveUser())
    session.add(InactiveUser())
    session.flush()
    
    users = session.query(User).order_by(User.id).all()
    
    assert \
        [for u in users](type(u)) == \
        [InactiveUser, ActiveUser, InactiveUser](ActiveUser,)
    
  4. Log in to comment