PageRenderTime 56ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/SQLAlchemy-0.7.8/test/orm/test_unitofwork.py

#
Python | 2520 lines | 2354 code | 146 blank | 20 comment | 3 complexity | acc97907fe2520ca34bc167f620b437f MD5 | raw file
  1. # coding: utf-8
  2. """Tests unitofwork operations."""
  3. from test.lib.testing import eq_, assert_raises, assert_raises_message
  4. import datetime
  5. import operator
  6. from sqlalchemy.orm import mapper as orm_mapper
  7. import sqlalchemy as sa
  8. from sqlalchemy import Integer, String, ForeignKey, literal_column, event
  9. from test.lib import engines, testing, pickleable
  10. from test.lib.schema import Table
  11. from test.lib.schema import Column
  12. from sqlalchemy.orm import mapper, relationship, create_session, \
  13. column_property, attributes, Session, reconstructor, object_session
  14. from test.lib.testing import eq_, ne_
  15. from test.lib.util import gc_collect
  16. from test.lib import fixtures
  17. from test.orm import _fixtures
  18. from test.lib import fixtures
  19. from test.lib.assertsql import AllOf, CompiledSQL
  20. import gc
  21. class UnitOfWorkTest(object):
  22. pass
  23. class HistoryTest(_fixtures.FixtureTest):
  24. run_inserts = None
  25. @classmethod
  26. def setup_classes(cls):
  27. class User(cls.Comparable):
  28. pass
  29. class Address(cls.Comparable):
  30. pass
  31. def test_backref(self):
  32. Address, addresses, users, User = (self.classes.Address,
  33. self.tables.addresses,
  34. self.tables.users,
  35. self.classes.User)
  36. am = mapper(Address, addresses)
  37. m = mapper(User, users, properties=dict(
  38. addresses = relationship(am, backref='user', lazy='joined')))
  39. session = create_session(autocommit=False)
  40. u = User(name='u1')
  41. a = Address(email_address='u1@e')
  42. a.user = u
  43. session.add(u)
  44. eq_(u.addresses, [a])
  45. session.commit()
  46. session.expunge_all()
  47. u = session.query(m).one()
  48. assert u.addresses[0].user == u
  49. session.close()
  50. class UnicodeTest(fixtures.MappedTest):
  51. __requires__ = ('unicode_connections',)
  52. @classmethod
  53. def define_tables(cls, metadata):
  54. if testing.against('mysql+oursql'):
  55. from sqlalchemy.dialects.mysql import VARCHAR
  56. uni_type = VARCHAR(50, collation='utf8_unicode_ci')
  57. else:
  58. uni_type = sa.Unicode(50)
  59. Table('uni_t1', metadata,
  60. Column('id', Integer, primary_key=True,
  61. test_needs_autoincrement=True),
  62. Column('txt', uni_type, unique=True))
  63. Table('uni_t2', metadata,
  64. Column('id', Integer, primary_key=True,
  65. test_needs_autoincrement=True),
  66. Column('txt', uni_type, ForeignKey('uni_t1')))
  67. @classmethod
  68. def setup_classes(cls):
  69. class Test(cls.Basic):
  70. pass
  71. class Test2(cls.Basic):
  72. pass
  73. def test_basic(self):
  74. Test, uni_t1 = self.classes.Test, self.tables.uni_t1
  75. mapper(Test, uni_t1)
  76. txt = u"\u0160\u0110\u0106\u010c\u017d"
  77. t1 = Test(id=1, txt=txt)
  78. self.assert_(t1.txt == txt)
  79. session = create_session(autocommit=False)
  80. session.add(t1)
  81. session.commit()
  82. self.assert_(t1.txt == txt)
  83. def test_relationship(self):
  84. Test, uni_t2, uni_t1, Test2 = (self.classes.Test,
  85. self.tables.uni_t2,
  86. self.tables.uni_t1,
  87. self.classes.Test2)
  88. mapper(Test, uni_t1, properties={
  89. 't2s': relationship(Test2)})
  90. mapper(Test2, uni_t2)
  91. txt = u"\u0160\u0110\u0106\u010c\u017d"
  92. t1 = Test(txt=txt)
  93. t1.t2s.append(Test2())
  94. t1.t2s.append(Test2())
  95. session = create_session(autocommit=False)
  96. session.add(t1)
  97. session.commit()
  98. session.close()
  99. session = create_session()
  100. t1 = session.query(Test).filter_by(id=t1.id).one()
  101. assert len(t1.t2s) == 2
  102. class UnicodeSchemaTest(fixtures.MappedTest):
  103. __requires__ = ('unicode_connections', 'unicode_ddl',)
  104. run_dispose_bind = 'once'
  105. @classmethod
  106. def create_engine(cls):
  107. return engines.utf8_engine()
  108. @classmethod
  109. def define_tables(cls, metadata):
  110. t1 = Table('unitable1', metadata,
  111. Column(u'méil', Integer, primary_key=True, key='a', test_needs_autoincrement=True),
  112. Column(u'\u6e2c\u8a66', Integer, key='b'),
  113. Column('type', String(20)),
  114. test_needs_fk=True,
  115. test_needs_autoincrement=True)
  116. t2 = Table(u'Unitéble2', metadata,
  117. Column(u'méil', Integer, primary_key=True, key="cc", test_needs_autoincrement=True),
  118. Column(u'\u6e2c\u8a66', Integer,
  119. ForeignKey(u'unitable1.a'), key="d"),
  120. Column(u'\u6e2c\u8a66_2', Integer, key="e"),
  121. test_needs_fk=True,
  122. test_needs_autoincrement=True)
  123. cls.tables['t1'] = t1
  124. cls.tables['t2'] = t2
  125. @classmethod
  126. def setup_class(cls):
  127. super(UnicodeSchemaTest, cls).setup_class()
  128. @classmethod
  129. def teardown_class(cls):
  130. super(UnicodeSchemaTest, cls).teardown_class()
  131. @testing.fails_on('mssql+pyodbc',
  132. 'pyodbc returns a non unicode encoding of the results description.')
  133. def test_mapping(self):
  134. t2, t1 = self.tables.t2, self.tables.t1
  135. class A(fixtures.ComparableEntity):
  136. pass
  137. class B(fixtures.ComparableEntity):
  138. pass
  139. mapper(A, t1, properties={
  140. 't2s':relationship(B)})
  141. mapper(B, t2)
  142. a1 = A()
  143. b1 = B()
  144. a1.t2s.append(b1)
  145. session = create_session()
  146. session.add(a1)
  147. session.flush()
  148. session.expunge_all()
  149. new_a1 = session.query(A).filter(t1.c.a == a1.a).one()
  150. assert new_a1.a == a1.a
  151. assert new_a1.t2s[0].d == b1.d
  152. session.expunge_all()
  153. new_a1 = (session.query(A).options(sa.orm.joinedload('t2s')).
  154. filter(t1.c.a == a1.a)).one()
  155. assert new_a1.a == a1.a
  156. assert new_a1.t2s[0].d == b1.d
  157. session.expunge_all()
  158. new_a1 = session.query(A).filter(A.a == a1.a).one()
  159. assert new_a1.a == a1.a
  160. assert new_a1.t2s[0].d == b1.d
  161. session.expunge_all()
  162. @testing.fails_on('mssql+pyodbc',
  163. 'pyodbc returns a non unicode encoding of the results description.')
  164. def test_inheritance_mapping(self):
  165. t2, t1 = self.tables.t2, self.tables.t1
  166. class A(fixtures.ComparableEntity):
  167. pass
  168. class B(A):
  169. pass
  170. mapper(A, t1,
  171. polymorphic_on=t1.c.type,
  172. polymorphic_identity='a')
  173. mapper(B, t2,
  174. inherits=A,
  175. polymorphic_identity='b')
  176. a1 = A(b=5)
  177. b1 = B(e=7)
  178. session = create_session()
  179. session.add_all((a1, b1))
  180. session.flush()
  181. session.expunge_all()
  182. eq_([A(b=5), B(e=7)], session.query(A).all())
  183. class BinaryHistTest(fixtures.MappedTest, testing.AssertsExecutionResults):
  184. @classmethod
  185. def define_tables(cls, metadata):
  186. Table('t1', metadata,
  187. Column('id', sa.Integer, primary_key=True, test_needs_autoincrement=True),
  188. Column('data', sa.LargeBinary),
  189. )
  190. @classmethod
  191. def setup_classes(cls):
  192. class Foo(cls.Basic):
  193. pass
  194. def test_binary_equality(self):
  195. Foo, t1 = self.classes.Foo, self.tables.t1
  196. # Py3K
  197. #data = b"this is some data"
  198. # Py2K
  199. data = "this is some data"
  200. # end Py2K
  201. mapper(Foo, t1)
  202. s = create_session()
  203. f1 = Foo(data=data)
  204. s.add(f1)
  205. s.flush()
  206. s.expire_all()
  207. f1 = s.query(Foo).first()
  208. assert f1.data == data
  209. f1.data = data
  210. eq_(
  211. sa.orm.attributes.get_history(f1, "data"),
  212. ((), [data], ())
  213. )
  214. def go():
  215. s.flush()
  216. self.assert_sql_count(testing.db, go, 0)
  217. class PKTest(fixtures.MappedTest):
  218. @classmethod
  219. def define_tables(cls, metadata):
  220. Table('multipk1', metadata,
  221. Column('multi_id', Integer, primary_key=True,
  222. test_needs_autoincrement=True),
  223. Column('multi_rev', Integer, primary_key=True),
  224. Column('name', String(50), nullable=False),
  225. Column('value', String(100)))
  226. Table('multipk2', metadata,
  227. Column('pk_col_1', String(30), primary_key=True),
  228. Column('pk_col_2', String(30), primary_key=True),
  229. Column('data', String(30)))
  230. Table('multipk3', metadata,
  231. Column('pri_code', String(30), key='primary', primary_key=True),
  232. Column('sec_code', String(30), key='secondary', primary_key=True),
  233. Column('date_assigned', sa.Date, key='assigned', primary_key=True),
  234. Column('data', String(30)))
  235. @classmethod
  236. def setup_classes(cls):
  237. class Entry(cls.Basic):
  238. pass
  239. # not supported on sqlite since sqlite's auto-pk generation only works with
  240. # single column primary keys
  241. @testing.fails_on('sqlite', 'FIXME: unknown')
  242. def test_primary_key(self):
  243. Entry, multipk1 = self.classes.Entry, self.tables.multipk1
  244. mapper(Entry, multipk1)
  245. e = Entry(name='entry1', value='this is entry 1', multi_rev=2)
  246. session = create_session()
  247. session.add(e)
  248. session.flush()
  249. session.expunge_all()
  250. e2 = session.query(Entry).get((e.multi_id, 2))
  251. self.assert_(e is not e2)
  252. state = sa.orm.attributes.instance_state(e)
  253. state2 = sa.orm.attributes.instance_state(e2)
  254. eq_(state.key, state2.key)
  255. # this one works with sqlite since we are manually setting up pk values
  256. def test_manual_pk(self):
  257. Entry, multipk2 = self.classes.Entry, self.tables.multipk2
  258. mapper(Entry, multipk2)
  259. e = Entry(pk_col_1='pk1', pk_col_2='pk1_related', data='im the data')
  260. session = create_session()
  261. session.add(e)
  262. session.flush()
  263. def test_key_pks(self):
  264. Entry, multipk3 = self.classes.Entry, self.tables.multipk3
  265. mapper(Entry, multipk3)
  266. e = Entry(primary= 'pk1', secondary='pk2',
  267. assigned=datetime.date.today(), data='some more data')
  268. session = create_session()
  269. session.add(e)
  270. session.flush()
  271. class ForeignPKTest(fixtures.MappedTest):
  272. """Detection of the relationship direction on PK joins."""
  273. @classmethod
  274. def define_tables(cls, metadata):
  275. Table("people", metadata,
  276. Column('person', String(10), primary_key=True),
  277. Column('firstname', String(10)),
  278. Column('lastname', String(10)))
  279. Table("peoplesites", metadata,
  280. Column('person', String(10), ForeignKey("people.person"),
  281. primary_key=True),
  282. Column('site', String(10)))
  283. @classmethod
  284. def setup_classes(cls):
  285. class Person(cls.Basic):
  286. pass
  287. class PersonSite(cls.Basic):
  288. pass
  289. def test_basic(self):
  290. peoplesites, PersonSite, Person, people = (self.tables.peoplesites,
  291. self.classes.PersonSite,
  292. self.classes.Person,
  293. self.tables.people)
  294. m1 = mapper(PersonSite, peoplesites)
  295. m2 = mapper(Person, people, properties={
  296. 'sites' : relationship(PersonSite)})
  297. sa.orm.configure_mappers()
  298. eq_(list(m2.get_property('sites').synchronize_pairs),
  299. [(people.c.person, peoplesites.c.person)])
  300. p = Person(person='im the key', firstname='asdf')
  301. ps = PersonSite(site='asdf')
  302. p.sites.append(ps)
  303. session = create_session()
  304. session.add(p)
  305. session.flush()
  306. p_count = people.count(people.c.person=='im the key').scalar()
  307. eq_(p_count, 1)
  308. eq_(peoplesites.count(peoplesites.c.person=='im the key').scalar(), 1)
  309. class ClauseAttributesTest(fixtures.MappedTest):
  310. @classmethod
  311. def define_tables(cls, metadata):
  312. Table('users_t', metadata,
  313. Column('id', Integer, primary_key=True,
  314. test_needs_autoincrement=True),
  315. Column('name', String(30)),
  316. Column('counter', Integer, default=1))
  317. @classmethod
  318. def setup_classes(cls):
  319. class User(cls.Comparable):
  320. pass
  321. @classmethod
  322. def setup_mappers(cls):
  323. User, users_t = cls.classes.User, cls.tables.users_t
  324. mapper(User, users_t)
  325. def test_update(self):
  326. User = self.classes.User
  327. u = User(name='test')
  328. session = create_session()
  329. session.add(u)
  330. session.flush()
  331. eq_(u.counter, 1)
  332. u.counter = User.counter + 1
  333. session.flush()
  334. def go():
  335. assert (u.counter == 2) is True # ensure its not a ClauseElement
  336. self.sql_count_(1, go)
  337. def test_multi_update(self):
  338. User = self.classes.User
  339. u = User(name='test')
  340. session = create_session()
  341. session.add(u)
  342. session.flush()
  343. eq_(u.counter, 1)
  344. u.name = 'test2'
  345. u.counter = User.counter + 1
  346. session.flush()
  347. def go():
  348. eq_(u.name, 'test2')
  349. assert (u.counter == 2) is True
  350. self.sql_count_(1, go)
  351. session.expunge_all()
  352. u = session.query(User).get(u.id)
  353. eq_(u.name, 'test2')
  354. eq_(u.counter, 2)
  355. def test_insert(self):
  356. User = self.classes.User
  357. u = User(name='test', counter=sa.select([5]))
  358. session = create_session()
  359. session.add(u)
  360. session.flush()
  361. assert (u.counter == 5) is True
  362. class PassiveDeletesTest(fixtures.MappedTest):
  363. __requires__ = ('foreign_keys',)
  364. @classmethod
  365. def define_tables(cls, metadata):
  366. Table('mytable', metadata,
  367. Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
  368. Column('data', String(30)),
  369. test_needs_fk=True)
  370. Table('myothertable', metadata,
  371. Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
  372. Column('parent_id', Integer),
  373. Column('data', String(30)),
  374. sa.ForeignKeyConstraint(['parent_id'],
  375. ['mytable.id'],
  376. ondelete="CASCADE"),
  377. test_needs_fk=True)
  378. @classmethod
  379. def setup_classes(cls):
  380. class MyClass(cls.Basic):
  381. pass
  382. class MyOtherClass(cls.Basic):
  383. pass
  384. def test_basic(self):
  385. myothertable, MyClass, MyOtherClass, mytable = (self.tables.myothertable,
  386. self.classes.MyClass,
  387. self.classes.MyOtherClass,
  388. self.tables.mytable)
  389. mapper(MyOtherClass, myothertable)
  390. mapper(MyClass, mytable, properties={
  391. 'children':relationship(MyOtherClass,
  392. passive_deletes=True,
  393. cascade="all")})
  394. session = create_session()
  395. mc = MyClass()
  396. mc.children.append(MyOtherClass())
  397. mc.children.append(MyOtherClass())
  398. mc.children.append(MyOtherClass())
  399. mc.children.append(MyOtherClass())
  400. session.add(mc)
  401. session.flush()
  402. session.expunge_all()
  403. assert myothertable.count().scalar() == 4
  404. mc = session.query(MyClass).get(mc.id)
  405. session.delete(mc)
  406. session.flush()
  407. assert mytable.count().scalar() == 0
  408. assert myothertable.count().scalar() == 0
  409. @testing.emits_warning(r".*'passive_deletes' is normally configured on one-to-many")
  410. def test_backwards_pd(self):
  411. """Test that passive_deletes=True disables a delete from an m2o.
  412. This is not the usual usage and it now raises a warning, but test
  413. that it works nonetheless.
  414. """
  415. myothertable, MyClass, MyOtherClass, mytable = (self.tables.myothertable,
  416. self.classes.MyClass,
  417. self.classes.MyOtherClass,
  418. self.tables.mytable)
  419. mapper(MyOtherClass, myothertable, properties={
  420. 'myclass':relationship(MyClass, cascade="all, delete", passive_deletes=True)
  421. })
  422. mapper(MyClass, mytable)
  423. session = create_session()
  424. mc = MyClass()
  425. mco = MyOtherClass()
  426. mco.myclass = mc
  427. session.add(mco)
  428. session.flush()
  429. assert mytable.count().scalar() == 1
  430. assert myothertable.count().scalar() == 1
  431. session.expire(mco, ['myclass'])
  432. session.delete(mco)
  433. session.flush()
  434. # mytable wasn't deleted, is the point.
  435. assert mytable.count().scalar() == 1
  436. assert myothertable.count().scalar() == 0
  437. def test_aaa_m2o_emits_warning(self):
  438. myothertable, MyClass, MyOtherClass, mytable = (self.tables.myothertable,
  439. self.classes.MyClass,
  440. self.classes.MyOtherClass,
  441. self.tables.mytable)
  442. mapper(MyOtherClass, myothertable, properties={
  443. 'myclass':relationship(MyClass, cascade="all, delete", passive_deletes=True)
  444. })
  445. mapper(MyClass, mytable)
  446. assert_raises(sa.exc.SAWarning, sa.orm.configure_mappers)
  447. class BatchDeleteIgnoresRowcountTest(fixtures.DeclarativeMappedTest):
  448. __requires__ = ('foreign_keys',)
  449. @classmethod
  450. def setup_classes(cls):
  451. class A(cls.DeclarativeBasic):
  452. __tablename__ = 'A'
  453. __table_args__ = dict(test_needs_fk=True)
  454. id = Column(Integer, primary_key=True)
  455. parent_id = Column(Integer, ForeignKey('A.id', ondelete='CASCADE'))
  456. def test_delete_both(self):
  457. A = self.classes.A
  458. session = Session(testing.db)
  459. a1, a2 = A(id=1),A(id=2, parent_id=1)
  460. session.add_all([a1, a2])
  461. session.flush()
  462. session.delete(a1)
  463. session.delete(a2)
  464. # no issue with multi-row count here
  465. session.flush()
  466. class ExtraPassiveDeletesTest(fixtures.MappedTest):
  467. __requires__ = ('foreign_keys',)
  468. @classmethod
  469. def define_tables(cls, metadata):
  470. Table('mytable', metadata,
  471. Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
  472. Column('data', String(30)),
  473. test_needs_fk=True)
  474. Table('myothertable', metadata,
  475. Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
  476. Column('parent_id', Integer),
  477. Column('data', String(30)),
  478. # no CASCADE, the same as ON DELETE RESTRICT
  479. sa.ForeignKeyConstraint(['parent_id'],
  480. ['mytable.id']),
  481. test_needs_fk=True)
  482. @classmethod
  483. def setup_classes(cls):
  484. class MyClass(cls.Basic):
  485. pass
  486. class MyOtherClass(cls.Basic):
  487. pass
  488. def test_assertions(self):
  489. myothertable, MyOtherClass = self.tables.myothertable, self.classes.MyOtherClass
  490. mapper(MyOtherClass, myothertable)
  491. assert_raises_message(
  492. sa.exc.ArgumentError,
  493. "Can't set passive_deletes='all' in conjunction with 'delete' "
  494. "or 'delete-orphan' cascade",
  495. relationship, MyOtherClass,
  496. passive_deletes='all',
  497. cascade="all"
  498. )
  499. def test_extra_passive(self):
  500. myothertable, MyClass, MyOtherClass, mytable = (self.tables.myothertable,
  501. self.classes.MyClass,
  502. self.classes.MyOtherClass,
  503. self.tables.mytable)
  504. mapper(MyOtherClass, myothertable)
  505. mapper(MyClass, mytable, properties={
  506. 'children': relationship(MyOtherClass,
  507. passive_deletes='all',
  508. cascade="save-update")})
  509. session = create_session()
  510. mc = MyClass()
  511. mc.children.append(MyOtherClass())
  512. mc.children.append(MyOtherClass())
  513. mc.children.append(MyOtherClass())
  514. mc.children.append(MyOtherClass())
  515. session.add(mc)
  516. session.flush()
  517. session.expunge_all()
  518. assert myothertable.count().scalar() == 4
  519. mc = session.query(MyClass).get(mc.id)
  520. session.delete(mc)
  521. assert_raises(sa.exc.DBAPIError, session.flush)
  522. def test_extra_passive_2(self):
  523. myothertable, MyClass, MyOtherClass, mytable = (self.tables.myothertable,
  524. self.classes.MyClass,
  525. self.classes.MyOtherClass,
  526. self.tables.mytable)
  527. mapper(MyOtherClass, myothertable)
  528. mapper(MyClass, mytable, properties={
  529. 'children': relationship(MyOtherClass,
  530. passive_deletes='all',
  531. cascade="save-update")})
  532. session = create_session()
  533. mc = MyClass()
  534. mc.children.append(MyOtherClass())
  535. session.add(mc)
  536. session.flush()
  537. session.expunge_all()
  538. assert myothertable.count().scalar() == 1
  539. mc = session.query(MyClass).get(mc.id)
  540. session.delete(mc)
  541. mc.children[0].data = 'some new data'
  542. assert_raises(sa.exc.DBAPIError, session.flush)
  543. def test_dont_emit(self):
  544. myothertable, MyClass, MyOtherClass, mytable = (self.tables.myothertable,
  545. self.classes.MyClass,
  546. self.classes.MyOtherClass,
  547. self.tables.mytable)
  548. mapper(MyOtherClass, myothertable)
  549. mapper(MyClass, mytable, properties={
  550. 'children': relationship(MyOtherClass,
  551. passive_deletes='all',
  552. cascade="save-update")})
  553. session = Session()
  554. mc = MyClass()
  555. session.add(mc)
  556. session.commit()
  557. mc.id
  558. session.delete(mc)
  559. # no load for "children" should occur
  560. self.assert_sql_count(testing.db, session.flush, 1)
  561. class ColumnCollisionTest(fixtures.MappedTest):
  562. """Ensure the mapper doesn't break bind param naming rules on flush."""
  563. @classmethod
  564. def define_tables(cls, metadata):
  565. Table('book', metadata,
  566. Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
  567. Column('book_id', String(50)),
  568. Column('title', String(50))
  569. )
  570. def test_naming(self):
  571. book = self.tables.book
  572. class Book(fixtures.ComparableEntity):
  573. pass
  574. mapper(Book, book)
  575. sess = create_session()
  576. b1 = Book(book_id='abc', title='def')
  577. sess.add(b1)
  578. sess.flush()
  579. b1.title = 'ghi'
  580. sess.flush()
  581. sess.close()
  582. eq_(
  583. sess.query(Book).first(),
  584. Book(book_id='abc', title='ghi')
  585. )
  586. class DefaultTest(fixtures.MappedTest):
  587. """Exercise mappings on columns with DefaultGenerators.
  588. Tests that when saving objects whose table contains DefaultGenerators,
  589. either python-side, preexec or database-side, the newly saved instances
  590. receive all the default values either through a post-fetch or getting the
  591. pre-exec'ed defaults back from the engine.
  592. """
  593. @classmethod
  594. def define_tables(cls, metadata):
  595. use_string_defaults = testing.against('postgresql', 'oracle', 'sqlite', 'mssql')
  596. if use_string_defaults:
  597. hohotype = String(30)
  598. hohoval = "im hoho"
  599. althohoval = "im different hoho"
  600. else:
  601. hohotype = Integer
  602. hohoval = 9
  603. althohoval = 15
  604. cls.other['hohoval'] = hohoval
  605. cls.other['althohoval'] = althohoval
  606. dt = Table('default_t', metadata,
  607. Column('id', Integer, primary_key=True,
  608. test_needs_autoincrement=True),
  609. Column('hoho', hohotype, server_default=str(hohoval)),
  610. Column('counter', Integer, default=sa.func.char_length("1234567", type_=Integer)),
  611. Column('foober', String(30), default="im foober", onupdate="im the update"),
  612. mysql_engine='MyISAM')
  613. st = Table('secondary_table', metadata,
  614. Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
  615. Column('data', String(50)),
  616. mysql_engine='MyISAM')
  617. if testing.against('postgresql', 'oracle'):
  618. dt.append_column(
  619. Column('secondary_id', Integer, sa.Sequence('sec_id_seq'),
  620. unique=True))
  621. st.append_column(
  622. Column('fk_val', Integer,
  623. ForeignKey('default_t.secondary_id')))
  624. elif testing.against('mssql'):
  625. st.append_column(
  626. Column('fk_val', Integer,
  627. ForeignKey('default_t.id')))
  628. else:
  629. st.append_column(
  630. Column('hoho', hohotype, ForeignKey('default_t.hoho')))
  631. @classmethod
  632. def setup_classes(cls):
  633. class Hoho(cls.Comparable):
  634. pass
  635. class Secondary(cls.Comparable):
  636. pass
  637. @testing.fails_on('firebird', 'Data type unknown on the parameter')
  638. def test_insert(self):
  639. althohoval, hohoval, default_t, Hoho = (self.other.althohoval,
  640. self.other.hohoval,
  641. self.tables.default_t,
  642. self.classes.Hoho)
  643. mapper(Hoho, default_t)
  644. h1 = Hoho(hoho=althohoval)
  645. h2 = Hoho(counter=12)
  646. h3 = Hoho(hoho=althohoval, counter=12)
  647. h4 = Hoho()
  648. h5 = Hoho(foober='im the new foober')
  649. session = create_session(autocommit=False)
  650. session.add_all((h1, h2, h3, h4, h5))
  651. session.commit()
  652. eq_(h1.hoho, althohoval)
  653. eq_(h3.hoho, althohoval)
  654. def go():
  655. # test deferred load of attribues, one select per instance
  656. self.assert_(h2.hoho == h4.hoho == h5.hoho == hohoval)
  657. self.sql_count_(3, go)
  658. def go():
  659. self.assert_(h1.counter == h4.counter == h5.counter == 7)
  660. self.sql_count_(1, go)
  661. def go():
  662. self.assert_(h3.counter == h2.counter == 12)
  663. self.assert_(h2.foober == h3.foober == h4.foober == 'im foober')
  664. self.assert_(h5.foober == 'im the new foober')
  665. self.sql_count_(0, go)
  666. session.expunge_all()
  667. (h1, h2, h3, h4, h5) = session.query(Hoho).order_by(Hoho.id).all()
  668. eq_(h1.hoho, althohoval)
  669. eq_(h3.hoho, althohoval)
  670. self.assert_(h2.hoho == h4.hoho == h5.hoho == hohoval)
  671. self.assert_(h3.counter == h2.counter == 12)
  672. self.assert_(h1.counter == h4.counter == h5.counter == 7)
  673. self.assert_(h2.foober == h3.foober == h4.foober == 'im foober')
  674. eq_(h5.foober, 'im the new foober')
  675. @testing.fails_on('firebird', 'Data type unknown on the parameter')
  676. def test_eager_defaults(self):
  677. hohoval, default_t, Hoho = (self.other.hohoval,
  678. self.tables.default_t,
  679. self.classes.Hoho)
  680. mapper(Hoho, default_t, eager_defaults=True)
  681. h1 = Hoho()
  682. session = create_session()
  683. session.add(h1)
  684. session.flush()
  685. self.sql_count_(0, lambda: eq_(h1.hoho, hohoval))
  686. def test_insert_nopostfetch(self):
  687. default_t, Hoho = self.tables.default_t, self.classes.Hoho
  688. # populates from the FetchValues explicitly so there is no
  689. # "post-update"
  690. mapper(Hoho, default_t)
  691. h1 = Hoho(hoho="15", counter=15)
  692. session = create_session()
  693. session.add(h1)
  694. session.flush()
  695. def go():
  696. eq_(h1.hoho, "15")
  697. eq_(h1.counter, 15)
  698. eq_(h1.foober, "im foober")
  699. self.sql_count_(0, go)
  700. @testing.fails_on('firebird', 'Data type unknown on the parameter')
  701. def test_update(self):
  702. default_t, Hoho = self.tables.default_t, self.classes.Hoho
  703. mapper(Hoho, default_t)
  704. h1 = Hoho()
  705. session = create_session()
  706. session.add(h1)
  707. session.flush()
  708. eq_(h1.foober, 'im foober')
  709. h1.counter = 19
  710. session.flush()
  711. eq_(h1.foober, 'im the update')
  712. @testing.fails_on('firebird', 'Data type unknown on the parameter')
  713. def test_used_in_relationship(self):
  714. """A server-side default can be used as the target of a foreign key"""
  715. Hoho, hohoval, default_t, secondary_table, Secondary = (self.classes.Hoho,
  716. self.other.hohoval,
  717. self.tables.default_t,
  718. self.tables.secondary_table,
  719. self.classes.Secondary)
  720. mapper(Hoho, default_t, properties={
  721. 'secondaries':relationship(Secondary, order_by=secondary_table.c.id)})
  722. mapper(Secondary, secondary_table)
  723. h1 = Hoho()
  724. s1 = Secondary(data='s1')
  725. h1.secondaries.append(s1)
  726. session = create_session()
  727. session.add(h1)
  728. session.flush()
  729. session.expunge_all()
  730. eq_(session.query(Hoho).get(h1.id),
  731. Hoho(hoho=hohoval,
  732. secondaries=[
  733. Secondary(data='s1')]))
  734. h1 = session.query(Hoho).get(h1.id)
  735. h1.secondaries.append(Secondary(data='s2'))
  736. session.flush()
  737. session.expunge_all()
  738. eq_(session.query(Hoho).get(h1.id),
  739. Hoho(hoho=hohoval,
  740. secondaries=[
  741. Secondary(data='s1'),
  742. Secondary(data='s2')]))
  743. class ColumnPropertyTest(fixtures.MappedTest):
  744. @classmethod
  745. def define_tables(cls, metadata):
  746. Table('data', metadata,
  747. Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
  748. Column('a', String(50)),
  749. Column('b', String(50))
  750. )
  751. Table('subdata', metadata,
  752. Column('id', Integer, ForeignKey('data.id'), primary_key=True),
  753. Column('c', String(50)),
  754. )
  755. @classmethod
  756. def setup_mappers(cls):
  757. class Data(cls.Basic):
  758. pass
  759. def test_refreshes(self):
  760. Data, data = self.classes.Data, self.tables.data
  761. mapper(Data, data, properties={
  762. 'aplusb':column_property(data.c.a + literal_column("' '") + data.c.b)
  763. })
  764. self._test(True)
  765. def test_no_refresh(self):
  766. Data, data = self.classes.Data, self.tables.data
  767. mapper(Data, data, properties={
  768. 'aplusb':column_property(data.c.a + literal_column("' '") + data.c.b,
  769. expire_on_flush=False)
  770. })
  771. self._test(False)
  772. def test_refreshes_post_init(self):
  773. Data, data = self.classes.Data, self.tables.data
  774. m = mapper(Data, data)
  775. m.add_property('aplusb', column_property(data.c.a + literal_column("' '") + data.c.b))
  776. self._test(True)
  777. def test_with_inheritance(self):
  778. subdata, data, Data = (self.tables.subdata,
  779. self.tables.data,
  780. self.classes.Data)
  781. class SubData(Data):
  782. pass
  783. mapper(Data, data, properties={
  784. 'aplusb':column_property(data.c.a + literal_column("' '") + data.c.b)
  785. })
  786. mapper(SubData, subdata, inherits=Data)
  787. sess = create_session()
  788. sd1 = SubData(a="hello", b="there", c="hi")
  789. sess.add(sd1)
  790. sess.flush()
  791. eq_(sd1.aplusb, "hello there")
  792. def _test(self, expect_expiry):
  793. Data = self.classes.Data
  794. sess = create_session()
  795. d1 = Data(a="hello", b="there")
  796. sess.add(d1)
  797. sess.flush()
  798. eq_(d1.aplusb, "hello there")
  799. d1.b = "bye"
  800. sess.flush()
  801. if expect_expiry:
  802. eq_(d1.aplusb, "hello bye")
  803. else:
  804. eq_(d1.aplusb, "hello there")
  805. d1.b = 'foobar'
  806. d1.aplusb = 'im setting this explicitly'
  807. sess.flush()
  808. eq_(d1.aplusb, "im setting this explicitly")
  809. class OneToManyTest(_fixtures.FixtureTest):
  810. run_inserts = None
  811. def test_one_to_many_1(self):
  812. """Basic save of one to many."""
  813. Address, addresses, users, User = (self.classes.Address,
  814. self.tables.addresses,
  815. self.tables.users,
  816. self.classes.User)
  817. m = mapper(User, users, properties=dict(
  818. addresses = relationship(mapper(Address, addresses), lazy='select')
  819. ))
  820. u = User(name= 'one2manytester')
  821. a = Address(email_address='one2many@test.org')
  822. u.addresses.append(a)
  823. a2 = Address(email_address='lala@test.org')
  824. u.addresses.append(a2)
  825. session = create_session()
  826. session.add(u)
  827. session.flush()
  828. user_rows = users.select(users.c.id.in_([u.id])).execute().fetchall()
  829. eq_(user_rows[0].values(), [u.id, 'one2manytester'])
  830. address_rows = addresses.select(
  831. addresses.c.id.in_([a.id, a2.id]),
  832. order_by=[addresses.c.email_address]).execute().fetchall()
  833. eq_(address_rows[0].values(), [a2.id, u.id, 'lala@test.org'])
  834. eq_(address_rows[1].values(), [a.id, u.id, 'one2many@test.org'])
  835. userid = u.id
  836. addressid = a2.id
  837. a2.email_address = 'somethingnew@foo.com'
  838. session.flush()
  839. address_rows = addresses.select(
  840. addresses.c.id == addressid).execute().fetchall()
  841. eq_(address_rows[0].values(),
  842. [addressid, userid, 'somethingnew@foo.com'])
  843. self.assert_(u.id == userid and a2.id == addressid)
  844. def test_one_to_many_2(self):
  845. """Modifying the child items of an object."""
  846. Address, addresses, users, User = (self.classes.Address,
  847. self.tables.addresses,
  848. self.tables.users,
  849. self.classes.User)
  850. m = mapper(User, users, properties=dict(
  851. addresses = relationship(mapper(Address, addresses), lazy='select')))
  852. u1 = User(name='user1')
  853. u1.addresses = []
  854. a1 = Address(email_address='emailaddress1')
  855. u1.addresses.append(a1)
  856. u2 = User(name='user2')
  857. u2.addresses = []
  858. a2 = Address(email_address='emailaddress2')
  859. u2.addresses.append(a2)
  860. a3 = Address(email_address='emailaddress3')
  861. session = create_session()
  862. session.add_all((u1, u2, a3))
  863. session.flush()
  864. # modify user2 directly, append an address to user1.
  865. # upon commit, user2 should be updated, user1 should not
  866. # both address1 and address3 should be updated
  867. u2.name = 'user2modified'
  868. u1.addresses.append(a3)
  869. del u1.addresses[0]
  870. self.assert_sql(testing.db, session.flush, [
  871. ("UPDATE users SET name=:name "
  872. "WHERE users.id = :users_id",
  873. {'users_id': u2.id, 'name': 'user2modified'}),
  874. ("UPDATE addresses SET user_id=:user_id "
  875. "WHERE addresses.id = :addresses_id",
  876. {'user_id': None, 'addresses_id': a1.id}),
  877. ("UPDATE addresses SET user_id=:user_id "
  878. "WHERE addresses.id = :addresses_id",
  879. {'user_id': u1.id, 'addresses_id': a3.id})])
  880. def test_child_move(self):
  881. """Moving a child from one parent to another, with a delete.
  882. Tests that deleting the first parent properly updates the child with
  883. the new parent. This tests the 'trackparent' option in the attributes
  884. module.
  885. """
  886. Address, addresses, users, User = (self.classes.Address,
  887. self.tables.addresses,
  888. self.tables.users,
  889. self.classes.User)
  890. m = mapper(User, users, properties=dict(
  891. addresses = relationship(mapper(Address, addresses), lazy='select')))
  892. u1 = User(name='user1')
  893. u2 = User(name='user2')
  894. a = Address(email_address='address1')
  895. u1.addresses.append(a)
  896. session = create_session()
  897. session.add_all((u1, u2))
  898. session.flush()
  899. del u1.addresses[0]
  900. u2.addresses.append(a)
  901. session.delete(u1)
  902. session.flush()
  903. session.expunge_all()
  904. u2 = session.query(User).get(u2.id)
  905. eq_(len(u2.addresses), 1)
  906. def test_child_move_2(self):
  907. Address, addresses, users, User = (self.classes.Address,
  908. self.tables.addresses,
  909. self.tables.users,
  910. self.classes.User)
  911. m = mapper(User, users, properties=dict(
  912. addresses = relationship(mapper(Address, addresses), lazy='select')))
  913. u1 = User(name='user1')
  914. u2 = User(name='user2')
  915. a = Address(email_address='address1')
  916. u1.addresses.append(a)
  917. session = create_session()
  918. session.add_all((u1, u2))
  919. session.flush()
  920. del u1.addresses[0]
  921. u2.addresses.append(a)
  922. session.flush()
  923. session.expunge_all()
  924. u2 = session.query(User).get(u2.id)
  925. eq_(len(u2.addresses), 1)
  926. def test_o2m_delete_parent(self):
  927. Address, addresses, users, User = (self.classes.Address,
  928. self.tables.addresses,
  929. self.tables.users,
  930. self.classes.User)
  931. m = mapper(User, users, properties=dict(
  932. address = relationship(mapper(Address, addresses),
  933. lazy='select',
  934. uselist=False)))
  935. u = User(name='one2onetester')
  936. a = Address(email_address='myonlyaddress@foo.com')
  937. u.address = a
  938. session = create_session()
  939. session.add(u)
  940. session.flush()
  941. session.delete(u)
  942. session.flush()
  943. assert a.id is not None
  944. assert a.user_id is None
  945. assert sa.orm.attributes.instance_state(a).key in session.identity_map
  946. assert sa.orm.attributes.instance_state(u).key not in session.identity_map
  947. def test_one_to_one(self):
  948. Address, addresses, users, User = (self.classes.Address,
  949. self.tables.addresses,
  950. self.tables.users,
  951. self.classes.User)
  952. m = mapper(User, users, properties=dict(
  953. address = relationship(mapper(Address, addresses),
  954. lazy='select',
  955. uselist=False)))
  956. u = User(name='one2onetester')
  957. u.address = Address(email_address='myonlyaddress@foo.com')
  958. session = create_session()
  959. session.add(u)
  960. session.flush()
  961. u.name = 'imnew'
  962. session.flush()
  963. u.address.email_address = 'imnew@foo.com'
  964. session.flush()
  965. def test_bidirectional(self):
  966. users, Address, addresses, User = (self.tables.users,
  967. self.classes.Address,
  968. self.tables.addresses,
  969. self.classes.User)
  970. m1 = mapper(User, users)
  971. m2 = mapper(Address, addresses, properties=dict(
  972. user = relationship(m1, lazy='joined', backref='addresses')))
  973. u = User(name='test')
  974. a = Address(email_address='testaddress', user=u)
  975. session = create_session()
  976. session.add(u)
  977. session.flush()
  978. session.delete(u)
  979. session.flush()
  980. def test_double_relationship(self):
  981. Address, addresses, users, User = (self.classes.Address,
  982. self.tables.addresses,
  983. self.tables.users,
  984. self.classes.User)
  985. m2 = mapper(Address, addresses)
  986. m = mapper(User, users, properties={
  987. 'boston_addresses' : relationship(m2, primaryjoin=
  988. sa.and_(users.c.id==addresses.c.user_id,
  989. addresses.c.email_address.like('%boston%'))),
  990. 'newyork_addresses' : relationship(m2, primaryjoin=
  991. sa.and_(users.c.id==addresses.c.user_id,
  992. addresses.c.email_address.like('%newyork%')))})
  993. u = User(name='u1')
  994. a = Address(email_address='foo@boston.com')
  995. b = Address(email_address='bar@newyork.com')
  996. u.boston_addresses.append(a)
  997. u.newyork_addresses.append(b)
  998. session = create_session()
  999. session.add(u)
  1000. session.flush()
  1001. class SaveTest(_fixtures.FixtureTest):
  1002. run_inserts = None
  1003. def test_basic(self):
  1004. User, users = self.classes.User, self.tables.users
  1005. m = mapper(User, users)
  1006. # save two users
  1007. u = User(name='savetester')
  1008. u2 = User(name='savetester2')
  1009. session = create_session()
  1010. session.add_all((u, u2))
  1011. session.flush()
  1012. # assert the first one retreives the same from the identity map
  1013. nu = session.query(m).get(u.id)
  1014. assert u is nu
  1015. # clear out the identity map, so next get forces a SELECT
  1016. session.expunge_all()
  1017. # check it again, identity should be different but ids the same
  1018. nu = session.query(m).get(u.id)
  1019. assert u is not nu and u.id == nu.id and nu.name == 'savetester'
  1020. # change first users name and save
  1021. session = create_session()
  1022. session.add(u)
  1023. u.name = 'modifiedname'
  1024. assert u in session.dirty
  1025. session.flush()
  1026. # select both
  1027. userlist = session.query(User).filter(
  1028. users.c.id.in_([u.id, u2.id])).order_by(users.c.name).all()
  1029. eq_(u.id, userlist[0].id)
  1030. eq_(userlist[0].name, 'modifiedname')
  1031. eq_(u2.id, userlist[1].id)
  1032. eq_(userlist[1].name, 'savetester2')
  1033. def test_synonym(self):
  1034. users = self.tables.users
  1035. class SUser(fixtures.BasicEntity):
  1036. def _get_name(self):
  1037. return "User:" + self.name
  1038. def _set_name(self, name):
  1039. self.name = name + ":User"
  1040. syn_name = property(_get_name, _set_name)
  1041. mapper(SUser, users, properties={
  1042. 'syn_name': sa.orm.synonym('name')
  1043. })
  1044. u = SUser(syn_name="some name")
  1045. eq_(u.syn_name, 'User:some name:User')
  1046. session = create_session()
  1047. session.add(u)
  1048. session.flush()
  1049. session.expunge_all()
  1050. u = session.query(SUser).first()
  1051. eq_(u.syn_name, 'User:some name:User')
  1052. def test_lazyattr_commit(self):
  1053. """Lazily loaded relationships.
  1054. When a lazy-loaded list is unloaded, and a commit occurs, that the
  1055. 'passive' call on that list does not blow away its value
  1056. """
  1057. users, Address, addresses, User = (self.tables.users,
  1058. self.classes.Address,
  1059. self.tables.addresses,
  1060. self.classes.User)
  1061. mapper(User, users, properties = {
  1062. 'addresses': relationship(mapper(Address, addresses))})
  1063. u = User(name='u1')
  1064. u.addresses.append(Address(email_address='u1@e1'))
  1065. u.addresses.append(Address(email_address='u1@e2'))
  1066. u.addresses.append(Address(email_address='u1@e3'))
  1067. u.addresses.append(Address(email_address='u1@e4'))
  1068. session = create_session()
  1069. session.add(u)
  1070. session.flush()
  1071. session.expunge_all()
  1072. u = session.query(User).one()
  1073. u.name = 'newname'
  1074. session.flush()
  1075. eq_(len(u.addresses), 4)
  1076. def test_inherits(self):
  1077. """a user object that also has the users mailing address."""
  1078. users, addresses, User = (self.tables.users,
  1079. self.tables.addresses,
  1080. self.classes.User)
  1081. m1 = mapper(User, users)
  1082. class AddressUser(User):
  1083. pass
  1084. # define a mapper for AddressUser that inherits the User.mapper, and
  1085. # joins on the id column
  1086. mapper(AddressUser, addresses, inherits=m1)
  1087. au = AddressUser(name='u', email_address='u@e')
  1088. session = create_session()
  1089. session.add(au)
  1090. session.flush()
  1091. session.expunge_all()
  1092. rt = session.query(AddressUser).one()
  1093. eq_(au.user_id, rt.user_id)
  1094. eq_(rt.id, rt.id)
  1095. def test_deferred(self):
  1096. """Deferred column operations"""
  1097. orders, Order = self.tables.orders, self.classes.Order
  1098. mapper(Order, orders, properties={
  1099. 'description': sa.orm.deferred(orders.c.description)})
  1100. # dont set deferred attribute, commit session
  1101. o = Order(id=42)
  1102. session = create_session(autocommit=False)
  1103. session.add(o)
  1104. session.commit()
  1105. # assert that changes get picked up
  1106. o.description = 'foo'
  1107. session.commit()
  1108. eq_(list(session.execute(orders.select(), mapper=Order)),
  1109. [(42, None, None, 'foo', None)])
  1110. session.expunge_all()
  1111. # assert that a set operation doesn't trigger a load operation
  1112. o = session.query(Order).filter(Order.description == 'foo').one()
  1113. def go():
  1114. o.description = 'hoho'
  1115. self.sql_count_(0, go)
  1116. session.flush()
  1117. eq_(list(session.execute(orders.select(), mapper=Order)),
  1118. [(42, None, None, 'hoho', None)])
  1119. session.expunge_all()
  1120. # test assigning None to an unloaded deferred also works
  1121. o = session.query(Order).filter(Order.description == 'hoho').one()
  1122. o.description = None
  1123. session.flush()
  1124. eq_(list(session.execute(orders.select(), mapper=Order)),
  1125. [(42, None, None, None, None)])
  1126. session.close()
  1127. # why no support on oracle ? because oracle doesn't save
  1128. # "blank" strings; it saves a single space character.
  1129. @testing.fails_on('oracle', 'FIXME: unknown')
  1130. def test_dont_update_blanks(self):
  1131. User, users = self.classes.User, self.tables.users
  1132. mapper(User, users)
  1133. u = User(name='')
  1134. session = create_session()
  1135. session.add(u)
  1136. session.flush()
  1137. session.expunge_all()
  1138. u = session.query(User).get(u.id)
  1139. u.name = ''
  1140. self.sql_count_(0, session.flush)
  1141. def test_multi_table_selectable(self):
  1142. """Mapped selectables that span tables.
  1143. Also tests redefinition of the keynames for the column properties.
  1144. """
  1145. addresses, users, User = (self.tables.addresses,
  1146. self.tables.users,
  1147. self.classes.User)
  1148. usersaddresses = sa.join(users, addresses,
  1149. users.c.id == addresses.c.user_id)
  1150. m = mapper(User, usersaddresses,
  1151. properties=dict(
  1152. email = addresses.c.email_address,
  1153. foo_id = [users.c.id, addresses.c.user_id]))
  1154. u = User(name='multitester', email='multi@test.org')
  1155. session = create_session()
  1156. session.add(u)
  1157. session.flush()
  1158. session.expunge_all()
  1159. id = m.primary_key_from_instance(u)
  1160. u = session.query(User).get(id)
  1161. assert u.name == 'multitester'
  1162. user_rows = users.select(users.c.id.in_([u.foo_id])).execute().fetchall()
  1163. eq_(user_rows[0].values(), [u.foo_id, 'multitester'])
  1164. address_rows = addresses.select(addresses.c.id.in_([u.id])).execute().fetchall()
  1165. eq_(address_rows[0].values(), [u.id, u.foo_id, 'multi@test.org'])
  1166. u.email = 'lala@hey.com'
  1167. u.name = 'imnew'
  1168. session.flush()
  1169. user_rows = users.select(users.c.id.in_([u.foo_id])).execute().fetchall()
  1170. eq_(user_rows[0].values(), [u.foo_id, 'imnew'])
  1171. address_rows = addresses.select(addresses.c.id.in_([u.id])).execute().fetchall()
  1172. eq_(address_rows[0].values(), [u.id, u.foo_id, 'lala@hey.com'])
  1173. session.expunge_all()
  1174. u = session.query(User).get(id)
  1175. assert u.name == 'imnew'
  1176. def test_history_get(self):
  1177. """The history lazy-fetches data when it wasn't otherwise loaded."""
  1178. users, Address, addresses, User = (self.tables.users,
  1179. self.classes.Address,
  1180. self.tables.addresses,
  1181. self.classes.User)
  1182. mapper(User, users, properties={
  1183. 'addresses':relationship(Address, cascade="all, delete-orphan")})
  1184. mapper(Address, addresses)
  1185. u = User(name='u1')
  1186. u.addresses.append(Address(email_address='u1@e1'))
  1187. u.addresses.append(Address(email_address='u1@e2'))
  1188. session = create_session()
  1189. session.add(u)
  1190. session.flush()
  1191. session.expunge_all()
  1192. u = session.query(User).get(u.id)
  1193. session.delete(u)
  1194. session.flush()
  1195. assert users.count().scalar() == 0
  1196. assert addresses.count().scalar() == 0
  1197. def test_batch_mode(self):
  1198. """The 'batch=False' flag on mapper()"""
  1199. users, User = self.tables.users, self.classes.User
  1200. names = []
  1201. class Events(object):
  1202. def before_insert(self, mapper, connection, instance):
  1203. self.current_instance = instance
  1204. names.append(instance.name)
  1205. def after_insert(self, mapper, connection, instance):
  1206. assert instance is self.current_instance
  1207. mapper(User, users, batch=False)
  1208. evt = Events()
  1209. event.listen(User, "before_insert", evt.before_insert)
  1210. event.listen(User, "after_insert", evt.after_insert)
  1211. u1 = User(name='user1')
  1212. u2 = User(name='user2')
  1213. session = create_session()
  1214. session.add_all((u1, u2))
  1215. session.flush()
  1216. u3 = User(name='user3')
  1217. u4 = User(name='user4')
  1218. u5 = User(name='user5')
  1219. session.add_all([u4, u5, u3])
  1220. session.flush()
  1221. # test insert ordering is maintained
  1222. assert names == ['user1', 'user2', 'user4', 'user5', 'user3']
  1223. session.expunge_all()
  1224. sa.orm.clear_mappers()
  1225. m = mapper(User, users)
  1226. evt = Events()
  1227. event.listen(User, "before_insert", evt.before_insert)
  1228. event.listen(User, "after_insert", evt.after_insert)
  1229. u1 = User(name='user1')
  1230. u2 = User(name='user2')
  1231. session.add_all((u1, u2))
  1232. assert_raises(AssertionError, session.flush)
  1233. class ManyToOneTest(_fixtures.FixtureTest):
  1234. run_inserts = None
  1235. def test_m2o_one_to_one(self):
  1236. users, Address, addresses, User = (self.tables.users,
  1237. self.classes.Address,
  1238. self.tables.addresses,
  1239. self.classes.User)
  1240. # TODO: put assertion in here !!!
  1241. m = mapper(Address, addresses, properties=dict(
  1242. user = relationship(mapper(User, users), lazy='select', uselist=False)))
  1243. session = create_session()
  1244. data = [
  1245. {'name': 'thesub' , 'email_address': 'bar@foo.com'},
  1246. {'name': 'assdkfj' , 'email_address': 'thesdf@asdf.com'},
  1247. {'name': 'n4knd' , 'email_address': 'asf3@bar.org'},
  1248. {'name': 'v88f4' , 'email_address': 'adsd5@llala.net'},
  1249. {'name': 'asdf8d' , 'email_address': 'theater@foo.com'}
  1250. ]
  1251. objects = []
  1252. for elem in data:
  1253. a = Address()
  1254. a.email_address = elem['email_address']
  1255. a.user = User()
  1256. a.user.name = elem['name']
  1257. objects.append(a)
  1258. session.add(a)
  1259. session.flush()
  1260. objects[2].email_address = 'imnew@foo.bar'
  1261. objects[3].user = User()
  1262. objects[3].user.name = 'imnewlyadded'
  1263. self.assert_sql_execution(testing.db,
  1264. session.flush,
  1265. CompiledSQL("INSERT INTO users (name) VALUES (:name)",
  1266. {'name': 'imnewlyadded'} ),
  1267. AllOf(
  1268. CompiledSQL("UPDATE addresses SET email_address=:email_address "
  1269. "WHERE addresses.id = :addresses_id",
  1270. lambda ctx: {'email_address': 'imnew@foo.bar',
  1271. 'addresses_id': objects[2].id}),
  1272. CompiledSQL("UPDATE addresses SET user_id=:user_id "
  1273. "WHERE addresses.id = :addresses_id",
  1274. lambda ctx: {'user_id': objects[3].user.id,
  1275. 'addresses_id': objects[3].id})
  1276. )
  1277. )
  1278. l = sa.select([users, addresses],
  1279. sa.and_(users.c.id==addresses.c.user_id,
  1280. addresses.c.id==a.id)).execute()
  1281. eq_(l.first().values(),
  1282. [a.user.id, 'asdf8d', a.id, a.user_id, 'theater@foo.com'])
  1283. def test_many_to_one_1(self):
  1284. users, Address, addresses, User = (self.tables.users,
  1285. self.classes.Address,
  1286. self.tables.addresses,
  1287. self.classes.User)
  1288. m = mapper(Address, addresses, properties=dict(
  1289. user = relationship(mapper(User, users), lazy='select')))
  1290. a1 = Address(email_address='emailaddress1')
  1291. u1 = User(name='user1')
  1292. a1.user = u1
  1293. session = create_session()
  1294. session.add(a1)
  1295. session.flush()
  1296. session.expunge_all()
  1297. a1 = session.query(Address).get(a1.id)
  1298. u1 = session.query(User).get(u1.id)
  1299. assert a1.user is u1
  1300. a1.user = None
  1301. session.flush()
  1302. session.expunge_all()
  1303. a1 = session.query(Address).get(a1.id)
  1304. u1 = session.query(User).get(u1.id)
  1305. assert a1.user is None
  1306. def test_many_to_one_2(self):
  1307. users, Address, addresses, User = (self.tables.users,
  1308. self.classes.Address,
  1309. self.tables.addresses,
  1310. self.classes.User)
  1311. m = mapper(Address, addresses, properties=dict(
  1312. user = relationship(mapper(User, users), lazy='select')))
  1313. a1 = Address(email_address='emailaddress1')
  1314. a2 = Address(email_address='emailaddress2')
  1315. u1 = User(name='user1')
  1316. a1.user = u1
  1317. session = create_session()
  1318. session.add_all((a1, a2))
  1319. session.flush()
  1320. session.expunge_all()
  1321. a1 = session.query(Address).get(a1.id)
  1322. a2 = session.query(Address).get(a2.id)
  1323. u1 = session.query(User).get(u1.id)
  1324. assert a1.user is u1
  1325. a1.user = None
  1326. a2.user = u1
  1327. session.flush()
  1328. session.expunge_all()
  1329. a1 = session.query(Address).get(a1.id)
  1330. a2 = session.query(Address).get(a2.id)
  1331. u1 = session.query(User).get(u1.id)
  1332. assert a1.user is None
  1333. assert a2.user is u1
  1334. def test_many_to_one_3(self):
  1335. users, Address, addresses, User = (self.tables.users,
  1336. self.classes.Address,
  1337. self.tables.addresses,
  1338. self.classes.User)
  1339. m = mapper(Address, addresses, properties=dict(
  1340. user = relationship(mapper(User, users), lazy='select')))
  1341. a1 = Address(email_address='emailaddress1')
  1342. u1 = User(name='user1')
  1343. u2 = User(name='user2')
  1344. a1.user = u1
  1345. session = create_session()
  1346. session.add_all((a1, u1, u2))
  1347. session.flush()
  1348. session.expunge_all()
  1349. a1 = session.query(Address).get(a1.id)
  1350. u1 = session.query(User).get(u1.id)
  1351. u2 = session.query(User).get(u2.id)
  1352. assert a1.user is u1
  1353. a1.user = u2
  1354. session.flush()
  1355. session.expunge_all()
  1356. a1 = session.query(Address).get(a1.id)
  1357. u1 = session.query(User).get(u1.id)
  1358. u2 = session.query(User).get(u2.id)
  1359. assert a1.user is u2
  1360. def test_bidirectional_no_load(self):
  1361. users, Address, addresses, User = (self.tables.users,
  1362. self.classes.Address,
  1363. self.tables.addresses,
  1364. self.classes.User)
  1365. mapper(User, users, properties={
  1366. 'addresses':relationship(Address, backref='user', lazy='noload')})
  1367. mapper(Address, addresses)
  1368. # try it on unsaved objects
  1369. u1 = User(name='u1')
  1370. a1 = Address(email_address='e1')
  1371. a1.user = u1
  1372. session = create_session()
  1373. session.add(u1)
  1374. session.flush()
  1375. session.expunge_all()
  1376. a1 = session.query(Address).get(a1.id)
  1377. a1.user = None
  1378. session.flush()
  1379. session.expunge_all()
  1380. assert session.query(Address).get(a1.id).user is None
  1381. assert session.query(User).get(u1.id).addresses == []
  1382. class ManyToManyTest(_fixtures.FixtureTest):
  1383. run_inserts = None
  1384. def test_many_to_many(self):
  1385. keywords, items, item_keywords, Keyword, Item = (self.tables.keywords,
  1386. self.tables.items,
  1387. self.tables.item_keywords,
  1388. self.classes.Keyword,
  1389. self.classes.Item)
  1390. mapper(Keyword, keywords)
  1391. m = mapper(Item, items, properties=dict(
  1392. keywords=relationship(Keyword,
  1393. item_keywords,
  1394. lazy='joined',
  1395. order_by=keywords.c.name)))
  1396. data = [Item,
  1397. {'description': 'mm_item1',
  1398. 'keywords' : (Keyword, [{'name': 'big'},
  1399. {'name': 'green'},
  1400. {'name': 'purple'},
  1401. {'name': 'round'}])},
  1402. {'description': 'mm_item2',
  1403. 'keywords' : (Keyword, [{'name':'blue'},
  1404. {'name':'imnew'},
  1405. {'name':'round'},
  1406. {'name':'small'}])},
  1407. {'description': 'mm_item3',
  1408. 'keywords' : (Keyword, [])},
  1409. {'description': 'mm_item4',
  1410. 'keywords' : (Keyword, [{'name':'big'},
  1411. {'name':'blue'},])},
  1412. {'description': 'mm_item5',
  1413. 'keywords' : (Keyword, [{'name':'big'},
  1414. {'name':'exacting'},
  1415. {'name':'green'}])},
  1416. {'description': 'mm_item6',
  1417. 'keywords' : (Keyword, [{'name':'red'},
  1418. {'name':'round'},
  1419. {'name':'small'}])}]
  1420. session = create_session()
  1421. objects = []
  1422. _keywords = dict([(k.name, k) for k in session.query(Keyword)])
  1423. for elem in data[1:]:
  1424. item = Item(description=elem['description'])
  1425. objects.append(item)
  1426. for spec in elem['keywords'][1]:
  1427. keyword_name = spec['name']
  1428. try:
  1429. kw = _keywords[keyword_name]
  1430. except KeyError:
  1431. _keywords[keyword_name] = kw = Keyword(name=keyword_name)
  1432. item.keywords.append(kw)
  1433. session.add_all(objects)
  1434. session.flush()
  1435. l = (session.query(Item).
  1436. filter(Item.description.in_([e['description']
  1437. for e in data[1:]])).
  1438. order_by(Item.description).all())
  1439. self.assert_result(l, *data)
  1440. objects[4].description = 'item4updated'
  1441. k = Keyword()
  1442. k.name = 'yellow'
  1443. objects[5].keywords.append(k)
  1444. self.assert_sql_execution(
  1445. testing.db,
  1446. session.flush,
  1447. AllOf(
  1448. CompiledSQL("UPDATE items SET description=:description "
  1449. "WHERE items.id = :items_id",
  1450. {'description': 'item4updated',
  1451. 'items_id': objects[4].id},
  1452. ),
  1453. CompiledSQL("INSERT INTO keywords (name) "
  1454. "VALUES (:name)",
  1455. {'name': 'yellow'},
  1456. )
  1457. ),
  1458. CompiledSQL("INSERT INTO item_keywords (item_id, keyword_id) "
  1459. "VALUES (:item_id, :keyword_id)",
  1460. lambda ctx: [{'item_id': objects[5].id,
  1461. 'keyword_id': k.id}])
  1462. )
  1463. objects[2].keywords.append(k)
  1464. dkid = objects[5].keywords[1].id
  1465. del objects[5].keywords[1]
  1466. self.assert_sql_execution(
  1467. testing.db,
  1468. session.flush,
  1469. CompiledSQL("DELETE FROM item_keywords "
  1470. "WHERE item_keywords.item_id = :item_id AND "
  1471. "item_keywords.keyword_id = :keyword_id",
  1472. [{'item_id': objects[5].id, 'keyword_id': dkid}]),
  1473. CompiledSQL("INSERT INTO item_keywords (item_id, keyword_id) "
  1474. "VALUES (:item_id, :keyword_id)",
  1475. lambda ctx: [{'item_id': objects[2].id, 'keyword_id': k.id}]
  1476. ))
  1477. session.delete(objects[3])
  1478. session.flush()
  1479. def test_many_to_many_remove(self):
  1480. """Setting a collection to empty deletes many-to-many rows.
  1481. Tests that setting a list-based attribute to '[]' properly affects the
  1482. history and allows the many-to-many rows to be deleted
  1483. """
  1484. keywords, items, item_keywords, Keyword, Item = (self.tables.keywords,
  1485. self.tables.items,
  1486. self.tables.item_keywords,
  1487. self.classes.Keyword,
  1488. self.classes.Item)
  1489. mapper(Keyword, keywords)
  1490. mapper(Item, items, properties=dict(
  1491. keywords = relationship(Keyword, item_keywords, lazy='joined'),
  1492. ))
  1493. i = Item(description='i1')
  1494. k1 = Keyword(name='k1')
  1495. k2 = Keyword(name='k2')
  1496. i.keywords.append(k1)
  1497. i.keywords.append(k2)
  1498. session = create_session()
  1499. session.add(i)
  1500. session.flush()
  1501. assert item_keywords.count().scalar() == 2
  1502. i.keywords = []
  1503. session.flush()
  1504. assert item_keywords.count().scalar() == 0
  1505. def test_scalar(self):
  1506. """sa.dependency won't delete an m2m relationship referencing None."""
  1507. keywords, items, item_keywords, Keyword, Item = (self.tables.keywords,
  1508. self.tables.items,
  1509. self.tables.item_keywords,
  1510. self.classes.Keyword,
  1511. self.classes.Item)
  1512. mapper(Keyword, keywords)
  1513. mapper(Item, items, properties=dict(
  1514. keyword=relationship(Keyword, secondary=item_keywords, uselist=False)))
  1515. i = Item(description='x')
  1516. session = create_session()
  1517. session.add(i)
  1518. session.flush()
  1519. session.delete(i)
  1520. session.flush()
  1521. def test_many_to_many_update(self):
  1522. """Assorted history operations on a many to many"""
  1523. keywords, items, item_keywords, Keyword, Item = (self.tables.keywords,
  1524. self.tables.items,
  1525. self.tables.item_keywords,
  1526. self.classes.Keyword,
  1527. self.classes.Item)
  1528. mapper(Keyword, keywords)
  1529. mapper(Item, items, properties=dict(
  1530. keywords=relationship(Keyword,
  1531. secondary=item_keywords,
  1532. lazy='joined',
  1533. order_by=keywords.c.name)))
  1534. k1 = Keyword(name='keyword 1')
  1535. k2 = Keyword(name='keyword 2')
  1536. k3 = Keyword(name='keyword 3')
  1537. item = Item(description='item 1')
  1538. item.keywords.extend([k1, k2, k3])
  1539. session = create_session()
  1540. session.add(item)
  1541. session.flush()
  1542. item.keywords = []
  1543. item.keywords.append(k1)
  1544. item.keywords.append(k2)
  1545. session.flush()
  1546. session.expunge_all()
  1547. item = session.query(Item).get(item.id)
  1548. assert item.keywords == [k1, k2]
  1549. def test_association(self):
  1550. """Basic test of an association object"""
  1551. keywords, items, item_keywords, Keyword, Item = (self.tables.keywords,
  1552. self.tables.items,
  1553. self.tables.item_keywords,
  1554. self.classes.Keyword,
  1555. self.classes.Item)
  1556. class IKAssociation(fixtures.ComparableEntity):
  1557. pass
  1558. mapper(Keyword, keywords)
  1559. # note that we are breaking a rule here, making a second
  1560. # mapper(Keyword, keywords) the reorganization of mapper construction
  1561. # affected this, but was fixed again
  1562. mapper(IKAssociation, item_keywords,
  1563. primary_key=[item_keywords.c.item_id, item_keywords.c.keyword_id],
  1564. properties=dict(
  1565. keyword=relationship(mapper(Keyword, keywords, non_primary=True),
  1566. lazy='joined',
  1567. uselist=False,
  1568. order_by=keywords.c.name # note here is a valid place where order_by can be used
  1569. ))) # on a scalar relationship(); to determine eager ordering of
  1570. # the parent object within its collection.
  1571. mapper(Item, items, properties=dict(
  1572. keywords=relationship(IKAssociation, lazy='joined')))
  1573. session = create_session()
  1574. def fixture():
  1575. _kw = dict([(k.name, k) for k in session.query(Keyword)])
  1576. for n in ('big', 'green', 'purple', 'round', 'huge',
  1577. 'violet', 'yellow', 'blue'):
  1578. if n not in _kw:
  1579. _kw[n] = Keyword(name=n)
  1580. def assocs(*names):
  1581. return [IKAssociation(keyword=kw)
  1582. for kw in [_kw[n] for n in names]]
  1583. return [
  1584. Item(description='a_item1',
  1585. keywords=assocs('big', 'green', 'purple', 'round')),
  1586. Item(description='a_item2',
  1587. keywords=assocs('huge', 'violet', 'yellow')),
  1588. Item(description='a_item3',
  1589. keywords=assocs('big', 'blue'))]
  1590. session.add_all(fixture())
  1591. session.flush()
  1592. eq_(fixture(), session.query(Item).order_by(Item.description).all())
  1593. class SaveTest2(_fixtures.FixtureTest):
  1594. run_inserts = None
  1595. def test_m2o_nonmatch(self):
  1596. users, Address, addresses, User = (self.tables.users,
  1597. self.classes.Address,
  1598. self.tables.addresses,
  1599. self.classes.User)
  1600. mapper(User, users)
  1601. mapper(Address, addresses, properties=dict(
  1602. user = relationship(User, lazy='select', uselist=False)))
  1603. session = create_session()
  1604. def fixture():
  1605. return [
  1606. Address(email_address='a1', user=User(name='u1')),
  1607. Address(email_address='a2', user=User(name='u2'))]
  1608. session.add_all(fixture())
  1609. self.assert_sql_execution(
  1610. testing.db,
  1611. session.flush,
  1612. CompiledSQL("INSERT INTO users (name) VALUES (:name)",
  1613. {'name': 'u1'}),
  1614. CompiledSQL("INSERT INTO users (name) VALUES (:name)",
  1615. {'name': 'u2'}),
  1616. CompiledSQL("INSERT INTO addresses (user_id, email_address) "
  1617. "VALUES (:user_id, :email_address)",
  1618. {'user_id': 1, 'email_address': 'a1'}),
  1619. CompiledSQL("INSERT INTO addresses (user_id, email_address) "
  1620. "VALUES (:user_id, :email_address)",
  1621. {'user_id': 2, 'email_address': 'a2'}),
  1622. )
  1623. class SaveTest3(fixtures.MappedTest):
  1624. @classmethod
  1625. def define_tables(cls, metadata):
  1626. Table('items', metadata,
  1627. Column('item_id', Integer, primary_key=True,
  1628. test_needs_autoincrement=True),
  1629. Column('item_name', String(50)))
  1630. Table('keywords', metadata,
  1631. Column('keyword_id', Integer, primary_key=True,
  1632. test_needs_autoincrement=True),
  1633. Column('name', String(50)))
  1634. Table('assoc', metadata,
  1635. Column('item_id', Integer, ForeignKey("items")),
  1636. Column('keyword_id', Integer, ForeignKey("keywords")),
  1637. Column('foo', sa.Boolean, default=True))
  1638. @classmethod
  1639. def setup_classes(cls):
  1640. class Keyword(cls.Basic):
  1641. pass
  1642. class Item(cls.Basic):
  1643. pass
  1644. def test_manytomany_xtracol_delete(self):
  1645. """A many-to-many on a table that has an extra column can properly delete rows from the table without referencing the extra column"""
  1646. keywords, items, assoc, Keyword, Item = (self.tables.keywords,
  1647. self.tables.items,
  1648. self.tables.assoc,
  1649. self.classes.Keyword,
  1650. self.classes.Item)
  1651. mapper(Keyword, keywords)
  1652. mapper(Item, items, properties=dict(
  1653. keywords = relationship(Keyword, secondary=assoc, lazy='joined'),))
  1654. i = Item()
  1655. k1 = Keyword()
  1656. k2 = Keyword()
  1657. i.keywords.append(k1)
  1658. i.keywords.append(k2)
  1659. session = create_session()
  1660. session.add(i)
  1661. session.flush()
  1662. assert assoc.count().scalar() == 2
  1663. i.keywords = []
  1664. print i.keywords
  1665. session.flush()
  1666. assert assoc.count().scalar() == 0
  1667. class BooleanColTest(fixtures.MappedTest):
  1668. @classmethod
  1669. def define_tables(cls, metadata):
  1670. Table('t1_t', metadata,
  1671. Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
  1672. Column('name', String(30)),
  1673. Column('value', sa.Boolean))
  1674. def test_boolean(self):
  1675. t1_t = self.tables.t1_t
  1676. # use the regular mapper
  1677. class T(fixtures.ComparableEntity):
  1678. pass
  1679. orm_mapper(T, t1_t, order_by=t1_t.c.id)
  1680. sess = create_session()
  1681. t1 = T(value=True, name="t1")
  1682. t2 = T(value=False, name="t2")
  1683. t3 = T(value=True, name="t3")
  1684. sess.add_all((t1, t2, t3))
  1685. sess.flush()
  1686. for clear in (False, True):
  1687. if clear:
  1688. sess.expunge_all()
  1689. eq_(sess.query(T).all(), [T(value=True, name="t1"), T(value=False, name="t2"), T(value=True, name="t3")])
  1690. if clear:
  1691. sess.expunge_all()
  1692. eq_(sess.query(T).filter(T.value==True).all(), [T(value=True, name="t1"),T(value=True, name="t3")])
  1693. if clear:
  1694. sess.expunge_all()
  1695. eq_(sess.query(T).filter(T.value==False).all(), [T(value=False, name="t2")])
  1696. t2 = sess.query(T).get(t2.id)
  1697. t2.value = True
  1698. sess.flush()
  1699. eq_(sess.query(T).filter(T.value==True).all(), [T(value=True, name="t1"), T(value=True, name="t2"), T(value=True, name="t3")])
  1700. t2.value = False
  1701. sess.flush()
  1702. eq_(sess.query(T).filter(T.value==True).all(), [T(value=True, name="t1"),T(value=True, name="t3")])
  1703. class RowSwitchTest(fixtures.MappedTest):
  1704. @classmethod
  1705. def define_tables(cls, metadata):
  1706. # parent
  1707. Table('t5', metadata,
  1708. Column('id', Integer, primary_key=True),
  1709. Column('data', String(30), nullable=False))
  1710. # onetomany
  1711. Table('t6', metadata,
  1712. Column('id', Integer, primary_key=True),
  1713. Column('data', String(30), nullable=False),
  1714. Column('t5id', Integer, ForeignKey('t5.id'),nullable=False))
  1715. # associated
  1716. Table('t7', metadata,
  1717. Column('id', Integer, primary_key=True),
  1718. Column('data', String(30), nullable=False))
  1719. #manytomany
  1720. Table('t5t7', metadata,
  1721. Column('t5id', Integer, ForeignKey('t5.id'),nullable=False),
  1722. Column('t7id', Integer, ForeignKey('t7.id'),nullable=False))
  1723. @classmethod
  1724. def setup_classes(cls):
  1725. class T5(cls.Comparable):
  1726. pass
  1727. class T6(cls.Comparable):
  1728. pass
  1729. class T7(cls.Comparable):
  1730. pass
  1731. def test_onetomany(self):
  1732. t6, T6, t5, T5 = (self.tables.t6,
  1733. self.classes.T6,
  1734. self.tables.t5,
  1735. self.classes.T5)
  1736. mapper(T5, t5, properties={
  1737. 't6s':relationship(T6, cascade="all, delete-orphan")
  1738. })
  1739. mapper(T6, t6)
  1740. sess = create_session()
  1741. o5 = T5(data='some t5', id=1)
  1742. o5.t6s.append(T6(data='some t6', id=1))
  1743. o5.t6s.append(T6(data='some other t6', id=2))
  1744. sess.add(o5)
  1745. sess.flush()
  1746. eq_(
  1747. list(sess.execute(t5.select(), mapper=T5)),
  1748. [(1, 'some t5')]
  1749. )
  1750. eq_(
  1751. list(sess.execute(t6.select().order_by(t6.c.id), mapper=T5)),
  1752. [(1, 'some t6', 1), (2, 'some other t6', 1)]
  1753. )
  1754. o6 = T5(data='some other t5', id=o5.id, t6s=[
  1755. T6(data='third t6', id=3),
  1756. T6(data='fourth t6', id=4),
  1757. ])
  1758. sess.delete(o5)
  1759. sess.add(o6)
  1760. sess.flush()
  1761. eq_(
  1762. list(sess.execute(t5.select(), mapper=T5)),
  1763. [(1, 'some other t5')]
  1764. )
  1765. eq_(
  1766. list(sess.execute(t6.select().order_by(t6.c.id), mapper=T5)),
  1767. [(3, 'third t6', 1), (4, 'fourth t6', 1)]
  1768. )
  1769. def test_manytomany(self):
  1770. t7, t5, t5t7, T5, T7 = (self.tables.t7,
  1771. self.tables.t5,
  1772. self.tables.t5t7,
  1773. self.classes.T5,
  1774. self.classes.T7)
  1775. mapper(T5, t5, properties={
  1776. 't7s':relationship(T7, secondary=t5t7, cascade="all")
  1777. })
  1778. mapper(T7, t7)
  1779. sess = create_session()
  1780. o5 = T5(data='some t5', id=1)
  1781. o5.t7s.append(T7(data='some t7', id=1))
  1782. o5.t7s.append(T7(data='some other t7', id=2))
  1783. sess.add(o5)
  1784. sess.flush()
  1785. assert list(sess.execute(t5.select(), mapper=T5)) == [(1, 'some t5')]
  1786. assert testing.rowset(sess.execute(t5t7.select(), mapper=T5)) == set([(1,1), (1, 2)])
  1787. assert list(sess.execute(t7.select(), mapper=T5)) == [(1, 'some t7'), (2, 'some other t7')]
  1788. o6 = T5(data='some other t5', id=1, t7s=[
  1789. T7(data='third t7', id=3),
  1790. T7(data='fourth t7', id=4),
  1791. ])
  1792. sess.delete(o5)
  1793. assert o5 in sess.deleted
  1794. assert o5.t7s[0] in sess.deleted
  1795. assert o5.t7s[1] in sess.deleted
  1796. sess.add(o6)
  1797. sess.flush()
  1798. assert list(sess.execute(t5.select(), mapper=T5)) == [(1, 'some other t5')]
  1799. assert list(sess.execute(t7.select(), mapper=T5)) == [(3, 'third t7'), (4, 'fourth t7')]
  1800. def test_manytoone(self):
  1801. t6, T6, t5, T5 = (self.tables.t6,
  1802. self.classes.T6,
  1803. self.tables.t5,
  1804. self.classes.T5)
  1805. mapper(T6, t6, properties={
  1806. 't5':relationship(T5)
  1807. })
  1808. mapper(T5, t5)
  1809. sess = create_session()
  1810. o5 = T6(data='some t6', id=1)
  1811. o5.t5 = T5(data='some t5', id=1)
  1812. sess.add(o5)
  1813. sess.flush()
  1814. assert list(sess.execute(t5.select(), mapper=T5)) == [(1, 'some t5')]
  1815. assert list(sess.execute(t6.select(), mapper=T5)) == [(1, 'some t6', 1)]
  1816. o6 = T6(data='some other t6', id=1, t5=T5(data='some other t5', id=2))
  1817. sess.delete(o5)
  1818. sess.delete(o5.t5)
  1819. sess.add(o6)
  1820. sess.flush()
  1821. assert list(sess.execute(t5.select(), mapper=T5)) == [(2, 'some other t5')]
  1822. assert list(sess.execute(t6.select(), mapper=T5)) == [(1, 'some other t6', 2)]
  1823. class InheritingRowSwitchTest(fixtures.MappedTest):
  1824. @classmethod
  1825. def define_tables(cls, metadata):
  1826. Table('parent', metadata,
  1827. Column('id', Integer, primary_key=True),
  1828. Column('pdata', String(30))
  1829. )
  1830. Table('child', metadata,
  1831. Column('id', Integer, primary_key=True),
  1832. Column('pid', Integer, ForeignKey('parent.id')),
  1833. Column('cdata', String(30))
  1834. )
  1835. @classmethod
  1836. def setup_classes(cls):
  1837. class P(cls.Comparable):
  1838. pass
  1839. class C(P):
  1840. pass
  1841. def test_row_switch_no_child_table(self):
  1842. P, C, parent, child = (self.classes.P,
  1843. self.classes.C,
  1844. self.tables.parent,
  1845. self.tables.child)
  1846. mapper(P, parent)
  1847. mapper(C, child, inherits=P)
  1848. sess = create_session()
  1849. c1 = C(id=1, pdata='c1', cdata='c1')
  1850. sess.add(c1)
  1851. sess.flush()
  1852. # establish a row switch between c1 and c2.
  1853. # c2 has no value for the "child" table
  1854. c2 = C(id=1, pdata='c2')
  1855. sess.add(c2)
  1856. sess.delete(c1)
  1857. self.assert_sql_execution(testing.db, sess.flush,
  1858. CompiledSQL("UPDATE parent SET pdata=:pdata WHERE parent.id = :parent_id",
  1859. {'pdata':'c2', 'parent_id':1}
  1860. ),
  1861. # this fires as of [ticket:1362], since we synchronzize
  1862. # PK/FKs on UPDATES. c2 is new so the history shows up as
  1863. # pure added, update occurs. If a future change limits the
  1864. # sync operation during _save_obj().update, this is safe to remove again.
  1865. CompiledSQL("UPDATE child SET pid=:pid WHERE child.id = :child_id",
  1866. {'pid':1, 'child_id':1}
  1867. )
  1868. )
  1869. class TransactionTest(fixtures.MappedTest):
  1870. __requires__ = ('deferrable_or_no_constraints',)
  1871. @classmethod
  1872. def define_tables(cls, metadata):
  1873. t1 = Table('t1', metadata,
  1874. Column('id', Integer, primary_key=True))
  1875. t2 = Table('t2', metadata,
  1876. Column('id', Integer, primary_key=True),
  1877. Column('t1_id', Integer,
  1878. ForeignKey('t1.id', deferrable=True, initially='deferred')
  1879. ))
  1880. @classmethod
  1881. def setup_classes(cls):
  1882. class T1(cls.Comparable):
  1883. pass
  1884. class T2(cls.Comparable):
  1885. pass
  1886. @classmethod
  1887. def setup_mappers(cls):
  1888. T2, T1, t2, t1 = (cls.classes.T2,
  1889. cls.classes.T1,
  1890. cls.tables.t2,
  1891. cls.tables.t1)
  1892. orm_mapper(T1, t1)
  1893. orm_mapper(T2, t2)
  1894. def test_close_transaction_on_commit_fail(self):
  1895. T2, t1 = self.classes.T2, self.tables.t1
  1896. session = create_session(autocommit=True)
  1897. # with a deferred constraint, this fails at COMMIT time instead
  1898. # of at INSERT time.
  1899. session.add(T2(t1_id=123))
  1900. try:
  1901. session.flush()
  1902. assert False
  1903. except:
  1904. # Flush needs to rollback also when commit fails
  1905. assert session.transaction is None
  1906. # todo: on 8.3 at least, the failed commit seems to close the cursor?
  1907. # needs investigation. leaving in the DDL above now to help verify
  1908. # that the new deferrable support on FK isn't involved in this issue.
  1909. if testing.against('postgresql'):
  1910. t1.bind.engine.dispose()
  1911. class PartialNullPKTest(fixtures.MappedTest):
  1912. # sqlite totally fine with NULLs in pk columns.
  1913. # no other DB is like this.
  1914. __only_on__ = ('sqlite',)
  1915. @classmethod
  1916. def define_tables(cls, metadata):
  1917. Table('t1', metadata,
  1918. Column('col1', String(10), primary_key=True, nullable=True),
  1919. Column('col2', String(10), primary_key=True, nullable=True),
  1920. Column('col3', String(50))
  1921. )
  1922. @classmethod
  1923. def setup_classes(cls):
  1924. class T1(cls.Basic):
  1925. pass
  1926. @classmethod
  1927. def setup_mappers(cls):
  1928. orm_mapper(cls.classes.T1, cls.tables.t1)
  1929. def test_key_switch(self):
  1930. T1 = self.classes.T1
  1931. s = Session()
  1932. s.add(T1(col1="1", col2=None))
  1933. t1 = s.query(T1).first()
  1934. t1.col2 = 5
  1935. assert_raises_message(
  1936. sa.exc.FlushError,
  1937. "Can't update table using NULL for primary key value",
  1938. s.commit
  1939. )
  1940. def test_plain_update(self):
  1941. T1 = self.classes.T1
  1942. s = Session()
  1943. s.add(T1(col1="1", col2=None))
  1944. t1 = s.query(T1).first()
  1945. t1.col3 = 'hi'
  1946. assert_raises_message(
  1947. sa.exc.FlushError,
  1948. "Can't update table using NULL for primary key value",
  1949. s.commit
  1950. )
  1951. def test_delete(self):
  1952. T1 = self.classes.T1
  1953. s = Session()
  1954. s.add(T1(col1="1", col2=None))
  1955. t1 = s.query(T1).first()
  1956. s.delete(t1)
  1957. assert_raises_message(
  1958. sa.exc.FlushError,
  1959. "Can't delete from table using NULL for primary key value",
  1960. s.commit
  1961. )
  1962. def test_total_null(self):
  1963. T1 = self.classes.T1
  1964. s = Session()
  1965. s.add(T1(col1=None, col2=None))
  1966. assert_raises_message(
  1967. sa.exc.FlushError,
  1968. r"Instance \<T1 at .+?\> has a NULL "
  1969. "identity key. If this is an auto-generated value, "
  1970. "check that the database table allows generation ",
  1971. s.commit
  1972. )
  1973. def test_dont_complain_if_no_update(self):
  1974. T1 = self.classes.T1
  1975. s = Session()
  1976. t = T1(col1="1", col2=None)
  1977. s.add(t)
  1978. s.commit()
  1979. t.col1 = "1"
  1980. s.commit()