Can't set to None relationship property of object being in collection of collections

Issue #3209 duplicate
Denis Otkidach created an issue

Test case:

from sqlalchemy import Column, Integer, String, ForeignKey, create_engine
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base


Base = declarative_base()


class DocLink(Base):
    __tablename__ = 'DocLink'
    id = Column(Integer, primary_key=True)
    block_id = Column(ForeignKey('DocLinkBlock.id', ondelete='CASCADE'),
                      nullable=False)
    block = relationship('DocLinkBlock',
                         primaryjoin='DocLink.block_id==DocLinkBlock.id')
    ref_url = Column(String(250), nullable=True)
    ref_doc_id = Column(ForeignKey('Doc.id'))
    ref_doc = relationship('Doc', primaryjoin='DocLink.ref_doc_id==Doc.id')


class DocLinkBlock(Base):
    __tablename__ = 'DocLinkBlock'
    id = Column(Integer, primary_key=True)
    doc_id = Column(ForeignKey('Doc.id', ondelete='CASCADE'), nullable=False)
    doc = relationship('Doc')
    links = relationship(DocLink, cascade='all, delete-orphan')


class Doc(Base):
    __tablename__ = 'Doc'
    id = Column(Integer, primary_key=True)
    link_blocks = relationship(DocLinkBlock, cascade='all, delete-orphan')


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

ref_doc = Doc(id=1)
session.add(ref_doc)
session.commit()

doc = Doc(
    id=2,
    link_blocks=[
        DocLinkBlock(
            id=1,
            links=[
                DocLink(
                    id=1,
                    ref_url='url',
                    ref_doc=ref_doc,
                ),
            ],
        ),
    ],
)
session.add(doc)
session.commit()
assert doc.link_blocks[0].links[0].ref_url=='url'
assert doc.link_blocks[0].links[0].ref_doc is ref_doc

doc.link_blocks = [
    DocLinkBlock(
        id=1,
        links=[
            DocLink(
                id=1,
                ref_url=None,
                ref_doc=None,
            ),
        ],
    ),
]
session.commit()
assert doc.link_blocks[0].links[0].ref_url is None
assert doc.link_blocks[0].links[0].ref_doc is None # Fails

There is no such problem when DocLinkBlock.links is updated directly (no nested collections).

Comments (8)

  1. Mike Bayer repo owner

    the reason you're hitting these issues is because these patterns rely heavily on an edge case called "row switch". when you have a relationship A->B(1), and then you just stick a totally different B(1) onto A and rely upon delete-cascade to take out the old B(1), the ORM turns the DELETE + INSERT into an UPDATE. You'd be better off avoiding this pattern, and just modifying the existing B(1) instead, either by loading it or using merge(). the patterns here look a lot like trying to avoid using merge().

  2. Mike Bayer repo owner

    it's not really saving you on any SQL here either, the old row is loaded, so merge() would do a get() and just pull it up.

  3. Denis Otkidach reporter

    This code is a simplified from what is generated from DSL for web forms. Fields in forms can be arbitrary nested. Convertion of field in form must not have side effect, so it can't call merge for object (at least untill all other fields are verified).

  4. Log in to comment