]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- expire/fetch strategies are now default for Query.update/Query.delete.
authorAnts Aasma <ants.aasma@gmail.com>
Mon, 25 Aug 2008 00:04:01 +0000 (00:04 +0000)
committerAnts Aasma <ants.aasma@gmail.com>
Mon, 25 Aug 2008 00:04:01 +0000 (00:04 +0000)
- added API docs for Query.update/Query.delete

CHANGES
lib/sqlalchemy/orm/query.py
test/orm/query.py

diff --git a/CHANGES b/CHANGES
index 79ed4436e5f56357eb1146f097d7c823652c1202..be0d9aab04ac1e4ddfcdd54ca019ae51c8803ce6 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -8,6 +8,9 @@ CHANGES
 ========
 
 - orm
+    - Query now has delete() and update(values) methods. This allows
+      to perform bulk deletes/updates with the Query object.
+
     - The RowTuple object returned by Query(*cols) now features
       keynames which prefer mapped attribute names over column keys,
       column keys over column names, i.e.  Query(Class.foo,
index 4cd6ec877ea304e7714aa363aa4f498ca80d6cf2..c1030aaf74563796b41e7ac8253ce9f41c38d9ae 100644 (file)
@@ -1226,9 +1226,40 @@ class Query(object):
             self.session._autoflush()
         return self.session.scalar(s, params=self._params, mapper=self._mapper_zero())
 
-    def delete(self, synchronize_session='evaluate'):
-        """EXPERIMENTAL"""
+    def delete(self, synchronize_session='fetch'):
+        """Perform a bulk delete query.
+
+        Deletes rows matched by this query from the database. The synchronize_session
+        parameter chooses the strategy for the removal of matched objects from the
+        session. Valid values are:
+
+        False
+          don't synchronize the session. Use this when you don't need to use the
+          session after the delete or you can be sure that none of the matched objects
+          are in the session. The behavior of deleted objects still in the session is
+          undefined.
+
+        'fetch'
+          performs a select query before the delete to find objects that are matched
+          by the delete query and need to be removed from the session. Matched objects
+          are removed from the session. 'fetch' is the default strategy.
+
+        'evaluate'
+          experimental feature. Tries to evaluate the querys criteria in Python
+          straight on the objects in the session. If evaluation of the criteria isn't
+          implemented, the 'fetch' strategy will be used as a fallback.
+
+          The expression evaluator currently doesn't account for differing string
+          collations between the database and Python.
+
+        Warning - this currently doesn't account for any foreign key/relation cascades.
+        """
         #TODO: lots of duplication and ifs - probably needs to be refactored to strategies
+        #TODO: cascades need handling.
+
+        if synchronize_session not in [False, 'evaluate', 'fetch']:
+            raise sa_exc.ArgumentError("Valid strategies for session synchronization are False, 'evaluate' and 'fetch'")
+
         context = self._compile_context()
         if len(context.statement.froms) != 1 or not isinstance(context.statement.froms[0], schema.Table):
             raise sa_exc.ArgumentError("Only deletion via a single table query is currently supported")
@@ -1269,11 +1300,40 @@ class Query(object):
                 if identity_key in session.identity_map:
                     session._remove_newly_deleted(attributes.instance_state(session.identity_map[identity_key]))
 
-    def update(self, values, synchronize_session='evaluate'):
-        """EXPERIMENTAL"""
+    def update(self, values, synchronize_session='expire'):
+        """Perform a bulk update query.
+
+        Updates rows matched by this query in the database. The values parameter takes
+        a dictionary with object attributes as keys and literal values or sql expressions
+        as values. The synchronize_session parameter chooses the strategy to update the
+        attributes on objects in the session. Valid values are:
+
+        False
+          don't synchronize the session. Use this when you don't need to use the
+          session after the update or you can be sure that none of the matched objects
+          are in the session.
+
+        'expire'
+          performs a select query before the update to find objects that are matched
+          by the update query. The updated attributes are expired on matched objects.
+
+        'evaluate'
+          experimental feature. Tries to evaluate the querys criteria in Python
+          straight on the objects in the session. If evaluation of the criteria isn't
+          implemented, the 'expire' strategy will be used as a fallback.
+
+          The expression evaluator currently doesn't account for differing string
+          collations between the database and Python.
+
+        Warning - this currently doesn't account for any foreign key/relation cascades.
+        """
 
         #TODO: value keys need to be mapped to corresponding sql cols and instr.attr.s to string keys
         #TODO: updates of manytoone relations need to be converted to fk assignments
+        #TODO: cascades need handling.
+
+        if synchronize_session not in [False, 'evaluate', 'expire']:
+            raise sa_exc.ArgumentError("Valid strategies for session synchronization are False, 'evaluate' and 'expire'")
 
         context = self._compile_context()
         if len(context.statement.froms) != 1 or not isinstance(context.statement.froms[0], schema.Table):
index d3e69573ddf0fe74d83925f386243a84c69c0143..8f0148e75cc8b78c2ab517d1fc7e445334aa9509 100644 (file)
@@ -2237,7 +2237,7 @@ class UpdateDeleteTest(_base.MappedTest):
     def test_delete_rollback(self):
         sess = sessionmaker()()
         john,jack,jill,jane = sess.query(User).order_by(User.id).all()
-        sess.query(User).filter(or_(User.name == 'john', User.name == 'jill')).delete()
+        sess.query(User).filter(or_(User.name == 'john', User.name == 'jill')).delete(synchronize_session='evaluate')
         assert john not in sess and jill not in sess
         sess.rollback()
         assert john in sess and jill in sess
@@ -2279,7 +2279,7 @@ class UpdateDeleteTest(_base.MappedTest):
         sess = create_session(bind=testing.db, autocommit=False)
         
         john,jack,jill,jane = sess.query(User).order_by(User.id).all()
-        sess.query(User).filter(User.name == select([func.max(User.name)])).delete()
+        sess.query(User).filter(User.name == select([func.max(User.name)])).delete(synchronize_session='evaluate')
         
         assert john not in sess
         
@@ -2290,7 +2290,7 @@ class UpdateDeleteTest(_base.MappedTest):
         sess = create_session(bind=testing.db, autocommit=False)
         
         john,jack,jill,jane = sess.query(User).order_by(User.id).all()
-        sess.query(User).filter(User.age > 29).update({'age': User.age - 10})
+        sess.query(User).filter(User.age > 29).update({'age': User.age - 10}, synchronize_session='evaluate')
         
         eq_([john.age, jack.age, jill.age, jane.age], [25,37,29,27])
         eq_(sess.query(User.age).order_by(User.id).all(), zip([25,37,29,27]))
@@ -2306,7 +2306,7 @@ class UpdateDeleteTest(_base.MappedTest):
         
         # autoflush is false.  therefore our '50' and '37' are getting blown away by this operation.
         
-        sess.query(User).filter(User.age > 29).update({'age': User.age - 10})
+        sess.query(User).filter(User.age > 29).update({'age': User.age - 10}, synchronize_session='evaluate')
 
         for x in (john, jack, jill, jane):
             assert not sess.is_modified(x)
@@ -2329,7 +2329,7 @@ class UpdateDeleteTest(_base.MappedTest):
         john.age = 50
         jack.age = 37
 
-        sess.query(User).filter(User.age > 29).update({'age': User.age - 10})
+        sess.query(User).filter(User.age > 29).update({'age': User.age - 10}, synchronize_session='evaluate')
 
         for x in (john, jack, jill, jane):
             assert not sess.is_modified(x)