From dcad710de2cff2ceeef18dbd06eb4b263b8c39ad Mon Sep 17 00:00:00 2001 From: Ants Aasma Date: Mon, 25 Aug 2008 00:04:01 +0000 Subject: [PATCH] - expire/fetch strategies are now default for Query.update/Query.delete. - added API docs for Query.update/Query.delete --- CHANGES | 3 ++ lib/sqlalchemy/orm/query.py | 68 ++++++++++++++++++++++++++++++++++--- test/orm/query.py | 10 +++--- 3 files changed, 72 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 79ed4436e5..be0d9aab04 100644 --- 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, diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 4cd6ec877e..c1030aaf74 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -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): diff --git a/test/orm/query.py b/test/orm/query.py index d3e69573dd..8f0148e75c 100644 --- a/test/orm/query.py +++ b/test/orm/query.py @@ -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) -- 2.47.3