join(prop) would fail to render the second join outside the
subquery, when joining on the same criterion as was on the
inside.
-
+
+ - Fixed bug which affected all eagerload() and similar options
+ such that "remote" eager loads, i.e. eagerloads off of a lazy
+ load such as query(A).options(eagerload(A.b, B.c))
+ wouldn't eagerload anything, but using eagerload("b.c") would
+ work fine.
+
0.6beta2
========
self._process(query, False)
def _process(self, query, raiseerr):
- paths, mappers = self.__get_paths(query, raiseerr)
+ paths, mappers = self._get_paths(query, raiseerr)
if paths:
self.process_query_property(query, paths, mappers)
def process_query_property(self, query, paths, mappers):
pass
- def __find_entity(self, query, mapper, raiseerr):
+ def __getstate__(self):
+ d = self.__dict__.copy()
+ d['key'] = ret = []
+ for token in util.to_list(self.key):
+ if isinstance(token, PropComparator):
+ ret.append((token.mapper.class_, token.key))
+ else:
+ ret.append(token)
+ return d
+
+ def __setstate__(self, state):
+ ret = []
+ for key in state['key']:
+ if isinstance(key, tuple):
+ cls, propkey = key
+ ret.append(getattr(cls, propkey))
+ else:
+ ret.append(key)
+ state['key'] = tuple(ret)
+ self.__dict__ = state
+
+ def _find_entity(self, query, mapper, raiseerr):
from sqlalchemy.orm.util import _class_to_mapper, _is_aliased_class
if _is_aliased_class(mapper):
else:
searchfor = _class_to_mapper(mapper)
isa = True
-
+
for ent in query._mapper_entities:
if searchfor is ent.path_entity or (
isa and
else:
return None
- def __getstate__(self):
- d = self.__dict__.copy()
- d['key'] = ret = []
- for token in util.to_list(self.key):
- if isinstance(token, PropComparator):
- ret.append((token.mapper.class_, token.key))
- else:
- ret.append(token)
- return d
-
- def __setstate__(self, state):
- ret = []
- for key in state['key']:
- if isinstance(key, tuple):
- cls, propkey = key
- ret.append(getattr(cls, propkey))
- else:
- ret.append(key)
- state['key'] = tuple(ret)
- self.__dict__ = state
-
- def __get_paths(self, query, raiseerr):
+ def _get_paths(self, query, raiseerr):
path = None
entity = None
l = []
# with an existing path
current_path = list(query._current_path)
- if self.mapper:
- entity = self.__find_entity(query, self.mapper, raiseerr)
- mapper = entity.mapper
- path_element = entity.path_entity
-
+ tokens = []
for key in util.to_list(self.key):
if isinstance(key, basestring):
- tokens = key.split('.')
+ tokens += key.split('.')
else:
- tokens = [key]
- for token in tokens:
- if isinstance(token, basestring):
- if not entity:
- entity = query._entity_zero()
- path_element = entity.path_entity
- mapper = entity.mapper
- mappers.append(mapper)
- prop = mapper.get_property(token, resolve_synonyms=True, raiseerr=raiseerr)
- key = token
- elif isinstance(token, PropComparator):
- prop = token.property
- if not entity:
- entity = self.__find_entity(query, token.parententity, raiseerr)
- if not entity:
- return [], []
- path_element = entity.path_entity
- mappers.append(prop.parent)
- key = prop.key
- else:
- raise sa_exc.ArgumentError("mapper option expects string key "
- "or list of attributes")
-
- if current_path and key == current_path[1]:
- current_path = current_path[2:]
- continue
+ tokens += [key]
+
+ for token in tokens:
+ if isinstance(token, basestring):
+ if not entity:
+ if current_path:
+ if current_path[1] == token:
+ current_path = current_path[2:]
+ continue
- if prop is None:
- return [], []
-
- path = build_path(path_element, prop.key, path)
- l.append(path)
- if getattr(token, '_of_type', None):
- path_element = mapper = token._of_type
- else:
- path_element = mapper = getattr(prop, 'mapper', None)
-
- if path_element:
- path_element = path_element
+ entity = query._entity_zero()
+ path_element = entity.path_entity
+ mapper = entity.mapper
+ mappers.append(mapper)
+ prop = mapper.get_property(
+ token,
+ resolve_synonyms=True,
+ raiseerr=raiseerr)
+ key = token
+ elif isinstance(token, PropComparator):
+ prop = token.property
+ if not entity:
+ if current_path:
+ if current_path[0:2] == [token.parententity, prop.key]:
+ current_path = current_path[2:]
+ continue
+
+ entity = self._find_entity(
+ query,
+ token.parententity,
+ raiseerr)
+ if not entity:
+ return [], []
+ path_element = entity.path_entity
+ mapper = entity.mapper
+ mappers.append(prop.parent)
+ key = prop.key
+ else:
+ raise sa_exc.ArgumentError("mapper option expects string key "
+ "or list of attributes")
+
+ if prop is None:
+ return [], []
+
+ path = build_path(path_element, prop.key, path)
+ l.append(path)
+ if getattr(token, '_of_type', None):
+ path_element = mapper = token._of_type
+ else:
+ path_element = mapper = getattr(prop, 'mapper', None)
+
+ if path_element:
+ path_element = path_element
# if current_path tokens remain, then
# we didn't have an exact path match.
if current_path:
return [], []
-
+
return l, mappers
class AttributeExtension(object):
if not context.query._enable_eagerloads:
return
-
-# path = path + (self.key,)
-
-
- if ("orig_query", SubqueryLoader) not in context.attributes:
- context.attributes[("orig_query", SubqueryLoader)] =\
- context.query
- orig_query = context.attributes[("orig_query", SubqueryLoader)]
+ orig_query = context.attributes.get(("orig_query", SubqueryLoader),
+ context.query)
-# orig_query = context.query
- path = context.query._current_path + path + (self.key, )
+ path = path + (self.key, )
- prop = path[0].get_property(path[1])
+ local_cols, remote_cols = self._local_remote_columns(self.parent_property)
+ if len(path) > 1:
+ leftmost_mapper, leftmost_prop = path[0], path[0].get_property(path[1])
+ leftmost_cols, remote_cols = self._local_remote_columns(leftmost_prop)
+ else:
+ leftmost_cols = local_cols
+ leftmost_mapper = self.parent
- local_cols, remote_cols = self._local_remote_columns(prop)
+ leftmost_attr = [
+ leftmost_mapper._get_col_to_prop(c).class_attribute
+ for c in leftmost_cols
+ ]
local_attr = [
- path[0]._get_col_to_prop(c).class_attribute
+ self.parent._get_col_to_prop(c).class_attribute
for c in local_cols
]
- #attr = self.parent_property.class_attribute
-
# modify the query to just look for parent columns in the
# join condition
q = orig_query._clone() #context.query._clone()
q._attributes = q._attributes.copy()
q._attributes[("orig_query", SubqueryLoader)] = orig_query
- q._set_entities(local_attr)
+ q._set_entities(leftmost_attr)
q._order_by = None
# now select from it as a subquery.
q = q.order_by(*local_attr)
- q._attributes = q._attributes.copy()
for attr in orig_query._attributes:
strat, opt_path = attr
if strat == "loaderstrategy":
opt_path = opt_path[len(path):]
q._attributes[("loaderstrategy", opt_path)] =\
context.query._attributes[attr]
-
- q = q._with_current_path(path)
+
if self.parent_property.order_by:
q = q.order_by(*self.parent_property.order_by)
context.attributes[('subquery', path)] = \
- q._attributes[('subquery', path)] = \
- q
-
-# for value in self.mapper._iterate_polymorphic_properties():
-# strat = value._get_context_strategy(
-# context, path +
-# (self.mapper,value.key)
-# )
- #print "VALUE", value, "PATH", path + (self.mapper,), "STRAT", type(strat)
-# if isinstance(strat, SubqueryLoader):
-# value.setup(
-# context,
-# entity,
-## path + (self.mapper,),
-# adapter,
-# parentmapper=self.mapper,
-# )
+ q._attributes[('subquery', path)] = q
+
def _local_remote_columns(self, prop):
if prop.secondary is None:
]
def create_row_processor(self, context, path, mapper, row, adapter):
-# path = path + (self.key,)
- path = context.query._current_path + path + (self.key,)
-
+ path = path + (self.key,)
+
+ if ('subquery', path) not in context.attributes:
+ return None, None
+
local_cols, remote_cols = self._local_remote_columns(self.parent_property)
local_attr = [self.parent._get_col_to_prop(c).key for c in local_cols]
self.mapper._get_col_to_prop(c).key
for c in remote_cols]
- print "STRAT LOOKING FOR SUBQ AT PATH", path
q = context.attributes[('subquery', path)]
collections = dict(
lambda x:x[1:]
))
+ if adapter:
+ local_cols = [adapter.columns[c] for c in local_cols]
+
def execute(state, dict_, row):
collection = collections.get(
tuple([row[col] for col in local_cols]),
log.class_logger(EagerLoader)
class EagerLazyOption(StrategizedOption):
- def __init__(self, key, lazy=True, chained=False,
- mapper=None, propagate_to_loaders=True,
+ def __init__(self, key, lazy=True, chained=False,
+ propagate_to_loaders=True,
_strategy_cls=None
):
- super(EagerLazyOption, self).__init__(key, mapper)
+ super(EagerLazyOption, self).__init__(key)
self.lazy = lazy
self.chained = chained
self.propagate_to_loaders = propagate_to_loaders
items=[item1, item2, item3]),
Order(id=4,
items=[item1, item5])]),
- User(id=10)]
+ User(id=10, orders=[])]
return user_result
FixtureTest.static = CannedResults()
u)
self.assert_sql_count(testing.db, go, 1)
+ @testing.resolve_artifact_names
+ def test_options_pathing(self):
+ 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 [
+ ((
+ eagerload(User.orders, Order.items),
+ ), 10),
+ ((eagerload("orders.items"), ), 10),
+ ((
+ eagerload(User.orders, ),
+ eagerload(User.orders, Order.items),
+ eagerload(User.orders, Order.items, Item.keywords),
+ ), 1),
+ ((
+ eagerload(User.orders, Order.items, Item.keywords),
+ ), 10),
+ ((
+ eagerload(User.orders, Order.items),
+ eagerload(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)
+
+
+
@testing.resolve_artifact_names
def test_many_to_many(self):
x = u[0].orders[1].items[0].keywords[1]
self.sql_count_(2, go)
+ sess = create_session()
+ q3 = sess.query(User).options(
+ sa.orm.eagerload(User.orders, Order.items, Item.keywords))
+ u = q3.all()
+ def go():
+ x = u[0].orders[1].items[0].keywords[1]
+ self.sql_count_(2, go)
+
class ValidatorTest(_fixtures.FixtureTest):
@testing.resolve_artifact_names
def test_scalar(self):
from sqlalchemy.test.testing import eq_, is_, is_not_
from sqlalchemy.test import testing
from sqlalchemy.orm import backref, subqueryload, subqueryload_all, \
- mapper, relationship, \
+ mapper, relationship, clear_mappers,\
create_session, lazyload, aliased, eagerload
from sqlalchemy.test.testing import eq_, assert_raises
from sqlalchemy.test.assertsql import CompiledSQL
class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
run_inserts = 'once'
run_deletes = None
-
+
@testing.resolve_artifact_names
def test_basic(self):
mapper(User, users, properties={
User(id=10, addresses=[])
], sess.query(User).order_by(User.id).all())
+ _pathing_runs = [
+ ( "lazyload", "lazyload", "lazyload", 15 ),
+ ("eagerload", "eagerload", "eagerload", 1),
+ ("subqueryload", "lazyload", "lazyload", 12),
+ ("subqueryload", "subqueryload", "lazyload", 8),
+ ("eagerload", "subqueryload", "lazyload", 7),
+ ("lazyload", "lazyload", "subqueryload", 12),
+
+ # here's the one that fails:
+ #("subqueryload", "subqueryload", "subqueryload", 4),
+ ]
+# _pathing_runs = [("subqueryload", "subqueryload", "subqueryload", 4)]
+ _pathing_runs = [("lazyload", "lazyload", "subqueryload", 12)]
+
+ def test_options_pathing(self):
+ self._do_options_test(self._pathing_runs)
+
+ def test_mapper_pathing(self):
+ self._do_mapper_test(self._pathing_runs)
+
@testing.resolve_artifact_names
- def test_pathing(self):
+ def _do_options_test(self, configs):
mapper(User, users, properties={
'orders':relationship(Order, order_by=orders.c.id), # o2m, m2o
})
})
mapper(Keyword, keywords)
+ callables = {
+ 'eagerload':eagerload,
+ 'subqueryload':subqueryload
+ }
+
+ for o, i, k, count in configs:
+ options = []
+ if o in callables:
+ options.append(callables[o](User.orders))
+ if i in callables:
+ options.append(callables[i](User.orders, Order.items))
+ if k in callables:
+ options.append(callables[k](User.orders, Order.items, Item.keywords))
- for opt, count in [
-# ((
-# lazyload(User.orders),
-# lazyload(User.orders, Order.items),
-# lazyload(User.orders, Order.items, Item.keywords)
-# ), 14),
-# ((
-# eagerload(User.orders),
-# eagerload(User.orders, Order.items),
-# eagerload(User.orders, Order.items, Item.keywords)
-# ), 1),
-# ((
-# subqueryload(User.orders),
-# ), 12),
- ((
- subqueryload(User.orders),
- subqueryload(User.orders, Order.items),
- ), 8),
-# ((
-# subqueryload(User.orders),
-# subqueryload(User.orders, Order.items),
-# subqueryload(User.orders, Order.items, Item.keywords),
-# ), 4),
- ]:
sess = create_session()
def go():
eq_(
- sess.query(User).options(*opt).order_by(User.id).all(),
+ sess.query(User).options(*options).order_by(User.id).all(),
self.static.user_item_keyword_result
)
self.assert_sql_count(testing.db, go, count)
+
+ sess = create_session()
+# def go():
+ eq_(
+ sess.query(User).filter(User.name=='fred').
+ options(*options).order_by(User.id).all(),
+ self.static.user_item_keyword_result[2:3]
+ )
+# self.assert_sql_count(testing.db, go, count)
+
+ @testing.resolve_artifact_names
+ def _do_mapper_test(self, configs):
+ opts = {
+ 'lazyload':'select',
+ 'eagerload':'joined',
+ 'subqueryload':'subquery',
+ }
+
+ for o, i, k, count in configs:
+ mapper(User, users, properties={
+ 'orders':relationship(Order, lazy=opts[o], order_by=orders.c.id),
+ })
+ mapper(Order, orders, properties={
+ 'items':relationship(Item,
+ secondary=order_items, lazy=opts[i], order_by=items.c.id),
+ })
+ mapper(Item, items, properties={
+ 'keywords':relationship(Keyword,
+ lazy=opts[k],
+ secondary=item_keywords,
+ order_by=keywords.c.id)
+ })
+ mapper(Keyword, keywords)
+
+ sess = create_session()
+ def go():
+ eq_(
+ sess.query(User).order_by(User.id).all(),
+ self.static.user_item_keyword_result
+ )
+ try:
+ self.assert_sql_count(testing.db, go, count)
+
+ eq_(
+ sess.query(User).filter(User.name=='fred').
+ order_by(User.id).all(),
+ self.static.user_item_keyword_result[2:3]
+ )
+
+ finally:
+ clear_mappers()
+
+
# TODO: all the tests in test_eager_relations
# TODO: ensure state stuff works out OK, existing objects/collections