backrefs for polymorphic multi table inheritance doesn't work for relations between children

Issue #259 resolved
Former user created an issue

Given a hierarchy of: * Parent * Child1 * Child2

Where Child1 has a reference to Child2, creating Child1 with a reference to Child2 doesn't work (Child1 assumes it's primary key is that of Child2). When Child1 is created with NULL reference to Child2, correct primary key is created.

import sqlalchemy as sql

engine = sql.create_engine("sqlite://")
engine.echo=True
meta = sql.BoundMetaData(engine)

devices = sql.Table("devs", meta,
    sql.Column("dev_id", sql.types.Integer, primary_key=True),
    sql.Column("type", sql.types.String, nullable=False),
    sql.Column("name", sql.types.String, nullable=False)
)

r_groups = sql.Table("r_groups", meta,
    sql.Column("rg_id", sql.types.Integer, sql.ForeignKey("devs.dev_id"), primary_key=True)
)

routers = sql.Table("routers", meta,
    sql.Column("r_id", sql.types.Integer, sql.ForeignKey("devs.dev_id"), primary_key=True),
    sql.Column("group_id", sql.types.Integer, sql.ForeignKey("r_groups.rg_id")),
    sql.Column("rdata", sql.types.String)
)

class Device(object):
    pass

class RouterGroup(Device):
    pass

class Router(Device):
    pass

dev_join = sql.polymorphic_union(
    {
        "router": devices.join(routers),
        "r_group": devices.join(r_groups),
        "device": devices.select(devices.c.type == "device")
    }, None, "dev_pjoin"
)

device_mapper = sql.mapper(Device, devices,
    select_table=dev_join,
    polymorphic_on=dev_join.c.type,
    polymorphic_identity="device")
sql.mapper(RouterGroup, r_groups,
    inherits=device_mapper,
    polymorphic_identity="r_group")

sql.mapper(Router, routers,
    inherits=device_mapper,
    properties = {
        "group": sql.relation(RouterGroup, backref="routers") #doesn't work
#        "group": sql.relation(RouterGroup)
    },
    polymorphic_identity="router")


meta.drop_all()
meta.create_all()

session = sql.create_session(engine)

rg = RouterGroup()
rg.name = "rg1"
session.save(rg)

r1 = Router()
r1.name = "router1"
session.save(r1)

r2 = Router()
r2.name = "router2"
r2.group = rg
session.save(r2)

session.flush()

Comments (1)

  1. Mike Bayer repo owner

    ive committed a better check for the condition here in changeset:1759, changeset:1760. The error message your program will generate is now:

    sqlalchemy.exceptions.ArgumentError: Error determining primary and/or secondary join for relationship 'group' between mappers 'Mapper|Router|routers' and 'Mapper|RouterGroup|r_groups'. You should specify the 'primaryjoin' (and 'secondaryjoin', if there is an association table present) keyword arguments to the relation() function (or for backrefs, by specifying the backref using the backref() function with keyword arguments) to explicitly specify the join conditions. Nested error is "Cant determine join between 'routers' and 'Join object on devs r_groups'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly."

    and your fix should now be one of:

            "group": sql.relation(RouterGroup, primaryjoin=routers.c.group_id==r_groups.c.rg_id, backref="routers")
    

    or more verbosely:

            "group": sql.relation(RouterGroup, primaryjoin=routers.c.group_id==r_groups.c.rg_id, backref=sql.backref("routers", primaryjoin=routers.c.group_id==r_groups.c.rg_id))
    
  2. Log in to comment