]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Fixed bug where many-to-many relationship with uselist=False
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 21 Apr 2013 15:31:29 +0000 (11:31 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 21 Apr 2013 15:31:29 +0000 (11:31 -0400)
would fail to delete the association row and raise an error
if the scalar attribute were set to None.  Also in 0.7.11.
[ticket:2710]

doc/build/changelog/changelog_07.rst
doc/build/changelog/changelog_08.rst
lib/sqlalchemy/orm/dependency.py
test/orm/test_manytomany.py

index c650e769f5657718f62018d8b0b4cd12a4950517..08f5cd94c62b7ec17e39340590896ace443705b1 100644 (file)
@@ -6,6 +6,14 @@
 .. changelog::
     :version: 0.7.11
 
+    .. change::
+      :tags: bug, orm
+      :tickets: 2710
+
+      Fixed bug where many-to-many relationship with uselist=False
+      would fail to delete the association row and raise an error
+      if the scalar attribute were set to None.
+
     .. change::
       :tags: bug, orm
       :tickets: 2699
index 2a2925726c3a23f90f0bc43dd351cd79f9642514..eab1c2f9e2e34941d6c4dd37238939906e796fe2 100644 (file)
@@ -6,6 +6,14 @@
 .. changelog::
     :version: 0.8.1
 
+    .. change::
+      :tags: bug, orm
+      :tickets: 2710
+
+      Fixed bug where many-to-many relationship with uselist=False
+      would fail to delete the association row and raise an error
+      if the scalar attribute were set to None.  Also in 0.7.11.
+
     .. change::
       :tags: bug, orm
       :tickets: 2708
index 8236e6944791e3a9c33fa061bd6c34e4cb376dfd..7bb1d97369bf0dc4c880f5e80761def51f8bb457 100644 (file)
@@ -863,7 +863,7 @@ class DetectKeySwitch(DependencyProcessor):
                 related = state.get_impl(self.key).get(state, dict_,
                         passive=self._passive_update_flag)
                 if related is not attributes.PASSIVE_NO_RESULT and \
-                    related is not None:
+                        related is not None:
                     related_state = attributes.instance_state(dict_[self.key])
                     if related_state in switchers:
                         uowcommit.register_object(state,
@@ -1127,11 +1127,15 @@ class ManyToManyDP(DependencyProcessor):
 
     def _synchronize(self, state, child, associationrow,
                                             clearkeys, uowcommit, operation):
-        if associationrow is None:
-            return
 
+        # this checks for None if uselist=True
         self._verify_canload(child)
 
+        # but if uselist=False we get here.   If child is None,
+        # no association row can be generated, so return.
+        if child is None:
+            return False
+
         if child is not None and not uowcommit.session._contains_state(child):
             if not child.deleted:
                 util.warn(
index d3609b4633e78d23e6d62f933c24cfc3d44a06e7..3135258930144399df0af6fe6b391b21e8be0954 100644 (file)
@@ -316,8 +316,10 @@ class AssortedPersistenceTests(fixtures.MappedTest):
             )
 
         Table('secondary', metadata,
-            Column('left_id', Integer, ForeignKey('left.id')),
-            Column('right_id', Integer, ForeignKey('right.id')),
+            Column('left_id', Integer, ForeignKey('left.id'),
+                                        primary_key=True),
+            Column('right_id', Integer, ForeignKey('right.id'),
+                                        primary_key=True),
             )
 
     @classmethod
@@ -337,6 +339,17 @@ class AssortedPersistenceTests(fixtures.MappedTest):
         })
         mapper(B, right)
 
+    def _bidirectional_onescalar_fixture(self):
+        left, secondary, right = self.tables.left, \
+                    self.tables.secondary, self.tables.right
+        A, B = self.classes.A, self.classes.B
+        mapper(A, left, properties={
+            'bs': relationship(B, secondary=secondary,
+                            backref=backref('a', uselist=False),
+                            order_by=right.c.id)
+        })
+        mapper(B, right)
+
     def test_session_delete(self):
         self._standard_bidirectional_fixture()
         A, B = self.classes.A, self.classes.B
@@ -359,3 +372,25 @@ class AssortedPersistenceTests(fixtures.MappedTest):
         sess.flush()
         eq_(sess.query(secondary).count(), 0)
 
+    def test_remove_scalar(self):
+        # test setting a uselist=False to None
+        self._bidirectional_onescalar_fixture()
+        A, B = self.classes.A, self.classes.B
+        secondary = self.tables.secondary
+
+        sess = Session()
+        sess.add_all([
+            A(data='a1', bs=[B(data='b1'), B(data='b2')]),
+        ])
+        sess.commit()
+
+        a1 = sess.query(A).filter_by(data='a1').one()
+        b2 = sess.query(B).filter_by(data='b2').one()
+        assert b2.a is a1
+
+        b2.a = None
+        sess.commit()
+
+        eq_(a1.bs, [B(data='b1')])
+        eq_(b2.a, None)
+        eq_(sess.query(secondary).count(), 1)