Passive deletes in SQLAlchemy with a many-to-many relationship don't prevent DELETE from being issued for related object

StackOverflow https://stackoverflow.com/questions/23669198

  •  23-07-2023
  •  | 
  •  

Вопрос

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.

Это было полезно?

Решение

Here, the “related object” is not being deleted. That would be “tags”. The blog_tags_table is not an object, it is a many-to-many table. Right now the passive_deletes='all' option is not supported for many-to-many, that is, a relationship that includes "secondary". This would be an acceptable feature add but would require development and testing efforts.

Applying viewonly=True to the relationship() would prevent any changes from affecting the many-to-many table. If the blog_tags_table is otherwise special, then you'd want to use the association object pattern to have finer grained control.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top