Files
sqlalchemy/test/orm/test_eager_relations.py
T
Mike Bayer 60c36ca841 - Fixed joinedload bug which would occur when a. the query includes
limit/offset criteria that forces a subquery b. the relationship
uses "secondary" c. the primaryjoin of the relationship refers to
a column that is either not part of the primary key, or is a PK
col in a joined-inheritance subclass table that is under a different
attribute name than the parent table's primary key column d. the
query defers the columns that are present in the primaryjoin, typically
via not being included in load_only(); the necessary column(s) would
not be present in the subquery and produce invalid SQL.
fixes #3592
2015-11-21 16:36:50 -05:00

4181 lines
151 KiB
Python

"""tests of joined-eager loaded attributes"""
from sqlalchemy.testing import eq_, is_, is_not_
import sqlalchemy as sa
from sqlalchemy import testing
from sqlalchemy.orm import joinedload, deferred, undefer, \
joinedload_all, backref, Session,\
defaultload, Load, load_only
from sqlalchemy import Integer, String, Date, ForeignKey, and_, select, \
func, text
from sqlalchemy.testing.schema import Table, Column
from sqlalchemy.orm import mapper, relationship, create_session, \
lazyload, aliased, column_property
from sqlalchemy.sql import operators
from sqlalchemy.testing import assert_raises, assert_raises_message
from sqlalchemy.testing.assertsql import CompiledSQL
from sqlalchemy.testing import fixtures, expect_warnings
from test.orm import _fixtures
from sqlalchemy.util import OrderedDict as odict
import datetime
class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
run_inserts = 'once'
run_deletes = None
__dialect__ = 'default'
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), lazy='joined', order_by=Address.id)
})
sess = create_session()
q = sess.query(User)
eq_([User(id=7, addresses=[
Address(id=1, email_address='jack@bean.com')])],
q.filter(User.id == 7).all())
eq_(self.static.user_address_result, q.order_by(User.id).all())
def test_late_compile(self):
User, Address, addresses, users = (
self.classes.User,
self.classes.Address,
self.tables.addresses,
self.tables.users)
m = mapper(User, users)
sess = create_session()
sess.query(User).all()
m.add_property("addresses", relationship(mapper(Address, addresses)))
sess.expunge_all()
def go():
eq_(
[User(id=7, addresses=[
Address(id=1, email_address='jack@bean.com')])],
sess.query(User).options(
joinedload('addresses')).filter(User.id == 7).all()
)
self.assert_sql_count(testing.db, go, 1)
def test_no_orphan(self):
"""An eagerly loaded child object is not marked as an orphan"""
users, Address, addresses, User = (
self.tables.users,
self.classes.Address,
self.tables.addresses,
self.classes.User)
mapper(User, users, properties={
'addresses': relationship(
Address, cascade="all,delete-orphan", lazy='joined')
})
mapper(Address, addresses)
sess = create_session()
user = sess.query(User).get(7)
assert getattr(User, 'addresses').\
hasparent(
sa.orm.attributes.instance_state(
user.addresses[0]), optimistic=True)
assert not sa.orm.class_mapper(Address).\
_is_orphan(
sa.orm.attributes.instance_state(user.addresses[0]))
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='joined', 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='joined',
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='joined', order_by=addresses.c.id),
))
q = create_session().query(User)
l = 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)
]),
], l)
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='joined',
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())
def test_no_ad_hoc_orderby(self):
"""part of #2992; make sure string label references can't
access an eager loader, else an eager load can corrupt the query.
"""
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),
))
sess = create_session()
q = sess.query(User).\
join("addresses").\
options(joinedload("addresses")).\
order_by("email_address")
self.assert_compile(
q,
"SELECT users.id AS users_id, users.name AS users_name, "
"addresses_1.id AS addresses_1_id, addresses_1.user_id AS "
"addresses_1_user_id, addresses_1.email_address AS "
"addresses_1_email_address FROM users JOIN addresses "
"ON users.id = addresses.user_id LEFT OUTER JOIN addresses "
"AS addresses_1 ON users.id = addresses_1.user_id "
"ORDER BY addresses.email_address"
)
q = sess.query(User).options(joinedload("addresses")).\
order_by("email_address")
with expect_warnings("Can't resolve label reference 'email_address'"):
self.assert_compile(
q,
"SELECT users.id AS users_id, users.name AS users_name, "
"addresses_1.id AS addresses_1_id, addresses_1.user_id AS "
"addresses_1_user_id, addresses_1.email_address AS "
"addresses_1_email_address FROM users LEFT OUTER JOIN "
"addresses AS addresses_1 ON users.id = addresses_1.user_id "
"ORDER BY email_address"
)
def test_deferred_fk_col(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(Address, addresses, properties={
'user_id': deferred(addresses.c.user_id),
'user': relationship(User, lazy='joined')
})
mapper(User, users)
sess = create_session()
for q in [
sess.query(Address).filter(
Address.id.in_([1, 4, 5])
).order_by(Address.id),
sess.query(Address).filter(
Address.id.in_([1, 4, 5])
).order_by(Address.id).limit(3)
]:
sess.expunge_all()
eq_(q.all(),
[Address(id=1, user=User(id=7)),
Address(id=4, user=User(id=8)),
Address(id=5, user=User(id=9))]
)
sess.expunge_all()
a = sess.query(Address).filter(Address.id == 1).all()[0]
# 1.0 change! we don't automatically undefer user_id here.
# if the user wants a column undeferred, add the option.
def go():
eq_(a.user_id, 7)
# self.assert_sql_count(testing.db, go, 0)
self.assert_sql_count(testing.db, go, 1)
sess.expunge_all()
a = sess.query(Address).filter(Address.id == 1).first()
def go():
eq_(a.user_id, 7)
# same, 1.0 doesn't check these
# self.assert_sql_count(testing.db, go, 0)
self.assert_sql_count(testing.db, go, 1)
# do the mapping in reverse
# (we would have just used an "addresses" backref but the test
# fixtures then require the whole backref to be set up, lazy loaders
# trigger, etc.)
sa.orm.clear_mappers()
mapper(Address, addresses, properties={
'user_id': deferred(addresses.c.user_id),
})
mapper(User, users, properties={
'addresses': relationship(Address, lazy='joined')})
for q in [
sess.query(User).filter(User.id == 7),
sess.query(User).filter(User.id == 7).limit(1)
]:
sess.expunge_all()
eq_(q.all(),
[User(id=7, addresses=[Address(id=1)])]
)
sess.expunge_all()
u = sess.query(User).get(7)
def go():
eq_(u.addresses[0].user_id, 7)
# assert that the eager loader didn't have to affect 'user_id' here
# and that its still deferred
self.assert_sql_count(testing.db, go, 1)
sa.orm.clear_mappers()
mapper(User, users, properties={
'addresses': relationship(Address, lazy='joined',
order_by=addresses.c.id)})
mapper(Address, addresses, properties={
'user_id': deferred(addresses.c.user_id),
'dingalings': relationship(Dingaling, lazy='joined')})
mapper(Dingaling, dingalings, properties={
'address_id': deferred(dingalings.c.address_id)})
sess.expunge_all()
def go():
u = sess.query(User).get(8)
eq_(User(id=8,
addresses=[Address(id=2, dingalings=[Dingaling(id=1)]),
Address(id=3),
Address(id=4)]),
u)
self.assert_sql_count(testing.db, go, 1)
def test_options_pathing(self):
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)
for opt, count in [
((
joinedload(User.orders, Order.items),
), 10),
((joinedload("orders.items"), ), 10),
((
joinedload(User.orders, ),
joinedload(User.orders, Order.items),
joinedload(User.orders, Order.items, Item.keywords),
), 1),
((
joinedload(User.orders, Order.items, Item.keywords),
), 10),
((
joinedload(User.orders, Order.items),
joinedload(User.orders, Order.items, Item.keywords),
), 5),
]:
sess = create_session()
def go():
eq_(
sess.query(User).options(*opt).order_by(User.id).all(),
self.static.user_item_keyword_result
)
self.assert_sql_count(testing.db, go, count)
def test_disable_dynamic(self):
"""test no joined 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()
assert_raises_message(
sa.exc.InvalidRequestError,
"User.addresses' does not support object "
"population - eager loading cannot be applied.",
sess.query(User).options(joinedload(User.addresses)).first,
)
def test_many_to_many(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='joined', 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, 1)
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, 1)
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, 1)
def test_eager_option(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='select',
order_by=keywords.c.id)))
q = create_session().query(Item)
def go():
eq_(self.static.item_keyword_result[0:2],
(q.options(
joinedload('keywords')
).join('keywords').
filter(keywords.c.name == 'red')).order_by(Item.id).all())
self.assert_sql_count(testing.db, go, 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='joined',
backref=sa.orm.backref('user', lazy='joined'),
order_by=Address.id)
))
eq_(sa.orm.class_mapper(User).get_property('addresses').lazy, 'joined')
eq_(sa.orm.class_mapper(Address).get_property('user').lazy, 'joined')
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='joined', 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='joined', 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='joined', 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, 1)
def test_double_same_mappers(self):
"""Eager loading with two relationships simulatneously,
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='joined',
order_by=items.c.id)})
mapper(Item, items)
mapper(User, users, properties=dict(
addresses=relationship(
Address, lazy='joined', order_by=addresses.c.id),
open_orders=relationship(
Order,
primaryjoin=sa.and_(orders.c.isopen == 1,
users.c.id == orders.c.user_id),
lazy='joined', order_by=orders.c.id),
closed_orders=relationship(
Order,
primaryjoin=sa.and_(orders.c.isopen == 0,
users.c.id == orders.c.user_id),
lazy='joined', 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, 1)
def test_no_false_hits(self):
"""Eager loaders don't interpret main table columns as
part of their eager load."""
addresses, orders, User, Address, Order, users = (
self.tables.addresses,
self.tables.orders,
self.classes.User,
self.classes.Address,
self.classes.Order,
self.tables.users)
mapper(User, users, properties={
'addresses': relationship(Address, lazy='joined'),
'orders': relationship(Order, lazy='joined')
})
mapper(Address, addresses)
mapper(Order, orders)
self.allusers = create_session().query(User).all()
# using a textual select, the columns will be 'id' and 'name'. the
# eager loaders have aliases which should not hit on those columns,
# they should be required to locate only their aliased/fully table
# qualified column name.
noeagers = create_session().query(User).\
from_statement(text("select * from users")).all()
assert 'orders' not in noeagers[0].__dict__
assert 'addresses' not in noeagers[0].__dict__
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='joined',
order_by=items.c.id)
})
mapper(User, users, properties={
'addresses': relationship(
mapper(Address, addresses),
lazy='joined', order_by=addresses.c.id),
'orders': relationship(Order, lazy='select', order_by=orders.c.id)
})
sess = create_session()
q = sess.query(User)
l = q.order_by(User.id).limit(2).offset(1).all()
eq_(self.static.user_all_result[1:3], l)
def test_distinct(self):
Address, addresses, users, User = (self.classes.Address,
self.tables.addresses,
self.tables.users,
self.classes.User)
# this is an involved 3x union of the users table to get a lot of rows.
# then see if the "distinct" works its way out. you actually get
# the same result with or without the distinct, just via less or
# more rows.
u2 = users.alias('u2')
s = sa.union_all(
u2.select(use_labels=True), u2.select(use_labels=True),
u2.select(use_labels=True)).alias('u')
mapper(User, users, properties={
'addresses': relationship(
mapper(Address, addresses),
lazy='joined', order_by=addresses.c.id),
})
sess = create_session()
q = sess.query(User)
def go():
l = q.filter(s.c.u2_id == User.id).distinct().\
order_by(User.id).all()
eq_(self.static.user_address_result, l)
self.assert_sql_count(testing.db, go, 1)
def test_limit_2(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='joined', order_by=[keywords.c.id]),
))
sess = create_session()
q = sess.query(Item)
l = q.filter((Item.description == 'item 2') |
(Item.description == 'item 5') |
(Item.description == 'item 3')).\
order_by(Item.id).limit(2).all()
eq_(self.static.item_keyword_result[1:3], l)
def test_limit_3(self):
"""test that the ORDER BY is propagated from the inner
select to the outer select, when using the
'wrapped' select statement resulting from the combination of
eager loading and limit/offset clauses."""
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(Item, items)
mapper(Order, orders, properties=dict(
items=relationship(Item, secondary=order_items, lazy='joined')
))
mapper(Address, addresses)
mapper(User, users, properties=dict(
addresses=relationship(
Address, lazy='joined', order_by=addresses.c.id),
orders=relationship(Order, lazy='joined', order_by=orders.c.id),
))
sess = create_session()
q = sess.query(User)
if not testing.against('mssql'):
l = q.join('orders').order_by(
Order.user_id.desc()).limit(2).offset(1)
eq_([
User(id=9,
orders=[Order(id=2), Order(id=4)],
addresses=[Address(id=5)]
),
User(id=7,
orders=[Order(id=1), Order(id=3), Order(id=5)],
addresses=[Address(id=1)]
)
], l.all())
l = q.join('addresses').order_by(
Address.email_address.desc()).limit(1).offset(0)
eq_([
User(id=7,
orders=[Order(id=1), Order(id=3), Order(id=5)],
addresses=[Address(id=1)]
)
], l.all())
def test_limit_4(self):
User, Order, addresses, users, orders = (self.classes.User,
self.classes.Order,
self.tables.addresses,
self.tables.users,
self.tables.orders)
# tests the LIMIT/OFFSET aliasing on a mapper
# against a select. original issue from ticket #904
sel = sa.select([users, addresses.c.email_address],
users.c.id == addresses.c.user_id).alias('useralias')
mapper(User, sel, properties={
'orders': relationship(
Order, primaryjoin=sel.c.id == orders.c.user_id,
lazy='joined', order_by=orders.c.id)
})
mapper(Order, orders)
sess = create_session()
eq_(sess.query(User).first(),
User(name='jack', orders=[
Order(
address_id=1,
description='order 1',
isopen=0,
user_id=7,
id=1),
Order(
address_id=1,
description='order 3',
isopen=1,
user_id=7,
id=3),
Order(
address_id=None, description='order 5', isopen=0,
user_id=7, id=5)],
email_address='jack@bean.com', id=7)
)
def test_useget_cancels_eager(self):
"""test that a one to many lazyload cancels the unnecessary
eager many-to-one join on the other side."""
users, Address, addresses, User = (
self.tables.users,
self.classes.Address,
self.tables.addresses,
self.classes.User)
mapper(User, users)
mapper(Address, addresses, properties={
'user': relationship(User, lazy='joined', backref='addresses')
})
sess = create_session()
u1 = sess.query(User).filter(User.id == 8).one()
def go():
eq_(u1.addresses[0].user, u1)
self.assert_sql_execution(
testing.db, go,
CompiledSQL(
"SELECT addresses.id AS addresses_id, addresses.user_id AS "
"addresses_user_id, addresses.email_address AS "
"addresses_email_address FROM addresses WHERE :param_1 = "
"addresses.user_id",
{'param_1': 8})
)
def test_manytoone_limit(self):
"""test that the subquery wrapping only occurs with
limit/offset and m2m or o2m joins present."""
users, items, order_items, Order, Item, User, \
Address, orders, addresses = (
self.tables.users,
self.tables.items,
self.tables.order_items,
self.classes.Order,
self.classes.Item,
self.classes.User,
self.classes.Address,
self.tables.orders,
self.tables.addresses)
mapper(User, users, properties=odict(
orders=relationship(Order, backref='user')
))
mapper(Order, orders, properties=odict([
('items', relationship(Item, secondary=order_items,
backref='orders')),
('address', relationship(Address))
]))
mapper(Address, addresses)
mapper(Item, items)
sess = create_session()
self.assert_compile(
sess.query(User).options(joinedload(User.orders)).limit(10),
"SELECT anon_1.users_id AS anon_1_users_id, anon_1.users_name "
"AS anon_1_users_name, orders_1.id AS orders_1_id, "
"orders_1.user_id AS orders_1_user_id, orders_1.address_id "
"AS orders_1_address_id, orders_1.description AS "
"orders_1_description, orders_1.isopen AS orders_1_isopen "
"FROM (SELECT users.id AS users_id, users.name AS users_name "
"FROM users "
"LIMIT :param_1) AS anon_1 LEFT OUTER JOIN orders AS "
"orders_1 ON anon_1.users_id = orders_1.user_id",
{'param_1': 10}
)
self.assert_compile(
sess.query(Order).options(joinedload(Order.user)).limit(10),
"SELECT orders.id AS orders_id, orders.user_id AS orders_user_id, "
"orders.address_id AS "
"orders_address_id, orders.description AS orders_description, "
"orders.isopen AS orders_isopen, "
"users_1.id AS users_1_id, users_1.name AS users_1_name "
"FROM orders LEFT OUTER JOIN users AS "
"users_1 ON users_1.id = orders.user_id LIMIT :param_1",
{'param_1': 10}
)
self.assert_compile(
sess.query(Order).options(
joinedload(Order.user, innerjoin=True)).limit(10),
"SELECT orders.id AS orders_id, orders.user_id AS orders_user_id, "
"orders.address_id AS "
"orders_address_id, orders.description AS orders_description, "
"orders.isopen AS orders_isopen, "
"users_1.id AS users_1_id, users_1.name AS users_1_name "
"FROM orders JOIN users AS "
"users_1 ON users_1.id = orders.user_id LIMIT :param_1",
{'param_1': 10}
)
self.assert_compile(
sess.query(User).options(
joinedload_all("orders.address")).limit(10),
"SELECT anon_1.users_id AS anon_1_users_id, "
"anon_1.users_name AS anon_1_users_name, "
"addresses_1.id AS addresses_1_id, "
"addresses_1.user_id AS addresses_1_user_id, "
"addresses_1.email_address AS addresses_1_email_address, "
"orders_1.id AS orders_1_id, "
"orders_1.user_id AS orders_1_user_id, "
"orders_1.address_id AS orders_1_address_id, "
"orders_1.description AS orders_1_description, "
"orders_1.isopen AS orders_1_isopen FROM "
"(SELECT users.id AS users_id, users.name AS users_name "
"FROM users LIMIT :param_1) AS anon_1 "
"LEFT OUTER JOIN orders AS orders_1 "
"ON anon_1.users_id = orders_1.user_id LEFT OUTER JOIN "
"addresses AS addresses_1 ON addresses_1.id = orders_1.address_id",
{'param_1': 10}
)
self.assert_compile(
sess.query(User).options(joinedload_all("orders.items"),
joinedload("orders.address")),
"SELECT users.id AS users_id, users.name AS users_name, "
"items_1.id AS items_1_id, "
"items_1.description AS items_1_description, "
"addresses_1.id AS addresses_1_id, "
"addresses_1.user_id AS addresses_1_user_id, "
"addresses_1.email_address AS "
"addresses_1_email_address, orders_1.id AS orders_1_id, "
"orders_1.user_id AS "
"orders_1_user_id, orders_1.address_id AS orders_1_address_id, "
"orders_1.description "
"AS orders_1_description, orders_1.isopen AS orders_1_isopen "
"FROM users LEFT OUTER JOIN orders AS orders_1 "
"ON users.id = orders_1.user_id "
"LEFT OUTER JOIN (order_items AS order_items_1 "
"JOIN items AS items_1 ON items_1.id = order_items_1.item_id) "
"ON orders_1.id = order_items_1.order_id "
"LEFT OUTER JOIN addresses AS addresses_1 "
"ON addresses_1.id = orders_1.address_id"
)
self.assert_compile(
sess.query(User).options(
joinedload("orders"),
joinedload(
"orders.address",
innerjoin=True)).limit(10),
"SELECT anon_1.users_id AS anon_1_users_id, anon_1.users_name "
"AS anon_1_users_name, addresses_1.id AS addresses_1_id, "
"addresses_1.user_id AS addresses_1_user_id, "
"addresses_1.email_address AS addresses_1_email_address, "
"orders_1.id AS orders_1_id, orders_1.user_id AS "
"orders_1_user_id, orders_1.address_id AS orders_1_address_id, "
"orders_1.description AS orders_1_description, "
"orders_1.isopen AS orders_1_isopen "
"FROM (SELECT users.id AS users_id, users.name AS users_name "
"FROM users"
" LIMIT :param_1) AS anon_1 LEFT OUTER JOIN "
"(orders AS orders_1 JOIN addresses AS addresses_1 "
"ON addresses_1.id = orders_1.address_id) ON "
"anon_1.users_id = orders_1.user_id",
{'param_1': 10}
)
self.assert_compile(
sess.query(User).options(
joinedload("orders", innerjoin=True),
joinedload("orders.address", innerjoin=True)).limit(10),
"SELECT anon_1.users_id AS anon_1_users_id, "
"anon_1.users_name AS anon_1_users_name, "
"addresses_1.id AS addresses_1_id, "
"addresses_1.user_id AS addresses_1_user_id, "
"addresses_1.email_address AS addresses_1_email_address, "
"orders_1.id AS orders_1_id, "
"orders_1.user_id AS orders_1_user_id, "
"orders_1.address_id AS orders_1_address_id, "
"orders_1.description AS orders_1_description, "
"orders_1.isopen AS orders_1_isopen "
"FROM (SELECT users.id AS users_id, users.name AS users_name "
"FROM users "
"LIMIT :param_1) AS anon_1 JOIN orders "
"AS orders_1 ON anon_1.users_id = "
"orders_1.user_id JOIN addresses AS addresses_1 "
"ON addresses_1.id = orders_1.address_id",
{'param_1': 10}
)
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='joined', uselist=False)
))
q = create_session().query(User)
def go():
l = q.filter(users.c.id == 7).all()
eq_([User(id=7, address=Address(id=1))], l)
self.assert_sql_count(testing.db, go, 1)
def test_one_to_many_scalar_subq_wrapping(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='joined', uselist=False)
))
q = create_session().query(User)
q = q.filter(users.c.id == 7).limit(1)
self.assert_compile(
q,
"SELECT users.id AS users_id, users.name AS users_name, "
"addresses_1.id AS addresses_1_id, "
"addresses_1.user_id AS addresses_1_user_id, "
"addresses_1.email_address AS addresses_1_email_address "
"FROM users LEFT OUTER JOIN addresses AS addresses_1 "
"ON users.id = addresses_1.user_id "
"WHERE users.id = :id_1 "
"LIMIT :param_1",
checkparams={'id_1': 7, 'param_1': 1}
)
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='joined')
))
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, 1)
def test_many_to_one_null(self):
"""test that a many-to-one eager load which loads None does
not later trigger a lazy load.
"""
Order, Address, addresses, orders = (self.classes.Order,
self.classes.Address,
self.tables.addresses,
self.tables.orders)
# use a primaryjoin intended to defeat SA's usage of
# query.get() for a many-to-one lazyload
mapper(Order, orders, properties=dict(
address=relationship(
mapper(Address, addresses),
primaryjoin=and_(
addresses.c.id == orders.c.address_id,
addresses.c.email_address != None
),
lazy='joined')
))
sess = create_session()
def go():
o1 = sess.query(Order).options(
lazyload('address')).filter(
Order.id == 5).one()
eq_(o1.address, None)
self.assert_sql_count(testing.db, go, 2)
sess.expunge_all()
def go():
o1 = sess.query(Order).filter(Order.id == 5).one()
eq_(o1.address, None)
self.assert_sql_count(testing.db, go, 1)
def test_one_and_many(self):
"""tests eager load for a parent object with a child object that
contains a many-to-many relationship to a third object."""
users, items, order_items, orders, Item, User, Order = (
self.tables.users,
self.tables.items,
self.tables.order_items,
self.tables.orders,
self.classes.Item,
self.classes.User,
self.classes.Order)
mapper(User, users, properties={
'orders': relationship(Order, lazy='joined', order_by=orders.c.id)
})
mapper(Item, items)
mapper(Order, orders, properties=dict(
items=relationship(
Item,
secondary=order_items,
lazy='joined',
order_by=items.c.id)
))
q = create_session().query(User)
l = q.filter(text("users.id in (7, 8, 9)")).order_by(text("users.id"))
def go():
eq_(self.static.user_order_result[0:3], l.all())
self.assert_sql_count(testing.db, go, 1)
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='joined',
order_by=orders.c.id),
'max_order': relationship(
mapper(Order, max_orders, non_primary=True),
lazy='joined', 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, 1)
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(joinedload(User.order)).all)
def test_wide(self):
users, items, order_items, Order, Item, \
User, Address, orders, addresses = (
self.tables.users,
self.tables.items,
self.tables.order_items,
self.classes.Order,
self.classes.Item,
self.classes.User,
self.classes.Address,
self.tables.orders,
self.tables.addresses)
mapper(
Order, orders, properties={
'items': relationship(
Item, secondary=order_items, lazy='joined',
order_by=items.c.id)})
mapper(Item, items)
mapper(User, users, properties=dict(
addresses=relationship(
mapper(
Address,
addresses),
lazy=False,
order_by=addresses.c.id),
orders=relationship(Order, lazy=False, order_by=orders.c.id),
))
q = create_session().query(User)
def go():
eq_(self.static.user_all_result, q.order_by(User.id).all())
self.assert_sql_count(testing.db, go, 1)
def test_against_select(self):
"""test eager loading of a mapper which is against a select"""
users, items, order_items, orders, Item, User, Order = (
self.tables.users,
self.tables.items,
self.tables.order_items,
self.tables.orders,
self.classes.Item,
self.classes.User,
self.classes.Order)
s = sa.select([orders], orders.c.isopen == 1).alias('openorders')
mapper(Order, s, properties={
'user': relationship(User, lazy='joined')
})
mapper(User, users)
mapper(Item, items)
q = create_session().query(Order)
eq_([
Order(id=3, user=User(id=7)),
Order(id=4, user=User(id=9))
], q.all())
q = q.select_from(s.join(order_items).join(items)).filter(
~Item.id.in_([1, 2, 5]))
eq_([
Order(id=3, user=User(id=7)),
], q.all())
def test_aliasing(self):
"""test that eager loading uses aliases to insulate the eager
load from regular criterion against those tables."""
Address, addresses, users, User = (self.classes.Address,
self.tables.addresses,
self.tables.users,
self.classes.User)
mapper(User, users, properties=dict(
addresses=relationship(mapper(Address, addresses),
lazy='joined', order_by=addresses.c.id)
))
q = create_session().query(User)
l = q.filter(addresses.c.email_address == 'ed@lala.com').filter(
Address.user_id == User.id).order_by(User.id)
eq_(self.static.user_address_result[1:2], l.all())
def test_inner_join(self):
Address, addresses, users, User = (self.classes.Address,
self.tables.addresses,
self.tables.users,
self.classes.User)
mapper(User, users, properties=dict(
addresses=relationship(mapper(Address, addresses), lazy='joined',
innerjoin=True, order_by=addresses.c.id)
))
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=3, email_address='ed@bettyboop.com'),
Address(id=4, email_address='ed@lala.com'), ]),
User(id=9, addresses=[Address(id=5)])], sess.query(User).all()
)
self.assert_compile(
sess.query(User),
"SELECT users.id AS users_id, users.name AS users_name, "
"addresses_1.id AS addresses_1_id, "
"addresses_1.user_id AS addresses_1_user_id, "
"addresses_1.email_address AS addresses_1_email_address "
"FROM users JOIN "
"addresses AS addresses_1 ON users.id = addresses_1.user_id "
"ORDER BY addresses_1.id")
def test_inner_join_unnested_chaining_options(self):
users, items, order_items, Order, Item, User, orders = (
self.tables.users,
self.tables.items,
self.tables.order_items,
self.classes.Order,
self.classes.Item,
self.classes.User,
self.tables.orders)
mapper(User, users, properties=dict(
orders=relationship(Order, innerjoin="unnested",
lazy=False)
))
mapper(Order, orders, properties=dict(
items=relationship(Item, secondary=order_items, lazy=False,
innerjoin="unnested")
))
mapper(Item, items)
sess = create_session()
self.assert_compile(
sess.query(User),
"SELECT users.id AS users_id, users.name AS users_name, "
"items_1.id AS "
"items_1_id, items_1.description AS items_1_description, "
"orders_1.id AS "
"orders_1_id, orders_1.user_id AS orders_1_user_id, "
"orders_1.address_id AS "
"orders_1_address_id, orders_1.description "
"AS orders_1_description, "
"orders_1.isopen AS orders_1_isopen FROM users "
"JOIN orders AS orders_1 ON "
"users.id = orders_1.user_id JOIN order_items AS order_items_1 "
"ON orders_1.id = "
"order_items_1.order_id JOIN items AS items_1 ON items_1.id = "
"order_items_1.item_id"
)
self.assert_compile(
sess.query(User).options(joinedload(User.orders, innerjoin=False)),
"SELECT users.id AS users_id, users.name AS users_name, "
"items_1.id AS "
"items_1_id, items_1.description AS items_1_description, "
"orders_1.id AS "
"orders_1_id, orders_1.user_id AS orders_1_user_id, "
"orders_1.address_id AS "
"orders_1_address_id, orders_1.description "
"AS orders_1_description, "
"orders_1.isopen AS orders_1_isopen "
"FROM users LEFT OUTER JOIN orders AS orders_1 "
"ON users.id = orders_1.user_id "
"LEFT OUTER JOIN (order_items AS order_items_1 "
"JOIN items AS items_1 ON items_1.id = order_items_1.item_id) "
"ON orders_1.id = order_items_1.order_id"
)
self.assert_compile(
sess.query(User).options(
joinedload(
User.orders,
Order.items,
innerjoin=False)),
"SELECT users.id AS users_id, users.name AS users_name, "
"items_1.id AS "
"items_1_id, items_1.description AS items_1_description, "
"orders_1.id AS "
"orders_1_id, orders_1.user_id AS orders_1_user_id, "
"orders_1.address_id AS "
"orders_1_address_id, "
"orders_1.description AS orders_1_description, "
"orders_1.isopen AS orders_1_isopen "
"FROM users JOIN orders AS orders_1 ON "
"users.id = orders_1.user_id "
"LEFT OUTER JOIN (order_items AS order_items_1 "
"JOIN items AS items_1 ON items_1.id = order_items_1.item_id) "
"ON orders_1.id = order_items_1.order_id"
)
def test_inner_join_nested_chaining_negative_options(self):
users, items, order_items, Order, Item, User, orders = (
self.tables.users,
self.tables.items,
self.tables.order_items,
self.classes.Order,
self.classes.Item,
self.classes.User,
self.tables.orders)
mapper(User, users, properties=dict(
orders=relationship(Order, innerjoin=True,
lazy=False, order_by=orders.c.id)
))
mapper(Order, orders, properties=dict(
items=relationship(Item, secondary=order_items, lazy=False,
innerjoin=True, order_by=items.c.id)
))
mapper(Item, items)
sess = create_session()
self.assert_compile(
sess.query(User),
"SELECT users.id AS users_id, users.name AS users_name, "
"items_1.id AS "
"items_1_id, items_1.description AS items_1_description, "
"orders_1.id AS "
"orders_1_id, orders_1.user_id AS orders_1_user_id, "
"orders_1.address_id AS "
"orders_1_address_id, orders_1.description "
"AS orders_1_description, "
"orders_1.isopen AS orders_1_isopen FROM users "
"JOIN orders AS orders_1 ON "
"users.id = orders_1.user_id JOIN order_items "
"AS order_items_1 ON orders_1.id = "
"order_items_1.order_id JOIN items AS items_1 ON items_1.id = "
"order_items_1.item_id ORDER BY orders_1.id, items_1.id"
)
q = sess.query(User).options(joinedload(User.orders, innerjoin=False))
self.assert_compile(
q,
"SELECT users.id AS users_id, users.name AS users_name, "
"items_1.id AS "
"items_1_id, items_1.description AS items_1_description, "
"orders_1.id AS "
"orders_1_id, orders_1.user_id AS orders_1_user_id, "
"orders_1.address_id AS "
"orders_1_address_id, orders_1.description "
"AS orders_1_description, "
"orders_1.isopen AS orders_1_isopen "
"FROM users LEFT OUTER JOIN "
"(orders AS orders_1 JOIN order_items AS order_items_1 "
"ON orders_1.id = order_items_1.order_id "
"JOIN items AS items_1 ON items_1.id = order_items_1.item_id) "
"ON users.id = orders_1.user_id ORDER BY orders_1.id, items_1.id"
)
eq_(
[
User(id=7,
orders=[
Order(
id=1, items=[
Item(
id=1), Item(
id=2), Item(
id=3)]),
Order(
id=3, items=[
Item(
id=3), Item(
id=4), Item(
id=5)]),
Order(id=5, items=[Item(id=5)])]),
User(id=8, orders=[]),
User(id=9, orders=[
Order(id=2, items=[Item(id=1), Item(id=2), Item(id=3)]),
Order(id=4, items=[Item(id=1), Item(id=5)])
]
),
User(id=10, orders=[])
],
q.order_by(User.id).all()
)
self.assert_compile(
sess.query(User).options(
joinedload(
User.orders,
Order.items,
innerjoin=False)),
"SELECT users.id AS users_id, users.name AS users_name, "
"items_1.id AS "
"items_1_id, items_1.description AS items_1_description, "
"orders_1.id AS "
"orders_1_id, orders_1.user_id AS orders_1_user_id, "
"orders_1.address_id AS "
"orders_1_address_id, orders_1.description AS "
"orders_1_description, "
"orders_1.isopen AS orders_1_isopen "
"FROM users JOIN orders AS orders_1 ON users.id = "
"orders_1.user_id "
"LEFT OUTER JOIN (order_items AS order_items_1 "
"JOIN items AS items_1 ON items_1.id = order_items_1.item_id) "
"ON orders_1.id = order_items_1.order_id ORDER BY "
"orders_1.id, items_1.id"
)
def test_inner_join_nested_chaining_positive_options(self):
users, items, order_items, Order, Item, User, orders = (
self.tables.users,
self.tables.items,
self.tables.order_items,
self.classes.Order,
self.classes.Item,
self.classes.User,
self.tables.orders)
mapper(User, users, properties=dict(
orders=relationship(Order, order_by=orders.c.id)
))
mapper(Order, orders, properties=dict(
items=relationship(
Item,
secondary=order_items,
order_by=items.c.id)
))
mapper(Item, items)
sess = create_session()
q = sess.query(User).options(
joinedload("orders", innerjoin=False).
joinedload("items", innerjoin=True)
)
self.assert_compile(
q,
"SELECT users.id AS users_id, users.name AS users_name, "
"items_1.id AS items_1_id, items_1.description "
"AS items_1_description, "
"orders_1.id AS orders_1_id, orders_1.user_id "
"AS orders_1_user_id, "
"orders_1.address_id AS orders_1_address_id, "
"orders_1.description AS "
"orders_1_description, orders_1.isopen AS orders_1_isopen "
"FROM users LEFT OUTER JOIN (orders AS orders_1 "
"JOIN order_items AS "
"order_items_1 ON orders_1.id = order_items_1.order_id "
"JOIN items AS "
"items_1 ON items_1.id = order_items_1.item_id) "
"ON users.id = orders_1.user_id "
"ORDER BY orders_1.id, items_1.id"
)
eq_(
[
User(id=7,
orders=[
Order(
id=1, items=[
Item(
id=1), Item(
id=2), Item(
id=3)]),
Order(
id=3, items=[
Item(
id=3), Item(
id=4), Item(
id=5)]),
Order(id=5, items=[Item(id=5)])]),
User(id=8, orders=[]),
User(id=9, orders=[
Order(id=2, items=[Item(id=1), Item(id=2), Item(id=3)]),
Order(id=4, items=[Item(id=1), Item(id=5)])
]
),
User(id=10, orders=[])
],
q.order_by(User.id).all()
)
def test_unnested_outerjoin_propagation_only_on_correct_path(self):
# test #3131
User, users = self.classes.User, self.tables.users
Order, orders = self.classes.Order, self.tables.orders
Address, addresses = self.classes.Address, self.tables.addresses
mapper(User, users, properties=odict([
('orders', relationship(Order)),
('addresses', relationship(Address))
]))
mapper(Order, orders)
mapper(Address, addresses)
sess = create_session()
q = sess.query(User).options(
joinedload("orders"),
joinedload("addresses", innerjoin="unnested"),
)
self.assert_compile(
q,
"SELECT users.id AS users_id, users.name AS users_name, "
"orders_1.id AS orders_1_id, "
"orders_1.user_id AS orders_1_user_id, "
"orders_1.address_id AS orders_1_address_id, "
"orders_1.description AS orders_1_description, "
"orders_1.isopen AS orders_1_isopen, "
"addresses_1.id AS addresses_1_id, "
"addresses_1.user_id AS addresses_1_user_id, "
"addresses_1.email_address AS addresses_1_email_address "
"FROM users LEFT OUTER JOIN orders AS orders_1 "
"ON users.id = orders_1.user_id JOIN addresses AS addresses_1 "
"ON users.id = addresses_1.user_id"
)
def test_nested_outerjoin_propagation_only_on_correct_path(self):
# test #3131
User, users = self.classes.User, self.tables.users
Order, orders = self.classes.Order, self.tables.orders
Address, addresses = self.classes.Address, self.tables.addresses
mapper(User, users, properties=odict([
('orders', relationship(Order)),
('addresses', relationship(Address))
]))
mapper(Order, orders)
mapper(Address, addresses)
sess = create_session()
q = sess.query(User).options(
joinedload("orders"),
joinedload("addresses", innerjoin=True),
)
self.assert_compile(
q,
"SELECT users.id AS users_id, users.name AS users_name, "
"orders_1.id AS orders_1_id, "
"orders_1.user_id AS orders_1_user_id, "
"orders_1.address_id AS orders_1_address_id, "
"orders_1.description AS orders_1_description, "
"orders_1.isopen AS orders_1_isopen, "
"addresses_1.id AS addresses_1_id, "
"addresses_1.user_id AS addresses_1_user_id, "
"addresses_1.email_address AS addresses_1_email_address "
"FROM users LEFT OUTER JOIN orders AS orders_1 "
"ON users.id = orders_1.user_id JOIN addresses AS addresses_1 "
"ON users.id = addresses_1.user_id"
)
def test_catch_the_right_target(self):
# test eager join chaining to the "nested" join on the left,
# a new feature as of [ticket:2369]
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, backref='user'), # 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)
sess = create_session()
q = sess.query(User).join(User.orders).join(Order.items).\
options(joinedload_all("orders.items.keywords"))
# here, the eager join for keywords can catch onto
# join(Order.items) or the nested (orders LEFT OUTER JOIN items),
# it should catch the latter
self.assert_compile(
q,
"SELECT users.id AS users_id, users.name AS users_name, "
"keywords_1.id AS keywords_1_id, keywords_1.name "
"AS keywords_1_name, "
"items_1.id AS items_1_id, items_1.description AS "
"items_1_description, "
"orders_1.id AS orders_1_id, orders_1.user_id AS "
"orders_1_user_id, "
"orders_1.address_id AS orders_1_address_id, "
"orders_1.description AS orders_1_description, "
"orders_1.isopen AS orders_1_isopen "
"FROM users JOIN orders ON users.id = orders.user_id "
"JOIN order_items AS order_items_1 ON orders.id = "
"order_items_1.order_id "
"JOIN items ON items.id = order_items_1.item_id "
"LEFT OUTER JOIN orders AS orders_1 ON users.id = "
"orders_1.user_id "
"LEFT OUTER JOIN (order_items AS order_items_2 "
"JOIN items AS items_1 ON items_1.id = order_items_2.item_id) "
"ON orders_1.id = order_items_2.order_id "
"LEFT OUTER JOIN (item_keywords AS item_keywords_1 "
"JOIN keywords AS keywords_1 ON keywords_1.id = "
"item_keywords_1.keyword_id) "
"ON items_1.id = item_keywords_1.item_id "
"ORDER BY items_1.id, keywords_1.id"
)
def test_inner_join_unnested_chaining_fixed(self):
users, items, order_items, Order, Item, User, orders = (
self.tables.users,
self.tables.items,
self.tables.order_items,
self.classes.Order,
self.classes.Item,
self.classes.User,
self.tables.orders)
mapper(User, users, properties=dict(
orders=relationship(Order, lazy=False)
))
mapper(Order, orders, properties=dict(
items=relationship(Item, secondary=order_items, lazy=False,
innerjoin="unnested")
))
mapper(Item, items)
sess = create_session()
# joining from user, its all LEFT OUTER JOINs
self.assert_compile(
sess.query(User),
"SELECT users.id AS users_id, users.name AS users_name, "
"items_1.id AS "
"items_1_id, items_1.description AS items_1_description, "
"orders_1.id AS "
"orders_1_id, orders_1.user_id AS orders_1_user_id, "
"orders_1.address_id AS "
"orders_1_address_id, orders_1.description AS "
"orders_1_description, "
"orders_1.isopen AS orders_1_isopen FROM users LEFT OUTER JOIN "
"orders AS orders_1 ON "
"users.id = orders_1.user_id LEFT OUTER JOIN "
"(order_items AS order_items_1 JOIN items AS items_1 ON "
"items_1.id = "
"order_items_1.item_id) ON orders_1.id = "
"order_items_1.order_id"
)
# joining just from Order, innerjoin=True can be respected
self.assert_compile(
sess.query(Order),
"SELECT orders.id AS orders_id, orders.user_id AS orders_user_id, "
"orders.address_id AS orders_address_id, orders.description AS "
"orders_description, orders.isopen AS orders_isopen, items_1.id "
"AS items_1_id, items_1.description AS items_1_description FROM "
"orders JOIN order_items AS order_items_1 ON orders.id = "
"order_items_1.order_id JOIN items AS items_1 ON items_1.id = "
"order_items_1.item_id"
)
def test_inner_join_nested_chaining_fixed(self):
users, items, order_items, Order, Item, User, orders = (
self.tables.users,
self.tables.items,
self.tables.order_items,
self.classes.Order,
self.classes.Item,
self.classes.User,
self.tables.orders)
mapper(User, users, properties=dict(
orders=relationship(Order, lazy=False)
))
mapper(Order, orders, properties=dict(
items=relationship(Item, secondary=order_items, lazy=False,
innerjoin='nested')
))
mapper(Item, items)
sess = create_session()
self.assert_compile(
sess.query(User),
"SELECT users.id AS users_id, users.name AS users_name, "
"items_1.id AS "
"items_1_id, items_1.description AS items_1_description, "
"orders_1.id AS "
"orders_1_id, orders_1.user_id AS orders_1_user_id, "
"orders_1.address_id AS "
"orders_1_address_id, orders_1.description AS "
"orders_1_description, "
"orders_1.isopen AS orders_1_isopen "
"FROM users LEFT OUTER JOIN "
"(orders AS orders_1 JOIN order_items AS order_items_1 "
"ON orders_1.id = order_items_1.order_id "
"JOIN items AS items_1 ON items_1.id = order_items_1.item_id) "
"ON users.id = orders_1.user_id"
)
def test_inner_join_options(self):
users, items, order_items, Order, Item, User, orders = (
self.tables.users,
self.tables.items,
self.tables.order_items,
self.classes.Order,
self.classes.Item,
self.classes.User,
self.tables.orders)
mapper(User, users, properties=dict(
orders=relationship(Order, backref=backref('user', innerjoin=True),
order_by=orders.c.id)
))
mapper(Order, orders, properties=dict(
items=relationship(
Item,
secondary=order_items,
order_by=items.c.id)
))
mapper(Item, items)
sess = create_session()
self.assert_compile(
sess.query(User).options(joinedload(User.orders, innerjoin=True)),
"SELECT users.id AS users_id, users.name AS users_name, "
"orders_1.id AS orders_1_id, "
"orders_1.user_id AS orders_1_user_id, orders_1.address_id AS "
"orders_1_address_id, "
"orders_1.description AS orders_1_description, orders_1.isopen "
"AS orders_1_isopen "
"FROM users JOIN orders AS orders_1 ON users.id = "
"orders_1.user_id ORDER BY orders_1.id")
self.assert_compile(
sess.query(User).options(
joinedload_all(User.orders, Order.items, innerjoin=True)),
"SELECT users.id AS users_id, users.name AS users_name, "
"items_1.id AS items_1_id, "
"items_1.description AS items_1_description, "
"orders_1.id AS orders_1_id, "
"orders_1.user_id AS orders_1_user_id, orders_1.address_id "
"AS orders_1_address_id, "
"orders_1.description AS orders_1_description, orders_1.isopen "
"AS orders_1_isopen "
"FROM users JOIN orders AS orders_1 ON users.id = "
"orders_1.user_id JOIN order_items AS "
"order_items_1 ON orders_1.id = order_items_1.order_id "
"JOIN items AS items_1 ON "
"items_1.id = order_items_1.item_id ORDER BY orders_1.id, "
"items_1.id")
def go():
eq_(
sess.query(User).options(
joinedload(User.orders, innerjoin=True),
joinedload(User.orders, Order.items, innerjoin=True)).
order_by(User.id).all(),
[User(id=7,
orders=[
Order(
id=1, items=[
Item(
id=1), Item(
id=2), Item(
id=3)]),
Order(
id=3, items=[
Item(
id=3), Item(
id=4), Item(
id=5)]),
Order(id=5, items=[Item(id=5)])]),
User(id=9, orders=[
Order(
id=2, items=[
Item(
id=1), Item(
id=2), Item(
id=3)]),
Order(id=4, items=[Item(id=1), Item(id=5)])])
]
)
self.assert_sql_count(testing.db, go, 1)
# test that default innerjoin setting is used for options
self.assert_compile(
sess.query(Order).options(
joinedload(
Order.user)).filter(
Order.description == 'foo'),
"SELECT orders.id AS orders_id, orders.user_id AS orders_user_id, "
"orders.address_id AS "
"orders_address_id, orders.description AS orders_description, "
"orders.isopen AS "
"orders_isopen, users_1.id AS users_1_id, users_1.name "
"AS users_1_name "
"FROM orders JOIN users AS users_1 ON users_1.id = orders.user_id "
"WHERE orders.description = :description_1"
)
def test_propagated_lazyload_wildcard_unbound(self):
self._test_propagated_lazyload_wildcard(False)
def test_propagated_lazyload_wildcard_bound(self):
self._test_propagated_lazyload_wildcard(True)
def _test_propagated_lazyload_wildcard(self, use_load):
users, items, order_items, Order, Item, User, orders = (
self.tables.users,
self.tables.items,
self.tables.order_items,
self.classes.Order,
self.classes.Item,
self.classes.User,
self.tables.orders)
mapper(User, users, properties=dict(
orders=relationship(Order, lazy="select")
))
mapper(Order, orders, properties=dict(
items=relationship(Item, secondary=order_items, lazy="joined")
))
mapper(Item, items)
sess = create_session()
if use_load:
opt = Load(User).defaultload("orders").lazyload("*")
else:
opt = defaultload("orders").lazyload("*")
q = sess.query(User).filter(User.id == 7).options(opt)
def go():
for u in q:
u.orders
self.sql_eq_(go, [
("SELECT users.id AS users_id, users.name AS users_name "
"FROM users WHERE users.id = :id_1", {"id_1": 7}),
("SELECT orders.id AS orders_id, "
"orders.user_id AS orders_user_id, "
"orders.address_id AS orders_address_id, "
"orders.description AS orders_description, "
"orders.isopen AS orders_isopen FROM orders "
"WHERE :param_1 = orders.user_id", {"param_1": 7}),
])
class InnerJoinSplicingTest(fixtures.MappedTest, testing.AssertsCompiledSQL):
__dialect__ = 'default'
__backend__ = True # exercise hardcore join nesting on backends
@classmethod
def define_tables(cls, metadata):
Table('a', metadata,
Column('id', Integer, primary_key=True)
)
Table('b', metadata,
Column('id', Integer, primary_key=True),
Column('a_id', Integer, ForeignKey('a.id')),
Column('value', String(10)),
)
Table('c1', metadata,
Column('id', Integer, primary_key=True),
Column('b_id', Integer, ForeignKey('b.id')),
Column('value', String(10)),
)
Table('c2', metadata,
Column('id', Integer, primary_key=True),
Column('b_id', Integer, ForeignKey('b.id')),
Column('value', String(10)),
)
Table('d1', metadata,
Column('id', Integer, primary_key=True),
Column('c1_id', Integer, ForeignKey('c1.id')),
Column('value', String(10)),
)
Table('d2', metadata,
Column('id', Integer, primary_key=True),
Column('c2_id', Integer, ForeignKey('c2.id')),
Column('value', String(10)),
)
Table('e1', metadata,
Column('id', Integer, primary_key=True),
Column('d1_id', Integer, ForeignKey('d1.id')),
Column('value', String(10)),
)
@classmethod
def setup_classes(cls):
class A(cls.Comparable):
pass
class B(cls.Comparable):
pass
class C1(cls.Comparable):
pass
class C2(cls.Comparable):
pass
class D1(cls.Comparable):
pass
class D2(cls.Comparable):
pass
class E1(cls.Comparable):
pass
@classmethod
def setup_mappers(cls):
A, B, C1, C2, D1, D2, E1 = (
cls.classes.A, cls.classes.B, cls.classes.C1,
cls.classes.C2, cls.classes.D1, cls.classes.D2, cls.classes.E1)
mapper(A, cls.tables.a, properties={
'bs': relationship(B)
})
mapper(B, cls.tables.b, properties=odict([
('c1s', relationship(C1, order_by=cls.tables.c1.c.id)),
('c2s', relationship(C2, order_by=cls.tables.c2.c.id))
]))
mapper(C1, cls.tables.c1, properties={
'd1s': relationship(D1, order_by=cls.tables.d1.c.id)
})
mapper(C2, cls.tables.c2, properties={
'd2s': relationship(D2, order_by=cls.tables.d2.c.id)
})
mapper(D1, cls.tables.d1, properties={
'e1s': relationship(E1, order_by=cls.tables.e1.c.id)
})
mapper(D2, cls.tables.d2)
mapper(E1, cls.tables.e1)
@classmethod
def _fixture_data(cls):
A, B, C1, C2, D1, D2, E1 = (
cls.classes.A, cls.classes.B, cls.classes.C1,
cls.classes.C2, cls.classes.D1, cls.classes.D2, cls.classes.E1)
return [
A(id=1, bs=[
B(
id=1,
c1s=[C1(
id=1, value='C11',
d1s=[
D1(id=1, e1s=[E1(id=1)]), D1(id=2, e1s=[E1(id=2)])
]
)
],
c2s=[C2(id=1, value='C21', d2s=[D2(id=3)]),
C2(id=2, value='C22', d2s=[D2(id=4)])]
),
B(
id=2,
c1s=[
C1(
id=4, value='C14',
d1s=[D1(
id=3, e1s=[
E1(id=3, value='E13'),
E1(id=4, value="E14")
]),
D1(id=4, e1s=[E1(id=5)])
]
)
],
c2s=[C2(id=4, value='C24', d2s=[])]
),
]),
A(id=2, bs=[
B(
id=3,
c1s=[
C1(
id=8,
d1s=[D1(id=5, value='D15', e1s=[E1(id=6)])]
)
],
c2s=[C2(id=8, d2s=[D2(id=6, value='D26')])]
)
])
]
@classmethod
def insert_data(cls):
s = Session(testing.db)
s.add_all(cls._fixture_data())
s.commit()
def _assert_result(self, query):
eq_(
query.all(),
self._fixture_data()
)
def test_nested_innerjoin_propagation_multiple_paths_one(self):
A, B, C1, C2 = (
self.classes.A, self.classes.B, self.classes.C1,
self.classes.C2)
s = Session()
q = s.query(A).options(
joinedload(A.bs, innerjoin=False).
joinedload(B.c1s, innerjoin=True).
joinedload(C1.d1s, innerjoin=True),
defaultload(A.bs).joinedload(B.c2s, innerjoin=True).
joinedload(C2.d2s, innerjoin=False)
)
self.assert_compile(
q,
"SELECT a.id AS a_id, d1_1.id AS d1_1_id, "
"d1_1.c1_id AS d1_1_c1_id, d1_1.value AS d1_1_value, "
"c1_1.id AS c1_1_id, c1_1.b_id AS c1_1_b_id, "
"c1_1.value AS c1_1_value, d2_1.id AS d2_1_id, "
"d2_1.c2_id AS d2_1_c2_id, d2_1.value AS d2_1_value, "
"c2_1.id AS c2_1_id, c2_1.b_id AS c2_1_b_id, "
"c2_1.value AS c2_1_value, b_1.id AS b_1_id, "
"b_1.a_id AS b_1_a_id, b_1.value AS b_1_value "
"FROM a "
"LEFT OUTER JOIN "
"(b AS b_1 JOIN c2 AS c2_1 ON b_1.id = c2_1.b_id "
"JOIN c1 AS c1_1 ON b_1.id = c1_1.b_id "
"JOIN d1 AS d1_1 ON c1_1.id = d1_1.c1_id) ON a.id = b_1.a_id "
"LEFT OUTER JOIN d2 AS d2_1 ON c2_1.id = d2_1.c2_id "
"ORDER BY c1_1.id, d1_1.id, c2_1.id, d2_1.id"
)
self._assert_result(q)
def test_nested_innerjoin_propagation_multiple_paths_two(self):
# test #3447
A = self.classes.A
s = Session()
q = s.query(A).options(
joinedload('bs'),
joinedload('bs.c2s', innerjoin=True),
joinedload('bs.c1s', innerjoin=True),
joinedload('bs.c1s.d1s')
)
self.assert_compile(
q,
"SELECT a.id AS a_id, d1_1.id AS d1_1_id, "
"d1_1.c1_id AS d1_1_c1_id, d1_1.value AS d1_1_value, "
"c1_1.id AS c1_1_id, c1_1.b_id AS c1_1_b_id, "
"c1_1.value AS c1_1_value, c2_1.id AS c2_1_id, "
"c2_1.b_id AS c2_1_b_id, c2_1.value AS c2_1_value, "
"b_1.id AS b_1_id, b_1.a_id AS b_1_a_id, "
"b_1.value AS b_1_value "
"FROM a LEFT OUTER JOIN "
"(b AS b_1 JOIN c2 AS c2_1 ON b_1.id = c2_1.b_id "
"JOIN c1 AS c1_1 ON b_1.id = c1_1.b_id) ON a.id = b_1.a_id "
"LEFT OUTER JOIN d1 AS d1_1 ON c1_1.id = d1_1.c1_id "
"ORDER BY c1_1.id, d1_1.id, c2_1.id"
)
self._assert_result(q)
def test_multiple_splice_points(self):
A = self.classes.A
s = Session()
q = s.query(A).options(
joinedload('bs', innerjoin=False),
joinedload('bs.c1s', innerjoin=True),
joinedload('bs.c2s', innerjoin=True),
joinedload('bs.c1s.d1s', innerjoin=False),
joinedload('bs.c2s.d2s'),
joinedload('bs.c1s.d1s.e1s', innerjoin=True)
)
self.assert_compile(
q,
"SELECT a.id AS a_id, e1_1.id AS e1_1_id, "
"e1_1.d1_id AS e1_1_d1_id, e1_1.value AS e1_1_value, "
"d1_1.id AS d1_1_id, d1_1.c1_id AS d1_1_c1_id, "
"d1_1.value AS d1_1_value, c1_1.id AS c1_1_id, "
"c1_1.b_id AS c1_1_b_id, c1_1.value AS c1_1_value, "
"d2_1.id AS d2_1_id, d2_1.c2_id AS d2_1_c2_id, "
"d2_1.value AS d2_1_value, c2_1.id AS c2_1_id, "
"c2_1.b_id AS c2_1_b_id, c2_1.value AS c2_1_value, "
"b_1.id AS b_1_id, b_1.a_id AS b_1_a_id, b_1.value AS b_1_value "
"FROM a LEFT OUTER JOIN "
"(b AS b_1 JOIN c2 AS c2_1 ON b_1.id = c2_1.b_id "
"JOIN c1 AS c1_1 ON b_1.id = c1_1.b_id) ON a.id = b_1.a_id "
"LEFT OUTER JOIN ("
"d1 AS d1_1 JOIN e1 AS e1_1 ON d1_1.id = e1_1.d1_id) "
"ON c1_1.id = d1_1.c1_id "
"LEFT OUTER JOIN d2 AS d2_1 ON c2_1.id = d2_1.c2_id "
"ORDER BY c1_1.id, d1_1.id, e1_1.id, c2_1.id, d2_1.id"
)
self._assert_result(q)
def test_splice_onto_np_mapper(self):
A = self.classes.A
B = self.classes.B
C1 = self.classes.C1
b_table = self.tables.b
c1_table = self.tables.c1
from sqlalchemy import inspect
weird_selectable = b_table.outerjoin(c1_table)
b_np = mapper(
B, weird_selectable, non_primary=True, properties=odict([
# note we need to make this fixed with lazy=False until
# [ticket:3348] is resolved
('c1s', relationship(C1, lazy=False, innerjoin=True)),
('c_id', c1_table.c.id),
('b_value', b_table.c.value),
])
)
a_mapper = inspect(A)
a_mapper.add_property(
"bs_np", relationship(b_np)
)
s = Session()
q = s.query(A).options(
joinedload('bs_np', innerjoin=False)
)
self.assert_compile(
q,
"SELECT a.id AS a_id, c1_1.id AS c1_1_id, c1_1.b_id AS c1_1_b_id, "
"c1_1.value AS c1_1_value, c1_2.id AS c1_2_id, "
"b_1.value AS b_1_value, b_1.id AS b_1_id, "
"b_1.a_id AS b_1_a_id, c1_2.b_id AS c1_2_b_id, "
"c1_2.value AS c1_2_value "
"FROM a LEFT OUTER JOIN "
"(b AS b_1 LEFT OUTER JOIN c1 AS c1_2 ON b_1.id = c1_2.b_id "
"JOIN c1 AS c1_1 ON b_1.id = c1_1.b_id) ON a.id = b_1.a_id"
)
class InnerJoinSplicingWSecondaryTest(
fixtures.MappedTest, testing.AssertsCompiledSQL):
__dialect__ = 'default'
__backend__ = True # exercise hardcore join nesting on backends
@classmethod
def define_tables(cls, metadata):
Table(
'a', metadata,
Column('id', Integer, primary_key=True),
Column('bid', ForeignKey('b.id'))
)
Table(
'b', metadata,
Column('id', Integer, primary_key=True),
Column('cid', ForeignKey('c.id'))
)
Table(
'c', metadata,
Column('id', Integer, primary_key=True),
)
Table('ctod', metadata,
Column('cid', ForeignKey('c.id'), primary_key=True),
Column('did', ForeignKey('d.id'), primary_key=True),
)
Table('d', metadata,
Column('id', Integer, primary_key=True),
)
@classmethod
def setup_classes(cls):
class A(cls.Comparable):
pass
class B(cls.Comparable):
pass
class C(cls.Comparable):
pass
class D(cls.Comparable):
pass
@classmethod
def setup_mappers(cls):
A, B, C, D = (
cls.classes.A, cls.classes.B, cls.classes.C,
cls.classes.D)
mapper(A, cls.tables.a, properties={
'b': relationship(B)
})
mapper(B, cls.tables.b, properties=odict([
('c', relationship(C)),
]))
mapper(C, cls.tables.c, properties=odict([
('ds', relationship(D, secondary=cls.tables.ctod,
order_by=cls.tables.d.c.id)),
]))
mapper(D, cls.tables.d)
@classmethod
def _fixture_data(cls):
A, B, C, D = (
cls.classes.A, cls.classes.B, cls.classes.C,
cls.classes.D)
d1, d2, d3 = D(id=1), D(id=2), D(id=3)
return [
A(
id=1,
b=B(
id=1,
c=C(
id=1,
ds=[d1, d2]
)
)
),
A(
id=2,
b=B(
id=2,
c=C(
id=2,
ds=[d2, d3]
)
)
)
]
@classmethod
def insert_data(cls):
s = Session(testing.db)
s.add_all(cls._fixture_data())
s.commit()
def _assert_result(self, query):
def go():
eq_(
query.all(),
self._fixture_data()
)
self.assert_sql_count(
testing.db,
go,
1
)
def test_joined_across(self):
A = self.classes.A
s = Session()
q = s.query(A) \
.options(
joinedload('b').
joinedload('c', innerjoin=True).
joinedload('ds', innerjoin=True))
self.assert_compile(
q,
"SELECT a.id AS a_id, a.bid AS a_bid, d_1.id AS d_1_id, "
"c_1.id AS c_1_id, b_1.id AS b_1_id, b_1.cid AS b_1_cid "
"FROM a LEFT OUTER JOIN "
"(b AS b_1 JOIN "
"(c AS c_1 JOIN ctod AS ctod_1 ON c_1.id = ctod_1.cid) "
"ON c_1.id = b_1.cid "
"JOIN d AS d_1 ON d_1.id = ctod_1.did) ON b_1.id = a.bid "
"ORDER BY d_1.id"
)
self._assert_result(q)
class SubqueryAliasingTest(fixtures.MappedTest, testing.AssertsCompiledSQL):
"""test #2188"""
__dialect__ = 'default'
run_create_tables = None
@classmethod
def define_tables(cls, metadata):
Table('a', metadata,
Column('id', Integer, primary_key=True)
)
Table('b', metadata,
Column('id', Integer, primary_key=True),
Column('a_id', Integer, ForeignKey('a.id')),
Column('value', Integer),
)
@classmethod
def setup_classes(cls):
class A(cls.Comparable):
pass
class B(cls.Comparable):
pass
def _fixture(self, props):
A, B = self.classes.A, self.classes.B
b_table, a_table = self.tables.b, self.tables.a
mapper(A, a_table, properties=props)
mapper(B, b_table, properties={
'a': relationship(A, backref="bs")
})
def test_column_property(self):
A = self.classes.A
b_table, a_table = self.tables.b, self.tables.a
cp = select([func.sum(b_table.c.value)]).\
where(b_table.c.a_id == a_table.c.id)
self._fixture({
'summation': column_property(cp)
})
self.assert_compile(
create_session().query(A).options(joinedload_all('bs')).
order_by(A.summation).
limit(50),
"SELECT anon_1.anon_2 AS anon_1_anon_2, anon_1.a_id "
"AS anon_1_a_id, b_1.id AS b_1_id, b_1.a_id AS "
"b_1_a_id, b_1.value AS b_1_value FROM (SELECT "
"(SELECT sum(b.value) AS sum_1 FROM b WHERE b.a_id = a.id) "
"AS anon_2, a.id AS a_id FROM a ORDER BY anon_2 "
"LIMIT :param_1) AS anon_1 LEFT OUTER JOIN b AS b_1 ON "
"anon_1.a_id = b_1.a_id ORDER BY anon_1.anon_2"
)
def test_column_property_desc(self):
A = self.classes.A
b_table, a_table = self.tables.b, self.tables.a
cp = select([func.sum(b_table.c.value)]).\
where(b_table.c.a_id == a_table.c.id)
self._fixture({
'summation': column_property(cp)
})
self.assert_compile(
create_session().query(A).options(joinedload_all('bs')).
order_by(A.summation.desc()).
limit(50),
"SELECT anon_1.anon_2 AS anon_1_anon_2, anon_1.a_id "
"AS anon_1_a_id, b_1.id AS b_1_id, b_1.a_id AS "
"b_1_a_id, b_1.value AS b_1_value FROM (SELECT "
"(SELECT sum(b.value) AS sum_1 FROM b WHERE b.a_id = a.id) "
"AS anon_2, a.id AS a_id FROM a ORDER BY anon_2 DESC "
"LIMIT :param_1) AS anon_1 LEFT OUTER JOIN b AS b_1 ON "
"anon_1.a_id = b_1.a_id ORDER BY anon_1.anon_2 DESC"
)
def test_column_property_correlated(self):
A = self.classes.A
b_table, a_table = self.tables.b, self.tables.a
cp = select([func.sum(b_table.c.value)]).\
where(b_table.c.a_id == a_table.c.id).\
correlate(a_table)
self._fixture({
'summation': column_property(cp)
})
self.assert_compile(
create_session().query(A).options(joinedload_all('bs')).
order_by(A.summation).
limit(50),
"SELECT anon_1.anon_2 AS anon_1_anon_2, anon_1.a_id "
"AS anon_1_a_id, b_1.id AS b_1_id, b_1.a_id AS "
"b_1_a_id, b_1.value AS b_1_value FROM (SELECT "
"(SELECT sum(b.value) AS sum_1 FROM b WHERE b.a_id = a.id) "
"AS anon_2, a.id AS a_id FROM a ORDER BY anon_2 "
"LIMIT :param_1) AS anon_1 LEFT OUTER JOIN b AS b_1 ON "
"anon_1.a_id = b_1.a_id ORDER BY anon_1.anon_2"
)
def test_standalone_subquery_unlabeled(self):
A = self.classes.A
b_table, a_table = self.tables.b, self.tables.a
self._fixture({})
cp = select([func.sum(b_table.c.value)]).\
where(b_table.c.a_id == a_table.c.id).\
correlate(a_table).as_scalar()
# up until 0.8, this was ordering by a new subquery.
# the removal of a separate _make_proxy() from ScalarSelect
# fixed that.
self.assert_compile(
create_session().query(A).options(joinedload_all('bs')).
order_by(cp).
limit(50),
"SELECT anon_1.a_id AS anon_1_a_id, anon_1.anon_2 "
"AS anon_1_anon_2, b_1.id AS b_1_id, b_1.a_id AS "
"b_1_a_id, b_1.value AS b_1_value FROM (SELECT a.id "
"AS a_id, (SELECT sum(b.value) AS sum_1 FROM b WHERE "
"b.a_id = a.id) AS anon_2 FROM a ORDER BY (SELECT "
"sum(b.value) AS sum_1 FROM b WHERE b.a_id = a.id) "
"LIMIT :param_1) AS anon_1 LEFT OUTER JOIN b AS b_1 "
"ON anon_1.a_id = b_1.a_id ORDER BY anon_1.anon_2"
)
def test_standalone_subquery_labeled(self):
A = self.classes.A
b_table, a_table = self.tables.b, self.tables.a
self._fixture({})
cp = select([func.sum(b_table.c.value)]).\
where(b_table.c.a_id == a_table.c.id).\
correlate(a_table).as_scalar().label('foo')
self.assert_compile(
create_session().query(A).options(joinedload_all('bs')).
order_by(cp).
limit(50),
"SELECT anon_1.a_id AS anon_1_a_id, anon_1.foo "
"AS anon_1_foo, b_1.id AS b_1_id, b_1.a_id AS "
"b_1_a_id, b_1.value AS b_1_value FROM (SELECT a.id "
"AS a_id, (SELECT sum(b.value) AS sum_1 FROM b WHERE "
"b.a_id = a.id) AS foo FROM a ORDER BY foo "
"LIMIT :param_1) AS anon_1 LEFT OUTER JOIN b AS b_1 "
"ON anon_1.a_id = b_1.a_id ORDER BY "
"anon_1.foo"
)
def test_standalone_negated(self):
A = self.classes.A
b_table, a_table = self.tables.b, self.tables.a
self._fixture({})
cp = select([func.sum(b_table.c.value)]).\
where(b_table.c.a_id == a_table.c.id).\
correlate(a_table).\
as_scalar()
# test a different unary operator
self.assert_compile(
create_session().query(A).options(joinedload_all('bs')).
order_by(~cp).
limit(50),
"SELECT anon_1.a_id AS anon_1_a_id, anon_1.anon_2 "
"AS anon_1_anon_2, b_1.id AS b_1_id, b_1.a_id AS "
"b_1_a_id, b_1.value AS b_1_value FROM (SELECT a.id "
"AS a_id, NOT (SELECT sum(b.value) AS sum_1 FROM b "
"WHERE b.a_id = a.id) FROM a ORDER BY NOT (SELECT "
"sum(b.value) AS sum_1 FROM b WHERE b.a_id = a.id) "
"LIMIT :param_1) AS anon_1 LEFT OUTER JOIN b AS b_1 "
"ON anon_1.a_id = b_1.a_id ORDER BY anon_1.anon_2"
)
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="joined"),
})
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_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(joinedload_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(joinedload_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(
joinedload("addresses")).one()
sess.query(User).filter_by(
id=8).options(
joinedload_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(
joinedload("orders")).one()
sess.query(User).filter_by(
id=7).options(
joinedload_all("orders.items")).first()
assert 'items' in u1.orders[0].__dict__
class AddEntityTest(_fixtures.FixtureTest):
run_inserts = 'once'
run_deletes = None
def _assert_result(self):
Item, Address, Order, User = (self.classes.Item,
self.classes.Address,
self.classes.Order,
self.classes.User)
return [
(
User(id=7,
addresses=[Address(id=1)]
),
Order(id=1,
items=[Item(id=1), Item(id=2), Item(id=3)]
),
),
(
User(id=7,
addresses=[Address(id=1)]
),
Order(id=3,
items=[Item(id=3), Item(id=4), Item(id=5)]
),
),
(
User(id=7,
addresses=[Address(id=1)]
),
Order(id=5,
items=[Item(id=5)]
),
),
(
User(id=9,
addresses=[Address(id=5)]
),
Order(id=2,
items=[Item(id=1), Item(id=2), Item(id=3)]
),
),
(
User(id=9,
addresses=[Address(id=5)]
),
Order(id=4,
items=[Item(id=1), Item(id=5)]
),
)
]
def test_mapper_configured(self):
users, items, order_items, Order, \
Item, User, Address, orders, addresses = (
self.tables.users,
self.tables.items,
self.tables.order_items,
self.classes.Order,
self.classes.Item,
self.classes.User,
self.classes.Address,
self.tables.orders,
self.tables.addresses)
mapper(User, users, properties={
'addresses': relationship(Address, lazy='joined'),
'orders': relationship(Order)
})
mapper(Address, addresses)
mapper(Order, orders, properties={
'items': relationship(
Item, secondary=order_items, lazy='joined',
order_by=items.c.id)
})
mapper(Item, items)
sess = create_session()
oalias = sa.orm.aliased(Order)
def go():
ret = sess.query(User, oalias).join(oalias, 'orders').\
order_by(User.id, oalias.id).all()
eq_(ret, self._assert_result())
self.assert_sql_count(testing.db, go, 1)
def test_options(self):
users, items, order_items, Order,\
Item, User, Address, orders, addresses = (
self.tables.users,
self.tables.items,
self.tables.order_items,
self.classes.Order,
self.classes.Item,
self.classes.User,
self.classes.Address,
self.tables.orders,
self.tables.addresses)
mapper(User, users, properties={
'addresses': relationship(Address),
'orders': relationship(Order)
})
mapper(Address, addresses)
mapper(Order, orders, properties={
'items': relationship(
Item, secondary=order_items, order_by=items.c.id)
})
mapper(Item, items)
sess = create_session()
oalias = sa.orm.aliased(Order)
def go():
ret = sess.query(User, oalias).options(joinedload('addresses')).\
join(oalias, 'orders').\
order_by(User.id, oalias.id).all()
eq_(ret, self._assert_result())
self.assert_sql_count(testing.db, go, 6)
sess.expunge_all()
def go():
ret = sess.query(User, oalias).\
options(joinedload('addresses'),
joinedload(oalias.items)).\
join(oalias, 'orders').\
order_by(User.id, oalias.id).all()
eq_(ret, self._assert_result())
self.assert_sql_count(testing.db, go, 1)
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='joined', order_by=m2m.c.id)
})
mapper(B, b)
sess = create_session()
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')])
])
class SelfReferentialEagerTest(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='joined',
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'))
sess.add(n1)
sess.flush()
sess.expunge_all()
def go():
d = sess.query(Node).filter_by(data='n1').all()[0]
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, 1)
sess.expunge_all()
def go():
d = sess.query(Node).filter_by(data='n1').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, 1)
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='joined', 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[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()
# eager load with join depth 1. when eager load of 'n1' hits the
# children of 'n12', no columns are present, eager loader degrades to
# lazy loader; fine. but then, 'n12' is *also* in the first level of
# columns since we're loading the whole table. when those rows
# arrive, now we *can* eager load its children and an eager collection
# should be initialized. essentially the 'n12' instance is present in
# not just two different rows but two distinct sets of columns in this
# result set.
def go():
allnodes = sess.query(Node).order_by(Node.data).all()
n12 = allnodes[2]
eq_(n12.data, 'n12')
eq_([
Node(data='n121'),
Node(data='n122'),
Node(data='n123')
], list(n12.children))
self.assert_sql_count(testing.db, go, 1)
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='joined', 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, 4)
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, 3)
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, 1)
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, lazy='select', order_by=nodes.c.id)
}, 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').\
options(joinedload('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, 2)
def go():
sess.query(Node).filter_by(data='n1').\
options(joinedload('children.children')).first()
# test that the query isn't wrapping the initial query for eager
# loading.
self.assert_sql_execution(
testing.db, go,
CompiledSQL(
"SELECT nodes.id AS nodes_id, nodes.parent_id AS "
"nodes_parent_id, nodes.data AS nodes_data FROM nodes "
"WHERE nodes.data = :data_1 ORDER BY nodes.id LIMIT :param_1",
{'data_1': 'n1'}
)
)
def test_no_depth(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='joined')
})
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').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)
class MixedSelfReferentialEagerTest(fixtures.MappedTest):
@classmethod
def define_tables(cls, metadata):
Table('a_table', metadata,
Column(
'id',
Integer,
primary_key=True,
test_needs_autoincrement=True)
)
Table('b_table', metadata,
Column(
'id',
Integer,
primary_key=True,
test_needs_autoincrement=True),
Column('parent_b1_id', Integer, ForeignKey('b_table.id')),
Column('parent_a_id', Integer, ForeignKey('a_table.id')),
Column('parent_b2_id', Integer, ForeignKey('b_table.id')))
@classmethod
def setup_mappers(cls):
b_table, a_table = cls.tables.b_table, cls.tables.a_table
class A(cls.Comparable):
pass
class B(cls.Comparable):
pass
mapper(A, a_table)
mapper(B, b_table, properties={
'parent_b1': relationship(
B,
remote_side=[b_table.c.id],
primaryjoin=(b_table.c.parent_b1_id == b_table.c.id),
order_by=b_table.c.id
),
'parent_z': relationship(A, lazy=True),
'parent_b2': relationship(
B,
remote_side=[b_table.c.id],
primaryjoin=(b_table.c.parent_b2_id == b_table.c.id),
order_by = b_table.c.id
)
})
@classmethod
def insert_data(cls):
b_table, a_table = cls.tables.b_table, cls.tables.a_table
a_table.insert().execute(dict(id=1), dict(id=2), dict(id=3))
b_table.insert().execute(
dict(id=1, parent_a_id=2, parent_b1_id=None, parent_b2_id=None),
dict(id=2, parent_a_id=1, parent_b1_id=1, parent_b2_id=None),
dict(id=3, parent_a_id=1, parent_b1_id=1, parent_b2_id=2),
dict(id=4, parent_a_id=3, parent_b1_id=1, parent_b2_id=None),
dict(id=5, parent_a_id=3, parent_b1_id=None, parent_b2_id=2),
dict(id=6, parent_a_id=1, parent_b1_id=1, parent_b2_id=3),
dict(id=7, parent_a_id=2, parent_b1_id=None, parent_b2_id=3),
dict(id=8, parent_a_id=2, parent_b1_id=1, parent_b2_id=2),
dict(id=9, parent_a_id=None, parent_b1_id=1, parent_b2_id=None),
dict(id=10, parent_a_id=3, parent_b1_id=7, parent_b2_id=2),
dict(id=11, parent_a_id=3, parent_b1_id=1, parent_b2_id=8),
dict(id=12, parent_a_id=2, parent_b1_id=5, parent_b2_id=2),
dict(id=13, parent_a_id=3, parent_b1_id=4, parent_b2_id=4),
dict(id=14, parent_a_id=3, parent_b1_id=7, parent_b2_id=2),
)
def test_eager_load(self):
A, B = self.classes.A, self.classes.B
session = create_session()
def go():
eq_(
session.query(B).
options(
joinedload('parent_b1'),
joinedload('parent_b2'),
joinedload('parent_z')
).
filter(B.id.in_([2, 8, 11])).order_by(B.id).all(),
[
B(id=2,
parent_z=A(id=1),
parent_b1=B(id=1),
parent_b2=None),
B(id=8,
parent_z=A(id=2),
parent_b1=B(id=1),
parent_b2=B(id=2)),
B(id=11,
parent_z=A(id=3),
parent_b1=B(id=1),
parent_b2=B(id=8))
]
)
self.assert_sql_count(testing.db, go, 1)
class SelfReferentialM2MEagerTest(fixtures.MappedTest):
@classmethod
def define_tables(cls, metadata):
Table('widget', metadata,
Column(
'id',
Integer,
primary_key=True,
test_needs_autoincrement=True),
Column('name', sa.String(40), nullable=False, unique=True),
)
Table('widget_rel', metadata,
Column('parent_id', Integer, ForeignKey('widget.id')),
Column('child_id', Integer, ForeignKey('widget.id')),
sa.UniqueConstraint('parent_id', 'child_id'),
)
def test_basic(self):
widget, widget_rel = self.tables.widget, self.tables.widget_rel
class Widget(fixtures.ComparableEntity):
pass
mapper(Widget, widget, properties={
'children': relationship(
Widget, secondary=widget_rel,
primaryjoin=widget_rel.c.parent_id == widget.c.id,
secondaryjoin=widget_rel.c.child_id == widget.c.id,
lazy='joined', join_depth=1,
)
})
sess = create_session()
w1 = Widget(name='w1')
w2 = Widget(name='w2')
w1.children.append(w2)
sess.add(w1)
sess.flush()
sess.expunge_all()
eq_([Widget(name='w1', children=[Widget(name='w2')])],
sess.query(Widget).filter(Widget.name == 'w1').all())
class MixedEntitiesTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
run_setup_mappers = 'once'
run_inserts = 'once'
run_deletes = None
__dialect__ = 'default'
__prefer_backends__ = ('postgresql', 'mysql', 'oracle')
@classmethod
def setup_mappers(cls):
users, Keyword, items, order_items, orders, \
Item, User, Address, keywords, Order, \
item_keywords, addresses = (
cls.tables.users,
cls.classes.Keyword,
cls.tables.items,
cls.tables.order_items,
cls.tables.orders,
cls.classes.Item,
cls.classes.User,
cls.classes.Address,
cls.tables.keywords,
cls.classes.Order,
cls.tables.item_keywords,
cls.tables.addresses)
mapper(User, users, properties={
'addresses': relationship(Address, backref='user'),
'orders': relationship(Order, backref='user'), # o2m, m2o
})
mapper(Address, addresses)
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) # m2m
})
mapper(Keyword, keywords)
def test_two_entities(self):
Item, Order, User, Address = (self.classes.Item,
self.classes.Order,
self.classes.User,
self.classes.Address)
sess = create_session()
# two FROM clauses
def go():
eq_(
[
(User(id=9, addresses=[Address(id=5)]),
Order(id=2, items=[
Item(id=1), Item(id=2), Item(id=3)])),
(User(id=9, addresses=[Address(id=5)]),
Order(id=4, items=[
Item(id=1), Item(id=5)])),
],
sess.query(User, Order).filter(User.id == Order.user_id).
options(joinedload(User.addresses), joinedload(Order.items)).
filter(User.id == 9).
order_by(User.id, Order.id).all(),
)
self.assert_sql_count(testing.db, go, 1)
# one FROM clause
def go():
eq_(
[
(User(id=9, addresses=[Address(id=5)]),
Order(id=2, items=[
Item(id=1), Item(id=2), Item(id=3)])),
(User(id=9, addresses=[Address(id=5)]),
Order(id=4, items=[
Item(id=1), Item(id=5)])),
],
sess.query(User, Order).join(User.orders).
options(joinedload(User.addresses), joinedload(Order.items)).
filter(User.id == 9).
order_by(User.id, Order.id).all(),
)
self.assert_sql_count(testing.db, go, 1)
@testing.exclude(
'sqlite', '>', (0, ), "sqlite flat out blows it on the multiple JOINs")
def test_two_entities_with_joins(self):
Item, Order, User, Address = (self.classes.Item,
self.classes.Order,
self.classes.User,
self.classes.Address)
sess = create_session()
# two FROM clauses where there's a join on each one
def go():
u1 = aliased(User)
o1 = aliased(Order)
eq_(
[
(
User(addresses=[
Address(email_address='fred@fred.com')],
name='fred'),
Order(description='order 2', isopen=0,
items=[
Item(description='item 1'),
Item(description='item 2'),
Item(description='item 3')]),
User(addresses=[
Address(email_address='jack@bean.com')],
name='jack'),
Order(description='order 3', isopen=1,
items=[
Item(description='item 3'),
Item(description='item 4'),
Item(description='item 5')])
),
(
User(
addresses=[
Address(
email_address='fred@fred.com')],
name='fred'),
Order(
description='order 2', isopen=0, items=[
Item(
description='item 1'), Item(
description='item 2'), Item(
description='item 3')]),
User(
addresses=[
Address(
email_address='jack@bean.com')],
name='jack'),
Order(
address_id=None,
description='order 5',
isopen=0,
items=[
Item(
description='item 5')])
),
(
User(
addresses=[
Address(
email_address='fred@fred.com')],
name='fred'),
Order(
description='order 4', isopen=1, items=[
Item(
description='item 1'), Item(
description='item 5')]),
User(
addresses=[
Address(
email_address='jack@bean.com')],
name='jack'),
Order(
address_id=None,
description='order 5',
isopen=0,
items=[
Item(
description='item 5')])
),
],
sess.query(User, Order, u1, o1).
join(Order, User.orders).
options(joinedload(User.addresses),
joinedload(Order.items)).filter(User.id == 9).
join(o1, u1.orders).
options(joinedload(u1.addresses),
joinedload(o1.items)).filter(u1.id == 7).
filter(Order.id < o1.id).
order_by(User.id, Order.id, u1.id, o1.id).all(),
)
self.assert_sql_count(testing.db, go, 1)
def test_aliased_entity_one(self):
Item, Order, User, Address = (self.classes.Item,
self.classes.Order,
self.classes.User,
self.classes.Address)
sess = create_session()
oalias = sa.orm.aliased(Order)
# two FROM clauses
def go():
eq_(
[
(
User(
id=9, addresses=[
Address(
id=5)]), Order(
id=2, items=[
Item(
id=1), Item(
id=2), Item(
id=3)])),
(User(id=9, addresses=[Address(id=5)]), Order(
id=4, items=[Item(id=1), Item(id=5)])),
],
sess.query(User, oalias).filter(User.id == oalias.user_id).
options(
joinedload(User.addresses),
joinedload(oalias.items)).filter(User.id == 9).
order_by(User.id, oalias.id).all(),
)
self.assert_sql_count(testing.db, go, 1)
def test_aliased_entity_two(self):
Item, Order, User, Address = (self.classes.Item,
self.classes.Order,
self.classes.User,
self.classes.Address)
sess = create_session()
oalias = sa.orm.aliased(Order)
# one FROM clause
def go():
eq_(
[
(
User(
id=9, addresses=[
Address(
id=5)]), Order(
id=2, items=[
Item(
id=1), Item(
id=2), Item(
id=3)])),
(User(id=9, addresses=[Address(id=5)]), Order(
id=4, items=[Item(id=1), Item(id=5)])),
],
sess.query(User, oalias).join(oalias, User.orders).
options(joinedload(User.addresses),
joinedload(oalias.items)).
filter(User.id == 9).
order_by(User.id, oalias.id).all(),
)
self.assert_sql_count(testing.db, go, 1)
def test_aliased_entity_three(self):
Order, User = (
self.classes.Order,
self.classes.User)
sess = create_session()
oalias = sa.orm.aliased(Order)
# improper setup: oalias in the columns clause but join to usual
# orders alias. this should create two FROM clauses even though the
# query has a from_clause set up via the join
self.assert_compile(
sess.query(User, oalias).join(User.orders).
options(joinedload(oalias.items)).with_labels().statement,
"SELECT users.id AS users_id, users.name AS users_name, "
"orders_1.id AS orders_1_id, "
"orders_1.user_id AS orders_1_user_id, "
"orders_1.address_id AS orders_1_address_id, "
"orders_1.description AS orders_1_description, "
"orders_1.isopen AS orders_1_isopen, items_1.id AS items_1_id, "
"items_1.description AS items_1_description FROM users "
"JOIN orders ON users.id = orders.user_id, "
"orders AS orders_1 LEFT OUTER JOIN (order_items AS order_items_1 "
"JOIN items AS items_1 ON items_1.id = order_items_1.item_id) "
"ON orders_1.id = order_items_1.order_id ORDER BY items_1.id"
)
class SubqueryTest(fixtures.MappedTest):
@classmethod
def define_tables(cls, metadata):
Table('users_table', metadata,
Column(
'id',
Integer,
primary_key=True,
test_needs_autoincrement=True),
Column('name', String(16))
)
Table('tags_table', metadata,
Column(
'id',
Integer,
primary_key=True,
test_needs_autoincrement=True),
Column('user_id', Integer, ForeignKey("users_table.id")),
Column('score1', sa.Float),
Column('score2', sa.Float),
)
def test_label_anonymizing(self):
"""Eager loading works with subqueries with labels,
Even if an explicit labelname which conflicts with a label on the
parent.
There's not much reason a column_property() would ever need to have a
label of a specific name (and they don't even need labels these days),
unless you'd like the name to line up with a name that you may be
using for a straight textual statement used for loading instances of
that type.
"""
tags_table, users_table = self.tables.tags_table, \
self.tables.users_table
class User(fixtures.ComparableEntity):
@property
def prop_score(self):
return sum([tag.prop_score for tag in self.tags])
class Tag(fixtures.ComparableEntity):
@property
def prop_score(self):
return self.score1 * self.score2
for labeled, labelname in [(True, 'score'), (True, None),
(False, None)]:
sa.orm.clear_mappers()
tag_score = (tags_table.c.score1 * tags_table.c.score2)
user_score = sa.select([sa.func.sum(tags_table.c.score1 *
tags_table.c.score2)],
tags_table.c.user_id == users_table.c.id)
if labeled:
tag_score = tag_score.label(labelname)
user_score = user_score.label(labelname)
else:
user_score = user_score.as_scalar()
mapper(Tag, tags_table, properties={
'query_score': sa.orm.column_property(tag_score),
})
mapper(User, users_table, properties={
'tags': relationship(Tag, backref='user', lazy='joined'),
'query_score': sa.orm.column_property(user_score),
})
session = create_session()
session.add(User(name='joe', tags=[Tag(score1=5.0, score2=3.0),
Tag(score1=55.0, score2=1.0)]))
session.add(User(name='bar', tags=[Tag(score1=5.0, score2=4.0),
Tag(score1=50.0, score2=1.0),
Tag(score1=15.0, score2=2.0)]))
session.flush()
session.expunge_all()
for user in session.query(User).all():
eq_(user.query_score, user.prop_score)
def go():
u = session.query(User).filter_by(name='joe').one()
eq_(u.query_score, u.prop_score)
self.assert_sql_count(testing.db, go, 1)
for t in (tags_table, users_table):
t.delete().execute()
class CorrelatedSubqueryTest(fixtures.MappedTest):
"""tests for #946, #947, #948.
The "users" table is joined to "stuff", and the relationship
would like to pull only the "stuff" entry with the most recent date.
Exercises a variety of ways to configure this.
"""
# another argument for joinedload learning about inner joins
__requires__ = ('correlated_outer_joins', )
@classmethod
def define_tables(cls, metadata):
Table(
'users', metadata,
Column(
'id',
Integer,
primary_key=True,
test_needs_autoincrement=True),
Column('name', String(50))
)
Table(
'stuff', metadata,
Column(
'id',
Integer,
primary_key=True,
test_needs_autoincrement=True),
Column('date', Date),
Column('user_id', Integer, ForeignKey('users.id')))
@classmethod
def insert_data(cls):
stuff, users = cls.tables.stuff, cls.tables.users
users.insert().execute(
{'id': 1, 'name': 'user1'},
{'id': 2, 'name': 'user2'},
{'id': 3, 'name': 'user3'},
)
stuff.insert().execute(
{'id': 1, 'user_id': 1, 'date': datetime.date(2007, 10, 15)},
{'id': 2, 'user_id': 1, 'date': datetime.date(2007, 12, 15)},
{'id': 3, 'user_id': 1, 'date': datetime.date(2007, 11, 15)},
{'id': 4, 'user_id': 2, 'date': datetime.date(2008, 1, 15)},
{'id': 5, 'user_id': 3, 'date': datetime.date(2007, 6, 15)},
{'id': 6, 'user_id': 3, 'date': datetime.date(2007, 3, 15)},
)
def test_labeled_on_date_noalias(self):
self._do_test('label', True, False)
def test_scalar_on_date_noalias(self):
self._do_test('scalar', True, False)
def test_plain_on_date_noalias(self):
self._do_test('none', True, False)
def test_labeled_on_limitid_noalias(self):
self._do_test('label', False, False)
def test_scalar_on_limitid_noalias(self):
self._do_test('scalar', False, False)
def test_plain_on_limitid_noalias(self):
self._do_test('none', False, False)
def test_labeled_on_date_alias(self):
self._do_test('label', True, True)
def test_scalar_on_date_alias(self):
self._do_test('scalar', True, True)
def test_plain_on_date_alias(self):
self._do_test('none', True, True)
def test_labeled_on_limitid_alias(self):
self._do_test('label', False, True)
def test_scalar_on_limitid_alias(self):
self._do_test('scalar', False, True)
def test_plain_on_limitid_alias(self):
self._do_test('none', False, True)
def _do_test(self, labeled, ondate, aliasstuff):
stuff, users = self.tables.stuff, self.tables.users
class User(fixtures.ComparableEntity):
pass
class Stuff(fixtures.ComparableEntity):
pass
mapper(Stuff, stuff)
if aliasstuff:
salias = stuff.alias()
else:
# if we don't alias the 'stuff' table within the correlated
# subquery,
# it gets aliased in the eager load along with the "stuff" table
# to "stuff_1".
# but it's a scalar subquery, and this doesn't actually matter
salias = stuff
if ondate:
# the more 'relational' way to do this, join on the max date
stuff_view = select([func.max(salias.c.date).label('max_date')]).\
where(salias.c.user_id == users.c.id).correlate(users)
else:
# a common method with the MySQL crowd, which actually might
# perform better in some
# cases - subquery does a limit with order by DESC, join on the id
stuff_view = select([salias.c.id]).\
where(salias.c.user_id == users.c.id).\
correlate(users).order_by(salias.c.date.desc()).limit(1)
# can't win on this one
if testing.against("mssql"):
operator = operators.in_op
else:
operator = operators.eq
if labeled == 'label':
stuff_view = stuff_view.label('foo')
operator = operators.eq
elif labeled == 'scalar':
stuff_view = stuff_view.as_scalar()
if ondate:
mapper(User, users, properties={
'stuff': relationship(
Stuff,
primaryjoin=and_(users.c.id == stuff.c.user_id,
operator(stuff.c.date, stuff_view)))
})
else:
mapper(User, users, properties={
'stuff': relationship(
Stuff,
primaryjoin=and_(users.c.id == stuff.c.user_id,
operator(stuff.c.id, stuff_view)))
})
sess = create_session()
def go():
eq_(
sess.query(User).order_by(User.name).options(
joinedload('stuff')).all(),
[
User(name='user1', stuff=[Stuff(id=2)]),
User(name='user2', stuff=[Stuff(id=4)]),
User(name='user3', stuff=[Stuff(id=5)])
]
)
self.assert_sql_count(testing.db, go, 1)
sess = create_session()
def go():
eq_(
sess.query(User).order_by(User.name).first(),
User(name='user1', stuff=[Stuff(id=2)])
)
self.assert_sql_count(testing.db, go, 2)
sess = create_session()
def go():
eq_(
sess.query(User).order_by(User.name).options(
joinedload('stuff')).first(),
User(name='user1', stuff=[Stuff(id=2)])
)
self.assert_sql_count(testing.db, go, 1)
sess = create_session()
def go():
eq_(
sess.query(User).filter(User.id == 2).options(
joinedload('stuff')).one(),
User(name='user2', stuff=[Stuff(id=4)])
)
self.assert_sql_count(testing.db, go, 1)
class CyclicalInheritingEagerTestOne(fixtures.MappedTest):
@classmethod
def define_tables(cls, metadata):
Table(
't1', metadata,
Column(
'c1', Integer, primary_key=True,
test_needs_autoincrement=True),
Column('c2', String(30)),
Column('type', String(30))
)
Table('t2', metadata,
Column('c1', Integer, primary_key=True,
test_needs_autoincrement=True),
Column('c2', String(30)),
Column('type', String(30)),
Column('t1.id', Integer, ForeignKey('t1.c1')))
def test_basic(self):
t2, t1 = self.tables.t2, self.tables.t1
class T(object):
pass
class SubT(T):
pass
class T2(object):
pass
class SubT2(T2):
pass
mapper(T, t1, polymorphic_on=t1.c.type, polymorphic_identity='t1')
mapper(
SubT, None, inherits=T, polymorphic_identity='subt1',
properties={
't2s': relationship(
SubT2, lazy='joined',
backref=sa.orm.backref('subt', lazy='joined'))
})
mapper(T2, t2, polymorphic_on=t2.c.type, polymorphic_identity='t2')
mapper(SubT2, None, inherits=T2, polymorphic_identity='subt2')
# testing a particular endless loop condition in eager load setup
create_session().query(SubT).all()
class CyclicalInheritingEagerTestTwo(fixtures.DeclarativeMappedTest,
testing.AssertsCompiledSQL):
__dialect__ = 'default'
@classmethod
def setup_classes(cls):
Base = cls.DeclarativeBasic
class PersistentObject(Base):
__tablename__ = 'persistent'
id = Column(Integer, primary_key=True,
test_needs_autoincrement=True)
class Movie(PersistentObject):
__tablename__ = 'movie'
id = Column(Integer, ForeignKey('persistent.id'), primary_key=True)
director_id = Column(Integer, ForeignKey('director.id'))
title = Column(String(50))
class Director(PersistentObject):
__tablename__ = 'director'
id = Column(Integer, ForeignKey('persistent.id'), primary_key=True)
movies = relationship("Movie", foreign_keys=Movie.director_id)
name = Column(String(50))
def test_from_subclass(self):
Director = self.classes.Director
s = create_session()
self.assert_compile(
s.query(Director).options(joinedload('*')),
"SELECT director.id AS director_id, "
"persistent.id AS persistent_id, "
"director.name AS director_name, movie_1.id AS movie_1_id, "
"persistent_1.id AS persistent_1_id, "
"movie_1.director_id AS movie_1_director_id, "
"movie_1.title AS movie_1_title "
"FROM persistent JOIN director ON persistent.id = director.id "
"LEFT OUTER JOIN "
"(persistent AS persistent_1 JOIN movie AS movie_1 "
"ON persistent_1.id = movie_1.id) "
"ON director.id = movie_1.director_id"
)
def test_integrate(self):
Director = self.classes.Director
Movie = self.classes.Movie
session = Session(testing.db)
rscott = Director(name="Ridley Scott")
alien = Movie(title="Alien")
brunner = Movie(title="Blade Runner")
rscott.movies.append(brunner)
rscott.movies.append(alien)
session.add_all([rscott, alien, brunner])
session.commit()
session.close_all()
self.d = session.query(Director).options(joinedload('*')).first()
assert len(list(session)) == 3
class CyclicalInheritingEagerTestThree(fixtures.DeclarativeMappedTest,
testing.AssertsCompiledSQL):
__dialect__ = 'default'
run_create_tables = None
@classmethod
def setup_classes(cls):
Base = cls.DeclarativeBasic
class PersistentObject(Base):
__tablename__ = 'persistent'
id = Column(Integer, primary_key=True,
test_needs_autoincrement=True)
__mapper_args__ = {'with_polymorphic': "*"}
class Director(PersistentObject):
__tablename__ = 'director'
id = Column(Integer, ForeignKey('persistent.id'), primary_key=True)
other_id = Column(Integer, ForeignKey('persistent.id'))
name = Column(String(50))
other = relationship(PersistentObject,
primaryjoin=other_id == PersistentObject.id,
lazy=False)
__mapper_args__ = {"inherit_condition": id == PersistentObject.id}
def test_gen_query_nodepth(self):
PersistentObject = self.classes.PersistentObject
sess = create_session()
self.assert_compile(
sess.query(PersistentObject),
"SELECT persistent.id AS persistent_id, "
"director.id AS director_id,"
" director.other_id AS director_other_id, "
"director.name AS director_name FROM persistent "
"LEFT OUTER JOIN director ON director.id = persistent.id"
)
def test_gen_query_depth(self):
PersistentObject = self.classes.PersistentObject
Director = self.classes.Director
sess = create_session()
self.assert_compile(
sess.query(PersistentObject).options(joinedload(Director.other)),
"SELECT persistent.id AS persistent_id, "
"director.id AS director_id, "
"director.other_id AS director_other_id, "
"director.name AS director_name, persistent_1.id AS "
"persistent_1_id, director_1.id AS director_1_id, "
"director_1.other_id AS director_1_other_id, "
"director_1.name AS director_1_name "
"FROM persistent LEFT OUTER JOIN director "
"ON director.id = persistent.id "
"LEFT OUTER JOIN (persistent AS persistent_1 "
"LEFT OUTER JOIN director AS director_1 ON "
"director_1.id = persistent_1.id) "
"ON director.other_id = persistent_1.id"
)
class EnsureColumnsAddedTest(
fixtures.DeclarativeMappedTest, testing.AssertsCompiledSQL):
__dialect__ = 'default'
run_create_tables = None
@classmethod
def setup_classes(cls):
Base = cls.DeclarativeBasic
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True,
test_needs_autoincrement=True)
arb = Column(Integer, unique=True)
data = Column(Integer)
o2mchild = relationship("O2MChild")
m2mchild = relationship("M2MChild", secondary=Table(
'parent_to_m2m', Base.metadata,
Column('parent_id', ForeignKey('parent.arb')),
Column('child_id', ForeignKey('m2mchild.id'))
))
class O2MChild(Base):
__tablename__ = 'o2mchild'
id = Column(Integer, primary_key=True,
test_needs_autoincrement=True)
parent_id = Column(ForeignKey('parent.arb'))
class M2MChild(Base):
__tablename__ = 'm2mchild'
id = Column(Integer, primary_key=True,
test_needs_autoincrement=True)
def test_joinedload_defered_pk_limit_o2m(self):
Parent = self.classes.Parent
s = Session()
self.assert_compile(
s.query(Parent).options(
load_only('data'),
joinedload(Parent.o2mchild)).limit(10),
"SELECT anon_1.parent_id AS anon_1_parent_id, "
"anon_1.parent_data AS anon_1_parent_data, "
"anon_1.parent_arb AS anon_1_parent_arb, "
"o2mchild_1.id AS o2mchild_1_id, "
"o2mchild_1.parent_id AS o2mchild_1_parent_id "
"FROM (SELECT parent.id AS parent_id, parent.data AS parent_data, "
"parent.arb AS parent_arb FROM parent LIMIT :param_1) AS anon_1 "
"LEFT OUTER JOIN o2mchild AS o2mchild_1 "
"ON anon_1.parent_arb = o2mchild_1.parent_id"
)
def test_joinedload_defered_pk_limit_m2m(self):
Parent = self.classes.Parent
s = Session()
self.assert_compile(
s.query(Parent).options(
load_only('data'),
joinedload(Parent.m2mchild)).limit(10),
"SELECT anon_1.parent_id AS anon_1_parent_id, "
"anon_1.parent_data AS anon_1_parent_data, "
"anon_1.parent_arb AS anon_1_parent_arb, "
"m2mchild_1.id AS m2mchild_1_id "
"FROM (SELECT parent.id AS parent_id, "
"parent.data AS parent_data, parent.arb AS parent_arb "
"FROM parent LIMIT :param_1) AS anon_1 "
"LEFT OUTER JOIN (parent_to_m2m AS parent_to_m2m_1 "
"JOIN m2mchild AS m2mchild_1 "
"ON m2mchild_1.id = parent_to_m2m_1.child_id) "
"ON anon_1.parent_arb = parent_to_m2m_1.parent_id"
)
def test_joinedload_defered_pk_o2m(self):
Parent = self.classes.Parent
s = Session()
self.assert_compile(
s.query(Parent).options(
load_only('data'),
joinedload(Parent.o2mchild)),
"SELECT parent.id AS parent_id, parent.data AS parent_data, "
"parent.arb AS parent_arb, o2mchild_1.id AS o2mchild_1_id, "
"o2mchild_1.parent_id AS o2mchild_1_parent_id "
"FROM parent LEFT OUTER JOIN o2mchild AS o2mchild_1 "
"ON parent.arb = o2mchild_1.parent_id"
)
def test_joinedload_defered_pk_m2m(self):
Parent = self.classes.Parent
s = Session()
self.assert_compile(
s.query(Parent).options(
load_only('data'),
joinedload(Parent.m2mchild)),
"SELECT parent.id AS parent_id, parent.data AS parent_data, "
"parent.arb AS parent_arb, m2mchild_1.id AS m2mchild_1_id "
"FROM parent LEFT OUTER JOIN (parent_to_m2m AS parent_to_m2m_1 "
"JOIN m2mchild AS m2mchild_1 "
"ON m2mchild_1.id = parent_to_m2m_1.child_id) "
"ON parent.arb = parent_to_m2m_1.parent_id"
)