summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/strategies.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/orm/strategies.py')
-rw-r--r--lib/sqlalchemy/orm/strategies.py656
1 files changed, 338 insertions, 318 deletions
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 65a8b019b..8ae3042a6 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -6,11 +6,13 @@
"""sqlalchemy.orm.interfaces.LoaderStrategy implementations, and related MapperOptions."""
-from sqlalchemy import sql, util, exceptions, logging
+import sqlalchemy.exceptions as sa_exc
+from sqlalchemy import sql, util, log
from sqlalchemy.sql import util as sql_util
from sqlalchemy.sql import visitors, expression, operators
from sqlalchemy.orm import mapper, attributes
-from sqlalchemy.orm.interfaces import LoaderStrategy, StrategizedOption, MapperOption, PropertyOption, serialize_path, deserialize_path
+from sqlalchemy.orm.interfaces import LoaderStrategy, StrategizedOption, \
+ MapperOption, PropertyOption, serialize_path, deserialize_path
from sqlalchemy.orm import session as sessionlib
from sqlalchemy.orm import util as mapperutil
@@ -21,28 +23,53 @@ class ColumnLoader(LoaderStrategy):
def init(self):
super(ColumnLoader, self).init()
self.columns = self.parent_property.columns
- self._should_log_debug = logging.is_debug_enabled(self.logger)
+ self._should_log_debug = log.is_debug_enabled(self.logger)
self.is_composite = hasattr(self.parent_property, 'composite_class')
- def setup_query(self, context, parentclauses=None, **kwargs):
+ def setup_query(self, context, entity, path, adapter, column_collection=None, **kwargs):
for c in self.columns:
- if parentclauses is not None:
- context.secondary_columns.append(parentclauses.aliased_column(c))
- else:
- context.primary_columns.append(c)
+ if adapter:
+ c = adapter.columns[c]
+ column_collection.append(c)
def init_class_attribute(self):
self.is_class_level = True
- if self.is_composite:
- self._init_composite_attribute()
+ self.logger.info("%s register managed attribute" % self)
+ coltype = self.columns[0].type
+ sessionlib.register_attribute(self.parent.class_, self.key, uselist=False, useobject=False, copy_function=coltype.copy_value, compare_function=coltype.compare_values, mutable_scalars=self.columns[0].type.is_mutable(), comparator=self.parent_property.comparator, parententity=self.parent)
+
+ def create_row_processor(self, selectcontext, path, mapper, row, adapter):
+ key, col = self.key, self.columns[0]
+ if adapter:
+ col = adapter.columns[col]
+ if col in row:
+ def new_execute(state, row, **flags):
+ state.dict[key] = row[col]
+
+ if self._should_log_debug:
+ new_execute = self.debug_callable(new_execute, self.logger,
+ "%s returning active column fetcher" % self,
+ lambda state, row, **flags: "%s populating %s" % (self, mapperutil.state_attribute_str(state, key))
+ )
+ return (new_execute, None)
else:
- self._init_scalar_attribute()
+ def new_execute(state, row, isnew, **flags):
+ if isnew:
+ state.expire_attributes([key])
+ if self._should_log_debug:
+ self.logger.debug("%s deferring load" % self)
+ return (new_execute, None)
+
+ColumnLoader.logger = log.class_logger(ColumnLoader)
+
+class CompositeColumnLoader(ColumnLoader):
+ def init_class_attribute(self):
+ self.is_class_level = True
+ self.logger.info("%s register managed composite attribute" % self)
- def _init_composite_attribute(self):
- self.logger.info("register managed composite attribute %s on class %s" % (self.key, self.parent.class_.__name__))
def copy(obj):
- return self.parent_property.composite_class(
- *obj.__composite_values__())
+ return self.parent_property.composite_class(*obj.__composite_values__())
+
def compare(a, b):
for col, aprop, bprop in zip(self.columns,
a.__composite_values__(),
@@ -51,63 +78,56 @@ class ColumnLoader(LoaderStrategy):
return False
else:
return True
- sessionlib.register_attribute(self.parent.class_, self.key, uselist=False, useobject=False, copy_function=copy, compare_function=compare, mutable_scalars=True, comparator=self.parent_property.comparator)
-
- def _init_scalar_attribute(self):
- self.logger.info("register managed attribute %s on class %s" % (self.key, self.parent.class_.__name__))
- coltype = self.columns[0].type
- sessionlib.register_attribute(self.parent.class_, self.key, uselist=False, useobject=False, copy_function=coltype.copy_value, compare_function=coltype.compare_values, mutable_scalars=self.columns[0].type.is_mutable(), comparator=self.parent_property.comparator)
-
- def create_row_processor(self, selectcontext, mapper, row):
- if self.is_composite:
- for c in self.columns:
- if c not in row:
- break
- else:
- def new_execute(instance, row, **flags):
- if self._should_log_debug:
- self.logger.debug("populating %s with %s/%s..." % (mapperutil.attribute_str(instance, self.key), row.__class__.__name__, self.columns[0].key))
- instance.__dict__[self.key] = self.parent_property.composite_class(*[row[c] for c in self.columns])
- if self._should_log_debug:
- self.logger.debug("Returning active composite column fetcher for %s %s" % (mapper, self.key))
- return (new_execute, None, None)
-
- elif self.columns[0] in row:
- def new_execute(instance, row, **flags):
+ sessionlib.register_attribute(self.parent.class_, self.key, uselist=False, useobject=False, copy_function=copy, compare_function=compare, mutable_scalars=True, comparator=self.parent_property.comparator, parententity=self.parent)
+
+ def create_row_processor(self, selectcontext, path, mapper, row, adapter):
+ key, columns, composite_class = self.key, self.columns, self.parent_property.composite_class
+ if adapter:
+ columns = [adapter.columns[c] for c in columns]
+ for c in columns:
+ if c not in row:
+ def new_execute(state, row, isnew, **flags):
+ if isnew:
+ state.expire_attributes([key])
if self._should_log_debug:
- self.logger.debug("populating %s with %s/%s" % (mapperutil.attribute_str(instance, self.key), row.__class__.__name__, self.columns[0].key))
- instance.__dict__[self.key] = row[self.columns[0]]
- if self._should_log_debug:
- self.logger.debug("Returning active column fetcher for %s %s" % (mapper, self.key))
- return (new_execute, None, None)
+ self.logger.debug("%s deferring load" % self)
+ return (new_execute, None)
else:
- def new_execute(instance, row, isnew, **flags):
- if isnew:
- instance._state.expire_attributes([self.key])
+ def new_execute(state, row, **flags):
+ state.dict[key] = composite_class(*[row[c] for c in columns])
+
if self._should_log_debug:
- self.logger.debug("Deferring load for %s %s" % (mapper, self.key))
- return (new_execute, None, None)
+ new_execute = self.debug_callable(new_execute, self.logger,
+ "%s returning active composite column fetcher" % self,
+ lambda state, row, **flags: "populating %s" % (mapperutil.state_attribute_str(state, key))
+ )
-ColumnLoader.logger = logging.class_logger(ColumnLoader)
+ return (new_execute, None)
+CompositeColumnLoader.logger = log.class_logger(CompositeColumnLoader)
+
class DeferredColumnLoader(LoaderStrategy):
"""Deferred column loader, a per-column or per-column-group lazy loader."""
- def create_row_processor(self, selectcontext, mapper, row):
- if self.columns[0] in row:
- return self.parent_property._get_strategy(ColumnLoader).create_row_processor(selectcontext, mapper, row)
+ def create_row_processor(self, selectcontext, path, mapper, row, adapter):
+ col = self.columns[0]
+ if adapter:
+ col = adapter.columns[col]
+ if col in row:
+ return self.parent_property._get_strategy(ColumnLoader).create_row_processor(selectcontext, path, mapper, row, adapter)
+
elif not self.is_class_level or len(selectcontext.options):
- def new_execute(instance, row, **flags):
- if self._should_log_debug:
- self.logger.debug("set deferred callable on %s" % mapperutil.attribute_str(instance, self.key))
- instance._state.set_callable(self.key, self.setup_loader(instance))
- return (new_execute, None, None)
+ def new_execute(state, row, **flags):
+ state.set_callable(self.key, self.setup_loader(state))
else:
- def new_execute(instance, row, **flags):
- if self._should_log_debug:
- self.logger.debug("set deferred callable on %s" % mapperutil.attribute_str(instance, self.key))
- instance._state.reset(self.key)
- return (new_execute, None, None)
+ def new_execute(state, row, **flags):
+ state.reset(self.key)
+
+ if self._should_log_debug:
+ new_execute = self.debug_callable(new_execute, self.logger, None,
+ lambda state, row, **flags: "set deferred callable on %s" % mapperutil.state_attribute_str(state, self.key)
+ )
+ return (new_execute, None)
def init(self):
super(DeferredColumnLoader, self).init()
@@ -115,25 +135,25 @@ class DeferredColumnLoader(LoaderStrategy):
raise NotImplementedError("Deferred loading for composite types not implemented yet")
self.columns = self.parent_property.columns
self.group = self.parent_property.group
- self._should_log_debug = logging.is_debug_enabled(self.logger)
+ self._should_log_debug = log.is_debug_enabled(self.logger)
def init_class_attribute(self):
self.is_class_level = True
- self.logger.info("register managed attribute %s on class %s" % (self.key, self.parent.class_.__name__))
- sessionlib.register_attribute(self.parent.class_, self.key, uselist=False, useobject=False, callable_=self.class_level_loader, copy_function=self.columns[0].type.copy_value, compare_function=self.columns[0].type.compare_values, mutable_scalars=self.columns[0].type.is_mutable(), comparator=self.parent_property.comparator)
+ self.logger.info("%s register managed attribute" % self)
+ sessionlib.register_attribute(self.parent.class_, self.key, uselist=False, useobject=False, callable_=self.class_level_loader, copy_function=self.columns[0].type.copy_value, compare_function=self.columns[0].type.compare_values, mutable_scalars=self.columns[0].type.is_mutable(), comparator=self.parent_property.comparator, parententity=self.parent)
- def setup_query(self, context, only_load_props=None, **kwargs):
+ def setup_query(self, context, entity, path, adapter, only_load_props=None, **kwargs):
if \
(self.group is not None and context.attributes.get(('undefer', self.group), False)) or \
(only_load_props and self.key in only_load_props):
- self.parent_property._get_strategy(ColumnLoader).setup_query(context, **kwargs)
+ self.parent_property._get_strategy(ColumnLoader).setup_query(context, entity, path, adapter, **kwargs)
- def class_level_loader(self, instance, props=None):
- if not mapper.has_mapper(instance):
+ def class_level_loader(self, state, props=None):
+ if not mapperutil._state_has_mapper(state):
return None
- localparent = mapper.object_mapper(instance)
+ localparent = mapper._state_mapper(state)
# adjust for the ColumnProperty associated with the instance
# not being our own ColumnProperty. This can occur when entity_name
@@ -141,38 +161,38 @@ class DeferredColumnLoader(LoaderStrategy):
# to the class.
prop = localparent.get_property(self.key)
if prop is not self.parent_property:
- return prop._get_strategy(DeferredColumnLoader).setup_loader(instance)
+ return prop._get_strategy(DeferredColumnLoader).setup_loader(state)
- return LoadDeferredColumns(instance, self.key, props)
+ return LoadDeferredColumns(state, self.key, props)
- def setup_loader(self, instance, props=None, create_statement=None):
- return LoadDeferredColumns(instance, self.key, props, optimizing_statement=create_statement)
+ def setup_loader(self, state, props=None, create_statement=None):
+ return LoadDeferredColumns(state, self.key, props)
-DeferredColumnLoader.logger = logging.class_logger(DeferredColumnLoader)
+DeferredColumnLoader.logger = log.class_logger(DeferredColumnLoader)
class LoadDeferredColumns(object):
- """callable, serializable loader object used by DeferredColumnLoader"""
+ """serializable loader object used by DeferredColumnLoader"""
- def __init__(self, instance, key, keys, optimizing_statement=None):
- self.instance = instance
+ def __init__(self, state, key, keys):
+ self.state = state
self.key = key
self.keys = keys
- self.optimizing_statement = optimizing_statement
def __getstate__(self):
- return {'instance':self.instance, 'key':self.key, 'keys':self.keys}
+ return {'state':self.state, 'key':self.key, 'keys':self.keys}
def __setstate__(self, state):
- self.instance = state['instance']
+ self.state = state['state']
self.key = state['key']
self.keys = state['keys']
- self.optimizing_statement = None
def __call__(self):
- if not mapper.has_identity(self.instance):
+ state = self.state
+
+ if not mapper._state_has_identity(state):
return None
-
- localparent = mapper.object_mapper(self.instance, raiseerror=False)
+
+ localparent = mapper._state_mapper(state)
prop = localparent.get_property(self.key)
strategy = prop._get_strategy(DeferredColumnLoader)
@@ -185,22 +205,18 @@ class LoadDeferredColumns(object):
toload = [self.key]
# narrow the keys down to just those which have no history
- group = [k for k in toload if k in self.instance._state.unmodified]
+ group = [k for k in toload if k in state.unmodified]
if strategy._should_log_debug:
- strategy.logger.debug("deferred load %s group %s" % (mapperutil.attribute_str(self.instance, self.key), group and ','.join(group) or 'None'))
+ strategy.logger.debug("deferred load %s group %s" % (mapperutil.state_attribute_str(state, self.key), group and ','.join(group) or 'None'))
- session = sessionlib.object_session(self.instance)
+ session = sessionlib._state_session(state)
if session is None:
- raise exceptions.UnboundExecutionError("Parent instance %s is not bound to a Session; deferred load operation of attribute '%s' cannot proceed" % (self.instance.__class__, self.key))
+ raise sa_exc.UnboundExecutionError("Parent instance %s is not bound to a Session; deferred load operation of attribute '%s' cannot proceed" % (mapperutil.state_str(state), self.key))
query = session.query(localparent)
- if not self.optimizing_statement:
- ident = self.instance._instance_key[1]
- query._get(None, ident=ident, only_load_props=group, refresh_instance=self.instance._state)
- else:
- statement, params = self.optimizing_statement(self.instance)
- query.from_statement(statement).params(params)._get(None, only_load_props=group, refresh_instance=self.instance._state)
+ ident = state.key[1]
+ query._get(None, ident=ident, only_load_props=group, refresh_instance=state)
return attributes.ATTR_WAS_SET
class DeferredOption(StrategizedOption):
@@ -223,55 +239,63 @@ class UndeferGroupOption(MapperOption):
class AbstractRelationLoader(LoaderStrategy):
def init(self):
super(AbstractRelationLoader, self).init()
- for attr in ['primaryjoin', 'secondaryjoin', 'secondary', 'foreign_keys', 'mapper', 'target', 'table', 'uselist', 'cascade', 'attributeext', 'order_by', 'remote_side', 'direction']:
+ for attr in ['mapper', 'target', 'table', 'uselist']:
setattr(self, attr, getattr(self.parent_property, attr))
- self._should_log_debug = logging.is_debug_enabled(self.logger)
+ self._should_log_debug = log.is_debug_enabled(self.logger)
- def _init_instance_attribute(self, instance, callable_=None):
+ def _init_instance_attribute(self, state, callable_=None):
if callable_:
- instance._state.set_callable(self.key, callable_)
+ state.set_callable(self.key, callable_)
else:
- instance._state.initialize(self.key)
+ state.initialize(self.key)
def _register_attribute(self, class_, callable_=None, **kwargs):
- self.logger.info("register managed %s attribute %s on class %s" % ((self.uselist and "list-holding" or "scalar"), self.key, self.parent.class_.__name__))
- sessionlib.register_attribute(class_, self.key, uselist=self.uselist, useobject=True, extension=self.attributeext, cascade=self.cascade, trackparent=True, typecallable=self.parent_property.collection_class, callable_=callable_, comparator=self.parent_property.comparator, **kwargs)
+ self.logger.info("%s register managed %s attribute" % (self, (self.uselist and "collection" or "scalar")))
+
+ if self.parent_property.backref:
+ attribute_ext = self.parent_property.backref.extension
+ else:
+ attribute_ext = None
+
+ sessionlib.register_attribute(class_, self.key, uselist=self.uselist, useobject=True, extension=attribute_ext, cascade=self.parent_property.cascade, trackparent=True, typecallable=self.parent_property.collection_class, callable_=callable_, comparator=self.parent_property.comparator, parententity=self.parent, **kwargs)
class NoLoader(AbstractRelationLoader):
def init_class_attribute(self):
self.is_class_level = True
self._register_attribute(self.parent.class_)
- def create_row_processor(self, selectcontext, mapper, row):
- def new_execute(instance, row, ispostselect, **flags):
- if not ispostselect:
- if self._should_log_debug:
- self.logger.debug("initializing blank scalar/collection on %s" % mapperutil.attribute_str(instance, self.key))
- self._init_instance_attribute(instance)
- return (new_execute, None, None)
+ def create_row_processor(self, selectcontext, path, mapper, row, adapter):
+ def new_execute(state, row, **flags):
+ self._init_instance_attribute(state)
+
+ if self._should_log_debug:
+ new_execute = self.debug_callable(new_execute, self.logger, None,
+ lambda state, row, **flags: "initializing blank scalar/collection on %s" % mapperutil.state_attribute_str(state, self.key)
+ )
+ return (new_execute, None)
-NoLoader.logger = logging.class_logger(NoLoader)
+NoLoader.logger = log.class_logger(NoLoader)
class LazyLoader(AbstractRelationLoader):
def init(self):
super(LazyLoader, self).init()
(self.__lazywhere, self.__bind_to_col, self._equated_columns) = self.__create_lazy_clause(self.parent_property)
- self.logger.info(str(self.parent_property) + " lazy loading clause " + str(self.__lazywhere))
+ self.logger.info("%s lazy loading clause %s" % (self, self.__lazywhere))
# determine if our "lazywhere" clause is the same as the mapper's
# get() clause. then we can just use mapper.get()
#from sqlalchemy.orm import query
self.use_get = not self.uselist and self.mapper._get_clause[0].compare(self.__lazywhere)
if self.use_get:
- self.logger.info(str(self.parent_property) + " will use query.get() to optimize instance loads")
+ self.logger.info("%s will use query.get() to optimize instance loads" % self)
def init_class_attribute(self):
self.is_class_level = True
self._register_attribute(self.parent.class_, callable_=self.class_level_loader)
- def lazy_clause(self, instance, reverse_direction=False):
- if instance is None:
+ def lazy_clause(self, state, reverse_direction=False):
+ if state is None:
return self._lazy_none_clause(reverse_direction)
if not reverse_direction:
@@ -285,8 +309,8 @@ class LazyLoader(AbstractRelationLoader):
# use the "committed" (database) version to get query column values
# also its a deferred value; so that when used by Query, the committed value is used
# after an autoflush occurs
- bindparam.value = lambda: mapper._get_committed_attr_by_column(instance, bind_to_col[bindparam.key])
- return visitors.traverse(criterion, clone=True, visit_bindparam=visit_bindparam)
+ bindparam.value = lambda: mapper._get_committed_state_attr_by_column(state, bind_to_col[bindparam.key])
+ return visitors.cloned_traverse(criterion, {}, {'bindparam':visit_bindparam})
def _lazy_none_clause(self, reverse_direction=False):
if not reverse_direction:
@@ -305,13 +329,13 @@ class LazyLoader(AbstractRelationLoader):
binary.right = expression.null()
binary.operator = operators.is_
- return visitors.traverse(criterion, clone=True, visit_binary=visit_binary)
+ return visitors.cloned_traverse(criterion, {}, {'binary':visit_binary})
- def class_level_loader(self, instance, options=None, path=None):
- if not mapper.has_mapper(instance):
+ def class_level_loader(self, state, options=None, path=None):
+ if not mapperutil._state_has_mapper(state):
return None
- localparent = mapper.object_mapper(instance)
+ localparent = mapper._state_mapper(state)
# adjust for the PropertyLoader associated with the instance
# not being our own PropertyLoader. This can occur when entity_name
@@ -319,35 +343,41 @@ class LazyLoader(AbstractRelationLoader):
# to the class.
prop = localparent.get_property(self.key)
if prop is not self.parent_property:
- return prop._get_strategy(LazyLoader).setup_loader(instance)
+ return prop._get_strategy(LazyLoader).setup_loader(state)
- return LoadLazyAttribute(instance, self.key, options, path)
+ return LoadLazyAttribute(state, self.key, options, path)
- def setup_loader(self, instance, options=None, path=None):
- return LoadLazyAttribute(instance, self.key, options, path)
+ def setup_loader(self, state, options=None, path=None):
+ return LoadLazyAttribute(state, self.key, options, path)
- def create_row_processor(self, selectcontext, mapper, row):
+ def create_row_processor(self, selectcontext, path, mapper, row, adapter):
if not self.is_class_level or len(selectcontext.options):
- def new_execute(instance, row, ispostselect, **flags):
- if not ispostselect:
- if self._should_log_debug:
- self.logger.debug("set instance-level lazy loader on %s" % mapperutil.attribute_str(instance, self.key))
- # we are not the primary manager for this attribute on this class - set up a per-instance lazyloader,
- # which will override the class-level behavior
-
- self._init_instance_attribute(instance, callable_=self.setup_loader(instance, selectcontext.options, selectcontext.query._current_path + selectcontext.path))
- return (new_execute, None, None)
+ path = path + (self.key,)
+ def new_execute(state, row, **flags):
+ # we are not the primary manager for this attribute on this class - set up a per-instance lazyloader,
+ # which will override the class-level behavior
+ self._init_instance_attribute(state, callable_=self.setup_loader(state, selectcontext.options, selectcontext.query._current_path + path))
+
+ if self._should_log_debug:
+ new_execute = self.debug_callable(new_execute, self.logger, None,
+ lambda state, row, **flags: "set instance-level lazy loader on %s" % mapperutil.state_attribute_str(state, self.key)
+ )
+
+ return (new_execute, None)
else:
- def new_execute(instance, row, ispostselect, **flags):
- if not ispostselect:
- if self._should_log_debug:
- self.logger.debug("set class-level lazy loader on %s" % mapperutil.attribute_str(instance, self.key))
- # we are the primary manager for this attribute on this class - reset its per-instance attribute state,
- # so that the class-level lazy loader is executed when next referenced on this instance.
- # this usually is not needed unless the constructor of the object referenced the attribute before we got
- # to load data into it.
- instance._state.reset(self.key)
- return (new_execute, None, None)
+ def new_execute(state, row, **flags):
+ # we are the primary manager for this attribute on this class - reset its per-instance attribute state,
+ # so that the class-level lazy loader is executed when next referenced on this instance.
+ # this usually is not needed unless the constructor of the object referenced the attribute before we got
+ # to load data into it.
+ state.reset(self.key)
+
+ if self._should_log_debug:
+ new_execute = self.debug_callable(new_execute, self.logger, None,
+ lambda state, row, **flags: "set class-level lazy loader on %s" % mapperutil.state_attribute_str(state, self.key)
+ )
+
+ return (new_execute, None)
def __create_lazy_clause(cls, prop, reverse_direction=False):
binds = {}
@@ -374,16 +404,16 @@ class LazyLoader(AbstractRelationLoader):
binds[col] = sql.bindparam(None, None, type_=col.type)
return binds[col]
return None
-
- lazywhere = prop.primaryjoin
+ lazywhere = prop.primaryjoin
+
if not prop.secondaryjoin or not reverse_direction:
- lazywhere = visitors.traverse(lazywhere, before_clone=col_to_bind, clone=True)
+ lazywhere = visitors.replacement_traverse(lazywhere, {}, col_to_bind)
if prop.secondaryjoin is not None:
secondaryjoin = prop.secondaryjoin
if reverse_direction:
- secondaryjoin = visitors.traverse(secondaryjoin, before_clone=col_to_bind, clone=True)
+ secondaryjoin = visitors.replacement_traverse(secondaryjoin, {}, col_to_bind)
lazywhere = sql.and_(lazywhere, secondaryjoin)
bind_to_col = dict([(binds[col].key, col) for col in binds])
@@ -391,47 +421,44 @@ class LazyLoader(AbstractRelationLoader):
return (lazywhere, bind_to_col, equated_columns)
__create_lazy_clause = classmethod(__create_lazy_clause)
-LazyLoader.logger = logging.class_logger(LazyLoader)
+LazyLoader.logger = log.class_logger(LazyLoader)
class LoadLazyAttribute(object):
- """callable, serializable loader object used by LazyLoader"""
+ """serializable loader object used by LazyLoader"""
- def __init__(self, instance, key, options, path):
- self.instance = instance
+ def __init__(self, state, key, options, path):
+ self.state = state
self.key = key
self.options = options
self.path = path
def __getstate__(self):
- return {'instance':self.instance, 'key':self.key, 'options':self.options, 'path':serialize_path(self.path)}
+ return {'state':self.state, 'key':self.key, 'options':self.options, 'path':serialize_path(self.path)}
def __setstate__(self, state):
- self.instance = state['instance']
+ self.state = state['state']
self.key = state['key']
- self.options= state['options']
+ self.options = state['options']
self.path = deserialize_path(state['path'])
def __call__(self):
- instance = self.instance
-
- if not mapper.has_identity(instance):
+ state = self.state
+ if not mapper._state_has_identity(state):
return None
- instance_mapper = mapper.object_mapper(instance)
+ instance_mapper = mapper._state_mapper(state)
prop = instance_mapper.get_property(self.key)
strategy = prop._get_strategy(LazyLoader)
if strategy._should_log_debug:
- strategy.logger.debug("lazy load attribute %s on instance %s" % (self.key, mapperutil.instance_str(instance)))
+ strategy.logger.debug("loading %s" % mapperutil.state_attribute_str(state, self.key))
- session = sessionlib.object_session(instance)
+ session = sessionlib._state_session(state)
if session is None:
- try:
- session = instance_mapper.get_session()
- except exceptions.InvalidRequestError:
- raise exceptions.UnboundExecutionError("Parent instance %s is not bound to a Session, and no contextual session is established; lazy load operation of attribute '%s' cannot proceed" % (instance.__class__, self.key))
+ raise sa_exc.UnboundExecutionError("Parent instance %s is not bound to a Session; lazy load operation of attribute '%s' cannot proceed" % (mapperutil.state_str(state), self.key))
- q = session.query(prop.mapper).autoflush(False)
+ q = session.query(prop.mapper).autoflush(False)._adapt_all_clauses()
+
if self.path:
q = q._with_current_path(self.path)
@@ -441,7 +468,7 @@ class LoadLazyAttribute(object):
ident = []
allnulls = True
for primary_key in prop.mapper.primary_key:
- val = instance_mapper._get_committed_attr_by_column(instance, strategy._equated_columns[primary_key])
+ val = instance_mapper._get_committed_state_attr_by_column(state, strategy._equated_columns[primary_key])
allnulls = allnulls and val is None
ident.append(val)
if allnulls:
@@ -450,14 +477,14 @@ class LoadLazyAttribute(object):
q = q._conditional_options(*self.options)
return q.get(ident)
- if strategy.order_by is not False:
- q = q.order_by(strategy.order_by)
- elif strategy.secondary is not None and strategy.secondary.default_order_by() is not None:
- q = q.order_by(strategy.secondary.default_order_by())
+ if prop.order_by is not False:
+ q = q.order_by(prop.order_by)
+ elif prop.secondary is not None and prop.secondary.default_order_by() is not None:
+ q = q.order_by(prop.secondary.default_order_by())
if self.options:
q = q._conditional_options(*self.options)
- q = q.filter(strategy.lazy_clause(instance))
+ q = q.filter(strategy.lazy_clause(state))
result = q.all()
if strategy.uselist:
@@ -478,19 +505,35 @@ class EagerLoader(AbstractRelationLoader):
self.join_depth = self.parent_property.join_depth
def init_class_attribute(self):
- # class-level eager strategy; add the PropertyLoader
- # to the parent's list of "eager loaders"; this tells the Query
- # that eager loaders will be used in a normal query
- self.parent._eager_loaders.add(self.parent_property)
-
- # initialize a lazy loader on the class level attribute
self.parent_property._get_strategy(LazyLoader).init_class_attribute()
- def setup_query(self, context, parentclauses=None, parentmapper=None, **kwargs):
+ def setup_query(self, context, entity, path, adapter, column_collection=None, parentmapper=None, **kwargs):
"""Add a left outer join to the statement thats being constructed."""
+
+ path = path + (self.key,)
+
+ # check for user-defined eager alias
+ if ("eager_row_processor", path) in context.attributes:
+ clauses = context.attributes[("eager_row_processor", path)]
+
+ adapter = entity._get_entity_clauses(context.query, context)
+ if adapter and clauses:
+ context.attributes[("eager_row_processor", path)] = clauses = adapter.wrap(clauses)
+ elif adapter:
+ context.attributes[("eager_row_processor", path)] = clauses = adapter
+
+ else:
- path = context.path
-
+ clauses = self.__create_eager_join(context, entity, path, adapter, parentmapper)
+ if not clauses:
+ return
+
+ context.attributes[("eager_row_processor", path)] = clauses
+
+ for value in self.mapper._iterate_polymorphic_properties():
+ value.setup(context, entity, path + (self.mapper.base_mapper,), clauses, parentmapper=self.mapper, column_collection=context.secondary_columns)
+
+ def __create_eager_join(self, context, entity, path, adapter, parentmapper):
# check for join_depth or basic recursion,
# if the current path was not explicitly stated as
# a desired "loaderstrategy" (i.e. via query.options())
@@ -502,159 +545,148 @@ class EagerLoader(AbstractRelationLoader):
if self.mapper.base_mapper in path:
return
- if ("eager_row_processor", path) in context.attributes:
- # if user defined eager_row_processor, that's contains_eager().
- # don't render LEFT OUTER JOIN, generate an AliasedClauses from
- # the decorator (this is a hack here, cleaned up in 0.5)
- cl = context.attributes[("eager_row_processor", path)]
- if cl:
- row = cl(None)
- class ActsLikeAliasedClauses(object):
- def aliased_column(self, col):
- return row.map[col]
- clauses = ActsLikeAliasedClauses()
- else:
- clauses = None
- else:
- clauses = self.__create_eager_join(context, path, parentclauses, parentmapper, **kwargs)
- if not clauses:
- return
-
- for value in self.mapper._iterate_polymorphic_properties():
- context.exec_with_path(self.mapper, value.key, value.setup, context, parentclauses=clauses, parentmapper=self.mapper)
-
- def __create_eager_join(self, context, path, parentclauses, parentmapper, **kwargs):
if parentmapper is None:
- localparent = context.mapper
+ localparent = entity.mapper
else:
localparent = parentmapper
-
- if context.eager_joins:
- towrap = context.eager_joins
+
+ # whether or not the Query will wrap the selectable in a subquery,
+ # and then attach eager load joins to that (i.e., in the case of LIMIT/OFFSET etc.)
+ should_nest_selectable = context.query._should_nest_selectable
+
+ if entity in context.eager_joins:
+ entity_key, default_towrap = entity, entity.selectable
+ elif should_nest_selectable or not context.from_clause or not sql_util.search(context.from_clause, entity.selectable):
+ # if no from_clause, or a from_clause we can't join to, or a subquery is going to be generated,
+ # store eager joins per _MappedEntity; Query._compile_context will
+ # add them as separate selectables to the select(), or splice them together
+ # after the subquery is generated
+ entity_key, default_towrap = entity, entity.selectable
else:
- towrap = context.from_clause
-
- # create AliasedClauses object to build up the eager query. this is cached after 1st creation.
+ # otherwise, create a single eager join from the from clause.
+ # Query._compile_context will adapt as needed and append to the
+ # FROM clause of the select().
+ entity_key, default_towrap = None, context.from_clause
+
+ towrap = context.eager_joins.setdefault(entity_key, default_towrap)
+
+ # create AliasedClauses object to build up the eager query. this is cached after 1st creation.
+ # this also allows ORMJoin to cache the aliased joins it produces since we pass the same
+ # args each time in the typical case.
+ path_key = util.WeakCompositeKey(*path)
try:
- clauses = self.clauses[path]
+ clauses = self.clauses[path_key]
except KeyError:
- clauses = mapperutil.PropertyAliasedClauses(self.parent_property, self.parent_property.primaryjoin, self.parent_property.secondaryjoin, parentclauses)
- self.clauses[path] = clauses
+ self.clauses[path_key] = clauses = mapperutil.ORMAdapter(mapperutil.AliasedClass(self.mapper),
+ equivalents=self.mapper._equivalent_columns,
+ chain_to=adapter)
- # place the "row_decorator" from the AliasedClauses into the QueryContext, where it will
- # be picked up in create_row_processor() when results are fetched
- context.attributes[("eager_row_processor", path)] = clauses.row_decorator
-
- if self.secondaryjoin is not None:
- context.eager_joins = sql.outerjoin(towrap, clauses.secondary, clauses.primaryjoin).outerjoin(clauses.alias, clauses.secondaryjoin)
-
- # TODO: check for "deferred" cols on parent/child tables here ? this would only be
- # useful if the primary/secondaryjoin are against non-PK columns on the tables (and therefore might be deferred)
-
- if self.order_by is False and self.secondary.default_order_by() is not None:
- context.eager_order_by += clauses.secondary.default_order_by()
+ if adapter:
+ if getattr(adapter, 'aliased_class', None):
+ onclause = getattr(adapter.aliased_class, self.key, self.parent_property)
+ else:
+ onclause = getattr(mapperutil.AliasedClass(self.parent, adapter.selectable), self.key, self.parent_property)
else:
- context.eager_joins = towrap.outerjoin(clauses.alias, clauses.primaryjoin)
- # ensure all the cols on the parent side are actually in the
+ onclause = self.parent_property
+
+ context.eager_joins[entity_key] = eagerjoin = mapperutil.outerjoin(towrap, clauses.aliased_class, onclause)
+
+ # send a hint to the Query as to where it may "splice" this join
+ eagerjoin.stop_on = entity.selectable
+
+ if not self.parent_property.secondary and context.query._should_nest_selectable and not parentmapper:
+ # for parentclause that is the non-eager end of the join,
+ # ensure all the parent cols in the primaryjoin are actually in the
# columns clause (i.e. are not deferred), so that aliasing applied by the Query propagates
# those columns outward. This has the effect of "undefering" those columns.
- for col in sql_util.find_columns(clauses.primaryjoin):
+ for col in sql_util.find_columns(self.parent_property.primaryjoin):
if localparent.mapped_table.c.contains_column(col):
+ if adapter:
+ col = adapter.columns[col]
context.primary_columns.append(col)
-
- if self.order_by is False and clauses.alias.default_order_by() is not None:
- context.eager_order_by += clauses.alias.default_order_by()
-
- if clauses.order_by:
- context.eager_order_by += util.to_list(clauses.order_by)
+ if self.parent_property.order_by is False:
+ if self.parent_property.secondaryjoin:
+ default_order_by = eagerjoin.left.right.default_order_by()
+ else:
+ default_order_by = eagerjoin.right.default_order_by()
+ if default_order_by:
+ context.eager_order_by += default_order_by
+ elif self.parent_property.order_by:
+ context.eager_order_by += eagerjoin._target_adapter.copy_and_process(util.to_list(self.parent_property.order_by))
+
return clauses
- def _create_row_decorator(self, selectcontext, row, path):
- """Create a *row decorating* function that will apply eager
- aliasing to the row.
-
- Also check that an identity key can be retrieved from the row,
- else return None.
- """
-
- #print "creating row decorator for path ", "->".join([str(s) for s in path])
-
- if ("eager_row_processor", path) in selectcontext.attributes:
- decorator = selectcontext.attributes[("eager_row_processor", path)]
- if decorator is None:
- decorator = lambda row: row
+ def __create_eager_adapter(self, context, row, adapter, path):
+ if ("eager_row_processor", path) in context.attributes:
+ decorator = context.attributes[("eager_row_processor", path)]
else:
if self._should_log_debug:
self.logger.debug("Could not locate aliased clauses for key: " + str(path))
- return None
+ return False
+ if adapter and decorator:
+ decorator = adapter.wrap(decorator)
+ elif adapter:
+ decorator = adapter
+
try:
- decorated_row = decorator(row)
- # check for identity key
- identity_key = self.mapper.identity_key_from_row(decorated_row)
- # and its good
+ identity_key = self.mapper.identity_key_from_row(row, decorator)
return decorator
except KeyError, k:
# no identity key - dont return a row processor, will cause a degrade to lazy
if self._should_log_debug:
- self.logger.debug("could not locate identity key from row '%s'; missing column '%s'" % (repr(decorated_row), str(k)))
- return None
-
- def create_row_processor(self, selectcontext, mapper, row):
+ self.logger.debug("could not locate identity key from row; missing column '%s'" % k)
+ return False
- row_decorator = self._create_row_decorator(selectcontext, row, selectcontext.path)
- pathstr = ','.join([str(x) for x in selectcontext.path])
- if row_decorator is not None:
- def execute(instance, row, isnew, **flags):
- decorated_row = row_decorator(row)
-
- if not self.uselist:
- if self._should_log_debug:
- self.logger.debug("eagerload scalar instance on %s" % mapperutil.attribute_str(instance, self.key))
+ def create_row_processor(self, context, path, mapper, row, adapter):
+ path = path + (self.key,)
+ eager_adapter = self.__create_eager_adapter(context, row, adapter, path)
+
+ if eager_adapter is not False:
+ key = self.key
+ _instance = self.mapper._instance_processor(context, path + (self.mapper.base_mapper,), eager_adapter)
+
+ if not self.uselist:
+ def execute(state, row, isnew, **flags):
if isnew:
# set a scalar object instance directly on the
# parent object, bypassing InstrumentedAttribute
# event handlers.
- #
- instance.__dict__[self.key] = self.mapper._instance(selectcontext, decorated_row, None)
+ state.dict[key] = _instance(row, None)
else:
# call _instance on the row, even though the object has been created,
# so that we further descend into properties
- self.mapper._instance(selectcontext, decorated_row, None)
- else:
- if isnew or self.key not in instance._state.appenders:
- # appender_key can be absent from selectcontext.attributes with isnew=False
+ _instance(row, None)
+ else:
+ def execute(state, row, isnew, **flags):
+ if isnew or (state, key) not in context.attributes:
+ # appender_key can be absent from context.attributes with isnew=False
# when self-referential eager loading is used; the same instance may be present
# in two distinct sets of result columns
-
- if self._should_log_debug:
- self.logger.debug("initialize UniqueAppender on %s" % mapperutil.attribute_str(instance, self.key))
- collection = attributes.init_collection(instance, self.key)
+ collection = attributes.init_collection(state, key)
appender = util.UniqueAppender(collection, 'append_without_event')
- instance._state.appenders[self.key] = appender
-
- result_list = instance._state.appenders[self.key]
- if self._should_log_debug:
- self.logger.debug("eagerload list instance on %s" % mapperutil.attribute_str(instance, self.key))
+ context.attributes[(state, key)] = appender
+
+ result_list = context.attributes[(state, key)]
- self.mapper._instance(selectcontext, decorated_row, result_list)
+ _instance(row, result_list)
if self._should_log_debug:
- self.logger.debug("Returning eager instance loader for %s" % str(self))
+ execute = self.debug_callable(execute, self.logger,
+ "%s returning eager instance loader" % self,
+ lambda state, row, isnew, **flags: "%s eagerload %s" % (self, self.uselist and "scalar attribute" or "collection")
+ )
- return (execute, execute, None)
+ return (execute, execute)
else:
if self._should_log_debug:
- self.logger.debug("eager loader %s degrading to lazy loader" % str(self))
- return self.parent_property._get_strategy(LazyLoader).create_row_processor(selectcontext, mapper, row)
+ self.logger.debug("%s degrading to lazy loader" % self)
+ return self.parent_property._get_strategy(LazyLoader).create_row_processor(context, path, mapper, row, adapter)
- def __str__(self):
- return str(self.parent) + "." + self.key
-
-EagerLoader.logger = logging.class_logger(EagerLoader)
+EagerLoader.logger = log.class_logger(EagerLoader)
class EagerLazyOption(StrategizedOption):
def __init__(self, key, lazy=True, chained=False, mapper=None):
@@ -665,20 +697,6 @@ class EagerLazyOption(StrategizedOption):
def is_chained(self):
return not self.lazy and self.chained
- def process_query_property(self, query, paths):
- if self.lazy:
- if paths[-1] in query._eager_loaders:
- query._eager_loaders = query._eager_loaders.difference(util.Set([paths[-1]]))
- else:
- if not self.chained:
- paths = [paths[-1]]
- res = util.Set()
- for path in paths:
- if len(path) - len(query._current_path) == 2:
- res.add(path)
- query._eager_loaders = query._eager_loaders.union(res)
- super(EagerLazyOption, self).process_query_property(query, paths)
-
def get_strategy_class(self):
if self.lazy:
return LazyLoader
@@ -687,24 +705,26 @@ class EagerLazyOption(StrategizedOption):
elif self.lazy is None:
return NoLoader
-EagerLazyOption.logger = logging.class_logger(EagerLazyOption)
-
-class RowDecorateOption(PropertyOption):
- def __init__(self, key, decorator=None, alias=None):
- super(RowDecorateOption, self).__init__(key)
- self.decorator = decorator
+class LoadEagerFromAliasOption(PropertyOption):
+ def __init__(self, key, alias=None):
+ super(LoadEagerFromAliasOption, self).__init__(key)
+ if alias:
+ if not isinstance(alias, basestring):
+ m, alias, is_aliased_class = mapperutil._entity_info(alias)
self.alias = alias
def process_query_property(self, query, paths):
- if self.alias is not None and self.decorator is None:
- (mapper, propname) = paths[-1][-2:]
-
- prop = mapper.get_property(propname, resolve_synonyms=True)
+ if self.alias:
if isinstance(self.alias, basestring):
- self.alias = prop.target.alias(self.alias)
+ (mapper, propname) = paths[-1][-2:]
- self.decorator = mapperutil.create_row_adapter(self.alias)
- query._attributes[("eager_row_processor", paths[-1])] = self.decorator
+ prop = mapper.get_property(propname, resolve_synonyms=True)
+ self.alias = prop.target.alias(self.alias)
+ if not isinstance(self.alias, expression.Alias):
+ import pdb
+ pdb.set_trace()
+ query._attributes[("eager_row_processor", paths[-1])] = sql_util.ColumnAdapter(self.alias)
+ else:
+ query._attributes[("eager_row_processor", paths[-1])] = None
-RowDecorateOption.logger = logging.class_logger(RowDecorateOption)