when query.update(), query.delete() are called.
The retrieval of records is done after autoflush
in all cases, and before update/delete is
emitted, guarding against unflushed data present
as well as expired objects failing during
the evaluation. [ticket:2122]
implementation/behavior are present.
- orm
+ - Some fixes to "evaulate" and "fetch" evaluation
+ when query.update(), query.delete() are called.
+ The retrieval of records is done after autoflush
+ in all cases, and before update/delete is
+ emitted, guarding against unflushed data present
+ as well as expired objects failing during
+ the evaluation. [ticket:2122]
+
- Reworded the exception raised when a flush
is attempted of a subclass that is not polymorphic
against the supertype. [ticket:2063]
session = self.session
+ if self._autoflush:
+ session._autoflush()
+
if synchronize_session == 'evaluate':
try:
evaluator_compiler = evaluator.EvaluatorCompiler()
"Specify 'fetch' or False for the synchronize_session "
"parameter.")
- delete_stmt = sql.delete(primary_table, context.whereclause)
+ target_cls = self._mapper_zero().class_
+
+ #TODO: detect when the where clause is a trivial primary key match
+ objs_to_expunge = [
+ obj for (cls, pk),obj in
+ session.identity_map.iteritems()
+ if issubclass(cls, target_cls) and
+ eval_condition(obj)]
- if synchronize_session == 'fetch':
+ elif synchronize_session == 'fetch':
#TODO: use RETURNING when available
select_stmt = context.statement.with_only_columns(
primary_table.primary_key)
select_stmt,
params=self._params).fetchall()
- if self._autoflush:
- session._autoflush()
+ delete_stmt = sql.delete(primary_table, context.whereclause)
+
result = session.execute(delete_stmt, params=self._params)
if synchronize_session == 'evaluate':
- target_cls = self._mapper_zero().class_
-
- #TODO: detect when the where clause is a trivial primary key match
- objs_to_expunge = [
- obj for (cls, pk),obj in
- session.identity_map.iteritems()
- if issubclass(cls, target_cls) and
- eval_condition(obj)]
for obj in objs_to_expunge:
session._remove_newly_deleted(attributes.instance_state(obj))
elif synchronize_session == 'fetch':
session = self.session
+ if self._autoflush:
+ session._autoflush()
+
if synchronize_session == 'evaluate':
try:
evaluator_compiler = evaluator.EvaluatorCompiler()
"Could not evaluate current criteria in Python. "
"Specify 'fetch' or False for the "
"synchronize_session parameter.")
+ target_cls = self._mapper_zero().class_
+ matched_objects = []
+ for (cls, pk),obj in session.identity_map.iteritems():
+ evaluated_keys = value_evaluators.keys()
- update_stmt = sql.update(primary_table, context.whereclause, values)
+ if issubclass(cls, target_cls) and eval_condition(obj):
+ matched_objects.append(obj)
- if synchronize_session == 'fetch':
+ elif synchronize_session == 'fetch':
select_stmt = context.statement.with_only_columns(
primary_table.primary_key)
matched_rows = session.execute(
select_stmt,
params=self._params).fetchall()
- if self._autoflush:
- session._autoflush()
+ update_stmt = sql.update(primary_table, context.whereclause, values)
+
result = session.execute(update_stmt, params=self._params)
if synchronize_session == 'evaluate':
target_cls = self._mapper_zero().class_
- for (cls, pk),obj in session.identity_map.iteritems():
- evaluated_keys = value_evaluators.keys()
+ for obj in matched_objects:
+ state, dict_ = attributes.instance_state(obj),\
+ attributes.instance_dict(obj)
- if issubclass(cls, target_cls) and eval_condition(obj):
- state, dict_ = attributes.instance_state(obj),\
- attributes.instance_dict(obj)
-
- # only evaluate unmodified attributes
- to_evaluate = state.unmodified.intersection(
- evaluated_keys)
- for key in to_evaluate:
- dict_[key] = value_evaluators[key](obj)
-
- state.commit(dict_, list(to_evaluate))
-
- # expire attributes with pending changes
- # (there was no autoflush, so they are overwritten)
- state.expire_attributes(dict_,
- set(evaluated_keys).
- difference(to_evaluate))
+ # only evaluate unmodified attributes
+ to_evaluate = state.unmodified.intersection(
+ evaluated_keys)
+ for key in to_evaluate:
+ dict_[key] = value_evaluators[key](obj)
+
+ state.commit(dict_, list(to_evaluate))
+
+ # expire attributes with pending changes
+ # (there was no autoflush, so they are overwritten)
+ state.expire_attributes(dict_,
+ set(evaluated_keys).
+ difference(to_evaluate))
elif synchronize_session == 'fetch':
target_mapper = self._mapper_zero()
sess.bind = testing.db
eq_(sess.query().value(sa.literal_column('1').label('x')), 1)
-class UpdateDeleteTest(fixtures.MappedTest):
- @classmethod
- def define_tables(cls, metadata):
- Table('users', metadata,
- Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
- Column('name', String(32)),
- Column('age', Integer))
-
- Table('documents', metadata,
- Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
- Column('user_id', None, ForeignKey('users.id')),
- Column('title', String(32)))
-
- @classmethod
- def setup_classes(cls):
- class User(cls.Comparable):
- pass
-
- class Document(cls.Comparable):
- pass
-
- @classmethod
- def insert_data(cls):
- users = cls.tables.users
-
- users.insert().execute([
- dict(id=1, name='john', age=25),
- dict(id=2, name='jack', age=47),
- dict(id=3, name='jill', age=29),
- dict(id=4, name='jane', age=37),
- ])
-
- def insert_documents(self):
- documents = self.tables.documents
-
- documents.insert().execute([
- dict(id=1, user_id=1, title='foo'),
- dict(id=2, user_id=1, title='bar'),
- dict(id=3, user_id=2, title='baz'),
- ])
-
- @classmethod
- def setup_mappers(cls):
- documents, Document, User, users = (cls.tables.documents,
- cls.classes.Document,
- cls.classes.User,
- cls.tables.users)
-
- mapper(User, users)
- mapper(Document, documents, properties={
- 'user': relationship(User, lazy='joined',
- backref=backref('documents', lazy='select'))
- })
-
- def test_illegal_operations(self):
- User = self.classes.User
-
- s = create_session()
-
- for q, mname in (
- (s.query(User).limit(2), "limit"),
- (s.query(User).offset(2), "offset"),
- (s.query(User).limit(2).offset(2), "limit"),
- (s.query(User).order_by(User.id), "order_by"),
- (s.query(User).group_by(User.id), "group_by"),
- (s.query(User).distinct(), "distinct")
- ):
- assert_raises_message(sa_exc.InvalidRequestError, r"Can't call Query.update\(\) when %s\(\) has been called" % mname, q.update, {'name':'ed'})
- assert_raises_message(sa_exc.InvalidRequestError, r"Can't call Query.delete\(\) when %s\(\) has been called" % mname, q.delete)
-
-
- def test_delete(self):
- User = self.classes.User
-
- sess = create_session(bind=testing.db, autocommit=False)
-
- john,jack,jill,jane = sess.query(User).order_by(User.id).all()
- sess.query(User).filter(or_(User.name == 'john', User.name == 'jill')).delete()
-
- assert john not in sess and jill not in sess
-
- eq_(sess.query(User).order_by(User.id).all(), [jack,jane])
-
- def test_delete_with_bindparams(self):
- User = self.classes.User
-
- sess = create_session(bind=testing.db, autocommit=False)
-
- john,jack,jill,jane = sess.query(User).order_by(User.id).all()
- sess.query(User).filter('name = :name').params(name='john').delete('fetch')
- assert john not in sess
-
- eq_(sess.query(User).order_by(User.id).all(), [jack,jill,jane])
-
- def test_delete_rollback(self):
- User = self.classes.User
-
- 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(synchronize_session='evaluate')
- assert john not in sess and jill not in sess
- sess.rollback()
- assert john in sess and jill in sess
-
- def test_delete_rollback_with_fetch(self):
- User = self.classes.User
-
- 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(synchronize_session='fetch')
- assert john not in sess and jill not in sess
- sess.rollback()
- assert john in sess and jill in sess
-
- def test_delete_without_session_sync(self):
- User = self.classes.User
-
- sess = create_session(bind=testing.db, autocommit=False)
-
- john,jack,jill,jane = sess.query(User).order_by(User.id).all()
- sess.query(User).filter(or_(User.name == 'john', User.name == 'jill')).delete(synchronize_session=False)
-
- assert john in sess and jill in sess
-
- eq_(sess.query(User).order_by(User.id).all(), [jack,jane])
-
- def test_delete_with_fetch_strategy(self):
- User = self.classes.User
-
- sess = create_session(bind=testing.db, autocommit=False)
-
- john,jack,jill,jane = sess.query(User).order_by(User.id).all()
- sess.query(User).filter(or_(User.name == 'john', User.name == 'jill')).delete(synchronize_session='fetch')
-
- assert john not in sess and jill not in sess
-
- eq_(sess.query(User).order_by(User.id).all(), [jack,jane])
-
- @testing.fails_on('mysql', 'FIXME: unknown')
- def test_delete_invalid_evaluation(self):
- User = self.classes.User
-
- sess = create_session(bind=testing.db, autocommit=False)
-
- john,jack,jill,jane = sess.query(User).order_by(User.id).all()
-
- assert_raises(sa_exc.InvalidRequestError,
- sess.query(User).filter(User.name == select([func.max(User.name)])).delete, synchronize_session='evaluate'
- )
-
- sess.query(User).filter(User.name == select([func.max(User.name)])).delete(synchronize_session='fetch')
-
- assert john not in sess
-
- eq_(sess.query(User).order_by(User.id).all(), [jack,jill,jane])
-
- def test_update(self):
- User, users = self.classes.User, self.tables.users
-
- 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}, 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]))
-
- sess.query(User).filter(User.age > 29).update({User.age: User.age - 10}, synchronize_session='evaluate')
- eq_([john.age, jack.age, jill.age, jane.age], [25,27,29,27])
- eq_(sess.query(User.age).order_by(User.id).all(), zip([25,27,29,27]))
-
- sess.query(User).filter(User.age > 27).update({users.c.age: User.age - 10}, synchronize_session='evaluate')
- eq_([john.age, jack.age, jill.age, jane.age], [25,27,19,27])
- eq_(sess.query(User.age).order_by(User.id).all(), zip([25,27,19,27]))
-
- sess.query(User).filter(User.age == 25).update({User.age: User.age - 10}, synchronize_session='fetch')
- eq_([john.age, jack.age, jill.age, jane.age], [15,27,19,27])
- eq_(sess.query(User.age).order_by(User.id).all(), zip([15,27,19,27]))
-
- @testing.provide_metadata
- def test_update_attr_names(self):
- metadata = self.metadata
- data = Table('data', metadata,
- Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
- Column('counter', Integer, nullable=False, default=0)
- )
- class Data(fixtures.ComparableEntity):
- pass
-
- mapper(Data, data, properties={'cnt':data.c.counter})
- metadata.create_all()
- d1 = Data()
- sess = Session()
- sess.add(d1)
- sess.commit()
- eq_(d1.cnt, 0)
-
- sess.query(Data).update({Data.cnt:Data.cnt + 1})
- sess.flush()
-
- eq_(d1.cnt, 1)
-
- sess.query(Data).update({Data.cnt:Data.cnt + 1}, 'fetch')
- sess.flush()
-
- eq_(d1.cnt, 2)
- sess.close()
-
- def test_update_with_bindparams(self):
- User = self.classes.User
-
- sess = create_session(bind=testing.db, autocommit=False)
-
- john,jack,jill,jane = sess.query(User).order_by(User.id).all()
-
- sess.query(User).filter('age > :x').params(x=29).update({'age': User.age - 10}, synchronize_session='fetch')
-
- 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]))
-
- def test_update_changes_resets_dirty(self):
- User = self.classes.User
-
- sess = create_session(bind=testing.db, autocommit=False, autoflush=False)
-
- john,jack,jill,jane = sess.query(User).order_by(User.id).all()
-
- john.age = 50
- jack.age = 37
-
- # 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}, synchronize_session='evaluate')
-
- for x in (john, jack, jill, jane):
- assert not sess.is_modified(x)
-
- eq_([john.age, jack.age, jill.age, jane.age], [25,37,29,27])
-
- john.age = 25
- assert john in sess.dirty
- assert jack in sess.dirty
- assert jill not in sess.dirty
- assert not sess.is_modified(john)
- assert not sess.is_modified(jack)
-
- def test_update_changes_with_autoflush(self):
- User = self.classes.User
-
- sess = create_session(bind=testing.db, autocommit=False, autoflush=True)
-
- john,jack,jill,jane = sess.query(User).order_by(User.id).all()
-
- john.age = 50
- jack.age = 37
-
- 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)
-
- eq_([john.age, jack.age, jill.age, jane.age], [40, 27, 29, 27])
-
- john.age = 25
- assert john in sess.dirty
- assert jack not in sess.dirty
- assert jill not in sess.dirty
- assert sess.is_modified(john)
- assert not sess.is_modified(jack)
-
-
-
- def test_update_with_expire_strategy(self):
- User = self.classes.User
-
- 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}, synchronize_session='fetch')
-
- 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]))
-
- @testing.fails_if(lambda: not testing.db.dialect.supports_sane_rowcount)
- def test_update_returns_rowcount(self):
- User = self.classes.User
-
- sess = create_session(bind=testing.db, autocommit=False)
-
- rowcount = sess.query(User).filter(User.age > 29).update({'age': User.age + 0})
- eq_(rowcount, 2)
-
- rowcount = sess.query(User).filter(User.age > 29).update({'age': User.age - 10})
- eq_(rowcount, 2)
-
- @testing.fails_if(lambda: not testing.db.dialect.supports_sane_rowcount)
- def test_delete_returns_rowcount(self):
- User = self.classes.User
-
- sess = create_session(bind=testing.db, autocommit=False)
-
- rowcount = sess.query(User).filter(User.age > 26).delete(synchronize_session=False)
- eq_(rowcount, 3)
-
- def test_update_with_eager_relationships(self):
- Document = self.classes.Document
-
- self.insert_documents()
-
- sess = create_session(bind=testing.db, autocommit=False)
-
- foo,bar,baz = sess.query(Document).order_by(Document.id).all()
- sess.query(Document).filter(Document.user_id == 1).update({'title': Document.title+Document.title}, synchronize_session='fetch')
-
- eq_([foo.title, bar.title, baz.title], ['foofoo','barbar', 'baz'])
- eq_(sess.query(Document.title).order_by(Document.id).all(), zip(['foofoo','barbar', 'baz']))
-
- def test_update_with_explicit_joinedload(self):
- User = self.classes.User
-
- sess = create_session(bind=testing.db, autocommit=False)
-
- john,jack,jill,jane = sess.query(User).order_by(User.id).all()
- sess.query(User).options(joinedload(User.documents)).filter(User.age > 29).update({'age': User.age - 10}, synchronize_session='fetch')
-
- 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]))
-
- def test_delete_with_eager_relationships(self):
- Document = self.classes.Document
-
- self.insert_documents()
-
- sess = create_session(bind=testing.db, autocommit=False)
-
- sess.query(Document).filter(Document.user_id == 1).delete(synchronize_session=False)
-
- eq_(sess.query(Document.title).all(), zip(['baz']))
-
- def test_update_all(self):
- User = self.classes.User
-
- sess = create_session(bind=testing.db, autocommit=False)
-
- john,jack,jill,jane = sess.query(User).order_by(User.id).all()
- sess.query(User).update({'age': 42}, synchronize_session='evaluate')
-
- eq_([john.age, jack.age, jill.age, jane.age], [42,42,42,42])
- eq_(sess.query(User.age).order_by(User.id).all(), zip([42,42,42,42]))
-
- def test_delete_all(self):
- User = self.classes.User
-
- sess = create_session(bind=testing.db, autocommit=False)
-
- john,jack,jill,jane = sess.query(User).order_by(User.id).all()
- sess.query(User).delete(synchronize_session='evaluate')
-
- assert not (john in sess or jack in sess or jill in sess or jane in sess)
- eq_(sess.query(User).count(), 0)
-
-
class StatementOptionsTest(QueryTest):
def test_query_with_statement_option(self):
--- /dev/null
+from test.lib.testing import eq_, assert_raises, assert_raises_message
+from test.lib import fixtures, testing
+from sqlalchemy import Integer, String, ForeignKey, or_, and_, exc, select, func
+from sqlalchemy.orm import mapper, relationship, backref, Session, joinedload
+
+from test.lib.schema import Table, Column
+
+class UpdateDeleteTest(fixtures.MappedTest):
+ @classmethod
+ def define_tables(cls, metadata):
+ Table('users', metadata,
+ Column('id', Integer, primary_key=True,
+ test_needs_autoincrement=True),
+ Column('name', String(32)),
+ Column('age', Integer))
+
+ Table('documents', metadata,
+ Column('id', Integer, primary_key=True,
+ test_needs_autoincrement=True),
+ Column('user_id', None, ForeignKey('users.id')),
+ Column('title', String(32)))
+
+ @classmethod
+ def setup_classes(cls):
+ class User(cls.Comparable):
+ pass
+
+ class Document(cls.Comparable):
+ pass
+
+ @classmethod
+ def insert_data(cls):
+ users = cls.tables.users
+
+ users.insert().execute([
+ dict(id=1, name='john', age=25),
+ dict(id=2, name='jack', age=47),
+ dict(id=3, name='jill', age=29),
+ dict(id=4, name='jane', age=37),
+ ])
+
+ documents = cls.tables.documents
+
+ documents.insert().execute([
+ dict(id=1, user_id=1, title='foo'),
+ dict(id=2, user_id=1, title='bar'),
+ dict(id=3, user_id=2, title='baz'),
+ ])
+
+ @classmethod
+ def setup_mappers(cls):
+ documents, Document, User, users = (cls.tables.documents,
+ cls.classes.Document,
+ cls.classes.User,
+ cls.tables.users)
+
+ mapper(User, users)
+ mapper(Document, documents, properties={
+ 'user': relationship(User, lazy='joined',
+ backref=backref('documents', lazy='select'))
+ })
+
+ def test_illegal_operations(self):
+ User = self.classes.User
+
+ s = Session()
+
+ for q, mname in (
+ (s.query(User).limit(2), "limit"),
+ (s.query(User).offset(2), "offset"),
+ (s.query(User).limit(2).offset(2), "limit"),
+ (s.query(User).order_by(User.id), "order_by"),
+ (s.query(User).group_by(User.id), "group_by"),
+ (s.query(User).distinct(), "distinct")
+ ):
+ assert_raises_message(
+ exc.InvalidRequestError,
+ r"Can't call Query.update\(\) when %s\(\) has been called" % mname,
+ q.update,
+ {'name':'ed'})
+ assert_raises_message(
+ exc.InvalidRequestError,
+ r"Can't call Query.delete\(\) when %s\(\) has been called" % mname,
+ q.delete)
+
+
+ def test_delete(self):
+ User = self.classes.User
+
+ sess = Session()
+
+ john,jack,jill,jane = sess.query(User).order_by(User.id).all()
+ sess.query(User).filter(or_(User.name == 'john', User.name == 'jill')).delete()
+
+ assert john not in sess and jill not in sess
+
+ eq_(sess.query(User).order_by(User.id).all(), [jack,jane])
+
+ def test_delete_with_bindparams(self):
+ User = self.classes.User
+
+ sess = Session()
+
+ john,jack,jill,jane = sess.query(User).order_by(User.id).all()
+ sess.query(User).filter('name = :name').params(name='john').delete('fetch')
+ assert john not in sess
+
+ eq_(sess.query(User).order_by(User.id).all(), [jack,jill,jane])
+
+ def test_delete_rollback(self):
+ User = self.classes.User
+
+ sess = Session()
+ john,jack,jill,jane = sess.query(User).order_by(User.id).all()
+ 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
+
+ def test_delete_rollback_with_fetch(self):
+ User = self.classes.User
+
+ sess = Session()
+ john,jack,jill,jane = sess.query(User).order_by(User.id).all()
+ sess.query(User).filter(or_(User.name == 'john', User.name == 'jill')).\
+ delete(synchronize_session='fetch')
+ assert john not in sess and jill not in sess
+ sess.rollback()
+ assert john in sess and jill in sess
+
+ def test_delete_without_session_sync(self):
+ User = self.classes.User
+
+ sess = Session()
+
+ john,jack,jill,jane = sess.query(User).order_by(User.id).all()
+ sess.query(User).filter(or_(User.name == 'john', User.name == 'jill')).\
+ delete(synchronize_session=False)
+
+ assert john in sess and jill in sess
+
+ eq_(sess.query(User).order_by(User.id).all(), [jack,jane])
+
+ def test_delete_with_fetch_strategy(self):
+ User = self.classes.User
+
+ sess = Session()
+
+ john,jack,jill,jane = sess.query(User).order_by(User.id).all()
+ sess.query(User).filter(or_(User.name == 'john', User.name == 'jill')).\
+ delete(synchronize_session='fetch')
+
+ assert john not in sess and jill not in sess
+
+ eq_(sess.query(User).order_by(User.id).all(), [jack,jane])
+
+ @testing.fails_on('mysql', 'FIXME: unknown')
+ def test_delete_invalid_evaluation(self):
+ User = self.classes.User
+
+ sess = Session()
+
+ john,jack,jill,jane = sess.query(User).order_by(User.id).all()
+
+ assert_raises(exc.InvalidRequestError,
+ sess.query(User).
+ filter(User.name == select([func.max(User.name)])).delete,
+ synchronize_session='evaluate'
+ )
+
+ sess.query(User).filter(User.name == select([func.max(User.name)])).\
+ delete(synchronize_session='fetch')
+
+ assert john not in sess
+
+ eq_(sess.query(User).order_by(User.id).all(), [jack,jill,jane])
+
+ def test_update(self):
+ User, users = self.classes.User, self.tables.users
+
+ sess = Session()
+
+ john,jack,jill,jane = sess.query(User).order_by(User.id).all()
+ 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]))
+
+ sess.query(User).filter(User.age > 29).\
+ update({User.age: User.age - 10}, synchronize_session='evaluate')
+ eq_([john.age, jack.age, jill.age, jane.age], [25,27,29,27])
+ eq_(sess.query(User.age).order_by(User.id).all(), zip([25,27,29,27]))
+
+ sess.query(User).filter(User.age > 27).\
+ update({users.c.age: User.age - 10}, synchronize_session='evaluate')
+ eq_([john.age, jack.age, jill.age, jane.age], [25,27,19,27])
+ eq_(sess.query(User.age).order_by(User.id).all(), zip([25,27,19,27]))
+
+ sess.query(User).filter(User.age == 25).\
+ update({User.age: User.age - 10}, synchronize_session='fetch')
+ eq_([john.age, jack.age, jill.age, jane.age], [15,27,19,27])
+ eq_(sess.query(User.age).order_by(User.id).all(), zip([15,27,19,27]))
+
+ def test_update_with_bindparams(self):
+ User = self.classes.User
+
+ sess = Session()
+
+ john,jack,jill,jane = sess.query(User).order_by(User.id).all()
+
+ sess.query(User).filter('age > :x').params(x=29).update({'age': User.age - 10}, synchronize_session='fetch')
+
+ 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]))
+
+ def test_update_changes_resets_dirty(self):
+ User = self.classes.User
+
+ sess = Session(autoflush=False)
+
+ john,jack,jill,jane = sess.query(User).order_by(User.id).all()
+
+ john.age = 50
+ jack.age = 37
+
+ # 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}, synchronize_session='evaluate')
+
+ for x in (john, jack, jill, jane):
+ assert not sess.is_modified(x)
+
+ eq_([john.age, jack.age, jill.age, jane.age], [25,37,29,27])
+
+ john.age = 25
+ assert john in sess.dirty
+ assert jack in sess.dirty
+ assert jill not in sess.dirty
+ assert not sess.is_modified(john)
+ assert not sess.is_modified(jack)
+
+ def test_update_changes_with_autoflush(self):
+ User = self.classes.User
+
+ sess = Session()
+
+ john,jack,jill,jane = sess.query(User).order_by(User.id).all()
+
+ john.age = 50
+ jack.age = 37
+
+ 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)
+
+ eq_([john.age, jack.age, jill.age, jane.age], [40, 27, 29, 27])
+
+ john.age = 25
+ assert john in sess.dirty
+ assert jack not in sess.dirty
+ assert jill not in sess.dirty
+ assert sess.is_modified(john)
+ assert not sess.is_modified(jack)
+
+
+
+ def test_update_with_expire_strategy(self):
+ User = self.classes.User
+
+ sess = Session()
+
+ john,jack,jill,jane = sess.query(User).order_by(User.id).all()
+ sess.query(User).filter(User.age > 29).\
+ update({'age': User.age - 10}, synchronize_session='fetch')
+
+ 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]))
+
+ @testing.fails_if(lambda: not testing.db.dialect.supports_sane_rowcount)
+ def test_update_returns_rowcount(self):
+ User = self.classes.User
+
+ sess = Session()
+
+ rowcount = sess.query(User).filter(User.age > 29).update({'age': User.age + 0})
+ eq_(rowcount, 2)
+
+ rowcount = sess.query(User).filter(User.age > 29).update({'age': User.age - 10})
+ eq_(rowcount, 2)
+
+ @testing.fails_if(lambda: not testing.db.dialect.supports_sane_rowcount)
+ def test_delete_returns_rowcount(self):
+ User = self.classes.User
+
+ sess = Session()
+
+ rowcount = sess.query(User).filter(User.age > 26).delete(synchronize_session=False)
+ eq_(rowcount, 3)
+
+ def test_update_with_eager_relationships(self):
+ Document = self.classes.Document
+
+ sess = Session()
+
+ foo,bar,baz = sess.query(Document).order_by(Document.id).all()
+ sess.query(Document).filter(Document.user_id == 1).\
+ update({'title': Document.title+Document.title}, synchronize_session='fetch')
+
+ eq_([foo.title, bar.title, baz.title], ['foofoo','barbar', 'baz'])
+ eq_(sess.query(Document.title).order_by(Document.id).all(),
+ zip(['foofoo','barbar', 'baz']))
+
+ def test_update_with_explicit_joinedload(self):
+ User = self.classes.User
+
+ sess = Session()
+
+ john,jack,jill,jane = sess.query(User).order_by(User.id).all()
+ sess.query(User).options(joinedload(User.documents)).filter(User.age > 29).\
+ update({'age': User.age - 10}, synchronize_session='fetch')
+
+ 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]))
+
+ def test_delete_with_eager_relationships(self):
+ Document = self.classes.Document
+
+ sess = Session()
+
+ sess.query(Document).filter(Document.user_id == 1).\
+ delete(synchronize_session=False)
+
+ eq_(sess.query(Document.title).all(), zip(['baz']))
+
+ def test_update_all(self):
+ User = self.classes.User
+
+ sess = Session()
+
+ john,jack,jill,jane = sess.query(User).order_by(User.id).all()
+ sess.query(User).update({'age': 42}, synchronize_session='evaluate')
+
+ eq_([john.age, jack.age, jill.age, jane.age], [42,42,42,42])
+ eq_(sess.query(User.age).order_by(User.id).all(), zip([42,42,42,42]))
+
+ def test_delete_all(self):
+ User = self.classes.User
+
+ sess = Session()
+
+ john,jack,jill,jane = sess.query(User).order_by(User.id).all()
+ sess.query(User).delete(synchronize_session='evaluate')
+
+ assert not (john in sess or jack in sess or jill in sess or jane in sess)
+ eq_(sess.query(User).count(), 0)
+
+ def test_autoflush_before_evaluate_update(self):
+ User = self.classes.User
+
+ sess = Session()
+ john = sess.query(User).filter_by(name='john').one()
+ john.name = 'j2'
+
+ sess.query(User).filter_by(name='j2').\
+ update({'age':42},
+ synchronize_session='evaluate')
+ eq_(john.age, 42)
+
+ def test_autoflush_before_fetch_update(self):
+ User = self.classes.User
+
+ sess = Session()
+ john = sess.query(User).filter_by(name='john').one()
+ john.name = 'j2'
+
+ sess.query(User).filter_by(name='j2').\
+ update({'age':42},
+ synchronize_session='fetch')
+ eq_(john.age, 42)
+
+ def test_autoflush_before_evaluate_delete(self):
+ User = self.classes.User
+
+ sess = Session()
+ john = sess.query(User).filter_by(name='john').one()
+ john.name = 'j2'
+
+ sess.query(User).filter_by(name='j2').\
+ delete(
+ synchronize_session='evaluate')
+ assert john not in sess
+
+ def test_autoflush_before_fetch_delete(self):
+ User = self.classes.User
+
+ sess = Session()
+ john = sess.query(User).filter_by(name='john').one()
+ john.name = 'j2'
+
+ sess.query(User).filter_by(name='j2').\
+ delete(
+ synchronize_session='fetch')
+ assert john not in sess
+
+ def test_evaluate_before_update(self):
+ User = self.classes.User
+
+ sess = Session()
+ john = sess.query(User).filter_by(name='john').one()
+ sess.expire(john, ['age'])
+
+ # eval must be before the update. otherwise
+ # we eval john, age has been expired and doesn't
+ # match the new value coming in
+ sess.query(User).filter_by(name='john').filter_by(age=25).\
+ update({'name':'j2', 'age':40},
+ synchronize_session='evaluate')
+ eq_(john.name, 'j2')
+ eq_(john.age, 40)
+
+ def test_fetch_before_update(self):
+ User = self.classes.User
+
+ sess = Session()
+ john = sess.query(User).filter_by(name='john').one()
+ sess.expire(john, ['age'])
+
+ sess.query(User).filter_by(name='john').filter_by(age=25).\
+ update({'name':'j2', 'age':40},
+ synchronize_session='fetch')
+ eq_(john.name, 'j2')
+ eq_(john.age, 40)
+
+ def test_evaluate_before_delete(self):
+ User = self.classes.User
+
+ sess = Session()
+ john = sess.query(User).filter_by(name='john').one()
+ sess.expire(john, ['age'])
+
+ sess.query(User).filter_by(name='john').\
+ filter_by(age=25).\
+ delete(
+ synchronize_session='evaluate')
+ assert john not in sess
+
+ def test_fetch_before_delete(self):
+ User = self.classes.User
+
+ sess = Session()
+ john = sess.query(User).filter_by(name='john').one()
+ sess.expire(john, ['age'])
+
+ sess.query(User).filter_by(name='john').\
+ filter_by(age=25).\
+ delete(
+ synchronize_session='fetch')
+ assert john not in sess
+
+class ExpressionUpdateTest(fixtures.MappedTest):
+ @classmethod
+ def define_tables(cls, metadata):
+ data = Table('data', metadata,
+ Column('id', Integer, primary_key=True,
+ test_needs_autoincrement=True),
+ Column('counter', Integer, nullable=False, default=0)
+ )
+
+ @classmethod
+ def setup_classes(cls):
+ class Data(cls.Comparable):
+ pass
+
+ @classmethod
+ def setup_mappers(cls):
+ data = cls.tables.data
+ mapper(cls.classes.Data, data, properties={'cnt':data.c.counter})
+
+ @testing.provide_metadata
+ def test_update_attr_names(self):
+ Data = self.classes.Data
+
+ d1 = Data()
+ sess = Session()
+ sess.add(d1)
+ sess.commit()
+ eq_(d1.cnt, 0)
+
+ sess.query(Data).update({Data.cnt:Data.cnt + 1})
+ sess.flush()
+
+ eq_(d1.cnt, 1)
+
+ sess.query(Data).update({Data.cnt:Data.cnt + 1}, 'fetch')
+ sess.flush()
+
+ eq_(d1.cnt, 2)
+ sess.close()
+
+