mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-28 11:35:19 -04:00
19d2424e05
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
1986 lines
70 KiB
Python
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__)
|