- expire/fetch strategies are now default for Query.update/Query.delete.

- added API docs for Query.update/Query.delete
This commit is contained in:
Ants Aasma
2008-08-25 00:04:01 +00:00
parent 24ee97c610
commit dcad710de2
3 changed files with 72 additions and 9 deletions
+3
View File
@@ -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,
+64 -4
View 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):
+5 -5
View 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)