mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-06-02 13:59:02 -04:00
5d2bfc4df4
in Py3.2. Patch courtesy Arfrever Frehtes Taifersar Arahesis. fixes #2980
454 lines
18 KiB
Python
454 lines
18 KiB
Python
from test.orm import _fixtures
|
|
from sqlalchemy import testing
|
|
from sqlalchemy.orm import mapper, relationship, create_session
|
|
from sqlalchemy import util
|
|
import sqlalchemy as sa
|
|
from sqlalchemy.testing import eq_, assert_raises_message
|
|
|
|
class DefaultStrategyOptionsTest(_fixtures.FixtureTest):
|
|
|
|
def _assert_fully_loaded(self, users):
|
|
# verify everything loaded, with no additional sql needed
|
|
def go():
|
|
# comparison with no additional sql
|
|
eq_(users, self.static.user_all_result)
|
|
# keywords are not part of self.static.user_all_result, so
|
|
# verify all the item keywords were loaded, with no more sql.
|
|
# 'any' verifies at least some items have keywords; we build
|
|
# a list for any([...]) instead of any(...) to prove we've
|
|
# iterated all the items with no sql.
|
|
f = util.flatten_iterator
|
|
assert any([i.keywords for i in
|
|
f([o.items for o in f([u.orders for u in users])])])
|
|
self.assert_sql_count(testing.db, go, 0)
|
|
|
|
def _assert_addresses_loaded(self, users):
|
|
# verify all the addresses were joined loaded with no more sql
|
|
def go():
|
|
for u, static in zip(users, self.static.user_all_result):
|
|
eq_(u.addresses, static.addresses)
|
|
self.assert_sql_count(testing.db, go, 0)
|
|
|
|
def _downgrade_fixture(self):
|
|
users, Keyword, items, order_items, orders, Item, User, \
|
|
Address, keywords, item_keywords, Order, addresses = \
|
|
self.tables.users, self.classes.Keyword, self.tables.items, \
|
|
self.tables.order_items, self.tables.orders, \
|
|
self.classes.Item, self.classes.User, self.classes.Address, \
|
|
self.tables.keywords, self.tables.item_keywords, \
|
|
self.classes.Order, self.tables.addresses
|
|
|
|
mapper(Address, addresses)
|
|
|
|
mapper(Keyword, keywords)
|
|
|
|
mapper(Item, items, properties=dict(
|
|
keywords=relationship(Keyword, secondary=item_keywords,
|
|
lazy='subquery',
|
|
order_by=item_keywords.c.keyword_id)))
|
|
|
|
mapper(Order, orders, properties=dict(
|
|
items=relationship(Item, secondary=order_items, lazy='subquery',
|
|
order_by=order_items.c.item_id)))
|
|
|
|
mapper(User, users, properties=dict(
|
|
addresses=relationship(Address, lazy='joined',
|
|
order_by=addresses.c.id),
|
|
orders=relationship(Order, lazy='joined',
|
|
order_by=orders.c.id)))
|
|
|
|
return create_session()
|
|
|
|
def _upgrade_fixture(self):
|
|
users, Keyword, items, order_items, orders, Item, User, \
|
|
Address, keywords, item_keywords, Order, addresses = \
|
|
self.tables.users, self.classes.Keyword, self.tables.items, \
|
|
self.tables.order_items, self.tables.orders, \
|
|
self.classes.Item, self.classes.User, self.classes.Address, \
|
|
self.tables.keywords, self.tables.item_keywords, \
|
|
self.classes.Order, self.tables.addresses
|
|
|
|
mapper(Address, addresses)
|
|
|
|
mapper(Keyword, keywords)
|
|
|
|
mapper(Item, items, properties=dict(
|
|
keywords=relationship(Keyword, secondary=item_keywords,
|
|
lazy='select',
|
|
order_by=item_keywords.c.keyword_id)))
|
|
|
|
mapper(Order, orders, properties=dict(
|
|
items=relationship(Item, secondary=order_items, lazy=True,
|
|
order_by=order_items.c.item_id)))
|
|
|
|
mapper(User, users, properties=dict(
|
|
addresses=relationship(Address, lazy=True,
|
|
order_by=addresses.c.id),
|
|
orders=relationship(Order,
|
|
order_by=orders.c.id)))
|
|
|
|
return create_session()
|
|
|
|
def test_downgrade_baseline(self):
|
|
"""Mapper strategy defaults load as expected
|
|
(compare to rest of DefaultStrategyOptionsTest downgrade tests)."""
|
|
sess = self._downgrade_fixture()
|
|
users = []
|
|
|
|
# test _downgrade_fixture mapper defaults, 3 queries (2 subquery loads).
|
|
def go():
|
|
users[:] = sess.query(self.classes.User)\
|
|
.order_by(self.classes.User.id)\
|
|
.all()
|
|
self.assert_sql_count(testing.db, go, 3)
|
|
|
|
# all loaded with no additional sql
|
|
self._assert_fully_loaded(users)
|
|
|
|
def test_disable_eagerloads(self):
|
|
"""Mapper eager load strategy defaults can be shut off
|
|
with enable_eagerloads(False)."""
|
|
|
|
# While this isn't testing a mapper option, it is included
|
|
# as baseline reference for how XYZload('*') option
|
|
# should work, namely, it shouldn't affect later queries
|
|
# (see other test_select_s)
|
|
sess = self._downgrade_fixture()
|
|
users = []
|
|
|
|
# demonstrate that enable_eagerloads loads with only 1 sql
|
|
def go():
|
|
users[:] = sess.query(self.classes.User)\
|
|
.enable_eagerloads(False)\
|
|
.order_by(self.classes.User.id)\
|
|
.all()
|
|
self.assert_sql_count(testing.db, go, 1)
|
|
|
|
# demonstrate that users[0].orders must now be loaded with 3 sql
|
|
# (need to lazyload, and 2 subquery: 3 total)
|
|
def go():
|
|
users[0].orders
|
|
self.assert_sql_count(testing.db, go, 3)
|
|
|
|
def test_last_one_wins(self):
|
|
sess = self._downgrade_fixture()
|
|
users = []
|
|
|
|
def go():
|
|
users[:] = sess.query(self.classes.User)\
|
|
.options(sa.orm.subqueryload('*'))\
|
|
.options(sa.orm.joinedload(self.classes.User.addresses))\
|
|
.options(sa.orm.lazyload('*'))\
|
|
.order_by(self.classes.User.id)\
|
|
.all()
|
|
self.assert_sql_count(testing.db, go, 1)
|
|
|
|
# verify all the addresses were joined loaded (no more sql)
|
|
self._assert_addresses_loaded(users)
|
|
|
|
def test_star_must_be_alone(self):
|
|
sess = self._downgrade_fixture()
|
|
User = self.classes.User
|
|
opt = sa.orm.subqueryload('*', User.addresses)
|
|
assert_raises_message(
|
|
sa.exc.ArgumentError,
|
|
"Wildcard token cannot be followed by another entity",
|
|
sess.query(User).options, opt
|
|
)
|
|
|
|
def test_global_star_ignored_no_entities_unbound(self):
|
|
sess = self._downgrade_fixture()
|
|
User = self.classes.User
|
|
opt = sa.orm.lazyload('*')
|
|
q = sess.query(User.name).options(opt)
|
|
eq_(q.all(), [('jack',), ('ed',), ('fred',), ('chuck',)])
|
|
|
|
def test_global_star_ignored_no_entities_bound(self):
|
|
sess = self._downgrade_fixture()
|
|
User = self.classes.User
|
|
opt = sa.orm.Load(User).lazyload('*')
|
|
q = sess.query(User.name).options(opt)
|
|
eq_(q.all(), [('jack',), ('ed',), ('fred',), ('chuck',)])
|
|
|
|
def test_select_with_joinedload(self):
|
|
"""Mapper load strategy defaults can be downgraded with
|
|
lazyload('*') option, while explicit joinedload() option
|
|
is still honored"""
|
|
sess = self._downgrade_fixture()
|
|
users = []
|
|
|
|
# lazyload('*') shuts off 'orders' subquery: only 1 sql
|
|
def go():
|
|
users[:] = sess.query(self.classes.User)\
|
|
.options(sa.orm.lazyload('*'))\
|
|
.options(sa.orm.joinedload(self.classes.User.addresses))\
|
|
.order_by(self.classes.User.id)\
|
|
.all()
|
|
self.assert_sql_count(testing.db, go, 1)
|
|
|
|
# verify all the addresses were joined loaded (no more sql)
|
|
self._assert_addresses_loaded(users)
|
|
|
|
# users[0] has orders, which need to lazy load, and 2 subquery:
|
|
# (same as with test_disable_eagerloads): 3 total sql
|
|
def go():
|
|
users[0].orders
|
|
self.assert_sql_count(testing.db, go, 3)
|
|
|
|
def test_select_with_subqueryload(self):
|
|
"""Mapper load strategy defaults can be downgraded with
|
|
lazyload('*') option, while explicit subqueryload() option
|
|
is still honored"""
|
|
sess = self._downgrade_fixture()
|
|
users = []
|
|
|
|
# now test 'default_strategy' option combined with 'subquery'
|
|
# shuts off 'addresses' load AND orders.items load: 2 sql expected
|
|
def go():
|
|
users[:] = sess.query(self.classes.User)\
|
|
.options(sa.orm.lazyload('*'))\
|
|
.options(sa.orm.subqueryload(self.classes.User.orders))\
|
|
.order_by(self.classes.User.id)\
|
|
.all()
|
|
self.assert_sql_count(testing.db, go, 2)
|
|
|
|
# Verify orders have already been loaded: 0 sql
|
|
def go():
|
|
for u, static in zip(users, self.static.user_all_result):
|
|
assert len(u.orders) == len(static.orders)
|
|
self.assert_sql_count(testing.db, go, 0)
|
|
|
|
# Verify lazyload('*') prevented orders.items load
|
|
# users[0].orders[0] has 3 items, each with keywords: 2 sql
|
|
# ('items' and 'items.keywords' subquery)
|
|
def go():
|
|
for i in users[0].orders[0].items:
|
|
i.keywords
|
|
self.assert_sql_count(testing.db, go, 2)
|
|
|
|
# lastly, make sure they actually loaded properly
|
|
eq_(users, self.static.user_all_result)
|
|
|
|
def test_noload_with_joinedload(self):
|
|
"""Mapper load strategy defaults can be downgraded with
|
|
noload('*') option, while explicit joinedload() option
|
|
is still honored"""
|
|
sess = self._downgrade_fixture()
|
|
users = []
|
|
|
|
# test noload('*') shuts off 'orders' subquery, only 1 sql
|
|
def go():
|
|
users[:] = sess.query(self.classes.User)\
|
|
.options(sa.orm.noload('*'))\
|
|
.options(sa.orm.joinedload(self.classes.User.addresses))\
|
|
.order_by(self.classes.User.id)\
|
|
.all()
|
|
self.assert_sql_count(testing.db, go, 1)
|
|
|
|
# verify all the addresses were joined loaded (no more sql)
|
|
self._assert_addresses_loaded(users)
|
|
|
|
# User.orders should have loaded "noload" (meaning [])
|
|
def go():
|
|
for u in users:
|
|
assert u.orders == []
|
|
self.assert_sql_count(testing.db, go, 0)
|
|
|
|
def test_noload_with_subqueryload(self):
|
|
"""Mapper load strategy defaults can be downgraded with
|
|
noload('*') option, while explicit subqueryload() option
|
|
is still honored"""
|
|
sess = self._downgrade_fixture()
|
|
users = []
|
|
|
|
# test noload('*') option combined with subqueryload()
|
|
# shuts off 'addresses' load AND orders.items load: 2 sql expected
|
|
def go():
|
|
users[:] = sess.query(self.classes.User)\
|
|
.options(sa.orm.noload('*'))\
|
|
.options(sa.orm.subqueryload(self.classes.User.orders))\
|
|
.order_by(self.classes.User.id)\
|
|
.all()
|
|
self.assert_sql_count(testing.db, go, 2)
|
|
|
|
def go():
|
|
# Verify orders have already been loaded: 0 sql
|
|
for u, static in zip(users, self.static.user_all_result):
|
|
assert len(u.orders) == len(static.orders)
|
|
# Verify noload('*') prevented orders.items load
|
|
# and set 'items' to []
|
|
for u in users:
|
|
for o in u.orders:
|
|
assert o.items == []
|
|
self.assert_sql_count(testing.db, go, 0)
|
|
|
|
def test_joined(self):
|
|
"""Mapper load strategy defaults can be upgraded with
|
|
joinedload('*') option."""
|
|
sess = self._upgrade_fixture()
|
|
users = []
|
|
|
|
# test upgrade all to joined: 1 sql
|
|
def go():
|
|
users[:] = sess.query(self.classes.User)\
|
|
.options(sa.orm.joinedload('*'))\
|
|
.order_by(self.classes.User.id)\
|
|
.all()
|
|
self.assert_sql_count(testing.db, go, 1)
|
|
|
|
# verify everything loaded, with no additional sql needed
|
|
self._assert_fully_loaded(users)
|
|
|
|
def test_joined_path_wildcards(self):
|
|
sess = self._upgrade_fixture()
|
|
users = []
|
|
|
|
# test upgrade all to joined: 1 sql
|
|
def go():
|
|
users[:] = sess.query(self.classes.User)\
|
|
.options(sa.orm.joinedload('.*'))\
|
|
.options(sa.orm.joinedload("addresses.*"))\
|
|
.options(sa.orm.joinedload("orders.*"))\
|
|
.options(sa.orm.joinedload("orders.items.*"))\
|
|
.order_by(self.classes.User.id)\
|
|
.all()
|
|
|
|
self.assert_sql_count(testing.db, go, 1)
|
|
self._assert_fully_loaded(users)
|
|
|
|
def test_joined_with_lazyload(self):
|
|
"""Mapper load strategy defaults can be upgraded with
|
|
joinedload('*') option, while explicit lazyload() option
|
|
is still honored"""
|
|
sess = self._upgrade_fixture()
|
|
users = []
|
|
|
|
# test joined all but 'keywords': upgraded to 1 sql
|
|
def go():
|
|
users[:] = sess.query(self.classes.User)\
|
|
.options(sa.orm.lazyload('orders.items.keywords'))\
|
|
.options(sa.orm.joinedload('*'))\
|
|
.order_by(self.classes.User.id)\
|
|
.all()
|
|
self.assert_sql_count(testing.db, go, 1)
|
|
|
|
# everything (but keywords) loaded ok
|
|
# (note self.static.user_all_result contains no keywords)
|
|
def go():
|
|
eq_(users, self.static.user_all_result)
|
|
self.assert_sql_count(testing.db, go, 0)
|
|
|
|
# verify the items were loaded, while item.keywords were not
|
|
def go():
|
|
# redundant with last test, but illustrative
|
|
users[0].orders[0].items[0]
|
|
self.assert_sql_count(testing.db, go, 0)
|
|
def go():
|
|
users[0].orders[0].items[0].keywords
|
|
self.assert_sql_count(testing.db, go, 1)
|
|
|
|
def test_joined_with_subqueryload(self):
|
|
"""Mapper load strategy defaults can be upgraded with
|
|
joinedload('*') option, while explicit subqueryload() option
|
|
is still honored"""
|
|
sess = self._upgrade_fixture()
|
|
users = []
|
|
|
|
# test upgrade all but 'addresses', which is subquery loaded (2 sql)
|
|
def go():
|
|
users[:] = sess.query(self.classes.User)\
|
|
.options(sa.orm.subqueryload(self.classes.User.addresses))\
|
|
.options(sa.orm.joinedload('*'))\
|
|
.order_by(self.classes.User.id)\
|
|
.all()
|
|
self.assert_sql_count(testing.db, go, 2)
|
|
|
|
# verify everything loaded, with no additional sql needed
|
|
self._assert_fully_loaded(users)
|
|
|
|
def test_subquery(self):
|
|
"""Mapper load strategy defaults can be upgraded with
|
|
subqueryload('*') option."""
|
|
sess = self._upgrade_fixture()
|
|
users = []
|
|
|
|
# test upgrade all to subquery: 1 sql + 4 relationships = 5
|
|
def go():
|
|
users[:] = sess.query(self.classes.User)\
|
|
.options(sa.orm.subqueryload('*'))\
|
|
.order_by(self.classes.User.id)\
|
|
.all()
|
|
self.assert_sql_count(testing.db, go, 5)
|
|
|
|
# verify everything loaded, with no additional sql needed
|
|
self._assert_fully_loaded(users)
|
|
|
|
def test_subquery_path_wildcards(self):
|
|
sess = self._upgrade_fixture()
|
|
users = []
|
|
|
|
# test upgrade all to subquery: 1 sql + 4 relationships = 5
|
|
def go():
|
|
users[:] = sess.query(self.classes.User)\
|
|
.options(sa.orm.subqueryload('.*'))\
|
|
.options(sa.orm.subqueryload('addresses.*'))\
|
|
.options(sa.orm.subqueryload('orders.*'))\
|
|
.options(sa.orm.subqueryload('orders.items.*'))\
|
|
.order_by(self.classes.User.id)\
|
|
.all()
|
|
self.assert_sql_count(testing.db, go, 5)
|
|
|
|
# verify everything loaded, with no additional sql needed
|
|
self._assert_fully_loaded(users)
|
|
|
|
def test_subquery_with_lazyload(self):
|
|
"""Mapper load strategy defaults can be upgraded with
|
|
subqueryload('*') option, while explicit lazyload() option
|
|
is still honored"""
|
|
sess = self._upgrade_fixture()
|
|
users = []
|
|
|
|
# test subquery all but 'keywords' (1 sql + 3 relationships = 4)
|
|
def go():
|
|
users[:] = sess.query(self.classes.User)\
|
|
.options(sa.orm.lazyload('orders.items.keywords'))\
|
|
.options(sa.orm.subqueryload('*'))\
|
|
.order_by(self.classes.User.id)\
|
|
.all()
|
|
self.assert_sql_count(testing.db, go, 4)
|
|
|
|
# no more sql
|
|
# (note self.static.user_all_result contains no keywords)
|
|
def go():
|
|
eq_(users, self.static.user_all_result)
|
|
self.assert_sql_count(testing.db, go, 0)
|
|
|
|
# verify the item.keywords were not loaded
|
|
def go():
|
|
users[0].orders[0].items[0]
|
|
self.assert_sql_count(testing.db, go, 0)
|
|
def go():
|
|
users[0].orders[0].items[0].keywords
|
|
self.assert_sql_count(testing.db, go, 1)
|
|
|
|
def test_subquery_with_joinedload(self):
|
|
"""Mapper load strategy defaults can be upgraded with
|
|
subqueryload('*') option, while multiple explicit
|
|
joinedload() options are still honored"""
|
|
sess = self._upgrade_fixture()
|
|
users = []
|
|
|
|
# test upgrade all but 'addresses' & 'orders', which are joinedloaded
|
|
# (1 sql + items + keywords = 3)
|
|
def go():
|
|
users[:] = sess.query(self.classes.User)\
|
|
.options(sa.orm.joinedload(self.classes.User.addresses))\
|
|
.options(sa.orm.joinedload(self.classes.User.orders))\
|
|
.options(sa.orm.subqueryload('*'))\
|
|
.order_by(self.classes.User.id)\
|
|
.all()
|
|
self.assert_sql_count(testing.db, go, 3)
|
|
|
|
# verify everything loaded, with no additional sql needed
|
|
self._assert_fully_loaded(users)
|