Mapping multiple classes to a single table in a mapped inheritance hierarchy does not populate tables correctly

Issue #891 resolved
Former user created an issue

The following code:

from sqlalchemy import *
from sqlalchemy.orm import *

db = create_engine('sqlite:///sa.db')

db.echo = False  # Try changing this to True and see what happens

metadata = MetaData(db)

class Task(object):
  pass


class CustomTask(Task):
  pass


class MyTask(CustomTask):
  pass

task = Table('task', metadata,
    Column('task_id', Integer, primary_key=True),
    Column('type', String(64)),
    )

custom_task = Table('task_custom', metadata,
    Column('task_id', Integer, ForeignKey('task.task_id'), primary_key=True),
    )

mapper(
    Task, task,
    polymorphic_on=task.c.type,
    polymorphic_identity='Task',
    select_table=custom_task.outerjoin(task),
    )

mapper(
    CustomTask, custom_task,
    polymorphic_identity='CustomTask',
    inherits=Task,
    )

mapper(
    MyTask, custom_task,
    polymorphic_identity='MyTask',
    inherits=CustomTask,
    )

metadata.create_all()

Session = sessionmaker(autoflush=True, transactional=True)
Session.configure(bind=db)

session = Session()

task = Task()
rtask = CustomTask()
crtask = MyTask()

session.save_or_update(task)
session.save_or_update(rtask)
session.save_or_update(crtask)
session.commit()

session.flush()

for task in session.query(task):
  print task

Produces this SQL:

BEGIN TRANSACTION;
CREATE TABLE task (
    task_id INTEGER NOT NULL, 
    type VARCHAR(64), 
    PRIMARY KEY (task_id)
);
INSERT INTO "task" VALUES(1,'Task');
INSERT INTO "task" VALUES(2,'CustomTask');
CREATE TABLE task_custom (
    task_id INTEGER NOT NULL, 
    PRIMARY KEY (task_id), 
     FOREIGN KEY(task_id) REFERENCES task (task_id)
);
INSERT INTO "task_custom" VALUES(2);
INSERT INTO "task_custom" VALUES(3);
COMMIT;

Note the dangling task_custom row with task_id=3.

Comments (2)

  1. Mike Bayer repo owner

    the mapping for MyTask doesn't fit into any of the three available categories (single, joined, or concrete), and also the outerjoin on Task is backwards. try it like this:

    task = Table('task', metadata,
        Column('task_id', Integer, primary_key=True),
        Column('type', String(64)),
        )
    
    custom_task = Table('task_custom', metadata,
        Column('task_id', Integer, ForeignKey('task.task_id'),primary_key=True),
        )
    
    mapper(
        Task, task,
        polymorphic_on=task.c.type,
        polymorphic_identity='Task',
        select_table=task.outerjoin(custom_task), 
        )
    
    mapper(
        CustomTask, custom_task,
        polymorphic_identity='CustomTask',
        inherits=Task,
        )
    
    mapper(
        MyTask, task.join(custom_task),
        concrete=True,
        polymorphic_identity='MyTask',
        inherits=CustomTask,
        )
    

    output:

    2007-12-05 10:08:35,665 INFO sqlalchemy.engine.base.Engine.0x..70 BEGIN
    2007-12-05 10:08:35,666 INFO sqlalchemy.engine.base.Engine.0x..70 INSERT INTO task (type) VALUES (?)
    2007-12-05 10:08:35,666 INFO sqlalchemy.engine.base.Engine.0x..70 ['Task']('Task')
    2007-12-05 10:08:35,667 INFO sqlalchemy.engine.base.Engine.0x..70 INSERT INTO task (type) VALUES (?)
    2007-12-05 10:08:35,667 INFO sqlalchemy.engine.base.Engine.0x..70 ['CustomTask']('CustomTask')
    2007-12-05 10:08:35,667 INFO sqlalchemy.engine.base.Engine.0x..70 INSERT INTO task (type) VALUES (?)
    2007-12-05 10:08:35,667 INFO sqlalchemy.engine.base.Engine.0x..70 ['MyTask']('MyTask')
    2007-12-05 10:08:35,668 INFO sqlalchemy.engine.base.Engine.0x..70 INSERT INTO task_custom (task_id) VALUES (?)
    2007-12-05 10:08:35,668 INFO sqlalchemy.engine.base.Engine.0x..70 [2](2)
    2007-12-05 10:08:35,669 INFO sqlalchemy.engine.base.Engine.0x..70 INSERT INTO task_custom (task_id) VALUES (?)
    2007-12-05 10:08:35,669 INFO sqlalchemy.engine.base.Engine.0x..70 [3](3)
    2007-12-05 10:08:35,669 INFO sqlalchemy.engine.base.Engine.0x..70 COMMIT
    2007-12-05 10:08:35,670 INFO sqlalchemy.engine.base.Engine.0x..70 BEGIN
    2007-12-05 10:08:35,671 INFO sqlalchemy.engine.base.Engine.0x..70 SELECT task.task_id AS task_task_id, task_custom.task_id AS task_custom_task_id, task.type AS task_type 
    FROM task LEFT OUTER JOIN task_custom ON task.task_id = task_custom.task_id ORDER BY task.oid
    2007-12-05 10:08:35,671 INFO sqlalchemy.engine.base.Engine.0x..70 []
    <__main__.Task object at 0xc14930>
    <__main__.CustomTask object at 0xc0aa90>
    <__main__.MyTask object at 0xc14a30>
    
  2. Former user Account Deleted

    Ah, thank you.

    The bit I was missing was the task.join(custom_task). Apologies for the spurious ticket.

  3. Log in to comment