]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- fixed an attribute history bug whereby assigning a new collection
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 7 Jan 2008 18:52:02 +0000 (18:52 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 7 Jan 2008 18:52:02 +0000 (18:52 +0000)
to a collection-based attribute which already had pending changes
would generate incorrect history [ticket:922]

- fixed delete-orphan cascade bug whereby setting the same
object twice to a scalar attribute could log it as an orphan
[ticket:925]
- generative select.order_by(None) / group_by(None) was not managing to
reset order by/group by criterion, fixed [ticket:924]

CHANGES
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/sql/expression.py
test/dialect/oracle.py
test/orm/attributes.py
test/orm/cascade.py
test/sql/select.py

diff --git a/CHANGES b/CHANGES
index c9922fc34a1c262634cdbc53f967c19c22394c14..539b57a7f8e1970dff924dae95e12d79691210fa 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -10,10 +10,21 @@ CHANGES
       when no length is present is also deprecated until 0.5; will issue a
       warning when used for CREATE TABLE statements (String with no length
       for SQL expression purposes is still fine) [ticket:912]
-
+    
+    - generative select.order_by(None) / group_by(None) was not managing to 
+      reset order by/group by criterion, fixed [ticket:924]
+      
 - orm
     - suppressing *all* errors in InstanceState.__cleanup() now.  
-    
+
+    - fixed an attribute history bug whereby assigning a new collection
+      to a collection-based attribute which already had pending changes
+      would generate incorrect history [ticket:922]
+      
+    - fixed delete-orphan cascade bug whereby setting the same
+      object twice to a scalar attribute could log it as an orphan
+      [ticket:925]
+      
     - Fixed cascades on a += assignment to a list-based relation.
     
     - synonyms can now be created against props that don't exist yet,
index a95ae0500230cbe694d8a6b21f7cb50571c0ab99..84c9dfbb635bd7bff07cf68c67a9907a3d9b28f1 100644 (file)
@@ -251,8 +251,7 @@ class AttributeImpl(object):
     def set_committed_value(self, state, value):
         """set an attribute value on the given instance and 'commit' it."""
 
-        if state.committed_state is not None:
-            state.commit_attr(self, value)
+        state.commit_attr(self, value)
         # remove per-instance callable, if any
         state.callables.pop(self.key, None)
         state.dict[self.key] = value
@@ -395,7 +394,7 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
         if self.trackparent:
             if value is not None:
                 self.sethasparent(value._state, True)
-            if previous is not None:
+            if previous is not value and previous is not None:
                 self.sethasparent(previous._state, False)
 
         instance = state.obj()
@@ -544,7 +543,8 @@ class CollectionAttributeImpl(AttributeImpl):
         if old is value:
             return
 
-        state.committed_state[self.key] = self.copy(old)
+        if self.key not in state.committed_state:
+            state.committed_state[self.key] = self.copy(old)
 
         old_collection = self.get_collection(state, old)
 
index b3b04b678ce0dfac1cc84f6c8d706fba7816662e..3dc7242d7ae50deeec0840dcf96773716fea6ac6 100644 (file)
@@ -2815,7 +2815,7 @@ class _SelectBaseMixin(object):
         form of this method instead to prevent this issue.
         """
 
-        if clauses == [None]:
+        if len(clauses) == 1 and clauses[0] is None:
             self._order_by_clause = ClauseList()
         else:
             if getattr(self, '_order_by_clause', None):
@@ -2833,7 +2833,7 @@ class _SelectBaseMixin(object):
         form of this method instead to prevent this issue.
         """
 
-        if clauses == [None]:
+        if len(clauses) == 1 and clauses[0] is None:
             self._group_by_clause = ClauseList()
         else:
             if getattr(self, '_group_by_clause', None):
index 64f86329ed5f3002882f46c3f55016edb756c718..cff96c21fb7f5367de2809ec98a3c6a44bff39be 100644 (file)
@@ -59,7 +59,7 @@ class CompileTest(SQLCompileTest):
         s = select([t]).limit(10).offset(20).order_by(t.c.col2)
 
         self.assert_compile(s, "SELECT col1, col2 FROM (SELECT sometable.col1 AS col1, "
-            "sometable.col2 AS col2, ROW_NUMBER() OVER (ORDER BY sometable.col2) AS ora_rn FROM sometable ORDER BY sometable.col2) WHERE ora_rn>20 AND ora_rn<=30")
+            "sometable.col2 AS col2, ROW_NUMBER() OVER (ORDER BY sometable.col2) AS ora_rn FROM sometable) WHERE ora_rn>20 AND ora_rn<=30")
 
     def test_outer_join(self):
         table1 = table('mytable',
index a03123c9d86bfbdea42bd169821d2c88ab786d77..7ec838458cbffb88b88b4a46a86c22b8f7b6a97e 100644 (file)
@@ -337,6 +337,14 @@ class AttributesTest(PersistTest):
         
         b2.element = None
         assert not attributes.has_parent(Bar, f2, 'element')
+        
+        # test that double assignment doesn't accidentally reset the 'parent' flag.
+        b3 = Bar()
+        f4 = Foo()
+        b3.element = f4
+        assert attributes.has_parent(Bar, f4, 'element')
+        b3.element = f4
+        assert attributes.has_parent(Bar, f4, 'element')
 
     def test_mutablescalars(self):
         """test detection of changes on mutable scalar items"""
@@ -450,11 +458,6 @@ class BackrefTest(PersistTest):
         c.students = [s1, s2, s3]
         self.assert_(s2.courses == [c])
         self.assert_(s1.courses == [c])
-        print "--------------------------------"
-        print s1
-        print s1.courses
-        print c
-        print c.students
         s1.courses.remove(c)
         self.assert_(c.students == [s2,s3])        
     
@@ -766,6 +769,12 @@ class HistoryTest(PersistTest):
         f._state.commit(['someattr'])
 
         self.assertEquals(attributes.get_history(f._state, 'someattr'), ([], [there], []))
+        
+        f.someattr = [hi]
+        self.assertEquals(attributes.get_history(f._state, 'someattr'), ([hi], [], [there]))
+
+        f.someattr = [old, new]
+        self.assertEquals(attributes.get_history(f._state, 'someattr'), ([old, new], [], [there]))
 
         # case 2.  object with direct settings (similar to a load operation)
         f = Foo()
@@ -808,8 +817,6 @@ class HistoryTest(PersistTest):
         f._state.commit(['someattr'])
         self.assertEquals(tuple([set(x) for x in attributes.get_history(f._state, 'someattr')]), (set([]), set([hi, there]), set([])))
 
-
-
     def test_object_collections_mutate(self):
         class Foo(fixtures.Base):
             pass
@@ -872,13 +879,21 @@ class HistoryTest(PersistTest):
         collection = attributes.init_collection(f, 'someattr')
         collection.append_without_event(new)
         f._state.commit_all()
-        print f._state.dict
         self.assertEquals(attributes.get_history(f._state, 'someattr'), ([], [new], []))
         
         f.id = 1
         f.someattr.remove(new)
         self.assertEquals(attributes.get_history(f._state, 'someattr'), ([], [], [new]))
         
+        # case 3.  mixing appends with sets
+        f = Foo()
+        f.someattr.append(hi)
+        self.assertEquals(attributes.get_history(f._state, 'someattr'), ([hi], [], []))
+        f.someattr.append(there)
+        self.assertEquals(attributes.get_history(f._state, 'someattr'), ([hi, there], [], []))
+        f.someattr = [there]
+        self.assertEquals(attributes.get_history(f._state, 'someattr'), ([there], [], []))
+        
     def test_collections_via_backref(self):
         class Foo(fixtures.Base):
             pass
index 7583507b2cdbf3763b43115da3a9f47612c1a917..8b416fafb70b4c4c9b985300ad6fd0091f0f569c 100644 (file)
@@ -263,6 +263,15 @@ class M2OCascadeTest(AssertMixin):
         assert p not in ctx.current
         assert e not in ctx.current
 
+    def testorphan3(self):
+        """test that double assignment doesn't accidentally reset the 'parent' flag."""
+        
+        jack = ctx.current.query(User).get_by(user_name='jack')
+        newpref = Pref("newpref")
+        jack.pref = newpref
+        jack.pref = newpref
+        ctx.current.flush()
+        
 
 
 class M2MCascadeTest(AssertMixin):
index a5d45a6b8b49967e8b0c115eb72893654b230903..0df575c7c0e85bcf1f3c88aac96129a52c94551f 100644 (file)
@@ -487,6 +487,17 @@ sq.myothertable_othername AS sq_myothertable_othername FROM (" + sqstring + ") A
             table2.select(order_by = [table2.c.otherid, table2.c.othername.desc()]),
             "SELECT myothertable.otherid, myothertable.othername FROM myothertable ORDER BY myothertable.otherid, myothertable.othername DESC"
         )
+        
+        # generative order_by
+        self.assert_compile(
+            table2.select().order_by(table2.c.otherid).order_by(table2.c.othername.desc()), 
+            "SELECT myothertable.otherid, myothertable.othername FROM myothertable ORDER BY myothertable.otherid, myothertable.othername DESC"
+        )
+
+        self.assert_compile(
+            table2.select().order_by(table2.c.otherid).order_by(table2.c.othername.desc()).order_by(None), 
+            "SELECT myothertable.otherid, myothertable.othername FROM myothertable"
+        )
 
     def testgroupby(self):
         self.assert_compile(
@@ -494,6 +505,17 @@ sq.myothertable_othername AS sq_myothertable_othername FROM (" + sqstring + ") A
             "SELECT myothertable.othername, count(myothertable.otherid) AS count_1 FROM myothertable GROUP BY myothertable.othername"
         )
 
+        # generative group by
+        self.assert_compile(
+            select([table2.c.othername, func.count(table2.c.otherid)]).group_by(table2.c.othername),
+            "SELECT myothertable.othername, count(myothertable.otherid) AS count_1 FROM myothertable GROUP BY myothertable.othername"
+        )
+
+        self.assert_compile(
+            select([table2.c.othername, func.count(table2.c.otherid)]).group_by(table2.c.othername).group_by(None),
+            "SELECT myothertable.othername, count(myothertable.otherid) AS count_1 FROM myothertable"
+        )
+        
 
     def testgroupby_and_orderby(self):
         self.assert_compile(