mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-06-04 23:06:24 -04:00
- expire/fetch strategies are now default for Query.update/Query.delete.
- added API docs for Query.update/Query.delete
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user