zzzeek / sqla_issue_test

1 stars 0 forks source link

many-to-many relations dont delete/do anything properly if the is_backref flag is on #249

Closed zzzeek closed 6 years ago

zzzeek commented 17 years ago

Originally reported by: Joona Kulmala (Bitbucket: joona, GitHub: joona)


foo_baa = Table('foo_baa', activemapper.metadata,
            Column('foo_id', Integer, ForeignKey('foo.id'), primary_key=True),
            Column('baa_id', Integer, ForeignKey('baa.id'), primary_key=True))

class Foo(ActiveMapper):
    class mapping:
        name = column(String)
        baas = many_to_many('Baa', foo_baa, backref='foos')

    def __str__(self):
        return "<Foo %r>" % self.name

class Baa(ActiveMapper):
    class mapping:
        name = column(String)

    def __str__(self):
        return "<Baa %r>" % self.name

>>> for x in ('First', 'Second'):
...     Foo(name='Foo%s' % x)
...     Baa(name='Baa%s' % x)
>>> objectstore.flush()

[00:18:37,643](2006-07-21) [engine](engine): BEGIN
[00:18:37,741](2006-07-21) [engine](engine): INSERT INTO baa (name) VALUES (%s)
[00:18:37,743](2006-07-21) [engine](engine): ['BaaFirst']('BaaFirst')
[00:18:37,790](2006-07-21) [engine](engine): INSERT INTO baa (name) VALUES (%s)
[00:18:37,807](2006-07-21) [engine](engine): ['BaaSecond']('BaaSecond')
[00:18:37,839](2006-07-21) [engine](engine): INSERT INTO foo (name) VALUES (%s)
[00:18:37,840](2006-07-21) [engine](engine): ['FooFirst']('FooFirst')
[00:18:37,872](2006-07-21) [engine](engine): INSERT INTO foo (name) VALUES (%s)
[00:18:37,874](2006-07-21) [engine](engine): ['FooSecond']('FooSecond')
[00:18:37,902](2006-07-21) [engine](engine): COMMIT

>>> # get Foo object with id 1 and add relation to <Baa 'BaaFirst'> and flush
>>> obj = Foo.get(1)
>>> print obj.baas
[obj.baas.append(Baa.get(1))
>>> obj.flush()

[2006-07-21 00:18:38,027](]
>>>) [engine](engine): BEGIN
[00:18:38,031](2006-07-21) [engine](engine): INSERT INTO foo_baa (foo_id, baa_id) VALUES
(%s, %s)
[00:18:38,034](2006-07-21) [engine](engine): [1L](1L,)
[00:18:38,061](2006-07-21) [engine](engine): COMMIT

>>> print obj.baas
[object at 0x55de50>](<__main__.Baa)

>>> # get Baa object with id 1 (<Baa 'BaaFirst'>) and add relation to <Foo 'FooSecond'> 
>>> # from Baa and flush
>>> obj = Baa.get(1)
>>> print obj.foos
[object at 0x5220b0>](<__main__.Foo)
>>> obj.foos.append(Foo.get(2))
>>> obj.flush()

[00:18:38,123](2006-07-21) [engine](engine): BEGIN
[00:18:38,127](2006-07-21) [engine](engine): COMMIT

In my opinion obj.flush() should also commit changes between it's ManyToMany-relations. Now it works only from "active" side of relation (Foo in this example). objectstore.flush() would obviously do the trick, but that is something that id wouldn't like to do at my Web Framework's controller. obj.save() or obj.flush() would be much more logical.


zzzeek commented 10 years ago

Original comment by Michael Bayer (Bitbucket: zzzeek, GitHub: zzzeek):


Removing milestone: 0.3.6 (automated comment)

zzzeek commented 17 years ago

Original comment by Michael Bayer (Bitbucket: zzzeek, GitHub: zzzeek):


changeset:2431

zzzeek commented 17 years ago

Original comment by Michael Bayer (Bitbucket: zzzeek, GitHub: zzzeek):


another failing test:

from sqlalchemy import *

metadata = BoundMetaData('sqlite://')
studentTbl = Table('student', metadata, Column('name', String(20), primary_key=True))
courseTbl = Table('course', metadata, Column('name', String(20), primary_key=True))
enrolTbl = Table('enrol', metadata,
    Column('student_id', String(20), ForeignKey('student.name'),primary_key=True),
    Column('course_id', String(20), ForeignKey('course.name'), primary_key=True))

metadata.create_all()

class Student(object):
    def __init__(self, name=''):
        self.name = name
class Course(object):
    def __init__(self, name=''):
        self.name = name

Student.mapper = mapper(Student, studentTbl)
Course.mapper = mapper(Course, courseTbl, properties = {
    'students': relation(Student.mapper, enrolTbl, lazy=True, backref='courses')
})

sess = create_session()
s1 = Student('Student1')
c1 = Course('Course1')
c2 = Course('Course2')
c3 = Course('Course3')
s1.courses.append(c1)
s1.courses.append(c2)
c3.students.append(s1)
sess.save(s1)
sess.flush()
sess.delete(s1)
sess.flush()
assert enrolTbl.count().execute() == 0
zzzeek commented 17 years ago

Original comment by Michael Bayer (Bitbucket: zzzeek, GitHub: zzzeek):


in case you feel like playing with this, line 248 of sqlalchemy/orm/dependency.py is exactly where this happens :

 if self.is_backref:
            # if we are the "backref" half of a two-way backref 
            # relationship, let the other mapper handle inserting the rows
            return

the solution involves the two corresponding DependencyProcessors being aware of each other, and either coexisting within the UOWTransaction and somehow insuring that only one of them does the many-to-many update of the rows, or checking if the other one is not present and then placing itself in the UOWTransaction.