]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- speed enhancements to ORM object instantiation, eager loading of rows
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 28 Dec 2006 08:30:43 +0000 (08:30 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 28 Dec 2006 08:30:43 +0000 (08:30 +0000)
CHANGES
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/strategies.py

diff --git a/CHANGES b/CHANGES
index bbb69fcd45dc40010d78e5edc78534498a8bcdb5..57a763b99a4e0a81ca761b6ca2ab774bc8a9973a 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -7,6 +7,7 @@ thanks to jason (at) ncsmags.com [ticket:402]
 is constructed with individual calls to append_column(); this fixes an ORM
 bug whereby nested select statements were not getting correlated with the 
 main select generated by the Query object.
+- speed enhancements to ORM object instantiation, eager loading of rows
 
 0.3.3
 - string-based FROM clauses fixed, i.e. select(..., from_obj=["sometext"])
index 17f90961307ae7133b8bbc5ae52bf467d85c2654..2b51621dbe036563389388e23ad3bd0cf3de76b3 100644 (file)
@@ -758,11 +758,9 @@ class AttributeManager(object):
         #print self, "register attribute", key, "for class", class_
         if not hasattr(class_, '_state'):
             def _get_state(self):
-                try:
-                    return self.__sa_attr_state
-                except AttributeError:
-                    self.__sa_attr_state = {}
-                    return self.__sa_attr_state
+                if not hasattr(self, '_sa_attr_state'):
+                    self._sa_attr_state = {}
+                return self._sa_attr_state
             class_._state = property(_get_state)
         
         typecallable = kwargs.pop('typecallable', None)
index 0f607ebeecb8a5f348878f0c0b90906369be264a..74784d384668c7a4e512892a0ff00bc33187bcff 100644 (file)
@@ -61,11 +61,18 @@ class StrategizedProperty(MapperProperty):
     There is a single default strategy selected, and alternate strategies can be selected
     at selection time through the usage of StrategizedOption objects."""
     def _get_context_strategy(self, context):
-        return self._get_strategy(context.attributes.get((LoaderStrategy, self), self.strategy.__class__))
+        try:
+            return context.attributes[id(self)]
+        except KeyError:
+            # cache the located strategy per StrategizedProperty in the given context for faster re-lookup
+            ctx_strategy = self._get_strategy(context.attributes.get((LoaderStrategy, self), self.strategy.__class__))
+            context.attributes[id(self)] = ctx_strategy
+            return ctx_strategy
     def _get_strategy(self, cls):
         try:
             return self._all_strategies[cls]
         except KeyError:
+            # cache the located strategy per class for faster re-lookup
             strategy = cls(self)
             strategy.init()
             strategy.is_default = False
index 46ef5f60fa3ffdeba344094d4823c8a45d00960f..7de513508378170032d28ecdd6f0cdad19270e47 100644 (file)
@@ -1352,25 +1352,44 @@ class MapperExtension(object):
 class _ExtensionCarrier(MapperExtension):
     def __init__(self):
         self.__elements = []
-        self.__callables = {}
     def insert(self, extension):
         """insert a MapperExtension at the beginning of this ExtensionCarrier's list."""
         self.__elements.insert(0, extension)
     def append(self, extension):
         """append a MapperExtension at the end of this ExtensionCarrier's list."""
         self.__elements.append(extension)
-    def __getattribute__(self, key):
-        if key in MapperExtension.__dict__:
-            try:
-                return self.__callables[key]
-            except KeyError:
-                return self.__callables.setdefault(key, lambda *args, **kwargs:self._do(key, *args, **kwargs))
-        else:
-            return super(_ExtensionCarrier, self).__getattribute__(key)
+    def get_session(self, *args, **kwargs):
+        return self._do('get_session', *args, **kwargs)
+    def load(self, *args, **kwargs):
+        return self._do('load', *args, **kwargs)
+    def get(self, *args, **kwargs):
+        return self._do('get', *args, **kwargs)
+    def get_by(self, *args, **kwargs):
+        return self._do('get_by', *args, **kwargs)
+    def select_by(self, *args, **kwargs):
+        return self._do('select_by', *args, **kwargs)
+    def select(self, *args, **kwargs):
+        return self._do('select', *args, **kwargs)
+    def create_instance(self, *args, **kwargs):
+        return self._do('create_instance', *args, **kwargs)
+    def append_result(self, *args, **kwargs):
+        return self._do('append_result', *args, **kwargs)
+    def populate_instance(self, *args, **kwargs):
+        return self._do('populate_instance', *args, **kwargs)
+    def before_insert(self, *args, **kwargs):
+        return self._do('before_insert', *args, **kwargs)
+    def before_update(self, *args, **kwargs):
+        return self._do('before_update', *args, **kwargs)
+    def after_update(self, *args, **kwargs):
+        return self._do('after_update', *args, **kwargs)
+    def after_insert(self, *args, **kwargs):
+        return self._do('after_insert', *args, **kwargs)
+    def before_delete(self, *args, **kwargs):
+        return self._do('before_delete', *args, **kwargs)
+    def after_delete(self, *args, **kwargs):
+        return self._do('after_delete', *args, **kwargs)
     def _do(self, funcname, *args, **kwargs):
         for elem in self.__elements:
-            if elem is self:
-                raise exceptions.AssertionError("ExtensionCarrier set to itself")
             ret = getattr(elem, funcname)(*args, **kwargs)
             if ret is not EXT_PASS:
                 return ret
index 78a873c704b66d7db29cdcd55ebc74f68c0211d6..af56bda80c6a332b0e638e018948b77019d1ab1e 100644 (file)
@@ -463,31 +463,55 @@ class EagerLoader(AbstractRelationLoader):
         for value in self.mapper.props.values():
             value.setup(context, eagertable=clauses.eagertarget, parentclauses=clauses, parentmapper=self.mapper)
 
+    def _create_row_processor(self, selectcontext, row):
+        """create a 'row processing' function that will apply eager aliasing to the row.
+        
+        also check that an identity key can be retrieved from the row, else return None."""
+        # check for a user-defined decorator in the SelectContext (which was set up by the contains_eager() option)
+        if selectcontext.attributes.has_key((EagerLoader, self.parent_property)):
+            # custom row decoration function, placed in the selectcontext by the 
+            # contains_eager() mapper option
+            decorator = selectcontext.attributes[(EagerLoader, self.parent_property)]
+            if decorator is None:
+                decorator = lambda row: row
+        else:
+            try:
+                # decorate the row according to the stored AliasedClauses for this eager load
+                clauses = self.clauses_by_lead_mapper[selectcontext.mapper]
+                decorator = clauses._row_decorator
+            except KeyError:
+                # no stored AliasedClauses: eager loading was not set up in the query and
+                # AliasedClauses never got initialized
+                return None
+
+        try:
+            decorated_row = decorator(row)
+            # check for identity key
+            identity_key = self.mapper.identity_key_from_row(decorated_row)
+            # and its good
+            return decorator
+        except KeyError:
+            # no identity key - dont return a row processor, will cause a degrade to lazy
+            return None
+
     def process_row(self, selectcontext, instance, row, identitykey, isnew):
         """receive a row.  tell our mapper to look for a new object instance in the row, and attach
         it to a list on the parent instance."""
-
         if self in selectcontext.recursion_stack:
             return
         
         try:
-            # decorate the row according to the stored AliasedClauses for this eager load,
-            # or look for a user-defined decorator in the SelectContext (which was set up by the contains_eager() option)
-            if selectcontext.attributes.has_key((EagerLoader, self.parent_property)):
-                # custom row decoration function, placed in the selectcontext by the 
-                # contains_eager() mapper option
-                decorator = selectcontext.attributes[(EagerLoader, self.parent_property)]
-                if decorator is None:
-                    decorated_row = row
-                else:
-                    decorated_row = decorator(row)
-            else:
-                clauses = self.clauses_by_lead_mapper[selectcontext.mapper]
-                decorated_row = clauses._decorate_row(row)
-            # check for identity key
-            identity_key = self.mapper.identity_key_from_row(decorated_row)
+            # check for row processor
+            row_processor = selectcontext.attributes[id(self)]
         except KeyError:
-            # else degrade to a lazy loader
+            # create a row processor function and cache it in the context
+            row_processor = self._create_row_processor(selectcontext, row)
+            selectcontext.attributes[id(self)] = row_processor
+            
+        if row_processor is not None:
+            decorated_row = row_processor(row)
+        else:
+            # row_processor was None: degrade to a lazy loader
             if self._should_log_debug:
                 self.logger.debug("degrade to lazy loader on %s" % mapperutil.attribute_str(instance, self.key))
             self.parent_property._get_strategy(LazyLoader).process_row(selectcontext, instance, row, identitykey, isnew)