/test/orm/test_subquery_relations.py

https://gitlab.com/ztane/sqlalchemy · Python · 1307 lines · 1233 code · 69 blank · 5 comment · 10 complexity · 99e3422d8fbcfdbd88eea132aeb83c9a MD5 · raw file

  1. from sqlalchemy.testing import eq_, is_, is_not_
  2. from sqlalchemy import testing
  3. from sqlalchemy.testing.schema import Table, Column
  4. from sqlalchemy import Integer, String, ForeignKey, bindparam, inspect
  5. from sqlalchemy.orm import backref, subqueryload, subqueryload_all, \
  6. mapper, relationship, clear_mappers, create_session, lazyload, \
  7. aliased, joinedload, deferred, undefer, eagerload_all,\
  8. Session
  9. from sqlalchemy.testing import eq_, assert_raises, \
  10. assert_raises_message
  11. from sqlalchemy.testing.assertsql import CompiledSQL
  12. from sqlalchemy.testing import fixtures
  13. from sqlalchemy.testing.entities import ComparableEntity
  14. from test.orm import _fixtures
  15. import sqlalchemy as sa
  16. class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
  17. run_inserts = 'once'
  18. run_deletes = None
  19. def test_basic(self):
  20. users, Address, addresses, User = (self.tables.users,
  21. self.classes.Address,
  22. self.tables.addresses,
  23. self.classes.User)
  24. mapper(User, users, properties={
  25. 'addresses':relationship(
  26. mapper(Address, addresses),
  27. order_by=Address.id)
  28. })
  29. sess = create_session()
  30. q = sess.query(User).options(subqueryload(User.addresses))
  31. def go():
  32. eq_(
  33. [User(id=7, addresses=[
  34. Address(id=1, email_address='jack@bean.com')])],
  35. q.filter(User.id==7).all()
  36. )
  37. self.assert_sql_count(testing.db, go, 2)
  38. def go():
  39. eq_(
  40. self.static.user_address_result,
  41. q.order_by(User.id).all()
  42. )
  43. self.assert_sql_count(testing.db, go, 2)
  44. def test_from_aliased(self):
  45. users, Dingaling, User, dingalings, Address, addresses = (self.tables.users,
  46. self.classes.Dingaling,
  47. self.classes.User,
  48. self.tables.dingalings,
  49. self.classes.Address,
  50. self.tables.addresses)
  51. mapper(Dingaling, dingalings)
  52. mapper(Address, addresses, properties={
  53. 'dingalings':relationship(Dingaling, order_by=Dingaling.id)
  54. })
  55. mapper(User, users, properties={
  56. 'addresses':relationship(
  57. Address,
  58. order_by=Address.id)
  59. })
  60. sess = create_session()
  61. u = aliased(User)
  62. q = sess.query(u).options(subqueryload(u.addresses))
  63. def go():
  64. eq_(
  65. [User(id=7, addresses=[
  66. Address(id=1, email_address='jack@bean.com')])],
  67. q.filter(u.id==7).all()
  68. )
  69. self.assert_sql_count(testing.db, go, 2)
  70. def go():
  71. eq_(
  72. self.static.user_address_result,
  73. q.order_by(u.id).all()
  74. )
  75. self.assert_sql_count(testing.db, go, 2)
  76. q = sess.query(u).\
  77. options(subqueryload_all(u.addresses, Address.dingalings))
  78. def go():
  79. eq_(
  80. [
  81. User(id=8, addresses=[
  82. Address(id=2, email_address='ed@wood.com', dingalings=[Dingaling()]),
  83. Address(id=3, email_address='ed@bettyboop.com'),
  84. Address(id=4, email_address='ed@lala.com'),
  85. ]),
  86. User(id=9, addresses=[
  87. Address(id=5, dingalings=[Dingaling()])
  88. ]),
  89. ],
  90. q.filter(u.id.in_([8, 9])).all()
  91. )
  92. self.assert_sql_count(testing.db, go, 3)
  93. def test_from_get(self):
  94. users, Address, addresses, User = (self.tables.users,
  95. self.classes.Address,
  96. self.tables.addresses,
  97. self.classes.User)
  98. mapper(User, users, properties={
  99. 'addresses':relationship(
  100. mapper(Address, addresses),
  101. order_by=Address.id)
  102. })
  103. sess = create_session()
  104. q = sess.query(User).options(subqueryload(User.addresses))
  105. def go():
  106. eq_(
  107. User(id=7, addresses=[
  108. Address(id=1, email_address='jack@bean.com')]),
  109. q.get(7)
  110. )
  111. self.assert_sql_count(testing.db, go, 2)
  112. def test_from_params(self):
  113. users, Address, addresses, User = (self.tables.users,
  114. self.classes.Address,
  115. self.tables.addresses,
  116. self.classes.User)
  117. mapper(User, users, properties={
  118. 'addresses':relationship(
  119. mapper(Address, addresses),
  120. order_by=Address.id)
  121. })
  122. sess = create_session()
  123. q = sess.query(User).options(subqueryload(User.addresses))
  124. def go():
  125. eq_(
  126. User(id=7, addresses=[
  127. Address(id=1, email_address='jack@bean.com')]),
  128. q.filter(User.id==bindparam('foo')).params(foo=7).one()
  129. )
  130. self.assert_sql_count(testing.db, go, 2)
  131. def test_disable_dynamic(self):
  132. """test no subquery option on a dynamic."""
  133. users, Address, addresses, User = (self.tables.users,
  134. self.classes.Address,
  135. self.tables.addresses,
  136. self.classes.User)
  137. mapper(User, users, properties={
  138. 'addresses':relationship(Address, lazy="dynamic")
  139. })
  140. mapper(Address, addresses)
  141. sess = create_session()
  142. # previously this would not raise, but would emit
  143. # the query needlessly and put the result nowhere.
  144. assert_raises_message(
  145. sa.exc.InvalidRequestError,
  146. "User.addresses' does not support object population - eager loading cannot be applied.",
  147. sess.query(User).options(subqueryload(User.addresses)).first,
  148. )
  149. def test_many_to_many_plain(self):
  150. keywords, items, item_keywords, Keyword, Item = (self.tables.keywords,
  151. self.tables.items,
  152. self.tables.item_keywords,
  153. self.classes.Keyword,
  154. self.classes.Item)
  155. mapper(Keyword, keywords)
  156. mapper(Item, items, properties = dict(
  157. keywords = relationship(Keyword, secondary=item_keywords,
  158. lazy='subquery', order_by=keywords.c.id)))
  159. q = create_session().query(Item).order_by(Item.id)
  160. def go():
  161. eq_(self.static.item_keyword_result, q.all())
  162. self.assert_sql_count(testing.db, go, 2)
  163. def test_many_to_many_with_join(self):
  164. keywords, items, item_keywords, Keyword, Item = (self.tables.keywords,
  165. self.tables.items,
  166. self.tables.item_keywords,
  167. self.classes.Keyword,
  168. self.classes.Item)
  169. mapper(Keyword, keywords)
  170. mapper(Item, items, properties = dict(
  171. keywords = relationship(Keyword, secondary=item_keywords,
  172. lazy='subquery', order_by=keywords.c.id)))
  173. q = create_session().query(Item).order_by(Item.id)
  174. def go():
  175. eq_(self.static.item_keyword_result[0:2],
  176. q.join('keywords').filter(Keyword.name == 'red').all())
  177. self.assert_sql_count(testing.db, go, 2)
  178. def test_many_to_many_with_join_alias(self):
  179. keywords, items, item_keywords, Keyword, Item = (self.tables.keywords,
  180. self.tables.items,
  181. self.tables.item_keywords,
  182. self.classes.Keyword,
  183. self.classes.Item)
  184. mapper(Keyword, keywords)
  185. mapper(Item, items, properties = dict(
  186. keywords = relationship(Keyword, secondary=item_keywords,
  187. lazy='subquery', order_by=keywords.c.id)))
  188. q = create_session().query(Item).order_by(Item.id)
  189. def go():
  190. eq_(self.static.item_keyword_result[0:2],
  191. (q.join('keywords', aliased=True).
  192. filter(Keyword.name == 'red')).all())
  193. self.assert_sql_count(testing.db, go, 2)
  194. def test_orderby(self):
  195. users, Address, addresses, User = (self.tables.users,
  196. self.classes.Address,
  197. self.tables.addresses,
  198. self.classes.User)
  199. mapper(User, users, properties = {
  200. 'addresses':relationship(mapper(Address, addresses),
  201. lazy='subquery', order_by=addresses.c.email_address),
  202. })
  203. q = create_session().query(User)
  204. eq_([
  205. User(id=7, addresses=[
  206. Address(id=1)
  207. ]),
  208. User(id=8, addresses=[
  209. Address(id=3, email_address='ed@bettyboop.com'),
  210. Address(id=4, email_address='ed@lala.com'),
  211. Address(id=2, email_address='ed@wood.com')
  212. ]),
  213. User(id=9, addresses=[
  214. Address(id=5)
  215. ]),
  216. User(id=10, addresses=[])
  217. ], q.order_by(User.id).all())
  218. def test_orderby_multi(self):
  219. users, Address, addresses, User = (self.tables.users,
  220. self.classes.Address,
  221. self.tables.addresses,
  222. self.classes.User)
  223. mapper(User, users, properties = {
  224. 'addresses':relationship(mapper(Address, addresses),
  225. lazy='subquery',
  226. order_by=[
  227. addresses.c.email_address,
  228. addresses.c.id]),
  229. })
  230. q = create_session().query(User)
  231. eq_([
  232. User(id=7, addresses=[
  233. Address(id=1)
  234. ]),
  235. User(id=8, addresses=[
  236. Address(id=3, email_address='ed@bettyboop.com'),
  237. Address(id=4, email_address='ed@lala.com'),
  238. Address(id=2, email_address='ed@wood.com')
  239. ]),
  240. User(id=9, addresses=[
  241. Address(id=5)
  242. ]),
  243. User(id=10, addresses=[])
  244. ], q.order_by(User.id).all())
  245. def test_orderby_related(self):
  246. """A regular mapper select on a single table can
  247. order by a relationship to a second table"""
  248. Address, addresses, users, User = (self.classes.Address,
  249. self.tables.addresses,
  250. self.tables.users,
  251. self.classes.User)
  252. mapper(Address, addresses)
  253. mapper(User, users, properties = dict(
  254. addresses = relationship(Address,
  255. lazy='subquery',
  256. order_by=addresses.c.id),
  257. ))
  258. q = create_session().query(User)
  259. l = q.filter(User.id==Address.user_id).\
  260. order_by(Address.email_address).all()
  261. eq_([
  262. User(id=8, addresses=[
  263. Address(id=2, email_address='ed@wood.com'),
  264. Address(id=3, email_address='ed@bettyboop.com'),
  265. Address(id=4, email_address='ed@lala.com'),
  266. ]),
  267. User(id=9, addresses=[
  268. Address(id=5)
  269. ]),
  270. User(id=7, addresses=[
  271. Address(id=1)
  272. ]),
  273. ], l)
  274. def test_orderby_desc(self):
  275. Address, addresses, users, User = (self.classes.Address,
  276. self.tables.addresses,
  277. self.tables.users,
  278. self.classes.User)
  279. mapper(Address, addresses)
  280. mapper(User, users, properties = dict(
  281. addresses = relationship(Address, lazy='subquery',
  282. order_by=[
  283. sa.desc(addresses.c.email_address)
  284. ]),
  285. ))
  286. sess = create_session()
  287. eq_([
  288. User(id=7, addresses=[
  289. Address(id=1)
  290. ]),
  291. User(id=8, addresses=[
  292. Address(id=2, email_address='ed@wood.com'),
  293. Address(id=4, email_address='ed@lala.com'),
  294. Address(id=3, email_address='ed@bettyboop.com'),
  295. ]),
  296. User(id=9, addresses=[
  297. Address(id=5)
  298. ]),
  299. User(id=10, addresses=[])
  300. ], sess.query(User).order_by(User.id).all())
  301. _pathing_runs = [
  302. ( "lazyload", "lazyload", "lazyload", 15 ),
  303. ("subqueryload", "lazyload", "lazyload", 12),
  304. ("subqueryload", "subqueryload", "lazyload", 8),
  305. ("joinedload", "subqueryload", "lazyload", 7),
  306. ("lazyload", "lazyload", "subqueryload", 12),
  307. ("subqueryload", "subqueryload", "subqueryload", 4),
  308. ("subqueryload", "subqueryload", "joinedload", 3),
  309. ]
  310. def test_options_pathing(self):
  311. self._do_options_test(self._pathing_runs)
  312. def test_mapper_pathing(self):
  313. self._do_mapper_test(self._pathing_runs)
  314. def _do_options_test(self, configs):
  315. users, Keyword, orders, items, order_items, Order, Item, User, keywords, item_keywords = (self.tables.users,
  316. self.classes.Keyword,
  317. self.tables.orders,
  318. self.tables.items,
  319. self.tables.order_items,
  320. self.classes.Order,
  321. self.classes.Item,
  322. self.classes.User,
  323. self.tables.keywords,
  324. self.tables.item_keywords)
  325. mapper(User, users, properties={
  326. 'orders':relationship(Order, order_by=orders.c.id), # o2m, m2o
  327. })
  328. mapper(Order, orders, properties={
  329. 'items':relationship(Item,
  330. secondary=order_items, order_by=items.c.id), #m2m
  331. })
  332. mapper(Item, items, properties={
  333. 'keywords':relationship(Keyword,
  334. secondary=item_keywords,
  335. order_by=keywords.c.id) #m2m
  336. })
  337. mapper(Keyword, keywords)
  338. callables = {
  339. 'joinedload':joinedload,
  340. 'subqueryload':subqueryload
  341. }
  342. for o, i, k, count in configs:
  343. options = []
  344. if o in callables:
  345. options.append(callables[o](User.orders))
  346. if i in callables:
  347. options.append(callables[i](User.orders, Order.items))
  348. if k in callables:
  349. options.append(callables[k](User.orders, Order.items, Item.keywords))
  350. self._do_query_tests(options, count)
  351. def _do_mapper_test(self, configs):
  352. users, Keyword, orders, items, order_items, Order, Item, User, keywords, item_keywords = (self.tables.users,
  353. self.classes.Keyword,
  354. self.tables.orders,
  355. self.tables.items,
  356. self.tables.order_items,
  357. self.classes.Order,
  358. self.classes.Item,
  359. self.classes.User,
  360. self.tables.keywords,
  361. self.tables.item_keywords)
  362. opts = {
  363. 'lazyload':'select',
  364. 'joinedload':'joined',
  365. 'subqueryload':'subquery',
  366. }
  367. for o, i, k, count in configs:
  368. mapper(User, users, properties={
  369. 'orders':relationship(Order, lazy=opts[o], order_by=orders.c.id),
  370. })
  371. mapper(Order, orders, properties={
  372. 'items':relationship(Item,
  373. secondary=order_items, lazy=opts[i], order_by=items.c.id),
  374. })
  375. mapper(Item, items, properties={
  376. 'keywords':relationship(Keyword,
  377. lazy=opts[k],
  378. secondary=item_keywords,
  379. order_by=keywords.c.id)
  380. })
  381. mapper(Keyword, keywords)
  382. try:
  383. self._do_query_tests([], count)
  384. finally:
  385. clear_mappers()
  386. def _do_query_tests(self, opts, count):
  387. Order, User = self.classes.Order, self.classes.User
  388. sess = create_session()
  389. def go():
  390. eq_(
  391. sess.query(User).options(*opts).order_by(User.id).all(),
  392. self.static.user_item_keyword_result
  393. )
  394. self.assert_sql_count(testing.db, go, count)
  395. eq_(
  396. sess.query(User).options(*opts).filter(User.name=='fred').
  397. order_by(User.id).all(),
  398. self.static.user_item_keyword_result[2:3]
  399. )
  400. sess = create_session()
  401. eq_(
  402. sess.query(User).options(*opts).join(User.orders).
  403. filter(Order.id==3).\
  404. order_by(User.id).all(),
  405. self.static.user_item_keyword_result[0:1]
  406. )
  407. def test_cyclical(self):
  408. """A circular eager relationship breaks the cycle with a lazy loader"""
  409. Address, addresses, users, User = (self.classes.Address,
  410. self.tables.addresses,
  411. self.tables.users,
  412. self.classes.User)
  413. mapper(Address, addresses)
  414. mapper(User, users, properties = dict(
  415. addresses = relationship(Address, lazy='subquery',
  416. backref=sa.orm.backref('user', lazy='subquery'),
  417. order_by=Address.id)
  418. ))
  419. is_(sa.orm.class_mapper(User).get_property('addresses').lazy, 'subquery')
  420. is_(sa.orm.class_mapper(Address).get_property('user').lazy, 'subquery')
  421. sess = create_session()
  422. eq_(self.static.user_address_result, sess.query(User).order_by(User.id).all())
  423. def test_double(self):
  424. """Eager loading with two relationships simultaneously,
  425. from the same table, using aliases."""
  426. users, orders, User, Address, Order, addresses = (self.tables.users,
  427. self.tables.orders,
  428. self.classes.User,
  429. self.classes.Address,
  430. self.classes.Order,
  431. self.tables.addresses)
  432. openorders = sa.alias(orders, 'openorders')
  433. closedorders = sa.alias(orders, 'closedorders')
  434. mapper(Address, addresses)
  435. mapper(Order, orders)
  436. open_mapper = mapper(Order, openorders, non_primary=True)
  437. closed_mapper = mapper(Order, closedorders, non_primary=True)
  438. mapper(User, users, properties = dict(
  439. addresses = relationship(Address, lazy='subquery',
  440. order_by=addresses.c.id),
  441. open_orders = relationship(
  442. open_mapper,
  443. primaryjoin=sa.and_(openorders.c.isopen == 1,
  444. users.c.id==openorders.c.user_id),
  445. lazy='subquery', order_by=openorders.c.id),
  446. closed_orders = relationship(
  447. closed_mapper,
  448. primaryjoin=sa.and_(closedorders.c.isopen == 0,
  449. users.c.id==closedorders.c.user_id),
  450. lazy='subquery', order_by=closedorders.c.id)))
  451. q = create_session().query(User).order_by(User.id)
  452. def go():
  453. eq_([
  454. User(
  455. id=7,
  456. addresses=[Address(id=1)],
  457. open_orders = [Order(id=3)],
  458. closed_orders = [Order(id=1), Order(id=5)]
  459. ),
  460. User(
  461. id=8,
  462. addresses=[Address(id=2), Address(id=3), Address(id=4)],
  463. open_orders = [],
  464. closed_orders = []
  465. ),
  466. User(
  467. id=9,
  468. addresses=[Address(id=5)],
  469. open_orders = [Order(id=4)],
  470. closed_orders = [Order(id=2)]
  471. ),
  472. User(id=10)
  473. ], q.all())
  474. self.assert_sql_count(testing.db, go, 4)
  475. def test_double_same_mappers(self):
  476. """Eager loading with two relationships simulatneously,
  477. from the same table, using aliases."""
  478. addresses, items, order_items, orders, Item, User, Address, Order, users = (self.tables.addresses,
  479. self.tables.items,
  480. self.tables.order_items,
  481. self.tables.orders,
  482. self.classes.Item,
  483. self.classes.User,
  484. self.classes.Address,
  485. self.classes.Order,
  486. self.tables.users)
  487. mapper(Address, addresses)
  488. mapper(Order, orders, properties={
  489. 'items': relationship(Item, secondary=order_items, lazy='subquery',
  490. order_by=items.c.id)})
  491. mapper(Item, items)
  492. mapper(User, users, properties=dict(
  493. addresses=relationship(Address, lazy='subquery', order_by=addresses.c.id),
  494. open_orders=relationship(
  495. Order,
  496. primaryjoin=sa.and_(orders.c.isopen == 1,
  497. users.c.id==orders.c.user_id),
  498. lazy='subquery', order_by=orders.c.id),
  499. closed_orders=relationship(
  500. Order,
  501. primaryjoin=sa.and_(orders.c.isopen == 0,
  502. users.c.id==orders.c.user_id),
  503. lazy='subquery', order_by=orders.c.id)))
  504. q = create_session().query(User).order_by(User.id)
  505. def go():
  506. eq_([
  507. User(id=7,
  508. addresses=[
  509. Address(id=1)],
  510. open_orders=[Order(id=3,
  511. items=[
  512. Item(id=3),
  513. Item(id=4),
  514. Item(id=5)])],
  515. closed_orders=[Order(id=1,
  516. items=[
  517. Item(id=1),
  518. Item(id=2),
  519. Item(id=3)]),
  520. Order(id=5,
  521. items=[
  522. Item(id=5)])]),
  523. User(id=8,
  524. addresses=[
  525. Address(id=2),
  526. Address(id=3),
  527. Address(id=4)],
  528. open_orders = [],
  529. closed_orders = []),
  530. User(id=9,
  531. addresses=[
  532. Address(id=5)],
  533. open_orders=[
  534. Order(id=4,
  535. items=[
  536. Item(id=1),
  537. Item(id=5)])],
  538. closed_orders=[
  539. Order(id=2,
  540. items=[
  541. Item(id=1),
  542. Item(id=2),
  543. Item(id=3)])]),
  544. User(id=10)
  545. ], q.all())
  546. self.assert_sql_count(testing.db, go, 6)
  547. def test_limit(self):
  548. """Limit operations combined with lazy-load relationships."""
  549. users, items, order_items, orders, Item, User, Address, Order, addresses = (self.tables.users,
  550. self.tables.items,
  551. self.tables.order_items,
  552. self.tables.orders,
  553. self.classes.Item,
  554. self.classes.User,
  555. self.classes.Address,
  556. self.classes.Order,
  557. self.tables.addresses)
  558. mapper(Item, items)
  559. mapper(Order, orders, properties={
  560. 'items':relationship(Item, secondary=order_items, lazy='subquery',
  561. order_by=items.c.id)
  562. })
  563. mapper(User, users, properties={
  564. 'addresses':relationship(mapper(Address, addresses),
  565. lazy='subquery',
  566. order_by=addresses.c.id),
  567. 'orders':relationship(Order, lazy='select', order_by=orders.c.id)
  568. })
  569. sess = create_session()
  570. q = sess.query(User)
  571. l = q.order_by(User.id).limit(2).offset(1).all()
  572. eq_(self.static.user_all_result[1:3], l)
  573. sess = create_session()
  574. l = q.order_by(sa.desc(User.id)).limit(2).offset(2).all()
  575. eq_(list(reversed(self.static.user_all_result[0:2])), l)
  576. def test_mapper_order_by(self):
  577. users, User, Address, addresses = (self.tables.users,
  578. self.classes.User,
  579. self.classes.Address,
  580. self.tables.addresses)
  581. mapper(Address, addresses)
  582. mapper(User, users, properties={
  583. 'addresses':relationship(Address,
  584. lazy='subquery',
  585. order_by=addresses.c.id),
  586. },order_by=users.c.id.desc())
  587. sess = create_session()
  588. q = sess.query(User)
  589. l = q.limit(2).all()
  590. eq_(l, list(reversed(self.static.user_address_result[2:4])))
  591. def test_one_to_many_scalar(self):
  592. Address, addresses, users, User = (self.classes.Address,
  593. self.tables.addresses,
  594. self.tables.users,
  595. self.classes.User)
  596. mapper(User, users, properties = dict(
  597. address = relationship(mapper(Address, addresses),
  598. lazy='subquery', uselist=False)
  599. ))
  600. q = create_session().query(User)
  601. def go():
  602. l = q.filter(users.c.id == 7).all()
  603. eq_([User(id=7, address=Address(id=1))], l)
  604. self.assert_sql_count(testing.db, go, 2)
  605. def test_many_to_one(self):
  606. users, Address, addresses, User = (self.tables.users,
  607. self.classes.Address,
  608. self.tables.addresses,
  609. self.classes.User)
  610. mapper(Address, addresses, properties = dict(
  611. user = relationship(mapper(User, users), lazy='subquery')
  612. ))
  613. sess = create_session()
  614. q = sess.query(Address)
  615. def go():
  616. a = q.filter(addresses.c.id==1).one()
  617. is_not_(a.user, None)
  618. u1 = sess.query(User).get(7)
  619. is_(a.user, u1)
  620. self.assert_sql_count(testing.db, go, 2)
  621. def test_double_with_aggregate(self):
  622. User, users, orders, Order = (self.classes.User,
  623. self.tables.users,
  624. self.tables.orders,
  625. self.classes.Order)
  626. max_orders_by_user = sa.select([sa.func.max(orders.c.id).label('order_id')],
  627. group_by=[orders.c.user_id]
  628. ).alias('max_orders_by_user')
  629. max_orders = orders.select(orders.c.id==max_orders_by_user.c.order_id).\
  630. alias('max_orders')
  631. mapper(Order, orders)
  632. mapper(User, users, properties={
  633. 'orders':relationship(Order, backref='user', lazy='subquery',
  634. order_by=orders.c.id),
  635. 'max_order':relationship(
  636. mapper(Order, max_orders, non_primary=True),
  637. lazy='subquery', uselist=False)
  638. })
  639. q = create_session().query(User)
  640. def go():
  641. eq_([
  642. User(id=7, orders=[
  643. Order(id=1),
  644. Order(id=3),
  645. Order(id=5),
  646. ],
  647. max_order=Order(id=5)
  648. ),
  649. User(id=8, orders=[]),
  650. User(id=9, orders=[Order(id=2),Order(id=4)],
  651. max_order=Order(id=4)
  652. ),
  653. User(id=10),
  654. ], q.order_by(User.id).all())
  655. self.assert_sql_count(testing.db, go, 3)
  656. def test_uselist_false_warning(self):
  657. """test that multiple rows received by a
  658. uselist=False raises a warning."""
  659. User, users, orders, Order = (self.classes.User,
  660. self.tables.users,
  661. self.tables.orders,
  662. self.classes.Order)
  663. mapper(User, users, properties={
  664. 'order':relationship(Order, uselist=False)
  665. })
  666. mapper(Order, orders)
  667. s = create_session()
  668. assert_raises(sa.exc.SAWarning,
  669. s.query(User).options(subqueryload(User.order)).all)
  670. class LoadOnExistingTest(_fixtures.FixtureTest):
  671. """test that loaders from a base Query fully populate."""
  672. run_inserts = 'once'
  673. run_deletes = None
  674. def _collection_to_scalar_fixture(self):
  675. User, Address, Dingaling = self.classes.User, \
  676. self.classes.Address, self.classes.Dingaling
  677. mapper(User, self.tables.users, properties={
  678. 'addresses':relationship(Address),
  679. })
  680. mapper(Address, self.tables.addresses, properties={
  681. 'dingaling':relationship(Dingaling)
  682. })
  683. mapper(Dingaling, self.tables.dingalings)
  684. sess = Session(autoflush=False)
  685. return User, Address, Dingaling, sess
  686. def _collection_to_collection_fixture(self):
  687. User, Order, Item = self.classes.User, \
  688. self.classes.Order, self.classes.Item
  689. mapper(User, self.tables.users, properties={
  690. 'orders':relationship(Order),
  691. })
  692. mapper(Order, self.tables.orders, properties={
  693. 'items':relationship(Item, secondary=self.tables.order_items),
  694. })
  695. mapper(Item, self.tables.items)
  696. sess = Session(autoflush=False)
  697. return User, Order, Item, sess
  698. def _eager_config_fixture(self):
  699. User, Address = self.classes.User, self.classes.Address
  700. mapper(User, self.tables.users, properties={
  701. 'addresses':relationship(Address, lazy="subquery"),
  702. })
  703. mapper(Address, self.tables.addresses)
  704. sess = Session(autoflush=False)
  705. return User, Address, sess
  706. def _deferred_config_fixture(self):
  707. User, Address = self.classes.User, self.classes.Address
  708. mapper(User, self.tables.users, properties={
  709. 'name':deferred(self.tables.users.c.name),
  710. 'addresses':relationship(Address, lazy="subquery"),
  711. })
  712. mapper(Address, self.tables.addresses)
  713. sess = Session(autoflush=False)
  714. return User, Address, sess
  715. def test_no_query_on_refresh(self):
  716. User, Address, sess = self._eager_config_fixture()
  717. u1 = sess.query(User).get(8)
  718. assert 'addresses' in u1.__dict__
  719. sess.expire(u1)
  720. def go():
  721. eq_(u1.id, 8)
  722. self.assert_sql_count(testing.db, go, 1)
  723. assert 'addresses' not in u1.__dict__
  724. def test_no_query_on_deferred(self):
  725. User, Address, sess = self._deferred_config_fixture()
  726. u1 = sess.query(User).get(8)
  727. assert 'addresses' in u1.__dict__
  728. sess.expire(u1, ['addresses'])
  729. def go():
  730. eq_(u1.name, 'ed')
  731. self.assert_sql_count(testing.db, go, 1)
  732. assert 'addresses' not in u1.__dict__
  733. def test_populate_existing_propagate(self):
  734. User, Address, sess = self._eager_config_fixture()
  735. u1 = sess.query(User).get(8)
  736. u1.addresses[2].email_address = "foofoo"
  737. del u1.addresses[1]
  738. u1 = sess.query(User).populate_existing().filter_by(id=8).one()
  739. # collection is reverted
  740. eq_(len(u1.addresses), 3)
  741. # attributes on related items reverted
  742. eq_(u1.addresses[2].email_address, "ed@lala.com")
  743. def test_loads_second_level_collection_to_scalar(self):
  744. User, Address, Dingaling, sess = self._collection_to_scalar_fixture()
  745. u1 = sess.query(User).get(8)
  746. a1 = Address()
  747. u1.addresses.append(a1)
  748. a2 = u1.addresses[0]
  749. a2.email_address = 'foo'
  750. sess.query(User).options(subqueryload_all("addresses.dingaling")).\
  751. filter_by(id=8).all()
  752. assert u1.addresses[-1] is a1
  753. for a in u1.addresses:
  754. if a is not a1:
  755. assert 'dingaling' in a.__dict__
  756. else:
  757. assert 'dingaling' not in a.__dict__
  758. if a is a2:
  759. eq_(a2.email_address, 'foo')
  760. def test_loads_second_level_collection_to_collection(self):
  761. User, Order, Item, sess = self._collection_to_collection_fixture()
  762. u1 = sess.query(User).get(7)
  763. u1.orders
  764. o1 = Order()
  765. u1.orders.append(o1)
  766. sess.query(User).options(subqueryload_all("orders.items")).\
  767. filter_by(id=7).all()
  768. for o in u1.orders:
  769. if o is not o1:
  770. assert 'items' in o.__dict__
  771. else:
  772. assert 'items' not in o.__dict__
  773. def test_load_two_levels_collection_to_scalar(self):
  774. User, Address, Dingaling, sess = self._collection_to_scalar_fixture()
  775. u1 = sess.query(User).filter_by(id=8).options(subqueryload("addresses")).one()
  776. sess.query(User).filter_by(id=8).options(subqueryload_all("addresses.dingaling")).first()
  777. assert 'dingaling' in u1.addresses[0].__dict__
  778. def test_load_two_levels_collection_to_collection(self):
  779. User, Order, Item, sess = self._collection_to_collection_fixture()
  780. u1 = sess.query(User).filter_by(id=7).options(subqueryload("orders")).one()
  781. sess.query(User).filter_by(id=7).options(subqueryload_all("orders.items")).first()
  782. assert 'items' in u1.orders[0].__dict__
  783. class OrderBySecondaryTest(fixtures.MappedTest):
  784. @classmethod
  785. def define_tables(cls, metadata):
  786. Table('m2m', metadata,
  787. Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
  788. Column('aid', Integer, ForeignKey('a.id')),
  789. Column('bid', Integer, ForeignKey('b.id')))
  790. Table('a', metadata,
  791. Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
  792. Column('data', String(50)))
  793. Table('b', metadata,
  794. Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
  795. Column('data', String(50)))
  796. @classmethod
  797. def fixtures(cls):
  798. return dict(
  799. a=(('id', 'data'),
  800. (1, 'a1'),
  801. (2, 'a2')),
  802. b=(('id', 'data'),
  803. (1, 'b1'),
  804. (2, 'b2'),
  805. (3, 'b3'),
  806. (4, 'b4')),
  807. m2m=(('id', 'aid', 'bid'),
  808. (2, 1, 1),
  809. (4, 2, 4),
  810. (1, 1, 3),
  811. (6, 2, 2),
  812. (3, 1, 2),
  813. (5, 2, 3)))
  814. def test_ordering(self):
  815. a, m2m, b = (self.tables.a,
  816. self.tables.m2m,
  817. self.tables.b)
  818. class A(fixtures.ComparableEntity):pass
  819. class B(fixtures.ComparableEntity):pass
  820. mapper(A, a, properties={
  821. 'bs':relationship(B, secondary=m2m, lazy='subquery', order_by=m2m.c.id)
  822. })
  823. mapper(B, b)
  824. sess = create_session()
  825. def go():
  826. eq_(sess.query(A).all(), [
  827. A(data='a1', bs=[B(data='b3'), B(data='b1'), B(data='b2')]),
  828. A(bs=[B(data='b4'), B(data='b3'), B(data='b2')])
  829. ])
  830. self.assert_sql_count(testing.db, go, 2)
  831. from .inheritance._poly_fixtures import _Polymorphic, Person, Engineer, Paperwork
  832. class BaseRelationFromJoinedSubclassTest(_Polymorphic):
  833. @classmethod
  834. def define_tables(cls, metadata):
  835. people = Table('people', metadata,
  836. Column('person_id', Integer,
  837. primary_key=True,
  838. test_needs_autoincrement=True),
  839. Column('name', String(50)),
  840. Column('type', String(30)))
  841. # to test fully, PK of engineers table must be
  842. # named differently from that of people
  843. engineers = Table('engineers', metadata,
  844. Column('engineer_id', Integer,
  845. ForeignKey('people.person_id'),
  846. primary_key=True),
  847. Column('primary_language', String(50)))
  848. paperwork = Table('paperwork', metadata,
  849. Column('paperwork_id', Integer,
  850. primary_key=True,
  851. test_needs_autoincrement=True),
  852. Column('description', String(50)),
  853. Column('person_id', Integer,
  854. ForeignKey('people.person_id')))
  855. @classmethod
  856. def setup_mappers(cls):
  857. people = cls.tables.people
  858. engineers = cls.tables.engineers
  859. paperwork = cls.tables.paperwork
  860. mapper(Person, people,
  861. polymorphic_on=people.c.type,
  862. polymorphic_identity='person',
  863. properties={
  864. 'paperwork': relationship(
  865. Paperwork, order_by=paperwork.c.paperwork_id)})
  866. mapper(Engineer, engineers,
  867. inherits=Person,
  868. polymorphic_identity='engineer')
  869. mapper(Paperwork, paperwork)
  870. @classmethod
  871. def insert_data(cls):
  872. e1 = Engineer(primary_language="java")
  873. e2 = Engineer(primary_language="c++")
  874. e1.paperwork = [Paperwork(description="tps report #1"),
  875. Paperwork(description="tps report #2")]
  876. e2.paperwork = [Paperwork(description="tps report #3")]
  877. sess = create_session()
  878. sess.add_all([e1, e2])
  879. sess.flush()
  880. def test_correct_subquery_nofrom(self):
  881. sess = create_session()
  882. # use Person.paperwork here just to give the least
  883. # amount of context
  884. q = sess.query(Engineer).\
  885. filter(Engineer.primary_language == 'java').\
  886. options(subqueryload(Person.paperwork))
  887. def go():
  888. eq_(q.all()[0].paperwork,
  889. [Paperwork(description="tps report #1"),
  890. Paperwork(description="tps report #2")],
  891. )
  892. self.assert_sql_execution(
  893. testing.db,
  894. go,
  895. CompiledSQL(
  896. "SELECT people.person_id AS people_person_id, "
  897. "people.name AS people_name, people.type AS people_type, "
  898. "engineers.engineer_id AS engineers_engineer_id, "
  899. "engineers.primary_language AS engineers_primary_language "
  900. "FROM people JOIN engineers ON "
  901. "people.person_id = engineers.engineer_id "
  902. "WHERE engineers.primary_language = :primary_language_1",
  903. {"primary_language_1": "java"}
  904. ),
  905. # ensure we get "people JOIN engineer" here, even though
  906. # primary key "people.person_id" is against "Person"
  907. # *and* the path comes out as "Person.paperwork", still
  908. # want to select from "Engineer" entity
  909. CompiledSQL(
  910. "SELECT paperwork.paperwork_id AS paperwork_paperwork_id, "
  911. "paperwork.description AS paperwork_description, "
  912. "paperwork.person_id AS paperwork_person_id, "
  913. "anon_1.people_person_id AS anon_1_people_person_id "
  914. "FROM (SELECT people.person_id AS people_person_id "
  915. "FROM people JOIN engineers "
  916. "ON people.person_id = engineers.engineer_id "
  917. "WHERE engineers.primary_language = "
  918. ":primary_language_1) AS anon_1 "
  919. "JOIN paperwork "
  920. "ON anon_1.people_person_id = paperwork.person_id "
  921. "ORDER BY anon_1.people_person_id, paperwork.paperwork_id",
  922. {"primary_language_1": "java"}
  923. )
  924. )
  925. def test_correct_subquery_existingfrom(self):
  926. sess = create_session()
  927. # use Person.paperwork here just to give the least
  928. # amount of context
  929. q = sess.query(Engineer).\
  930. filter(Engineer.primary_language == 'java').\
  931. join(Engineer.paperwork).\
  932. filter(Paperwork.description == "tps report #2").\
  933. options(subqueryload(Person.paperwork))
  934. def go():
  935. eq_(q.one().paperwork,
  936. [Paperwork(description="tps report #1"),
  937. Paperwork(description="tps report #2")],
  938. )
  939. self.assert_sql_execution(
  940. testing.db,
  941. go,
  942. CompiledSQL(
  943. "SELECT people.person_id AS people_person_id, "
  944. "people.name AS people_name, people.type AS people_type, "
  945. "engineers.engineer_id AS engineers_engineer_id, "
  946. "engineers.primary_language AS engineers_primary_language "
  947. "FROM people JOIN engineers "
  948. "ON people.person_id = engineers.engineer_id "
  949. "JOIN paperwork ON people.person_id = paperwork.person_id "
  950. "WHERE engineers.primary_language = :primary_language_1 "
  951. "AND paperwork.description = :description_1",
  952. {"primary_language_1": "java",
  953. "description_1": "tps report #2"}
  954. ),
  955. CompiledSQL(
  956. "SELECT paperwork.paperwork_id AS paperwork_paperwork_id, "
  957. "paperwork.description AS paperwork_description, "
  958. "paperwork.person_id AS paperwork_person_id, "
  959. "anon_1.people_person_id AS anon_1_people_person_id "
  960. "FROM (SELECT people.person_id AS people_person_id "
  961. "FROM people JOIN engineers ON people.person_id = "
  962. "engineers.engineer_id JOIN paperwork "
  963. "ON people.person_id = paperwork.person_id "
  964. "WHERE engineers.primary_language = :primary_language_1 AND "
  965. "paperwork.description = :description_1) AS anon_1 "
  966. "JOIN paperwork ON anon_1.people_person_id = "
  967. "paperwork.person_id "
  968. "ORDER BY anon_1.people_person_id, paperwork.paperwork_id",
  969. {"primary_language_1": "java",
  970. "description_1": "tps report #2"}
  971. )
  972. )
  973. class SelfReferentialTest(fixtures.MappedTest):
  974. @classmethod
  975. def define_tables(cls, metadata):
  976. Table('nodes', metadata,
  977. Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
  978. Column('parent_id', Integer, ForeignKey('nodes.id')),
  979. Column('data', String(30)))
  980. def test_basic(self):
  981. nodes = self.tables.nodes
  982. class Node(fixtures.ComparableEntity):
  983. def append(self, node):
  984. self.children.append(node)
  985. mapper(Node, nodes, properties={
  986. 'children':relationship(Node,
  987. lazy='subquery',
  988. join_depth=3, order_by=nodes.c.id)
  989. })
  990. sess = create_session()
  991. n1 = Node(data='n1')
  992. n1.append(Node(data='n11'))
  993. n1.append(Node(data='n12'))
  994. n1.append(Node(data='n13'))
  995. n1.children[1].append(Node(data='n121'))
  996. n1.children[1].append(Node(data='n122'))
  997. n1.children[1].append(Node(data='n123'))
  998. n2 = Node(data='n2')
  999. n2.append(Node(data='n21'))
  1000. n2.children[0].append(Node(data='n211'))
  1001. n2.children[0].append(Node(data='n212'))
  1002. sess.add(n1)
  1003. sess.add(n2)
  1004. sess.flush()
  1005. sess.expunge_all()
  1006. def go():
  1007. d = sess.query(Node).filter(Node.data.in_(['n1', 'n2'])).\
  1008. order_by(Node.data).all()
  1009. eq_([Node(data='n1', children=[
  1010. Node(data='n11'),
  1011. Node(data='n12', children=[
  1012. Node(data='n121'),
  1013. Node(data='n122'),
  1014. Node(data='n123')
  1015. ]),
  1016. Node(data='n13')
  1017. ]),
  1018. Node(data='n2', children=[
  1019. Node(data='n21', children=[
  1020. Node(data='n211'),
  1021. Node(data='n212'),
  1022. ])
  1023. ])
  1024. ], d)
  1025. self.assert_sql_count(testing.db, go, 4)
  1026. def test_lazy_fallback_doesnt_affect_eager(self):
  1027. nodes = self.tables.nodes
  1028. class Node(fixtures.ComparableEntity):
  1029. def append(self, node):
  1030. self.children.append(node)
  1031. mapper(Node, nodes, properties={
  1032. 'children':relationship(Node, lazy='subquery', join_depth=1,
  1033. order_by=nodes.c.id)
  1034. })
  1035. sess = create_session()
  1036. n1 = Node(data='n1')
  1037. n1.append(Node(data='n11'))
  1038. n1.append(Node(data='n12'))
  1039. n1.append(Node(data='n13'))
  1040. n1.children[1].append(Node(data='n121'))
  1041. n1.children[1].append(Node(data='n122'))
  1042. n1.children[1].append(Node(data='n123'))
  1043. sess.add(n1)
  1044. sess.flush()
  1045. sess.expunge_all()
  1046. def go():
  1047. allnodes = sess.query(Node).order_by(Node.data).all()
  1048. n12 = allnodes[2]
  1049. eq_(n12.data, 'n12')
  1050. eq_([
  1051. Node(data='n121'),
  1052. Node(data='n122'),
  1053. Node(data='n123')
  1054. ], list(n12.children))
  1055. self.assert_sql_count(testing.db, go, 4)
  1056. def test_with_deferred(self):
  1057. nodes = self.tables.nodes
  1058. class Node(fixtures.ComparableEntity):
  1059. def append(self, node):
  1060. self.children.append(node)
  1061. mapper(Node, nodes, properties={
  1062. 'children':relationship(Node, lazy='subquery', join_depth=3,
  1063. order_by=nodes.c.id),
  1064. 'data':deferred(nodes.c.data)
  1065. })
  1066. sess = create_session()
  1067. n1 = Node(data='n1')
  1068. n1.append(Node(data='n11'))
  1069. n1.append(Node(data='n12'))
  1070. sess.add(n1)
  1071. sess.flush()
  1072. sess.expunge_all()
  1073. def go():
  1074. eq_(
  1075. Node(data='n1', children=[Node(data='n11'), Node(data='n12')]),
  1076. sess.query(Node).order_by(Node.id).first(),
  1077. )
  1078. self.assert_sql_count(testing.db, go, 6)
  1079. sess.expunge_all()
  1080. def go():
  1081. eq_(Node(data='n1', children=[Node(data='n11'), Node(data='n12')]),
  1082. sess.query(Node).options(undefer('data')).order_by(Node.id).first())
  1083. self.assert_sql_count(testing.db, go, 5)
  1084. sess.expunge_all()
  1085. def go():
  1086. eq_(Node(data='n1', children=[Node(data='n11'), Node(data='n12')]),
  1087. sess.query(Node).options(undefer('data'),
  1088. undefer('children.data')).first())
  1089. self.assert_sql_count(testing.db, go, 3)
  1090. def test_options(self):
  1091. nodes = self.tables.nodes
  1092. class Node(fixtures.ComparableEntity):
  1093. def append(self, node):
  1094. self.children.append(node)
  1095. mapper(Node, nodes, properties={
  1096. 'children':relationship(Node, order_by=nodes.c.id)
  1097. }, order_by=nodes.c.id)
  1098. sess = create_session()
  1099. n1 = Node(data='n1')
  1100. n1.append(Node(data='n11'))
  1101. n1.append(Node(data='n12'))
  1102. n1.append(Node(data='n13'))
  1103. n1.children[1].append(Node(data='n121'))
  1104. n1.children[1].append(Node(data='n122'))
  1105. n1.children[1].append(Node(data='n123'))
  1106. sess.add(n1)
  1107. sess.flush()
  1108. sess.expunge_all()
  1109. def go():
  1110. d = sess.query(Node).filter_by(data='n1').\
  1111. options(subqueryload_all('children.children')).first()
  1112. eq_(Node(data='n1', children=[
  1113. Node(data='n11'),
  1114. Node(data='n12', children=[
  1115. Node(data='n121'),
  1116. Node(data='n122'),
  1117. Node(data='n123')
  1118. ]),
  1119. Node(data='n13')