Files
sqlalchemy/test/orm/eager_relations.py
T

1304 lines
48 KiB
Python

"""basic tests of eager loaded attributes"""
import testenv; testenv.configure_for_tests()
from testlib import sa, testing
from sqlalchemy.orm import eagerload, deferred, undefer
from testlib.sa import Table, Column, Integer, String, ForeignKey, and_
from testlib.sa.orm import mapper, relation, create_session, lazyload
from testlib.testing import eq_
from orm import _base, _fixtures
class EagerTest(_fixtures.FixtureTest):
run_inserts = 'once'
run_deletes = None
@testing.resolve_artifact_names
def test_basic(self):
mapper(User, users, properties={
'addresses':relation(mapper(Address, addresses), lazy=False)
})
sess = create_session()
q = sess.query(User)
assert [User(id=7, addresses=[Address(id=1, email_address='jack@bean.com')])] == q.filter(User.id==7).all()
assert self.static.user_address_result == q.all()
@testing.resolve_artifact_names
def test_no_orphan(self):
"""An eagerly loaded child object is not marked as an orphan"""
mapper(User, users, properties={
'addresses':relation(Address, cascade="all,delete-orphan", lazy=False)
})
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]))
@testing.resolve_artifact_names
def test_orderby(self):
mapper(User, users, properties = {
'addresses':relation(mapper(Address, addresses), lazy=False, order_by=addresses.c.email_address),
})
q = create_session().query(User)
assert [
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()
@testing.resolve_artifact_names
def test_orderby_multi(self):
mapper(User, users, properties = {
'addresses':relation(mapper(Address, addresses), lazy=False, order_by=[addresses.c.email_address, addresses.c.id]),
})
q = create_session().query(User)
assert [
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()
@testing.resolve_artifact_names
def test_orderby_related(self):
"""A regular mapper select on a single table can order by a relation to a second table"""
mapper(Address, addresses)
mapper(User, users, properties = dict(
addresses = relation(Address, lazy=False, order_by=addresses.c.id),
))
q = create_session().query(User)
l = q.filter(User.id==Address.user_id).order_by(Address.email_address).all()
assert [
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
@testing.resolve_artifact_names
def test_orderby_desc(self):
mapper(Address, addresses)
mapper(User, users, properties = dict(
addresses = relation(Address, lazy=False,
order_by=[sa.desc(addresses.c.email_address)]),
))
sess = create_session()
assert [
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()
@testing.resolve_artifact_names
def test_deferred_fk_col(self):
User, Address, Dingaling = self.classes.get_all(
'User', 'Address', 'Dingaling')
users, addresses, dingalings = self.tables.get_all(
'users', 'addresses', 'dingalings')
mapper(Address, addresses, properties={
'user_id':deferred(addresses.c.user_id),
'user':relation(User, lazy=False)
})
mapper(User, users)
sess = create_session()
for q in [
sess.query(Address).filter(Address.id.in_([1, 4, 5])),
sess.query(Address).filter(Address.id.in_([1, 4, 5])).limit(3)
]:
sess.clear()
eq_(q.all(),
[Address(id=1, user=User(id=7)),
Address(id=4, user=User(id=8)),
Address(id=5, user=User(id=9))]
)
a = sess.query(Address).filter(Address.id==1).first()
def go():
eq_(a.user_id, 7)
# assert that the eager loader added 'user_id' to the row and deferred
# loading of that col was disabled
self.assert_sql_count(testing.db, go, 0)
# 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':relation(Address, lazy=False)})
for q in [
sess.query(User).filter(User.id==7),
sess.query(User).filter(User.id==7).limit(1)
]:
sess.clear()
eq_(q.all(),
[User(id=7, addresses=[Address(id=1)])]
)
sess.clear()
u = sess.query(User).get(7)
def go():
assert 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':relation(Address, lazy=False)})
mapper(Address, addresses, properties={
'user_id':deferred(addresses.c.user_id),
'dingalings':relation(Dingaling, lazy=False)})
mapper(Dingaling, dingalings, properties={
'address_id':deferred(dingalings.c.address_id)})
sess.clear()
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)
@testing.resolve_artifact_names
def test_many_to_many(self):
Keyword, Item = self.Keyword, self.Item
keywords, item_keywords, items = self.tables.get_all(
'keywords', 'item_keywords', 'items')
mapper(Keyword, keywords)
mapper(Item, items, properties = dict(
keywords = relation(Keyword, secondary=item_keywords,
lazy=False, order_by=keywords.c.id)))
q = create_session().query(Item).order_by(Item.id)
def go():
assert 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)
@testing.resolve_artifact_names
def test_eager_option(self):
Keyword, Item = self.Keyword, self.Item
keywords, item_keywords, items = self.tables.get_all(
'keywords', 'item_keywords', 'items')
mapper(Keyword, keywords)
mapper(Item, items, properties = dict(
keywords = relation(Keyword, secondary=item_keywords, lazy=True,
order_by=keywords.c.id)))
q = create_session().query(Item)
def go():
eq_(self.static.item_keyword_result[0:2],
(q.options(eagerload('keywords')).
join('keywords').filter(keywords.c.name == 'red')).order_by(Item.id).all())
self.assert_sql_count(testing.db, go, 1)
@testing.resolve_artifact_names
def test_cyclical(self):
"""A circular eager relationship breaks the cycle with a lazy loader"""
User, Address = self.User, self.Address
users, addresses = self.tables.get_all('users', 'addresses')
mapper(Address, addresses)
mapper(User, users, properties = dict(
addresses = relation(Address, lazy=False,
backref=sa.orm.backref('user', lazy=False))
))
assert sa.orm.class_mapper(User).get_property('addresses').lazy is False
assert sa.orm.class_mapper(Address).get_property('user').lazy is False
sess = create_session()
assert self.static.user_address_result == sess.query(User).all()
@testing.resolve_artifact_names
def test_double(self):
"""Eager loading with two relations simultaneously, from the same table, using aliases."""
User, Address, Order = self.classes.get_all(
'User', 'Address', 'Order')
users, addresses, orders = self.tables.get_all(
'users', 'addresses', 'orders')
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 = relation(Address, lazy=False),
open_orders = relation(
open_mapper,
primaryjoin=sa.and_(openorders.c.isopen == 1,
users.c.id==openorders.c.user_id),
lazy=False),
closed_orders = relation(
closed_mapper,
primaryjoin=sa.and_(closedorders.c.isopen == 0,
users.c.id==closedorders.c.user_id),
lazy=False)))
q = create_session().query(User)
def go():
assert [
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)
@testing.resolve_artifact_names
def test_double_same_mappers(self):
"""Eager loading with two relations simulatneously, from the same table, using aliases."""
User, Address, Order = self.classes.get_all(
'User', 'Address', 'Order')
users, addresses, orders = self.tables.get_all(
'users', 'addresses', 'orders')
mapper(Address, addresses)
mapper(Order, orders, properties={
'items': relation(Item, secondary=order_items, lazy=False,
order_by=items.c.id)})
mapper(Item, items)
mapper(User, users, properties=dict(
addresses=relation(Address, lazy=False, order_by=addresses.c.id),
open_orders=relation(
Order,
primaryjoin=sa.and_(orders.c.isopen == 1,
users.c.id==orders.c.user_id),
lazy=False, order_by=orders.c.id),
closed_orders=relation(
Order,
primaryjoin=sa.and_(orders.c.isopen == 0,
users.c.id==orders.c.user_id),
lazy=False, order_by=orders.c.id)))
q = create_session().query(User).order_by(User.id)
def go():
assert [
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)
@testing.resolve_artifact_names
def test_no_false_hits(self):
"""Eager loaders don't interpret main table columns as part of their eager load."""
User, Address, Order = self.classes.get_all(
'User', 'Address', 'Order')
users, addresses, orders = self.tables.get_all(
'users', 'addresses', 'orders')
mapper(User, users, properties={
'addresses':relation(Address, lazy=False),
'orders':relation(Order, lazy=False)
})
mapper(Address, addresses)
mapper(Order, orders)
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("select * from users").all()
assert 'orders' not in noeagers[0].__dict__
assert 'addresses' not in noeagers[0].__dict__
@testing.fails_on('maxdb')
@testing.resolve_artifact_names
def test_limit(self):
"""Limit operations combined with lazy-load relationships."""
User, Item, Address, Order = self.classes.get_all(
'User', 'Item', 'Address', 'Order')
users, items, order_items, orders, addresses = self.tables.get_all(
'users', 'items', 'order_items', 'orders', 'addresses')
mapper(Item, items)
mapper(Order, orders, properties={
'items':relation(Item, secondary=order_items, lazy=False, order_by=items.c.id)
})
mapper(User, users, properties={
'addresses':relation(mapper(Address, addresses), lazy=False, order_by=addresses.c.id),
'orders':relation(Order, lazy=True)
})
sess = create_session()
q = sess.query(User)
if testing.against('mysql'):
l = q.limit(2).all()
assert self.static.user_all_result[:2] == l
else:
l = q.order_by(User.id).limit(2).offset(1).all()
print self.static.user_all_result[1:3]
print l
assert self.static.user_all_result[1:3] == l
@testing.resolve_artifact_names
def test_distinct(self):
# 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':relation(mapper(Address, addresses), lazy=False),
})
sess = create_session()
q = sess.query(User)
def go():
l = q.filter(s.c.u2_id==User.id).distinct().all()
assert self.static.user_address_result == l
self.assert_sql_count(testing.db, go, 1)
@testing.fails_on('maxdb')
@testing.resolve_artifact_names
def test_limit_2(self):
mapper(Keyword, keywords)
mapper(Item, items, properties = dict(
keywords = relation(Keyword, secondary=item_keywords, lazy=False, 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()
assert self.static.item_keyword_result[1:3] == l
@testing.fails_on('maxdb')
@testing.resolve_artifact_names
def test_limit_3(self):
"""test that the ORDER BY is propigated 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."""
mapper(Item, items)
mapper(Order, orders, properties = dict(
items = relation(Item, secondary=order_items, lazy=False)
))
mapper(Address, addresses)
mapper(User, users, properties = dict(
addresses = relation(Address, lazy=False, order_by=addresses.c.id),
orders = relation(Order, lazy=False, order_by=orders.c.id),
))
sess = create_session()
q = sess.query(User)
if not testing.against('maxdb', 'mssql'):
l = q.join('orders').order_by(Order.user_id.desc()).limit(2).offset(1)
assert [
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)
assert [
User(id=7,
orders=[Order(id=1), Order(id=3), Order(id=5)],
addresses=[Address(id=1)]
)
] == l.all()
@testing.resolve_artifact_names
def test_limit_4(self):
# 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':relation(Order, primaryjoin=sel.c.id==orders.c.user_id, lazy=False)
})
mapper(Order, orders)
sess = create_session()
eq_(sess.query(User).first(),
User(name=u'jack',orders=[
Order(address_id=1,description=u'order 1',isopen=0,user_id=7,id=1),
Order(address_id=1,description=u'order 3',isopen=1,user_id=7,id=3),
Order(address_id=None,description=u'order 5',isopen=0,user_id=7,id=5)],
email_address=u'jack@bean.com',id=7)
)
@testing.resolve_artifact_names
def test_one_to_many_scalar(self):
mapper(User, users, properties = dict(
address = relation(mapper(Address, addresses), lazy=False, uselist=False)
))
q = create_session().query(User)
def go():
l = q.filter(users.c.id == 7).all()
assert [User(id=7, address=Address(id=1))] == l
self.assert_sql_count(testing.db, go, 1)
@testing.fails_on('maxdb')
@testing.resolve_artifact_names
def test_many_to_one(self):
mapper(Address, addresses, properties = dict(
user = relation(mapper(User, users), lazy=False)
))
sess = create_session()
q = sess.query(Address)
def go():
a = q.filter(addresses.c.id==1).one()
assert a.user is not None
u1 = sess.query(User).get(7)
assert a.user is u1
self.assert_sql_count(testing.db, go, 1)
@testing.resolve_artifact_names
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.
"""
# use a primaryjoin intended to defeat SA's usage of
# query.get() for a many-to-one lazyload
mapper(Order, orders, properties = dict(
address = relation(mapper(Address, addresses),
primaryjoin=and_(
addresses.c.id==orders.c.address_id,
addresses.c.email_address != None
),
lazy=False)
))
sess = create_session()
def go():
o1 = sess.query(Order).options(lazyload('address')).filter(Order.id==5).one()
self.assertEquals(o1.address, None)
self.assert_sql_count(testing.db, go, 2)
sess.clear()
def go():
o1 = sess.query(Order).filter(Order.id==5).one()
self.assertEquals(o1.address, None)
self.assert_sql_count(testing.db, go, 1)
@testing.resolve_artifact_names
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."""
mapper(User, users, properties={
'orders':relation(Order, lazy=False, order_by=orders.c.id)
})
mapper(Item, items)
mapper(Order, orders, properties = dict(
items = relation(Item, secondary=order_items, lazy=False, order_by=items.c.id)
))
q = create_session().query(User)
l = q.filter("users.id in (7, 8, 9)").order_by("users.id")
def go():
assert self.static.user_order_result[0:3] == l.all()
self.assert_sql_count(testing.db, go, 1)
@testing.resolve_artifact_names
def test_double_with_aggregate(self):
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':relation(Order, backref='user', lazy=False),
'max_order':relation(mapper(Order, max_orders, non_primary=True), lazy=False, uselist=False)
})
q = create_session().query(User)
def go():
assert [
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.all()
self.assert_sql_count(testing.db, go, 1)
@testing.resolve_artifact_names
def test_wide(self):
mapper(Order, orders, properties={'items':relation(Item, secondary=order_items, lazy=False, order_by=items.c.id)})
mapper(Item, items)
mapper(User, users, properties = dict(
addresses = relation(mapper(Address, addresses), lazy = False, order_by=addresses.c.id),
orders = relation(Order, lazy = False, order_by=orders.c.id),
))
q = create_session().query(User)
l = q.all()
assert self.static.user_all_result == q.order_by(User.id).all()
@testing.resolve_artifact_names
def test_against_select(self):
"""test eager loading of a mapper which is against a select"""
s = sa.select([orders], orders.c.isopen==1).alias('openorders')
mapper(Order, s, properties={
'user':relation(User, lazy=False)
})
mapper(User, users)
mapper(Item, items)
q = create_session().query(Order)
assert [
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]))
assert [
Order(id=3, user=User(id=7)),
] == q.all()
@testing.resolve_artifact_names
def test_aliasing(self):
"""test that eager loading uses aliases to insulate the eager load from regular criterion against those tables."""
mapper(User, users, properties = dict(
addresses = relation(mapper(Address, addresses), lazy=False, 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)
assert self.static.user_address_result[1:2] == l.all()
class AddEntityTest(_fixtures.FixtureTest):
run_inserts = 'once'
run_deletes = None
@testing.resolve_artifact_names
def _assert_result(self):
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)]
),
)
]
@testing.resolve_artifact_names
def test_mapper_configured(self):
mapper(User, users, properties={
'addresses':relation(Address, lazy=False),
'orders':relation(Order)
})
mapper(Address, addresses)
mapper(Order, orders, properties={
'items':relation(Item, secondary=order_items, lazy=False, order_by=items.c.id)
})
mapper(Item, items)
sess = create_session()
oalias = sa.orm.aliased(Order)
def go():
ret = sess.query(User, oalias).join(('orders', oalias)).order_by(User.id, oalias.id).all()
eq_(ret, self._assert_result())
self.assert_sql_count(testing.db, go, 1)
@testing.resolve_artifact_names
def test_options(self):
mapper(User, users, properties={
'addresses':relation(Address),
'orders':relation(Order)
})
mapper(Address, addresses)
mapper(Order, orders, properties={
'items':relation(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(eagerload('addresses')).join(('orders', oalias)).order_by(User.id, oalias.id).all()
eq_(ret, self._assert_result())
self.assert_sql_count(testing.db, go, 6)
sess.clear()
def go():
ret = sess.query(User, oalias).options(eagerload('addresses'), eagerload(oalias.items)).join(('orders', oalias)).order_by(User.id, oalias.id).all()
eq_(ret, self._assert_result())
self.assert_sql_count(testing.db, go, 1)
class OrderBySecondaryTest(_base.MappedTest):
def define_tables(self, metadata):
Table('m2m', metadata,
Column('id', Integer, primary_key=True),
Column('aid', Integer, ForeignKey('a.id')),
Column('bid', Integer, ForeignKey('b.id')))
Table('a', metadata,
Column('id', Integer, primary_key=True),
Column('data', String(50)))
Table('b', metadata,
Column('id', Integer, primary_key=True),
Column('data', String(50)))
def fixtures(self):
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)))
@testing.resolve_artifact_names
def test_ordering(self):
class A(_base.ComparableEntity):pass
class B(_base.ComparableEntity):pass
mapper(A, a, properties={
'bs':relation(B, secondary=m2m, lazy=False, 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(_base.MappedTest):
def define_tables(self, metadata):
Table('nodes', metadata,
Column('id', Integer, sa.Sequence('node_id_seq', optional=True),
primary_key=True),
Column('parent_id', Integer, ForeignKey('nodes.id')),
Column('data', String(30)))
@testing.fails_on('maxdb')
@testing.resolve_artifact_names
def test_basic(self):
class Node(_base.ComparableEntity):
def append(self, node):
self.children.append(node)
mapper(Node, nodes, properties={
'children':relation(Node, lazy=False, 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.save(n1)
sess.flush()
sess.clear()
def go():
d = sess.query(Node).filter_by(data='n1').first()
assert 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)
@testing.resolve_artifact_names
def test_lazy_fallback_doesnt_affect_eager(self):
class Node(_base.ComparableEntity):
def append(self, node):
self.children.append(node)
mapper(Node, nodes, properties={
'children':relation(Node, lazy=False, 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.save(n1)
sess.flush()
sess.clear()
# 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]
assert n12.data == 'n12'
assert [
Node(data='n121'),
Node(data='n122'),
Node(data='n123')
] == list(n12.children)
self.assert_sql_count(testing.db, go, 1)
@testing.resolve_artifact_names
def test_with_deferred(self):
class Node(_base.ComparableEntity):
def append(self, node):
self.children.append(node)
mapper(Node, nodes, properties={
'children':relation(Node, lazy=False, 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.save(n1)
sess.flush()
sess.clear()
def go():
self.assertEquals(
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.clear()
def go():
assert 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.clear()
def go():
assert 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)
@testing.resolve_artifact_names
def test_options(self):
class Node(_base.ComparableEntity):
def append(self, node):
self.children.append(node)
mapper(Node, nodes, properties={
'children':relation(Node, lazy=True, 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.save(n1)
sess.flush()
sess.clear()
def go():
d = sess.query(Node).filter_by(data='n1').options(eagerload('children.children')).first()
assert 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():
d = sess.query(Node).filter_by(data='n1').options(eagerload('children.children')).first()
# test that the query isn't wrapping the initial query for eager loading.
# testing only sqlite for now since the query text is slightly different on other
# dialects
if testing.against('sqlite'):
self.assert_sql(testing.db, go, [
(
"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 1 OFFSET 0",
{'data_1': 'n1'}
),
])
@testing.fails_on('maxdb')
@testing.resolve_artifact_names
def test_no_depth(self):
class Node(_base.ComparableEntity):
def append(self, node):
self.children.append(node)
mapper(Node, nodes, properties={
'children':relation(Node, lazy=False)
})
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.save(n1)
sess.flush()
sess.clear()
def go():
d = sess.query(Node).filter_by(data='n1').first()
assert 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 SelfReferentialM2MEagerTest(_base.MappedTest):
def define_tables(self, metadata):
Table('widget', metadata,
Column('id', Integer, primary_key=True),
Column('name', sa.Unicode(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'),
)
@testing.resolve_artifact_names
def test_basic(self):
class Widget(_base.ComparableEntity):
pass
mapper(Widget, widget, properties={
'children': relation(Widget, secondary=widget_rel,
primaryjoin=widget_rel.c.parent_id==widget.c.id,
secondaryjoin=widget_rel.c.child_id==widget.c.id,
lazy=False, join_depth=1,
)
})
sess = create_session()
w1 = Widget(name=u'w1')
w2 = Widget(name=u'w2')
w1.children.append(w2)
sess.save(w1)
sess.flush()
sess.clear()
assert [Widget(name='w1', children=[Widget(name='w2')])] == sess.query(Widget).filter(Widget.name==u'w1').all()
class MixedEntitiesTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
run_setup_mappers = 'once'
run_inserts = 'once'
run_deletes = None
@testing.resolve_artifact_names
def setup_mappers(self):
mapper(User, users, properties={
'addresses':relation(Address, backref='user'),
'orders':relation(Order, backref='user'), # o2m, m2o
})
mapper(Address, addresses)
mapper(Order, orders, properties={
'items':relation(Item, secondary=order_items, order_by=items.c.id), #m2m
})
mapper(Item, items, properties={
'keywords':relation(Keyword, secondary=item_keywords) #m2m
})
mapper(Keyword, keywords)
@testing.resolve_artifact_names
def test_two_entities(self):
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(eagerload(User.addresses), eagerload(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(eagerload(User.addresses), eagerload(Order.items)).filter(User.id==9).\
order_by(User.id, Order.id).all(),
)
self.assert_sql_count(testing.db, go, 1)
@testing.resolve_artifact_names
def test_aliased_entity(self):
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(eagerload(User.addresses), eagerload(oalias.items)).filter(User.id==9).\
order_by(User.id, oalias.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, oalias).join((User.orders, oalias)).options(eagerload(User.addresses), eagerload(oalias.items)).filter(User.id==9).\
order_by(User.id, oalias.id).all(),
)
self.assert_sql_count(testing.db, go, 1)
from sqlalchemy.engine.default import DefaultDialect
# 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(eagerload(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 ON orders_1.id = order_items_1.order_id "\
"LEFT OUTER JOIN items AS items_1 ON items_1.id = order_items_1.item_id ORDER BY items_1.id",
dialect=DefaultDialect()
)
class CyclicalInheritingEagerTest(_base.MappedTest):
def define_tables(self, metadata):
Table('t1', metadata,
Column('c1', Integer, primary_key=True),
Column('c2', String(30)),
Column('type', String(30))
)
Table('t2', metadata,
Column('c1', Integer, primary_key=True),
Column('c2', String(30)),
Column('type', String(30)),
Column('t1.id', Integer, ForeignKey('t1.c1')))
@testing.resolve_artifact_names
def test_basic(self):
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':relation(SubT2, lazy=False, backref=sa.orm.backref('subt', lazy=False))
})
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 join setup
create_session().query(SubT).all()
class SubqueryTest(_base.MappedTest):
def define_tables(self, metadata):
Table('users_table', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(16))
)
Table('tags_table', metadata,
Column('id', Integer, primary_key=True),
Column('user_id', Integer, ForeignKey("users_table.id")),
Column('score1', sa.Float),
Column('score2', sa.Float),
)
@testing.resolve_artifact_names
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.
"""
class User(_base.ComparableEntity):
@property
def prop_score(self):
return sum([tag.prop_score for tag in self.tags])
class Tag(_base.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': relation(Tag, backref='user', lazy=False),
'query_score': sa.orm.column_property(user_score),
})
session = create_session()
session.save(User(name='joe', tags=[Tag(score1=5.0, score2=3.0), Tag(score1=55.0, score2=1.0)]))
session.save(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.clear()
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()
if __name__ == '__main__':
testenv.main()