incorrect sql for with_polymorphic() along a relation path

Issue #1048 resolved
jek created an issue
(ProgrammingError) invalid reference to FROM-clause entry for table "principals" at character 737
HINT:  There is an entry for table "principals", but it cannot be referenced from this part of the query.

'SELECT anon_1.tickets_owner_id AS anon_1_tickets_owner_id, anon_1.tickets_id AS anon_1_tickets_id, anon_1.tickets_subject AS anon_1_tickets_subject, anon_1.principals_id AS anon_1_principals_id, anon_1.users_id AS anon_1_users_id, anon_1.principals_principal_type AS anon_1_principals_principal_type, anon_1.users_name AS anon_1_users_name,
   principals_1.id AS principals_1_id, principals_1.principal_type AS principals_1_principal_type
 FROM (SELECT tickets.owner_id AS tickets_owner_id, tickets.id AS tickets_id, tickets.subject AS tickets_subject, principals.id AS principals_id, users.id AS users_id, principals.principal_type AS principals_principal_type, users.name AS users_name
       FROM principals, tickets LEFT OUTER JOIN users ON principals.id = users.id
       LIMIT 1 OFFSET 0) AS anon_1
 LEFT OUTER JOIN principals AS principals_1 ON anon_1.tickets_owner_id = principals_1.id'



#!python
from sqlalchemy import *
from sqlalchemy.orm import *


metadata = MetaData()
Table('principals', metadata,
      Column('id', Integer, primary_key=True),
      Column('principal_type', String(16), nullable=False))

Table('users', metadata,
      Column('id', Integer, ForeignKey('principals.id'), primary_key=True),
      Column('name', String(200), nullable=False))

Table('groups', metadata,
      Column('id', Integer, ForeignKey('principals.id'), primary_key=True),
      Column('name', String(200), nullable=False),
      Column('description', String(255)))

Table('tickets', metadata,
      Column('id', Integer, primary_key=True),
      Column('owner_id', Integer, ForeignKey('principals.id'), nullable=False),
      Column('subject', String(200), default='[subject](no)'))


for name, value in metadata.tables.items():
    locals()[name](name) = value

class Principal(object):
    pass

class Group(Principal):
    pass

class User(Principal):
    pass

class Ticket(object):
    pass


mapper(Principal, principals,
       polymorphic_on=principals.c.principal_type)

mapper(User, users,
       inherits=Principal,
       polymorphic_identity='User')

mapper(Group, groups,
       inherits=Principal,
       polymorphic_identity='Group')

mapper(Ticket, tickets, properties={
    'owner': relation(Principal, backref='tickets',
                      primaryjoin=(tickets.c.owner_id ==
                                   principals.c.id))
    })

def data():
    principals.insert().execute(
        {'id': 1, 'principal_type': 'User'},
        {'id': 2, 'principal_type': 'Group'},
        {'id': 3, 'principal_type': 'Group'},
        {'id': 4, 'principal_type': 'User'})
    users.insert().execute(
        {'id': 1, 'name': 'user @ 1'},
        {'id': 4, 'name': 'user @ 4'})
    groups.insert().execute(
        {'id': 2, 'name': 'group @ 2'},
        {'id': 3, 'name': 'group @ 3'})
    tickets.insert().execute(
        {'id': 1, 'owner_id': 1, 'subject': 'Tix @ 1'},
        {'id': 2, 'owner_id': 2, 'subject': 'Tix @ 2'},
        {'id': 3, 'owner_id': 1, 'subject': 'Tix @ 3'},
        {'id': 4, 'owner_id': 1, 'subject': 'Tix @ 4'})


def test():
    s = create_session()
    s.query(Ticket).options(eagerload(Ticket.owner)).with_polymorphic(User).first()

if __name__ == '__main__':
    # passes on sqlite
    metadata.bind = 'postgres:///test'
    metadata.create_all()
    try:
        data()
        test()
    finally:
        metadata.drop_all()

Comments (5)

  1. Mike Bayer repo owner

    yeah this is because with_polymorphic() currently only applies to mapper zero. im not entirely sure how it is shoving principal/user into the query like that, that would be a bug within mapper._with_polymorphic_args that its accepting things which are not part of its mapping hierarchy (i.e. Ticket mapper here).

    we don't yet have a "with_polymorphic()" option that applies to paths on a per-Query basis. in this case you have to configure the Principal's with_polymorphic() behavior on the mapper level. wanted to get the more static use cases as rock solid as possible first before getting into more fringy optimizing cases like these (nobody has asked for this feature in 0.4, for example...even though its useful).

  2. Mike Bayer repo owner

    2beb99a60ee8565079f5efdb11c92b3026b9b90e adds an assertion to prevent this particular issue. What's not yet implemented is how I think the eager load in this case should be indicated:

    s.query(Ticket).options(eagerload(Ticket.owner.of_type(User))).first()
    

    of_type() currently takes a single class but I do have in mind that it can eventually take the same arguments as with_polymorphic()..though I think one class is just fine for now.

  3. Log in to comment