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")
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):
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
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
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]))
# 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)
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)