Files
sqlalchemy/test/orm/test_selectin_relations.py
T
Mike Bayer 19d2424e05 Add selectin loading
Adding a new kind of relationship loader that is
a cross between the "immediateload" and the "subquery"
eager loader, using an IN criteria to load related items
in bulk immediately after the lead query result is loaded.

Change-Id: If13713fba9b465865aef8fd50b5b6b977fe3ef7d
Fixes: #3944
2017-04-26 16:08:57 -04:00

1986 lines
70 KiB
Python

from sqlalchemy.testing import eq_, is_, is_not_, is_true
from sqlalchemy import testing
from sqlalchemy.testing.schema import Table, Column
from sqlalchemy import Integer, String, ForeignKey, bindparam
from sqlalchemy.orm import selectinload, selectinload_all, \
mapper, relationship, clear_mappers, create_session, \
aliased, joinedload, deferred, undefer,\
Session, subqueryload
from sqlalchemy.testing import assert_raises, \
assert_raises_message
from sqlalchemy.testing.assertsql import CompiledSQL
from sqlalchemy.testing import fixtures
from sqlalchemy.testing import mock
from test.orm import _fixtures
import sqlalchemy as sa
from sqlalchemy.orm import with_polymorphic
from .inheritance._poly_fixtures import _Polymorphic, Person, Engineer, \
Paperwork, Machine, MachineType, Company
class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
run_inserts = 'once'
run_deletes = None
def test_basic(self):
users, Address, addresses, User = (self.tables.users,
self.classes.Address,
self.tables.addresses,
self.classes.User)
mapper(User, users, properties={
'addresses': relationship(
mapper(Address, addresses),
order_by=Address.id)
})
sess = create_session()
q = sess.query(User).options(selectinload(User.addresses))
def go():
eq_(
[User(id=7, addresses=[
Address(id=1, email_address='jack@bean.com')])],
q.filter(User.id == 7).all()
)
self.assert_sql_count(testing.db, go, 2)
def go():
eq_(
self.static.user_address_result,
q.order_by(User.id).all()
)
self.assert_sql_count(testing.db, go, 2)
def test_from_aliased(self):
users, Dingaling, User, dingalings, Address, addresses = (
self.tables.users,
self.classes.Dingaling,
self.classes.User,
self.tables.dingalings,
self.classes.Address,
self.tables.addresses)
mapper(Dingaling, dingalings)
mapper(Address, addresses, properties={
'dingalings': relationship(Dingaling, order_by=Dingaling.id)
})
mapper(User, users, properties={
'addresses': relationship(
Address,
order_by=Address.id)
})
sess = create_session()
u = aliased(User)
q = sess.query(u).options(selectinload(u.addresses))
def go():
eq_(
[User(id=7, addresses=[
Address(id=1, email_address='jack@bean.com')])],
q.filter(u.id == 7).all()
)
self.assert_sql_count(testing.db, go, 2)
def go():
eq_(
self.static.user_address_result,
q.order_by(u.id).all()
)
self.assert_sql_count(testing.db, go, 2)
q = sess.query(u).\
options(selectinload_all(u.addresses, Address.dingalings))
def go():
eq_(
[
User(id=8, addresses=[
Address(id=2, email_address='ed@wood.com',
dingalings=[Dingaling()]),
Address(id=3, email_address='ed@bettyboop.com'),
Address(id=4, email_address='ed@lala.com'),
]),
User(id=9, addresses=[
Address(id=5, dingalings=[Dingaling()])
]),
],
q.filter(u.id.in_([8, 9])).all()
)
self.assert_sql_count(testing.db, go, 3)
def test_from_get(self):
users, Address, addresses, User = (self.tables.users,
self.classes.Address,
self.tables.addresses,
self.classes.User)
mapper(User, users, properties={
'addresses': relationship(
mapper(Address, addresses),
order_by=Address.id)
})
sess = create_session()
q = sess.query(User).options(selectinload(User.addresses))
def go():
eq_(
User(id=7, addresses=[
Address(id=1, email_address='jack@bean.com')]),
q.get(7)
)
self.assert_sql_count(testing.db, go, 2)
def test_from_params(self):
users, Address, addresses, User = (self.tables.users,
self.classes.Address,
self.tables.addresses,
self.classes.User)
mapper(User, users, properties={
'addresses': relationship(
mapper(Address, addresses),
order_by=Address.id)
})
sess = create_session()
q = sess.query(User).options(selectinload(User.addresses))
def go():
eq_(
User(id=7, addresses=[
Address(id=1, email_address='jack@bean.com')]),
q.filter(User.id == bindparam('foo')).params(foo=7).one()
)
self.assert_sql_count(testing.db, go, 2)
def test_disable_dynamic(self):
"""test no selectin option on a dynamic."""
users, Address, addresses, User = (self.tables.users,
self.classes.Address,
self.tables.addresses,
self.classes.User)
mapper(User, users, properties={
'addresses': relationship(Address, lazy="dynamic")
})
mapper(Address, addresses)
sess = create_session()
# previously this would not raise, but would emit
# the query needlessly and put the result nowhere.
assert_raises_message(
sa.exc.InvalidRequestError,
"User.addresses' does not support object population - eager "
"loading cannot be applied.",
sess.query(User).options(selectinload(User.addresses)).first,
)
def test_many_to_many_plain(self):
keywords, items, item_keywords, Keyword, Item = (
self.tables.keywords,
self.tables.items,
self.tables.item_keywords,
self.classes.Keyword,
self.classes.Item)
mapper(Keyword, keywords)
mapper(Item, items, properties=dict(
keywords=relationship(Keyword, secondary=item_keywords,
lazy='selectin', order_by=keywords.c.id)))
q = create_session().query(Item).order_by(Item.id)
def go():
eq_(self.static.item_keyword_result, q.all())
self.assert_sql_count(testing.db, go, 2)
def test_many_to_many_with_join(self):
keywords, items, item_keywords, Keyword, Item = (
self.tables.keywords,
self.tables.items,
self.tables.item_keywords,
self.classes.Keyword,
self.classes.Item)
mapper(Keyword, keywords)
mapper(Item, items, properties=dict(
keywords=relationship(Keyword, secondary=item_keywords,
lazy='selectin', order_by=keywords.c.id)))
q = create_session().query(Item).order_by(Item.id)
def go():
eq_(self.static.item_keyword_result[0:2],
q.join('keywords').filter(Keyword.name == 'red').all())
self.assert_sql_count(testing.db, go, 2)
def test_many_to_many_with_join_alias(self):
keywords, items, item_keywords, Keyword, Item = (
self.tables.keywords,
self.tables.items,
self.tables.item_keywords,
self.classes.Keyword,
self.classes.Item)
mapper(Keyword, keywords)
mapper(Item, items, properties=dict(
keywords=relationship(Keyword, secondary=item_keywords,
lazy='selectin', order_by=keywords.c.id)))
q = create_session().query(Item).order_by(Item.id)
def go():
eq_(self.static.item_keyword_result[0:2],
(q.join('keywords', aliased=True).
filter(Keyword.name == 'red')).all())
self.assert_sql_count(testing.db, go, 2)
def test_orderby(self):
users, Address, addresses, User = (self.tables.users,
self.classes.Address,
self.tables.addresses,
self.classes.User)
mapper(User, users, properties={
'addresses': relationship(mapper(Address, addresses),
lazy='selectin',
order_by=addresses.c.email_address),
})
q = create_session().query(User)
eq_([
User(id=7, addresses=[
Address(id=1)
]),
User(id=8, addresses=[
Address(id=3, email_address='ed@bettyboop.com'),
Address(id=4, email_address='ed@lala.com'),
Address(id=2, email_address='ed@wood.com')
]),
User(id=9, addresses=[
Address(id=5)
]),
User(id=10, addresses=[])
], q.order_by(User.id).all())
def test_orderby_multi(self):
users, Address, addresses, User = (self.tables.users,
self.classes.Address,
self.tables.addresses,
self.classes.User)
mapper(User, users, properties={
'addresses': relationship(mapper(Address, addresses),
lazy='selectin',
order_by=[
addresses.c.email_address,
addresses.c.id]),
})
q = create_session().query(User)
eq_([
User(id=7, addresses=[
Address(id=1)
]),
User(id=8, addresses=[
Address(id=3, email_address='ed@bettyboop.com'),
Address(id=4, email_address='ed@lala.com'),
Address(id=2, email_address='ed@wood.com')
]),
User(id=9, addresses=[
Address(id=5)
]),
User(id=10, addresses=[])
], q.order_by(User.id).all())
def test_orderby_related(self):
"""A regular mapper select on a single table can
order by a relationship to a second table"""
Address, addresses, users, User = (self.classes.Address,
self.tables.addresses,
self.tables.users,
self.classes.User)
mapper(Address, addresses)
mapper(User, users, properties=dict(
addresses=relationship(Address,
lazy='selectin',
order_by=addresses.c.id),
))
q = create_session().query(User)
result = q.filter(User.id == Address.user_id).\
order_by(Address.email_address).all()
eq_([
User(id=8, addresses=[
Address(id=2, email_address='ed@wood.com'),
Address(id=3, email_address='ed@bettyboop.com'),
Address(id=4, email_address='ed@lala.com'),
]),
User(id=9, addresses=[
Address(id=5)
]),
User(id=7, addresses=[
Address(id=1)
]),
], result)
def test_orderby_desc(self):
Address, addresses, users, User = (self.classes.Address,
self.tables.addresses,
self.tables.users,
self.classes.User)
mapper(Address, addresses)
mapper(User, users, properties=dict(
addresses=relationship(Address, lazy='selectin',
order_by=[
sa.desc(addresses.c.email_address)
]),
))
sess = create_session()
eq_([
User(id=7, addresses=[
Address(id=1)
]),
User(id=8, addresses=[
Address(id=2, email_address='ed@wood.com'),
Address(id=4, email_address='ed@lala.com'),
Address(id=3, email_address='ed@bettyboop.com'),
]),
User(id=9, addresses=[
Address(id=5)
]),
User(id=10, addresses=[])
], sess.query(User).order_by(User.id).all())
_pathing_runs = [
("lazyload", "lazyload", "lazyload", 15),
("selectinload", "lazyload", "lazyload", 12),
("selectinload", "selectinload", "lazyload", 8),
("joinedload", "selectinload", "lazyload", 7),
("lazyload", "lazyload", "selectinload", 12),
("selectinload", "selectinload", "selectinload", 4),
("selectinload", "selectinload", "joinedload", 3),
]
def test_options_pathing(self):
self._do_options_test(self._pathing_runs)
def test_mapper_pathing(self):
self._do_mapper_test(self._pathing_runs)
def _do_options_test(self, configs):
users, Keyword, orders, items, order_items, Order, Item, User, \
keywords, item_keywords = (self.tables.users,
self.classes.Keyword,
self.tables.orders,
self.tables.items,
self.tables.order_items,
self.classes.Order,
self.classes.Item,
self.classes.User,
self.tables.keywords,
self.tables.item_keywords)
mapper(User, users, properties={
'orders': relationship(Order, order_by=orders.c.id), # o2m, m2o
})
mapper(Order, orders, properties={
'items': relationship(Item,
secondary=order_items,
order_by=items.c.id), # m2m
})
mapper(Item, items, properties={
'keywords': relationship(Keyword,
secondary=item_keywords,
order_by=keywords.c.id) # m2m
})
mapper(Keyword, keywords)
callables = {
'joinedload': joinedload,
'selectinload': selectinload,
'subqueryload': subqueryload
}
for o, i, k, count in configs:
options = []
if o in callables:
options.append(callables[o](User.orders))
if i in callables:
options.append(callables[i](User.orders, Order.items))
if k in callables:
options.append(callables[k](
User.orders, Order.items, Item.keywords))
self._do_query_tests(options, count)
def _do_mapper_test(self, configs):
users, Keyword, orders, items, order_items, Order, Item, User, \
keywords, item_keywords = (self.tables.users,
self.classes.Keyword,
self.tables.orders,
self.tables.items,
self.tables.order_items,
self.classes.Order,
self.classes.Item,
self.classes.User,
self.tables.keywords,
self.tables.item_keywords)
opts = {
'lazyload': 'select',
'joinedload': 'joined',
'selectinload': 'selectin',
}
for o, i, k, count in configs:
mapper(User, users, properties={
'orders': relationship(Order, lazy=opts[o],
order_by=orders.c.id),
})
mapper(Order, orders, properties={
'items': relationship(Item,
secondary=order_items, lazy=opts[i],
order_by=items.c.id),
})
mapper(Item, items, properties={
'keywords': relationship(Keyword,
lazy=opts[k],
secondary=item_keywords,
order_by=keywords.c.id)
})
mapper(Keyword, keywords)
try:
self._do_query_tests([], count)
finally:
clear_mappers()
def _do_query_tests(self, opts, count):
Order, User = self.classes.Order, self.classes.User
sess = create_session()
def go():
eq_(
sess.query(User).options(*opts).order_by(User.id).all(),
self.static.user_item_keyword_result
)
self.assert_sql_count(testing.db, go, count)
eq_(
sess.query(User).options(*opts).filter(User.name == 'fred').
order_by(User.id).all(),
self.static.user_item_keyword_result[2:3]
)
sess = create_session()
eq_(
sess.query(User).options(*opts).join(User.orders).
filter(Order.id == 3).
order_by(User.id).all(),
self.static.user_item_keyword_result[0:1]
)
def test_cyclical(self):
"""A circular eager relationship breaks the cycle with a lazy loader"""
Address, addresses, users, User = (self.classes.Address,
self.tables.addresses,
self.tables.users,
self.classes.User)
mapper(Address, addresses)
mapper(User, users, properties=dict(
addresses=relationship(Address, lazy='selectin',
backref=sa.orm.backref(
'user', lazy='selectin'),
order_by=Address.id)
))
is_(sa.orm.class_mapper(User).get_property('addresses').lazy,
'selectin')
is_(sa.orm.class_mapper(Address).get_property('user').lazy, 'selectin')
sess = create_session()
eq_(self.static.user_address_result,
sess.query(User).order_by(User.id).all())
def test_cyclical_explicit_join_depth(self):
"""A circular eager relationship breaks the cycle with a lazy loader"""
Address, addresses, users, User = (self.classes.Address,
self.tables.addresses,
self.tables.users,
self.classes.User)
mapper(Address, addresses)
mapper(User, users, properties=dict(
addresses=relationship(Address, lazy='selectin', join_depth=1,
backref=sa.orm.backref(
'user', lazy='selectin', join_depth=1),
order_by=Address.id)
))
is_(sa.orm.class_mapper(User).get_property('addresses').lazy,
'selectin')
is_(sa.orm.class_mapper(Address).get_property('user').lazy, 'selectin')
sess = create_session()
eq_(self.static.user_address_result,
sess.query(User).order_by(User.id).all())
def test_double(self):
"""Eager loading with two relationships simultaneously,
from the same table, using aliases."""
users, orders, User, Address, Order, addresses = (
self.tables.users,
self.tables.orders,
self.classes.User,
self.classes.Address,
self.classes.Order,
self.tables.addresses)
openorders = sa.alias(orders, 'openorders')
closedorders = sa.alias(orders, 'closedorders')
mapper(Address, addresses)
mapper(Order, orders)
open_mapper = mapper(Order, openorders, non_primary=True)
closed_mapper = mapper(Order, closedorders, non_primary=True)
mapper(User, users, properties=dict(
addresses=relationship(Address, lazy='selectin',
order_by=addresses.c.id),
open_orders=relationship(
open_mapper,
primaryjoin=sa.and_(openorders.c.isopen == 1,
users.c.id == openorders.c.user_id),
lazy='selectin', order_by=openorders.c.id),
closed_orders=relationship(
closed_mapper,
primaryjoin=sa.and_(closedorders.c.isopen == 0,
users.c.id == closedorders.c.user_id),
lazy='selectin', order_by=closedorders.c.id)))
q = create_session().query(User).order_by(User.id)
def go():
eq_([
User(
id=7,
addresses=[Address(id=1)],
open_orders=[Order(id=3)],
closed_orders=[Order(id=1), Order(id=5)]
),
User(
id=8,
addresses=[Address(id=2), Address(id=3), Address(id=4)],
open_orders=[],
closed_orders=[]
),
User(
id=9,
addresses=[Address(id=5)],
open_orders=[Order(id=4)],
closed_orders=[Order(id=2)]
),
User(id=10)
], q.all())
self.assert_sql_count(testing.db, go, 4)
def test_double_same_mappers(self):
"""Eager loading with two relationships simultaneously,
from the same table, using aliases."""
addresses, items, order_items, orders, Item, User, Address, Order, \
users = (self.tables.addresses,
self.tables.items,
self.tables.order_items,
self.tables.orders,
self.classes.Item,
self.classes.User,
self.classes.Address,
self.classes.Order,
self.tables.users)
mapper(Address, addresses)
mapper(Order, orders, properties={
'items': relationship(Item, secondary=order_items, lazy='selectin',
order_by=items.c.id)})
mapper(Item, items)
mapper(User, users, properties=dict(
addresses=relationship(
Address, lazy='selectin', order_by=addresses.c.id),
open_orders=relationship(
Order,
primaryjoin=sa.and_(orders.c.isopen == 1,
users.c.id == orders.c.user_id),
lazy='selectin', order_by=orders.c.id),
closed_orders=relationship(
Order,
primaryjoin=sa.and_(orders.c.isopen == 0,
users.c.id == orders.c.user_id),
lazy='selectin', order_by=orders.c.id)))
q = create_session().query(User).order_by(User.id)
def go():
eq_([
User(id=7,
addresses=[
Address(id=1)],
open_orders=[Order(id=3,
items=[
Item(id=3),
Item(id=4),
Item(id=5)])],
closed_orders=[Order(id=1,
items=[
Item(id=1),
Item(id=2),
Item(id=3)]),
Order(id=5,
items=[
Item(id=5)])]),
User(id=8,
addresses=[
Address(id=2),
Address(id=3),
Address(id=4)],
open_orders=[],
closed_orders=[]),
User(id=9,
addresses=[
Address(id=5)],
open_orders=[
Order(id=4,
items=[
Item(id=1),
Item(id=5)])],
closed_orders=[
Order(id=2,
items=[
Item(id=1),
Item(id=2),
Item(id=3)])]),
User(id=10)
], q.all())
self.assert_sql_count(testing.db, go, 6)
def test_limit(self):
"""Limit operations combined with lazy-load relationships."""
users, items, order_items, orders, Item, User, Address, Order, \
addresses = (self.tables.users,
self.tables.items,
self.tables.order_items,
self.tables.orders,
self.classes.Item,
self.classes.User,
self.classes.Address,
self.classes.Order,
self.tables.addresses)
mapper(Item, items)
mapper(Order, orders, properties={
'items': relationship(Item, secondary=order_items, lazy='selectin',
order_by=items.c.id)
})
mapper(User, users, properties={
'addresses': relationship(mapper(Address, addresses),
lazy='selectin',
order_by=addresses.c.id),
'orders': relationship(Order, lazy='select', order_by=orders.c.id)
})
sess = create_session()
q = sess.query(User)
result = q.order_by(User.id).limit(2).offset(1).all()
eq_(self.static.user_all_result[1:3], result)
result = q.order_by(sa.desc(User.id)).limit(2).offset(2).all()
eq_(list(reversed(self.static.user_all_result[0:2])), result)
@testing.uses_deprecated("Mapper.order_by")
def test_mapper_order_by(self):
users, User, Address, addresses = (self.tables.users,
self.classes.User,
self.classes.Address,
self.tables.addresses)
mapper(Address, addresses)
mapper(User, users, properties={
'addresses': relationship(Address,
lazy='selectin',
order_by=addresses.c.id),
}, order_by=users.c.id.desc())
sess = create_session()
q = sess.query(User)
result = q.limit(2).all()
eq_(result, list(reversed(self.static.user_address_result[2:4])))
def test_one_to_many_scalar(self):
Address, addresses, users, User = (self.classes.Address,
self.tables.addresses,
self.tables.users,
self.classes.User)
mapper(User, users, properties=dict(
address=relationship(mapper(Address, addresses),
lazy='selectin', uselist=False)
))
q = create_session().query(User)
def go():
result = q.filter(users.c.id == 7).all()
eq_([User(id=7, address=Address(id=1))], result)
self.assert_sql_count(testing.db, go, 2)
def test_many_to_one(self):
users, Address, addresses, User = (self.tables.users,
self.classes.Address,
self.tables.addresses,
self.classes.User)
mapper(Address, addresses, properties=dict(
user=relationship(mapper(User, users), lazy='selectin')
))
sess = create_session()
q = sess.query(Address)
def go():
a = q.filter(addresses.c.id == 1).one()
is_not_(a.user, None)
u1 = sess.query(User).get(7)
is_(a.user, u1)
self.assert_sql_count(testing.db, go, 2)
def test_double_with_aggregate(self):
User, users, orders, Order = (self.classes.User,
self.tables.users,
self.tables.orders,
self.classes.Order)
max_orders_by_user = sa.select([sa.func.max(orders.c.id)
.label('order_id')],
group_by=[orders.c.user_id]) \
.alias('max_orders_by_user')
max_orders = orders.select(
orders.c.id == max_orders_by_user.c.order_id).\
alias('max_orders')
mapper(Order, orders)
mapper(User, users, properties={
'orders': relationship(Order, backref='user', lazy='selectin',
order_by=orders.c.id),
'max_order': relationship(
mapper(Order, max_orders, non_primary=True),
lazy='selectin', uselist=False)
})
q = create_session().query(User)
def go():
eq_([
User(id=7, orders=[
Order(id=1),
Order(id=3),
Order(id=5),
],
max_order=Order(id=5)
),
User(id=8, orders=[]),
User(id=9, orders=[Order(id=2), Order(id=4)],
max_order=Order(id=4)),
User(id=10),
], q.order_by(User.id).all())
self.assert_sql_count(testing.db, go, 3)
def test_uselist_false_warning(self):
"""test that multiple rows received by a
uselist=False raises a warning."""
User, users, orders, Order = (self.classes.User,
self.tables.users,
self.tables.orders,
self.classes.Order)
mapper(User, users, properties={
'order': relationship(Order, uselist=False)
})
mapper(Order, orders)
s = create_session()
assert_raises(sa.exc.SAWarning,
s.query(User).options(selectinload(User.order)).all)
class LoadOnExistingTest(_fixtures.FixtureTest):
"""test that loaders from a base Query fully populate."""
run_inserts = 'once'
run_deletes = None
def _collection_to_scalar_fixture(self):
User, Address, Dingaling = self.classes.User, \
self.classes.Address, self.classes.Dingaling
mapper(User, self.tables.users, properties={
'addresses': relationship(Address),
})
mapper(Address, self.tables.addresses, properties={
'dingaling': relationship(Dingaling)
})
mapper(Dingaling, self.tables.dingalings)
sess = Session(autoflush=False)
return User, Address, Dingaling, sess
def _collection_to_collection_fixture(self):
User, Order, Item = self.classes.User, \
self.classes.Order, self.classes.Item
mapper(User, self.tables.users, properties={
'orders': relationship(Order),
})
mapper(Order, self.tables.orders, properties={
'items': relationship(Item, secondary=self.tables.order_items),
})
mapper(Item, self.tables.items)
sess = Session(autoflush=False)
return User, Order, Item, sess
def _eager_config_fixture(self):
User, Address = self.classes.User, self.classes.Address
mapper(User, self.tables.users, properties={
'addresses': relationship(Address, lazy="selectin"),
})
mapper(Address, self.tables.addresses)
sess = Session(autoflush=False)
return User, Address, sess
def _deferred_config_fixture(self):
User, Address = self.classes.User, self.classes.Address
mapper(User, self.tables.users, properties={
'name': deferred(self.tables.users.c.name),
'addresses': relationship(Address, lazy="selectin"),
})
mapper(Address, self.tables.addresses)
sess = Session(autoflush=False)
return User, Address, sess
def test_no_query_on_refresh(self):
User, Address, sess = self._eager_config_fixture()
u1 = sess.query(User).get(8)
assert 'addresses' in u1.__dict__
sess.expire(u1)
def go():
eq_(u1.id, 8)
self.assert_sql_count(testing.db, go, 1)
assert 'addresses' not in u1.__dict__
def test_no_query_on_deferred(self):
User, Address, sess = self._deferred_config_fixture()
u1 = sess.query(User).get(8)
assert 'addresses' in u1.__dict__
sess.expire(u1, ['addresses'])
def go():
eq_(u1.name, 'ed')
self.assert_sql_count(testing.db, go, 1)
assert 'addresses' not in u1.__dict__
def test_populate_existing_propagate(self):
User, Address, sess = self._eager_config_fixture()
u1 = sess.query(User).get(8)
u1.addresses[2].email_address = "foofoo"
del u1.addresses[1]
u1 = sess.query(User).populate_existing().filter_by(id=8).one()
# collection is reverted
eq_(len(u1.addresses), 3)
# attributes on related items reverted
eq_(u1.addresses[2].email_address, "ed@lala.com")
def test_loads_second_level_collection_to_scalar(self):
User, Address, Dingaling, sess = self._collection_to_scalar_fixture()
u1 = sess.query(User).get(8)
a1 = Address()
u1.addresses.append(a1)
a2 = u1.addresses[0]
a2.email_address = 'foo'
sess.query(User).options(selectinload_all("addresses.dingaling")).\
filter_by(id=8).all()
assert u1.addresses[-1] is a1
for a in u1.addresses:
if a is not a1:
assert 'dingaling' in a.__dict__
else:
assert 'dingaling' not in a.__dict__
if a is a2:
eq_(a2.email_address, 'foo')
def test_loads_second_level_collection_to_collection(self):
User, Order, Item, sess = self._collection_to_collection_fixture()
u1 = sess.query(User).get(7)
u1.orders
o1 = Order()
u1.orders.append(o1)
sess.query(User).options(selectinload_all("orders.items")).\
filter_by(id=7).all()
for o in u1.orders:
if o is not o1:
assert 'items' in o.__dict__
else:
assert 'items' not in o.__dict__
def test_load_two_levels_collection_to_scalar(self):
User, Address, Dingaling, sess = self._collection_to_scalar_fixture()
u1 = sess.query(User).filter_by(id=8).options(
selectinload("addresses")).one()
sess.query(User).filter_by(id=8).options(
selectinload_all("addresses.dingaling")).first()
assert 'dingaling' in u1.addresses[0].__dict__
def test_load_two_levels_collection_to_collection(self):
User, Order, Item, sess = self._collection_to_collection_fixture()
u1 = sess.query(User).filter_by(id=7).options(
selectinload("orders")).one()
sess.query(User).filter_by(id=7).options(
selectinload_all("orders.items")).first()
assert 'items' in u1.orders[0].__dict__
class OrderBySecondaryTest(fixtures.MappedTest):
@classmethod
def define_tables(cls, metadata):
Table('m2m', metadata,
Column('id', Integer, primary_key=True,
test_needs_autoincrement=True),
Column('aid', Integer, ForeignKey('a.id')),
Column('bid', Integer, ForeignKey('b.id')))
Table('a', metadata,
Column('id', Integer, primary_key=True,
test_needs_autoincrement=True),
Column('data', String(50)))
Table('b', metadata,
Column('id', Integer, primary_key=True,
test_needs_autoincrement=True),
Column('data', String(50)))
@classmethod
def fixtures(cls):
return dict(
a=(('id', 'data'),
(1, 'a1'),
(2, 'a2')),
b=(('id', 'data'),
(1, 'b1'),
(2, 'b2'),
(3, 'b3'),
(4, 'b4')),
m2m=(('id', 'aid', 'bid'),
(2, 1, 1),
(4, 2, 4),
(1, 1, 3),
(6, 2, 2),
(3, 1, 2),
(5, 2, 3)))
def test_ordering(self):
a, m2m, b = (self.tables.a,
self.tables.m2m,
self.tables.b)
class A(fixtures.ComparableEntity):
pass
class B(fixtures.ComparableEntity):
pass
mapper(A, a, properties={
'bs': relationship(B, secondary=m2m, lazy='selectin',
order_by=m2m.c.id)
})
mapper(B, b)
sess = create_session()
def go():
eq_(sess.query(A).all(), [
A(data='a1', bs=[B(data='b3'), B(data='b1'), B(data='b2')]),
A(bs=[B(data='b4'), B(data='b3'), B(data='b2')])
])
self.assert_sql_count(testing.db, go, 2)
class BaseRelationFromJoinedSubclassTest(_Polymorphic):
"""Like most tests here, this is adapted from subquery_relations
as part of general inheritance testing.
The subquery test exercised the issue that the subquery load must
imitate the original query very closely so that filter criteria, ordering
etc. can be maintained with the original query embedded. However,
for selectin loading, none of that is really needed, so here the secondary
queries are all just a simple "people JOIN paperwork".
"""
@classmethod
def define_tables(cls, metadata):
Table('people', metadata,
Column('person_id', Integer,
primary_key=True,
test_needs_autoincrement=True),
Column('name', String(50)),
Column('type', String(30)))
# to test fully, PK of engineers table must be
# named differently from that of people
Table('engineers', metadata,
Column('engineer_id', Integer,
ForeignKey('people.person_id'),
primary_key=True),
Column('primary_language', String(50)))
Table('paperwork', metadata,
Column('paperwork_id', Integer,
primary_key=True,
test_needs_autoincrement=True),
Column('description', String(50)),
Column('person_id', Integer,
ForeignKey('people.person_id')))
@classmethod
def setup_mappers(cls):
people = cls.tables.people
engineers = cls.tables.engineers
paperwork = cls.tables.paperwork
mapper(Person, people,
polymorphic_on=people.c.type,
polymorphic_identity='person',
properties={
'paperwork': relationship(
Paperwork, order_by=paperwork.c.paperwork_id)})
mapper(Engineer, engineers,
inherits=Person,
polymorphic_identity='engineer')
mapper(Paperwork, paperwork)
@classmethod
def insert_data(cls):
e1 = Engineer(primary_language="java")
e2 = Engineer(primary_language="c++")
e1.paperwork = [Paperwork(description="tps report #1"),
Paperwork(description="tps report #2")]
e2.paperwork = [Paperwork(description="tps report #3")]
sess = create_session()
sess.add_all([e1, e2])
sess.flush()
def test_correct_select_nofrom(self):
sess = create_session()
# use Person.paperwork here just to give the least
# amount of context
q = sess.query(Engineer).\
filter(Engineer.primary_language == 'java').\
options(selectinload(Person.paperwork))
def go():
eq_(q.all()[0].paperwork,
[Paperwork(description="tps report #1"),
Paperwork(description="tps report #2")],
)
self.assert_sql_execution(
testing.db,
go,
CompiledSQL(
"SELECT people.person_id AS people_person_id, "
"people.name AS people_name, people.type AS people_type, "
"engineers.engineer_id AS engineers_engineer_id, "
"engineers.primary_language AS engineers_primary_language "
"FROM people JOIN engineers ON "
"people.person_id = engineers.engineer_id "
"WHERE engineers.primary_language = :primary_language_1",
{"primary_language_1": "java"}
),
CompiledSQL(
"SELECT people_1.person_id AS people_1_person_id, "
"paperwork.paperwork_id AS paperwork_paperwork_id, "
"paperwork.description AS paperwork_description, "
"paperwork.person_id AS paperwork_person_id "
"FROM people AS people_1 JOIN paperwork "
"ON people_1.person_id = paperwork.person_id "
"WHERE people_1.person_id IN ([EXPANDING_primary_keys]) "
"ORDER BY people_1.person_id, paperwork.paperwork_id",
[{'primary_keys': [1]}]
)
)
def test_correct_select_existingfrom(self):
sess = create_session()
# use Person.paperwork here just to give the least
# amount of context
q = sess.query(Engineer).\
filter(Engineer.primary_language == 'java').\
join(Engineer.paperwork).\
filter(Paperwork.description == "tps report #2").\
options(selectinload(Person.paperwork))
def go():
eq_(q.one().paperwork,
[Paperwork(description="tps report #1"),
Paperwork(description="tps report #2")],
)
self.assert_sql_execution(
testing.db,
go,
CompiledSQL(
"SELECT people.person_id AS people_person_id, "
"people.name AS people_name, people.type AS people_type, "
"engineers.engineer_id AS engineers_engineer_id, "
"engineers.primary_language AS engineers_primary_language "
"FROM people JOIN engineers "
"ON people.person_id = engineers.engineer_id "
"JOIN paperwork ON people.person_id = paperwork.person_id "
"WHERE engineers.primary_language = :primary_language_1 "
"AND paperwork.description = :description_1",
{"primary_language_1": "java",
"description_1": "tps report #2"}
),
CompiledSQL(
"SELECT people_1.person_id AS people_1_person_id, "
"paperwork.paperwork_id AS paperwork_paperwork_id, "
"paperwork.description AS paperwork_description, "
"paperwork.person_id AS paperwork_person_id "
"FROM people AS people_1 JOIN paperwork "
"ON people_1.person_id = paperwork.person_id "
"WHERE people_1.person_id IN ([EXPANDING_primary_keys]) "
"ORDER BY people_1.person_id, paperwork.paperwork_id",
[{'primary_keys': [1]}]
)
)
def test_correct_select_with_polymorphic_no_alias(self):
# test #3106
sess = create_session()
wp = with_polymorphic(Person, [Engineer])
q = sess.query(wp).\
options(selectinload(wp.paperwork)).\
order_by(Engineer.primary_language.desc())
def go():
eq_(q.first(),
Engineer(
paperwork=[
Paperwork(description="tps report #1"),
Paperwork(description="tps report #2")],
primary_language='java'
)
)
self.assert_sql_execution(
testing.db,
go,
CompiledSQL(
"SELECT people.person_id AS people_person_id, "
"people.name AS people_name, people.type AS people_type, "
"engineers.engineer_id AS engineers_engineer_id, "
"engineers.primary_language AS engineers_primary_language "
"FROM people LEFT OUTER JOIN engineers ON people.person_id = "
"engineers.engineer_id ORDER BY engineers.primary_language "
"DESC LIMIT :param_1"),
CompiledSQL(
"SELECT people_1.person_id AS people_1_person_id, "
"paperwork.paperwork_id AS paperwork_paperwork_id, "
"paperwork.description AS paperwork_description, "
"paperwork.person_id AS paperwork_person_id "
"FROM people AS people_1 "
"JOIN paperwork ON people_1.person_id = paperwork.person_id "
"WHERE people_1.person_id IN ([EXPANDING_primary_keys]) "
"ORDER BY people_1.person_id, paperwork.paperwork_id",
[{'primary_keys': [1]}]
)
)
def test_correct_select_with_polymorphic_alias(self):
# test #3106
sess = create_session()
wp = with_polymorphic(Person, [Engineer], aliased=True)
q = sess.query(wp).\
options(selectinload(wp.paperwork)).\
order_by(wp.Engineer.primary_language.desc())
def go():
eq_(q.first(),
Engineer(
paperwork=[
Paperwork(description="tps report #1"),
Paperwork(description="tps report #2")],
primary_language='java'
)
)
self.assert_sql_execution(
testing.db,
go,
CompiledSQL(
"SELECT anon_1.people_person_id AS anon_1_people_person_id, "
"anon_1.people_name AS anon_1_people_name, "
"anon_1.people_type AS anon_1_people_type, "
"anon_1.engineers_engineer_id AS "
"anon_1_engineers_engineer_id, "
"anon_1.engineers_primary_language "
"AS anon_1_engineers_primary_language FROM "
"(SELECT people.person_id AS people_person_id, "
"people.name AS people_name, people.type AS people_type, "
"engineers.engineer_id AS engineers_engineer_id, "
"engineers.primary_language AS engineers_primary_language "
"FROM people LEFT OUTER JOIN engineers ON people.person_id = "
"engineers.engineer_id) AS anon_1 "
"ORDER BY anon_1.engineers_primary_language DESC "
"LIMIT :param_1"),
CompiledSQL(
"SELECT people_1.person_id AS people_1_person_id, "
"paperwork.paperwork_id AS paperwork_paperwork_id, "
"paperwork.description AS paperwork_description, "
"paperwork.person_id AS paperwork_person_id "
"FROM people AS people_1 JOIN paperwork "
"ON people_1.person_id = paperwork.person_id "
"WHERE people_1.person_id IN ([EXPANDING_primary_keys]) "
"ORDER BY people_1.person_id, paperwork.paperwork_id",
[{'primary_keys': [1]}]
)
)
def test_correct_select_with_polymorphic_flat_alias(self):
# test #3106
sess = create_session()
wp = with_polymorphic(Person, [Engineer], aliased=True, flat=True)
q = sess.query(wp).\
options(selectinload(wp.paperwork)).\
order_by(wp.Engineer.primary_language.desc())
def go():
eq_(q.first(),
Engineer(
paperwork=[
Paperwork(description="tps report #1"),
Paperwork(description="tps report #2")],
primary_language='java'
)
)
self.assert_sql_execution(
testing.db,
go,
CompiledSQL(
"SELECT people_1.person_id AS people_1_person_id, "
"people_1.name AS people_1_name, "
"people_1.type AS people_1_type, "
"engineers_1.engineer_id AS engineers_1_engineer_id, "
"engineers_1.primary_language AS engineers_1_primary_language "
"FROM people AS people_1 "
"LEFT OUTER JOIN engineers AS engineers_1 "
"ON people_1.person_id = engineers_1.engineer_id "
"ORDER BY engineers_1.primary_language DESC LIMIT :param_1"),
CompiledSQL(
"SELECT people_1.person_id AS people_1_person_id, "
"paperwork.paperwork_id AS paperwork_paperwork_id, "
"paperwork.description AS paperwork_description, "
"paperwork.person_id AS paperwork_person_id "
"FROM people AS people_1 JOIN paperwork "
"ON people_1.person_id = paperwork.person_id "
"WHERE people_1.person_id IN ([EXPANDING_primary_keys]) "
"ORDER BY people_1.person_id, paperwork.paperwork_id",
[{'primary_keys': [1]}]
)
)
class ChunkingTest(fixtures.DeclarativeMappedTest):
"""test IN chunking.
the length of IN has a limit on at least some databases.
On Oracle it's 1000. In any case, you don't want a SQL statement with
500K entries in an IN, so larger results need to chunk.
"""
@classmethod
def setup_classes(cls):
Base = cls.DeclarativeBasic
class A(fixtures.ComparableEntity, Base):
__tablename__ = 'a'
id = Column(Integer, primary_key=True)
bs = relationship("B", order_by="B.id")
class B(fixtures.ComparableEntity, Base):
__tablename__ = 'b'
id = Column(Integer, primary_key=True)
a_id = Column(ForeignKey('a.id'))
@classmethod
def insert_data(cls):
A, B = cls.classes('A', 'B')
session = Session()
session.add_all([
A(id=i, bs=[B(id=(i * 6) + j) for j in range(1, 6)])
for i in range(1, 101)
])
session.commit()
def test_odd_number_chunks(self):
A, B = self.classes('A', 'B')
session = Session()
def go():
with mock.patch(
"sqlalchemy.orm.strategies.SelectInLoader._chunksize", 47):
q = session.query(A).options(selectinload(A.bs)).order_by(A.id)
for a in q:
a.bs
self.assert_sql_execution(
testing.db,
go,
CompiledSQL(
"SELECT a.id AS a_id FROM a ORDER BY a.id",
{}
),
CompiledSQL(
"SELECT a_1.id AS a_1_id, b.id AS b_id, b.a_id AS b_a_id "
"FROM a AS a_1 JOIN b ON a_1.id = b.a_id "
"WHERE a_1.id IN ([EXPANDING_primary_keys]) "
"ORDER BY a_1.id, b.id",
{"primary_keys": list(range(1, 48))}
),
CompiledSQL(
"SELECT a_1.id AS a_1_id, b.id AS b_id, b.a_id AS b_a_id "
"FROM a AS a_1 JOIN b ON a_1.id = b.a_id "
"WHERE a_1.id IN ([EXPANDING_primary_keys]) "
"ORDER BY a_1.id, b.id",
{"primary_keys": list(range(48, 95))}
),
CompiledSQL(
"SELECT a_1.id AS a_1_id, b.id AS b_id, b.a_id AS b_a_id "
"FROM a AS a_1 JOIN b ON a_1.id = b.a_id "
"WHERE a_1.id IN ([EXPANDING_primary_keys]) "
"ORDER BY a_1.id, b.id",
{"primary_keys": list(range(95, 101))}
)
)
@testing.requires.independent_cursors
def test_yield_per(self):
# the docs make a lot of guarantees about yield_per
# so test that it works
A, B = self.classes('A', 'B')
import random
session = Session()
yield_per = random.randint(8, 105)
offset = random.randint(0, 19)
total_rows = 100 - offset
total_expected_statements = 1 + int(total_rows / yield_per) + \
(1 if total_rows % yield_per else 0)
def go():
for a in session.query(A).\
yield_per(yield_per).\
offset(offset).\
options(selectinload(A.bs)):
# this part fails with joined eager loading
# (if you enable joined eager w/ yield_per)
eq_(
a.bs, [
B(id=(a.id * 6) + j) for j in range(1, 6)
]
)
# this part fails with subquery eager loading
# (if you enable subquery eager w/ yield_per)
self.assert_sql_count(testing.db, go, total_expected_statements)
class SubRelationFromJoinedSubclassMultiLevelTest(_Polymorphic):
@classmethod
def define_tables(cls, metadata):
Table('companies', metadata,
Column('company_id', Integer,
primary_key=True,
test_needs_autoincrement=True),
Column('name', String(50)))
Table('people', metadata,
Column('person_id', Integer,
primary_key=True,
test_needs_autoincrement=True),
Column('company_id', ForeignKey('companies.company_id')),
Column('name', String(50)),
Column('type', String(30)))
Table('engineers', metadata,
Column('engineer_id', ForeignKey('people.person_id'),
primary_key=True),
Column('primary_language', String(50)))
Table('machines', metadata,
Column('machine_id',
Integer, primary_key=True,
test_needs_autoincrement=True),
Column('name', String(50)),
Column('engineer_id', ForeignKey('engineers.engineer_id')),
Column('machine_type_id',
ForeignKey('machine_type.machine_type_id')))
Table('machine_type', metadata,
Column('machine_type_id',
Integer, primary_key=True,
test_needs_autoincrement=True),
Column('name', String(50)))
@classmethod
def setup_mappers(cls):
companies = cls.tables.companies
people = cls.tables.people
engineers = cls.tables.engineers
machines = cls.tables.machines
machine_type = cls.tables.machine_type
mapper(Company, companies, properties={
'employees': relationship(Person, order_by=people.c.person_id)
})
mapper(Person, people,
polymorphic_on=people.c.type,
polymorphic_identity='person',
with_polymorphic='*')
mapper(Engineer, engineers,
inherits=Person,
polymorphic_identity='engineer', properties={
'machines': relationship(Machine,
order_by=machines.c.machine_id)
})
mapper(Machine, machines, properties={
'type': relationship(MachineType)
})
mapper(MachineType, machine_type)
@classmethod
def insert_data(cls):
c1 = cls._fixture()
sess = create_session()
sess.add(c1)
sess.flush()
@classmethod
def _fixture(cls):
mt1 = MachineType(name='mt1')
mt2 = MachineType(name='mt2')
return Company(
employees=[
Engineer(
name='e1',
machines=[
Machine(name='m1', type=mt1),
Machine(name='m2', type=mt2)
]
),
Engineer(
name='e2',
machines=[
Machine(name='m3', type=mt1),
Machine(name='m4', type=mt1)
]
)
])
def test_chained_selectin_subclass(self):
s = Session()
q = s.query(Company).options(
selectinload(Company.employees.of_type(Engineer)).
selectinload(Engineer.machines).
selectinload(Machine.type)
)
def go():
eq_(
q.all(),
[self._fixture()]
)
self.assert_sql_count(testing.db, go, 4)
class SelfReferentialTest(fixtures.MappedTest):
@classmethod
def define_tables(cls, metadata):
Table('nodes', metadata,
Column('id', Integer, primary_key=True,
test_needs_autoincrement=True),
Column('parent_id', Integer, ForeignKey('nodes.id')),
Column('data', String(30)))
def test_basic(self):
nodes = self.tables.nodes
class Node(fixtures.ComparableEntity):
def append(self, node):
self.children.append(node)
mapper(Node, nodes, properties={
'children': relationship(Node,
lazy='selectin',
join_depth=3, order_by=nodes.c.id)
})
sess = create_session()
n1 = Node(data='n1')
n1.append(Node(data='n11'))
n1.append(Node(data='n12'))
n1.append(Node(data='n13'))
n1.children[1].append(Node(data='n121'))
n1.children[1].append(Node(data='n122'))
n1.children[1].append(Node(data='n123'))
n2 = Node(data='n2')
n2.append(Node(data='n21'))
n2.children[0].append(Node(data='n211'))
n2.children[0].append(Node(data='n212'))
sess.add(n1)
sess.add(n2)
sess.flush()
sess.expunge_all()
def go():
d = sess.query(Node).filter(Node.data.in_(['n1', 'n2'])).\
order_by(Node.data).all()
eq_([Node(data='n1', children=[
Node(data='n11'),
Node(data='n12', children=[
Node(data='n121'),
Node(data='n122'),
Node(data='n123')
]),
Node(data='n13')
]),
Node(data='n2', children=[
Node(data='n21', children=[
Node(data='n211'),
Node(data='n212'),
])
])
], d)
self.assert_sql_count(testing.db, go, 4)
def test_lazy_fallback_doesnt_affect_eager(self):
nodes = self.tables.nodes
class Node(fixtures.ComparableEntity):
def append(self, node):
self.children.append(node)
mapper(Node, nodes, properties={
'children': relationship(Node, lazy='selectin', join_depth=1,
order_by=nodes.c.id)
})
sess = create_session()
n1 = Node(data='n1')
n1.append(Node(data='n11'))
n1.append(Node(data='n12'))
n1.append(Node(data='n13'))
n1.children[0].append(Node(data='n111'))
n1.children[0].append(Node(data='n112'))
n1.children[1].append(Node(data='n121'))
n1.children[1].append(Node(data='n122'))
n1.children[1].append(Node(data='n123'))
sess.add(n1)
sess.flush()
sess.expunge_all()
def go():
allnodes = sess.query(Node).order_by(Node.data).all()
n11 = allnodes[1]
eq_(n11.data, 'n11')
eq_([
Node(data='n111'),
Node(data='n112'),
], list(n11.children))
n12 = allnodes[4]
eq_(n12.data, 'n12')
eq_([
Node(data='n121'),
Node(data='n122'),
Node(data='n123')
], list(n12.children))
self.assert_sql_count(testing.db, go, 2)
def test_with_deferred(self):
nodes = self.tables.nodes
class Node(fixtures.ComparableEntity):
def append(self, node):
self.children.append(node)
mapper(Node, nodes, properties={
'children': relationship(Node, lazy='selectin', join_depth=3,
order_by=nodes.c.id),
'data': deferred(nodes.c.data)
})
sess = create_session()
n1 = Node(data='n1')
n1.append(Node(data='n11'))
n1.append(Node(data='n12'))
sess.add(n1)
sess.flush()
sess.expunge_all()
def go():
eq_(
Node(data='n1', children=[Node(data='n11'), Node(data='n12')]),
sess.query(Node).order_by(Node.id).first(),
)
self.assert_sql_count(testing.db, go, 6)
sess.expunge_all()
def go():
eq_(Node(data='n1', children=[Node(data='n11'), Node(data='n12')]),
sess.query(Node).options(undefer('data')).order_by(Node.id)
.first())
self.assert_sql_count(testing.db, go, 5)
sess.expunge_all()
def go():
eq_(Node(data='n1', children=[Node(data='n11'), Node(data='n12')]),
sess.query(Node).options(undefer('data'),
undefer('children.data')).first())
self.assert_sql_count(testing.db, go, 3)
def test_options(self):
nodes = self.tables.nodes
class Node(fixtures.ComparableEntity):
def append(self, node):
self.children.append(node)
mapper(Node, nodes, properties={
'children': relationship(Node, order_by=nodes.c.id)
})
sess = create_session()
n1 = Node(data='n1')
n1.append(Node(data='n11'))
n1.append(Node(data='n12'))
n1.append(Node(data='n13'))
n1.children[1].append(Node(data='n121'))
n1.children[1].append(Node(data='n122'))
n1.children[1].append(Node(data='n123'))
sess.add(n1)
sess.flush()
sess.expunge_all()
def go():
d = sess.query(Node).filter_by(data='n1').order_by(Node.id).\
options(selectinload_all('children.children')).first()
eq_(Node(data='n1', children=[
Node(data='n11'),
Node(data='n12', children=[
Node(data='n121'),
Node(data='n122'),
Node(data='n123')
]),
Node(data='n13')
]), d)
self.assert_sql_count(testing.db, go, 3)
def test_no_depth(self):
"""no join depth is set, so no eager loading occurs."""
nodes = self.tables.nodes
class Node(fixtures.ComparableEntity):
def append(self, node):
self.children.append(node)
mapper(Node, nodes, properties={
'children': relationship(Node, lazy='selectin')
})
sess = create_session()
n1 = Node(data='n1')
n1.append(Node(data='n11'))
n1.append(Node(data='n12'))
n1.append(Node(data='n13'))
n1.children[1].append(Node(data='n121'))
n1.children[1].append(Node(data='n122'))
n1.children[1].append(Node(data='n123'))
n2 = Node(data='n2')
n2.append(Node(data='n21'))
sess.add(n1)
sess.add(n2)
sess.flush()
sess.expunge_all()
def go():
d = sess.query(Node).filter(Node.data.in_(
['n1', 'n2'])).order_by(Node.data).all()
eq_([
Node(data='n1', children=[
Node(data='n11'),
Node(data='n12', children=[
Node(data='n121'),
Node(data='n122'),
Node(data='n123')
]),
Node(data='n13')
]),
Node(data='n2', children=[
Node(data='n21')
])
], d)
self.assert_sql_count(testing.db, go, 4)
class SelfRefInheritanceAliasedTest(
fixtures.DeclarativeMappedTest,
testing.AssertsCompiledSQL):
__dialect__ = 'default'
@classmethod
def setup_classes(cls):
Base = cls.DeclarativeBasic
class Foo(Base):
__tablename__ = "foo"
id = Column(Integer, primary_key=True)
type = Column(String(50))
foo_id = Column(Integer, ForeignKey("foo.id"))
foo = relationship(
lambda: Foo, foreign_keys=foo_id, remote_side=id)
__mapper_args__ = {
"polymorphic_on": type,
"polymorphic_identity": "foo",
}
class Bar(Foo):
__mapper_args__ = {
"polymorphic_identity": "bar",
}
@classmethod
def insert_data(cls):
Foo, Bar = cls.classes('Foo', 'Bar')
session = Session()
target = Bar(id=1)
b1 = Bar(id=2, foo=Foo(id=3, foo=target))
session.add(b1)
session.commit()
def test_twolevel_selectin_w_polymorphic(self):
Foo, Bar = self.classes('Foo', 'Bar')
r = with_polymorphic(Foo, "*", aliased=True)
attr1 = Foo.foo.of_type(r)
attr2 = r.foo
s = Session()
q = s.query(Foo).filter(Foo.id == 2).options(
selectinload(attr1).selectinload(attr2),
)
self.assert_sql_execution(
testing.db,
q.all,
CompiledSQL(
"SELECT foo.id AS foo_id_1, foo.type AS foo_type, "
"foo.foo_id AS foo_foo_id FROM foo WHERE foo.id = :id_1",
[{'id_1': 2}]
),
CompiledSQL(
"SELECT foo_1.id AS foo_1_id, foo_2.id AS foo_2_id, "
"foo_2.type AS foo_2_type, foo_2.foo_id AS foo_2_foo_id "
"FROM foo AS foo_1 JOIN foo AS foo_2 "
"ON foo_2.id = foo_1.foo_id "
"WHERE foo_1.id "
"IN ([EXPANDING_primary_keys]) ORDER BY foo_1.id",
{'primary_keys': [2]}
),
CompiledSQL(
"SELECT foo_1.id AS foo_1_id, foo_2.id AS foo_2_id, "
"foo_2.type AS foo_2_type, foo_2.foo_id AS foo_2_foo_id "
"FROM foo AS foo_1 JOIN foo AS foo_2 "
"ON foo_2.id = foo_1.foo_id "
"WHERE foo_1.id IN ([EXPANDING_primary_keys]) "
"ORDER BY foo_1.id",
{'primary_keys': [3]}
),
)
class TestExistingRowPopulation(fixtures.DeclarativeMappedTest):
@classmethod
def setup_classes(cls):
Base = cls.DeclarativeBasic
class A(Base):
__tablename__ = 'a'
id = Column(Integer, primary_key=True)
b_id = Column(ForeignKey('b.id'))
a2_id = Column(ForeignKey('a2.id'))
a2 = relationship("A2")
b = relationship("B")
class A2(Base):
__tablename__ = 'a2'
id = Column(Integer, primary_key=True)
b_id = Column(ForeignKey('b.id'))
b = relationship("B")
class B(Base):
__tablename__ = 'b'
id = Column(Integer, primary_key=True)
c1_m2o_id = Column(ForeignKey('c1_m2o.id'))
c2_m2o_id = Column(ForeignKey('c2_m2o.id'))
c1_o2m = relationship("C1o2m")
c2_o2m = relationship("C2o2m")
c1_m2o = relationship("C1m2o")
c2_m2o = relationship("C2m2o")
class C1o2m(Base):
__tablename__ = 'c1_o2m'
id = Column(Integer, primary_key=True)
b_id = Column(ForeignKey('b.id'))
class C2o2m(Base):
__tablename__ = 'c2_o2m'
id = Column(Integer, primary_key=True)
b_id = Column(ForeignKey('b.id'))
class C1m2o(Base):
__tablename__ = 'c1_m2o'
id = Column(Integer, primary_key=True)
class C2m2o(Base):
__tablename__ = 'c2_m2o'
id = Column(Integer, primary_key=True)
@classmethod
def insert_data(cls):
A, A2, B, C1o2m, C2o2m, C1m2o, C2m2o = cls.classes(
'A', 'A2', 'B', 'C1o2m', 'C2o2m', 'C1m2o', 'C2m2o'
)
s = Session()
b = B(
c1_o2m=[C1o2m()],
c2_o2m=[C2o2m()],
c1_m2o=C1m2o(),
c2_m2o=C2m2o(),
)
s.add(A(b=b, a2=A2(b=b)))
s.commit()
def test_o2m(self):
A, A2, B, C1o2m, C2o2m = self.classes(
'A', 'A2', 'B', 'C1o2m', 'C2o2m'
)
s = Session()
# A -J-> B -L-> C1
# A -J-> B -S-> C2
# A -J-> A2 -J-> B -S-> C1
# A -J-> A2 -J-> B -L-> C2
q = s.query(A).options(
joinedload(A.b).selectinload(B.c2_o2m),
joinedload(A.a2).joinedload(A2.b).selectinload(B.c1_o2m)
)
a1 = q.all()[0]
is_true('c1_o2m' in a1.b.__dict__)
is_true('c2_o2m' in a1.b.__dict__)
def test_m2o(self):
A, A2, B, C1m2o, C2m2o = self.classes(
'A', 'A2', 'B', 'C1m2o', 'C2m2o'
)
s = Session()
# A -J-> B -L-> C1
# A -J-> B -S-> C2
# A -J-> A2 -J-> B -S-> C1
# A -J-> A2 -J-> B -L-> C2
q = s.query(A).options(
joinedload(A.b).selectinload(B.c2_m2o),
joinedload(A.a2).joinedload(A2.b).selectinload(B.c1_m2o)
)
a1 = q.all()[0]
is_true('c1_m2o' in a1.b.__dict__)
is_true('c2_m2o' in a1.b.__dict__)