2
2
"""Tests unitofwork operations."""
4
from sqlalchemy.test.testing import eq_, assert_raises, assert_raises_message
4
from test.lib.testing import eq_, assert_raises, assert_raises_message
7
7
from sqlalchemy.orm import mapper as orm_mapper
9
9
import sqlalchemy as sa
10
from sqlalchemy.test import engines, testing, pickleable
11
from sqlalchemy import Integer, String, ForeignKey, literal_column
12
from sqlalchemy.test.schema import Table
13
from sqlalchemy.test.schema import Column
10
from sqlalchemy import Integer, String, ForeignKey, literal_column, event
11
from test.lib import engines, testing, pickleable
12
from test.lib.schema import Table
13
from test.lib.schema import Column
14
14
from sqlalchemy.orm import mapper, relationship, create_session, \
15
15
column_property, attributes, Session, reconstructor, object_session
16
from sqlalchemy.test.testing import eq_, ne_
17
from sqlalchemy.test.util import gc_collect
18
from test.orm import _base, _fixtures
19
from test.engine import _base as engine_base
20
from sqlalchemy.test.assertsql import AllOf, CompiledSQL
16
from test.lib.testing import eq_, ne_
17
from test.lib.util import gc_collect
18
from test.lib import fixtures
19
from test.orm import _fixtures
20
from test.lib import fixtures
21
from test.lib.assertsql import AllOf, CompiledSQL
23
24
class UnitOfWorkTest(object):
253
268
self.assert_sql_count(testing.db, go, 0)
255
class MutableTypesTest(_base.MappedTest):
258
def define_tables(cls, metadata):
259
Table('mutable_t', metadata,
260
Column('id', Integer, primary_key=True,
261
test_needs_autoincrement=True),
262
Column('data', sa.PickleType),
263
Column('val', sa.Unicode(30)))
266
def setup_classes(cls):
267
class Foo(_base.BasicEntity):
271
@testing.resolve_artifact_names
272
def setup_mappers(cls):
273
mapper(Foo, mutable_t)
275
@testing.resolve_artifact_names
276
def test_modified_status(self):
277
f1 = Foo(data = pickleable.Bar(4,5))
283
f2 = session.query(Foo).first()
284
assert 'data' in sa.orm.attributes.instance_state(f2).unmodified
285
eq_(f2.data, f1.data)
288
assert f2 in session.dirty
289
assert 'data' not in sa.orm.attributes.instance_state(f2).unmodified
291
@testing.resolve_artifact_names
292
def test_mutations_persisted(self):
293
f1 = Foo(data = pickleable.Bar(4,5))
301
f2 = session.query(Foo).first()
307
f3 = session.query(Foo).first()
309
eq_(f3.data, pickleable.Bar(4, 19))
311
@testing.resolve_artifact_names
312
def test_no_unnecessary_update(self):
313
f1 = Foo(data = pickleable.Bar(4,5), val = u'hi')
319
self.sql_count_(0, session.commit)
321
f1.val = u'someothervalue'
322
self.assert_sql(testing.db, session.commit, [
323
("UPDATE mutable_t SET val=:val "
324
"WHERE mutable_t.id = :mutable_t_id",
325
{'mutable_t_id': f1.id, 'val': u'someothervalue'})])
329
self.assert_sql(testing.db, session.commit, [
330
("UPDATE mutable_t SET data=:data, val=:val "
331
"WHERE mutable_t.id = :mutable_t_id",
332
{'mutable_t_id': f1.id, 'val': u'hi', 'data':f1.data})])
334
@testing.resolve_artifact_names
335
def test_mutated_state_resurrected(self):
336
f1 = Foo(data = pickleable.Bar(4,5), val = u'hi')
346
assert len(session.identity_map) == 1
350
assert session.query(Foo).one().data == pickleable.Bar(4, 19)
352
@testing.resolve_artifact_names
353
def test_mutated_plus_scalar_state_change_resurrected(self):
354
"""test that a non-mutable attribute event subsequent to
355
a mutable event prevents the object from falling into
359
f1 = Foo(data = pickleable.Bar(4, 5), val=u'some val')
365
f1.val=u'some new val'
367
assert sa.orm.attributes.instance_state(f1)._strong_obj is not None
372
session.query(Foo.val).all(),
376
@testing.resolve_artifact_names
377
def test_non_mutated_state_not_resurrected(self):
378
f1 = Foo(data = pickleable.Bar(4,5))
385
f1 = session.query(Foo).first()
389
assert len(session.identity_map) == 0
390
f1 = session.query(Foo).first()
391
assert not attributes.instance_state(f1).modified
393
@testing.resolve_artifact_names
394
def test_scalar_no_net_change_no_update(self):
395
"""Test that a no-net-change on a scalar attribute event
396
doesn't cause an UPDATE for a mutable state.
407
f1 = session.query(Foo).first()
409
self.sql_count_(0, session.commit)
411
@testing.resolve_artifact_names
412
def test_expire_attribute_set(self):
413
"""test one SELECT emitted when assigning to an expired
414
mutable attribute - this will become 0 in 0.7.
418
f1 = Foo(data = pickleable.Bar(4, 5), val=u'some val')
423
assert 'data' not in f1.__dict__
425
f1.data = pickleable.Bar(10, 15)
426
self.sql_count_(1, go)
431
@testing.resolve_artifact_names
432
def test_expire_mutate(self):
433
"""test mutations are detected on an expired mutable
436
f1 = Foo(data = pickleable.Bar(4, 5), val=u'some val')
441
assert 'data' not in f1.__dict__
444
self.sql_count_(1, go)
449
@testing.resolve_artifact_names
450
def test_deferred_attribute_set(self):
451
"""test one SELECT emitted when assigning to a deferred
452
mutable attribute - this will become 0 in 0.7.
455
sa.orm.clear_mappers()
456
mapper(Foo, mutable_t, properties={
457
'data':sa.orm.deferred(mutable_t.c.data)
460
f1 = Foo(data = pickleable.Bar(4, 5), val=u'some val')
467
f1 = session.query(Foo).first()
469
f1.data = pickleable.Bar(10, 15)
470
self.sql_count_(1, go)
475
@testing.resolve_artifact_names
476
def test_deferred_mutate(self):
477
"""test mutations are detected on a deferred mutable
480
sa.orm.clear_mappers()
481
mapper(Foo, mutable_t, properties={
482
'data':sa.orm.deferred(mutable_t.c.data)
485
f1 = Foo(data = pickleable.Bar(4, 5), val=u'some val')
492
f1 = session.query(Foo).first()
495
self.sql_count_(1, go)
500
self.sql_count_(1, go)
503
class PickledDictsTest(_base.MappedTest):
506
def define_tables(cls, metadata):
507
Table('mutable_t', metadata,
508
Column('id', Integer, primary_key=True,
509
test_needs_autoincrement=True),
510
Column('data', sa.PickleType(comparator=operator.eq)))
513
def setup_classes(cls):
514
class Foo(_base.BasicEntity):
518
@testing.resolve_artifact_names
519
def setup_mappers(cls):
520
mapper(Foo, mutable_t)
522
@testing.resolve_artifact_names
523
def test_dicts(self):
524
"""Dictionaries may not pickle the same way twice."""
528
'personne': {'nom': u'Smith',
543
session = create_session(autocommit=False)
547
self.sql_count_(0, session.commit)
550
'personne': {'nom': u'Smith',
565
self.sql_count_(0, session.commit)
567
f1.data[0]['personne']['VenSoir']= False
568
self.sql_count_(1, session.commit)
570
session.expunge_all()
571
f = session.query(Foo).get(f1.id)
574
'personne': {'nom': u'Smith',
587
'SamAcc': False} } ])
590
class PKTest(_base.MappedTest):
270
class PKTest(fixtures.MappedTest):
593
273
def define_tables(cls, metadata):
2266
2134
eq_(sess.query(T).filter(T.value==True).all(), [T(value=True, name="t1"),T(value=True, name="t3")])
2268
class DontAllowFlushOnLoadingObjectTest(_base.MappedTest):
2269
"""Test that objects with NULL identity keys aren't permitted to complete a flush.
2271
User-defined callables that execute during a load may modify state
2272
on instances which results in their being autoflushed, before attributes
2273
are populated. If the primary key identifiers are missing, an explicit assertion
2274
is needed to check that the object doesn't go through the flush process with
2275
no net changes and gets placed in the identity map with an incorrect
2280
def define_tables(cls, metadata):
2281
t1 = Table('t1', metadata,
2282
Column('id', Integer, primary_key=True),
2283
Column('data', String(30)),
2286
@testing.resolve_artifact_names
2287
def test_flush_raises(self):
2288
class T1(_base.ComparableEntity):
2291
# blow away 'id', no change event.
2292
# this simulates a callable occurring
2293
# before 'id' was even populated, i.e. a callable
2294
# within an attribute_mapped_collection
2295
self.__dict__.pop('id', None)
2297
# generate a change event, perhaps this occurs because
2298
# someone wrote a broken attribute_mapped_collection that
2299
# inappropriately fires off change events when it should not,
2301
self.data = 'foo bar'
2303
# blow away that change, so an UPDATE does not occur
2304
# (since it would break)
2305
self.__dict__.pop('data', None)
2307
# flush ! any lazyloader here would trigger
2308
# autoflush, for example.
2314
sess.add(T1(data='test', id=5))
2318
# make sure that invalid state doesn't get into the session
2319
# with the wrong key. If the identity key is not NULL, at least
2320
# the population process would continue after the erroneous flush
2321
# and thing would right themselves.
2322
assert_raises_message(sa.orm.exc.FlushError,
2323
r"Instance \<T1 at .+?\> has a NULL "
2324
"identity key. If this is an auto-generated value, "
2325
"check that the database table allows generation ",
2326
sess.query(T1).first)
2330
class RowSwitchTest(_base.MappedTest):
2137
class RowSwitchTest(fixtures.MappedTest):
2332
2139
def define_tables(cls, metadata):
2574
2401
if testing.against('postgresql'):
2575
2402
t1.bind.engine.dispose()
2404
class PartialNullPKTest(fixtures.MappedTest):
2405
# sqlite totally fine with NULLs in pk columns.
2406
# no other DB is like this.
2407
__only_on__ = ('sqlite',)
2410
def define_tables(cls, metadata):
2411
Table('t1', metadata,
2412
Column('col1', String(10), primary_key=True, nullable=True),
2413
Column('col2', String(10), primary_key=True, nullable=True),
2414
Column('col3', String(50))
2418
def setup_classes(cls):
2419
class T1(cls.Basic):
2423
def setup_mappers(cls):
2424
orm_mapper(cls.classes.T1, cls.tables.t1)
2426
def test_key_switch(self):
2427
T1 = self.classes.T1
2429
s.add(T1(col1="1", col2=None))
2431
t1 = s.query(T1).first()
2433
assert_raises_message(
2435
"Can't update table using NULL for primary key value",
2439
def test_plain_update(self):
2440
T1 = self.classes.T1
2442
s.add(T1(col1="1", col2=None))
2444
t1 = s.query(T1).first()
2446
assert_raises_message(
2448
"Can't update table using NULL for primary key value",
2452
def test_delete(self):
2453
T1 = self.classes.T1
2455
s.add(T1(col1="1", col2=None))
2457
t1 = s.query(T1).first()
2459
assert_raises_message(
2461
"Can't delete from table using NULL for primary key value",
2465
def test_total_null(self):
2466
T1 = self.classes.T1
2468
s.add(T1(col1=None, col2=None))
2469
assert_raises_message(
2471
r"Instance \<T1 at .+?\> has a NULL "
2472
"identity key. If this is an auto-generated value, "
2473
"check that the database table allows generation ",