I am trying to get SQLAlchemy to let my database's foreign keys "on delete cascade" do the cleanup on the association table between two objects. I have setup the cascade
and passive_delete
options on the relationship as seems appropriate from the docs. However, when a related object is loaded into the collection of a primary object and the primary object is deleted from the session, then SQLAlchemy issues a delete statement on the secondary table for the record relating the primary and secondary objects.
For example:
import logging
import sqlalchemy as sa
import sqlalchemy.ext.declarative as sadec
import sqlalchemy.orm as saorm
engine = sa.create_engine('sqlite:///')
engine.execute('PRAGMA foreign_keys=ON')
logging.basicConfig()
_logger = logging.getLogger('sqlalchemy.engine')
meta = sa.MetaData(bind=engine)
Base = sadec.declarative_base(metadata=meta)
sess = saorm.sessionmaker(bind=engine)
session = sess()
blog_tags_table = sa.Table(
'blog_tag_map',
meta,
sa.Column('blog_id', sa.Integer, sa.ForeignKey('blogs.id', ondelete='cascade')),
sa.Column('tag_id', sa.Integer, sa.ForeignKey('tags.id', ondelete='cascade')),
sa.UniqueConstraint('blog_id', 'tag_id', name='uc_blog_tag_map')
)
class Blog(Base):
__tablename__ = 'blogs'
id = sa.Column(sa.Integer, primary_key=True)
title = sa.Column(sa.String, nullable=False)
tags = saorm.relationship('Tag', secondary=blog_tags_table, passive_deletes=True,
cascade='save-update, merge, refresh-expire, expunge')
class Tag(Base):
__tablename__ = 'tags'
id = sa.Column(sa.Integer, primary_key=True)
label = sa.Column(sa.String, nullable=False)
meta.create_all(bind=engine)
blog = Blog(title='foo')
blog.tags.append(Tag(label='bar'))
session.add(blog)
session.commit()
# sanity check
assert session.query(Blog.id).count() == 1
assert session.query(Tag.id).count() == 1
assert session.query(blog_tags_table).count() == 1
_logger.setLevel(logging.INFO)
session.commit()
# make sure the tag is loaded into the collection
assert blog.tags[0]
session.delete(blog)
session.commit()
_logger.setLevel(logging.WARNING)
# confirm check
assert session.query(Blog.id).count() == 0
assert session.query(Tag.id).count() == 1
assert session.query(blog_tags_table).count() == 0
The above code will produce DELETE statements as follows:
DELETE FROM blog_tag_map WHERE
blog_tag_map.blog_id = ? AND blog_tag_map.tag_id = ?
DELETE FROM blogs WHERE blogs.id = ?
Is there a way to setup the relationship so that no DELETE statement for blog_tag_map is issued? I've also tried setting passive_deletes='all'
with the same results.