summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2009-05-02 17:41:04 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2009-05-02 17:41:04 +0000
commitf8daff6da185188d8ed807a7f250320e291ad523 (patch)
tree229f0f9b496c2c6e9ff651761240a358ae03bef9
parentf554d0f02b3ad4001157a9f30a867f8682f96adb (diff)
downloadsqlalchemy-f8daff6da185188d8ed807a7f250320e291ad523.tar.gz
- MapperOptions and other state associated with query.options()
is no longer bundled within callables associated with each lazy/deferred-loading attribute during a load. The options are now associated with the instance's state object just once when it's populated. This removes the need in most cases for per-instance/attribute loader objects, improving load speed and memory overhead for individual instances. [ticket:1391]
-rw-r--r--CHANGES9
-rw-r--r--lib/sqlalchemy/orm/attributes.py6
-rw-r--r--lib/sqlalchemy/orm/mapper.py6
-rw-r--r--lib/sqlalchemy/orm/query.py2
-rw-r--r--lib/sqlalchemy/orm/strategies.py99
5 files changed, 61 insertions, 61 deletions
diff --git a/CHANGES b/CHANGES
index c3dc89916..e950ca0a1 100644
--- a/CHANGES
+++ b/CHANGES
@@ -32,6 +32,15 @@ CHANGES
parameters but this would require further development.
[ticket:1357]
+ - MapperOptions and other state associated with query.options()
+ is no longer bundled within callables associated with each
+ lazy/deferred-loading attribute during a load.
+ The options are now associated with the instance's
+ state object just once when it's populated. This removes
+ the need in most cases for per-instance/attribute loader
+ objects, improving load speed and memory overhead for
+ individual instances. [ticket:1391]
+
- Fixed another location where autoflush was interfering
with session.merge(). autoflush is disabled completely
for the duration of merge() now. [ticket:1360]
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index b39ef990e..68aa0d93a 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -828,6 +828,8 @@ class InstanceState(object):
key = None
runid = None
expired_attributes = EMPTY_SET
+ load_options = EMPTY_SET
+ load_path = ()
insert_order = None
def __init__(self, obj, manager):
@@ -937,6 +939,8 @@ class InstanceState(object):
'parents': self.parents,
'modified': self.modified,
'expired':self.expired,
+ 'load_options':self.load_options,
+ 'load_path':interfaces.serialize_path(self.load_path),
'instance': self.obj(),
'expired_attributes':self.expired_attributes,
'callables': self.callables}
@@ -949,6 +953,8 @@ class InstanceState(object):
self.pending = state['pending']
self.modified = state['modified']
self.obj = weakref.ref(state['instance'])
+ self.load_options = state['load_options'] or EMPTY_SET
+ self.load_path = interfaces.deserialize_path(state['load_path'])
self.class_ = self.obj().__class__
self.manager = manager_of_class(self.class_)
self.dict = self.obj().__dict__
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index 080540726..e5dbb4d03 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -1515,6 +1515,12 @@ class Mapper(object):
existing_populators = []
def populate_state(state, row, isnew, only_load_props, **flags):
+ if isnew:
+ if context.options:
+ state.load_options = context.options
+ if state.load_options:
+ state.load_path = context.query._current_path + path
+
if not new_populators:
new_populators[:], existing_populators[:] = self._populators(context, path, row, adapter)
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index e8169faa2..28ddcc5ea 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -2115,7 +2115,7 @@ class QueryContext(object):
self.froms = []
self.adapter = None
- self.options = query._with_options
+ self.options = set(query._with_options)
self.attributes = query._attributes.copy()
class AliasOption(interfaces.MapperOption):
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index dc64b283d..1aeb311e1 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -201,11 +201,13 @@ class DeferredColumnLoader(LoaderStrategy):
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):
+ elif not self.is_class_level:
def new_execute(state, row, **flags):
- state.set_callable(self.key, self.setup_loader(state))
+ state.set_callable(self.key, LoadDeferredColumns(state, self.key))
else:
def new_execute(state, row, **flags):
+ # reset state on the key so that deferred callables
+ # fire off on next access.
state.reset(self.key)
if self._should_log_debug:
@@ -227,7 +229,7 @@ class DeferredColumnLoader(LoaderStrategy):
compare_function=self.columns[0].type.compare_values,
copy_function=self.columns[0].type.copy_value,
mutable_scalars=self.columns[0].type.is_mutable(),
- callable_=self.class_level_loader,
+ callable_=self._class_level_loader,
dont_expire_missing=True
)
@@ -238,46 +240,31 @@ class DeferredColumnLoader(LoaderStrategy):
self.parent_property._get_strategy(ColumnLoader).setup_query(context, entity, path, adapter, **kwargs)
- def class_level_loader(self, state, props=None):
+ def _class_level_loader(self, state):
if not mapperutil._state_has_identity(state):
return None
- localparent = mapper._state_mapper(state)
-
- # adjust for the ColumnProperty associated with the instance
- # not being our own ColumnProperty.
- # TODO: this may no longer be relevant without entity_name.
- prop = localparent.get_property(self.key)
- if prop is not self.parent_property:
- return prop._get_strategy(DeferredColumnLoader).setup_loader(state)
-
- return LoadDeferredColumns(state, self.key, props)
+ return LoadDeferredColumns(state, self.key)
- def setup_loader(self, state, props=None, create_statement=None):
- return LoadDeferredColumns(state, self.key, props)
log.class_logger(DeferredColumnLoader)
class LoadDeferredColumns(object):
"""serializable loader object used by DeferredColumnLoader"""
- def __init__(self, *args):
- self.state, self.key, self.keys = args
+ def __init__(self, state, key):
+ self.state, self.key = state, key
def __call__(self):
state = self.state
- if not mapper._state_has_identity(state):
- return None
localparent = mapper._state_mapper(state)
prop = localparent.get_property(self.key)
strategy = prop._get_strategy(DeferredColumnLoader)
- if self.keys:
- toload = self.keys
- elif strategy.group:
+ if strategy.group:
toload = [
p.key for p in
localparent.iterate_properties
@@ -292,11 +279,18 @@ class LoadDeferredColumns(object):
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.state_attribute_str(state, 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._state_session(state)
if session is None:
- 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))
+ 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)
ident = state.key[1]
@@ -380,7 +374,7 @@ class LazyLoader(AbstractRelationLoader):
_register_attribute(self,
mapper,
useobject=True,
- callable_=self.class_level_loader,
+ callable_=self._class_level_loader,
uselist = self.parent_property.uselist,
typecallable = self.parent_property.collection_class,
)
@@ -435,31 +429,20 @@ class LazyLoader(AbstractRelationLoader):
criterion = adapt_source(criterion)
return criterion
- def class_level_loader(self, state, options=None, path=None):
+ def _class_level_loader(self, state):
if not mapperutil._state_has_identity(state):
return None
- localparent = mapper._state_mapper(state)
-
- # adjust for the PropertyLoader associated with the instance
- # not being our own PropertyLoader.
- # TODO: this may no longer be relevant without entity_name
- prop = localparent.get_property(self.key)
- if prop is not self.parent_property:
- return prop._get_strategy(LazyLoader).setup_loader(state)
-
- return LoadLazyAttribute(state, self.key, options, path)
-
- def setup_loader(self, state, options=None, path=None):
- return LoadLazyAttribute(state, self.key, options, path)
+ return LoadLazyAttribute(state, self.key)
def create_row_processor(self, selectcontext, path, mapper, row, adapter):
- if not self.is_class_level or len(selectcontext.options):
- path = path + (self.key,)
+ if not self.is_class_level:
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))
+ # which will override the class-level behavior.
+ # this currently only happens when using a "lazyload" option on a "no load" attribute -
+ # "eager" attributes always have a class-level lazyloader installed.
+ self._init_instance_attribute(state, callable_=LoadLazyAttribute(state, self.key))
if self._should_log_debug:
new_execute = self.debug_callable(new_execute, self.logger, None,
@@ -471,8 +454,7 @@ class LazyLoader(AbstractRelationLoader):
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.
+ # this is needed in populate_existing() types of scenarios to reset any existing state.
state.reset(self.key)
if self._should_log_debug:
@@ -481,7 +463,7 @@ class LazyLoader(AbstractRelationLoader):
)
return (new_execute, None)
-
+
def _create_lazy_clause(cls, prop, reverse_direction=False):
binds = util.column_dict()
lookup = util.column_dict()
@@ -529,20 +511,17 @@ log.class_logger(LazyLoader)
class LoadLazyAttribute(object):
"""serializable loader object used by LazyLoader"""
- def __init__(self, *args):
- self.state, self.key, self.options, self.path = args
+ def __init__(self, state, key):
+ self.state, self.key = state, key
def __getstate__(self):
- return (self.state, self.key, self.options, serialize_path(self.path))
+ return (self.state, self.key)
def __setstate__(self, state):
- self.state, self.key, self.options, path = state
- self.path = deserialize_path(path)
+ self.state, self.key = state
def __call__(self):
state = self.state
- if not mapper._state_has_identity(state):
- return None
instance_mapper = mapper._state_mapper(state)
prop = instance_mapper.get_property(self.key)
@@ -561,8 +540,8 @@ class LoadLazyAttribute(object):
q = session.query(prop.mapper)._adapt_all_clauses()
- if self.path:
- q = q._with_current_path(self.path)
+ if state.load_path:
+ q = q._with_current_path(state.load_path + (self.key,))
# if we have a simple primary key load, use mapper.get()
# to possibly save a DB round trip
@@ -575,15 +554,15 @@ class LoadLazyAttribute(object):
ident.append(val)
if allnulls:
return None
- if self.options:
- q = q._conditional_options(*self.options)
+ if state.load_options:
+ q = q._conditional_options(*state.load_options)
return q.get(ident)
if prop.order_by:
q = q.order_by(*util.to_list(prop.order_by))
- if self.options:
- q = q._conditional_options(*self.options)
+ if state.load_options:
+ q = q._conditional_options(*state.load_options)
q = q.filter(strategy.lazy_clause(state))
result = q.all()