/test/orm/test_subquery_relations.py

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