]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
added has_key to RowProxy, + caching of key lookups
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 27 May 2006 00:13:12 +0000 (00:13 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 27 May 2006 00:13:12 +0000 (00:13 +0000)
fix for mapper translate_row for deferred columns
continuing with the "polymorph-tizing" of the unit of work, dependency processing accesses objects on each target task polymorphically

lib/sqlalchemy/engine/base.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/properties.py
lib/sqlalchemy/orm/unitofwork.py
test/inheritance.py
test/select.py

index bf7b1c20ddfb57d0e09548dddf08ba340a11a554..83dfad04fca3d485f9a2c62133205656137a9120 100644 (file)
@@ -506,6 +506,7 @@ class ResultProxy:
         self.closed = False
         self.executioncontext = executioncontext
         self.echo = engine.echo=="debug"
+        self.__key_cache = {}
         if executioncontext:
             self.rowcount = executioncontext.get_rowcount(cursor)
         else:
@@ -534,19 +535,36 @@ class ResultProxy:
             self.closed = True
             if self.connection.should_close_with_result and self.dialect.supports_autoclose_results:
                 self.connection.close()
-    def _get_col(self, row, key):
-        if isinstance(key, sql.ColumnElement):
-            try:
-                rec = self.props[key._label.lower()]
-            except KeyError:
+    
+    def _convert_key(self, key):
+        """given a key, which could be a ColumnElement, string, etc., matches it to the 
+        appropriate key we got from the result set's metadata; then cache it locally for quick re-access."""
+        try:
+            return self.__key_cache[key]
+        except KeyError:
+            if isinstance(key, sql.ColumnElement):
                 try:
-                    rec = self.props[key.key.lower()]
+                    rec = self.props[key._label.lower()]
                 except KeyError:
-                    rec = self.props[key.name.lower()]
-        elif isinstance(key, str):
-            rec = self.props[key.lower()]
-        else:
-            rec = self.props[key]
+                    try:
+                        rec = self.props[key.key.lower()]
+                    except KeyError:
+                        rec = self.props[key.name.lower()]
+            elif isinstance(key, str):
+                rec = self.props[key.lower()]
+            else:
+                rec = self.props[key]
+            self.__key_cache[key] = rec
+            return rec
+    def _has_key(self, row, key):
+        try:
+            self._convert_key(key)
+            return True
+        except KeyError:
+            return False
+        
+    def _get_col(self, row, key):
+        rec = self._convert_key(key)
         return rec[0].dialect_impl(self.dialect).convert_result_value(row[rec[1]], self.dialect)
     
     def __iter__(self):
@@ -605,6 +623,8 @@ class RowProxy:
         return (other is self) or (other == tuple([self.__parent._get_col(self.__row, key) for key in range(0, len(self.__row))]))
     def __repr__(self):
         return repr(tuple([self.__parent._get_col(self.__row, key) for key in range(0, len(self.__row))]))
+    def has_key(self, key):
+        return self.__parent._has_key(self.__row, key)
     def __getitem__(self, key):
         return self.__parent._get_col(self.__row, key)
     def __getattr__(self, name):
index 4f1309fdfbb30043b1d938a2d9ca44cf04018032..9b636f244e54f5f2b2b233f756367a5a47cd0fd6 100644 (file)
@@ -822,7 +822,8 @@ class Mapper(object):
         newrow = util.DictDecorator(row)
         for c in tomapper.mapped_table.c:
             c2 = self.mapped_table.corresponding_column(c, keys_ok=True, raiseerr=True)
-            newrow[c] = row[c2]
+            if row.has_key(c2):
+                newrow[c] = row[c2]
         return newrow
         
     def populate_instance(self, session, instance, row, identitykey, imap, isnew, frommapper=None):
index 7b15aa77343e3e49024ec4cb6a35001d2acf032f..00e8e2a67eb37abf539403bfb1845c81c16126c5 100644 (file)
@@ -581,6 +581,8 @@ class EagerLoader(LazyLoader):
         class DecoratorDict(object):
             def __init__(self, row):
                 self.row = row
+            def has_key(self, key):
+                return map.has_key(key) or self.row.has_key(key)
             def __getitem__(self, key):
                 if map.has_key(key):
                     key = map[key]
index c6c4055f84adb2c68bfae78f405930ba69d8fbca..710326473986152a6cc9f00fe95cf8a92d30ba46 100644 (file)
@@ -506,12 +506,12 @@ class UOWDependencyProcessor(object):
             return elem.obj
             
         ret = False
-        elements = [getobj(elem) for elem in self.targettask.tosave_elements if elem.obj is not None and not elem.is_preprocessed(self)]
+        elements = [getobj(elem) for elem in self.targettask.polymorphic_tosave_elements if elem.obj is not None and not elem.is_preprocessed(self)]
         if len(elements):
             ret = True
             self.processor.preprocess_dependencies(self.targettask, elements, trans, delete=False)
 
-        elements = [getobj(elem) for elem in self.targettask.todelete_elements if elem.obj is not None and not elem.is_preprocessed(self)]
+        elements = [getobj(elem) for elem in self.targettask.polymorphic_todelete_elements if elem.obj is not None and not elem.is_preprocessed(self)]
         if len(elements):
             ret = True
             self.processor.preprocess_dependencies(self.targettask, elements, trans, delete=True)
@@ -519,9 +519,9 @@ class UOWDependencyProcessor(object):
         
     def execute(self, trans, delete):
         if not delete:
-            self.processor.process_dependencies(self.targettask, [elem.obj for elem in self.targettask.tosave_elements if elem.obj is not None], trans, delete=False)
+            self.processor.process_dependencies(self.targettask, [elem.obj for elem in self.targettask.polymorphic_tosave_elements if elem.obj is not None], trans, delete=False)
         else:            
-            self.processor.process_dependencies(self.targettask, [elem.obj for elem in self.targettask.todelete_elements if elem.obj is not None], trans, delete=True)
+            self.processor.process_dependencies(self.targettask, [elem.obj for elem in self.targettask.polymorphic_todelete_elements if elem.obj is not None], trans, delete=True)
 
     def get_object_dependencies(self, obj, trans, passive):
         return self.processor.get_object_dependencies(obj, trans, passive=passive)
@@ -533,6 +533,7 @@ class UOWDependencyProcessor(object):
         return UOWDependencyProcessor(self.processor, task)
 
 class UOWTask(object):
+    """represents the full list of objects that are to be saved/deleted by a specific Mapper."""
     def __init__(self, uowtransaction, mapper):
         if uowtransaction is not None:
             uowtransaction.tasks[mapper] = self
@@ -603,6 +604,22 @@ class UOWTask(object):
             child.execute(trans)
         for task in self.inheriting_tasks:
             task._execute_childtasks(trans)
+    def _execute_cyclical_dependencies(self, trans, isdelete):
+        for dep in self.cyclical_dependencies:
+            dep.execute(trans, isdelete)
+        for task in self.inheriting_tasks:
+            task._execute_cyclical_dependencies(trans, isdelete)
+    def _execute_per_element_childtasks(self, trans, isdelete):
+        if isdelete:
+            for element in self.todelete_elements:
+                for task in element.childtasks:
+                    task.execute(trans)
+        else:
+            for element in self.tosave_elements:
+                for task in element.childtasks:
+                    task.execute(trans)
+        for task in self.inheriting_tasks:
+            task._execute_per_element_childtasks(trans, isdelete)
             
     def execute(self, trans):
         """executes this UOWTask.  saves objects to be saved, processes all dependencies
@@ -615,25 +632,28 @@ class UOWTask(object):
             self.circular.execute(trans)
             return
 
-        # TODO: apply the same recursive inheritance logic to the cyclical tasks/dependencies
         # TODO: add a visitation system to the UOW classes and have this execution called
         # from a separate executor object ? (would also handle dumping)
         
         self._save_objects(trans)
-        for dep in self.cyclical_dependencies:
-            dep.execute(trans, False)
-        for element in self.tosave_elements:
-            for task in element.childtasks:
-                task.execute(trans)
+        self._execute_cyclical_dependencies(trans, False)
+        self._execute_per_element_childtasks(trans, False)
         self._execute_dependencies(trans)
-        for dep in self.cyclical_dependencies:
-            dep.execute(trans, True)
+        self._execute_cyclical_dependencies(trans, True)
         self._execute_childtasks(trans)
-        for element in self.todelete_elements:
-            for task in element.childtasks:
-                task.execute(trans)
+        self._execute_per_element_childtasks(trans, True)
         self._delete_objects(trans)
 
+    def _polymorphic_elements(self):
+        for rec in self.objects.values():
+            yield rec
+            for task in self.inheriting_tasks:
+                for rec in task._polymorphic_elements():
+                    yield rec
+    
+    polymorphic_tosave_elements = property(lambda self: [rec for rec in self._polymorphic_elements() if not rec.isdelete])
+    polymorphic_todelete_elements = property(lambda self: [rec for rec in self._polymorphic_elements() if rec.isdelete])
+    
     tosave_elements = property(lambda self: [rec for rec in self.objects.values() if not rec.isdelete])
     todelete_elements = property(lambda self:[rec for rec in self.objects.values() if rec.isdelete])
     tosave_objects = property(lambda self:[rec.obj for rec in self.objects.values() if rec.obj is not None and not rec.listonly and rec.isdelete is False])
@@ -808,9 +828,9 @@ class UOWTask(object):
             
         def _dump_processor(proc, deletes):
             if deletes:
-                val = [t for t in proc.targettask.objects.values() if t.isdelete]
+                val = proc.targettask.polymorphic_todelete_elements
             else:
-                val = [t for t in proc.targettask.objects.values() if not t.isdelete]
+                val = proc.targettask.polymorphic_tosave_elements
 
             buf.write(_indent() + "  |- %s attribute on %s (UOWDependencyProcessor(%d) processing %s)\n" % (
                 repr(proc.processor.key), 
index 265134173f374600f33f35aeda0f8fa9eab14971..0957638676bcdbbf0322ae29637bf2a5bfca68e0 100644 (file)
@@ -350,7 +350,8 @@ class InheritTest5(testbase.AssertMixin):
         #contents.add_property('content_type', relation(content_types)) #adding this makes the inheritance stop working
         # shouldnt throw exception
         products = mapper(Product, product, inherits=contents)
-    
+        # TODO: assertion ??
+
     def testbackref(self):
         """tests adding a property to the superclass mapper"""
         class ContentType(object): pass
@@ -364,6 +365,7 @@ class InheritTest5(testbase.AssertMixin):
         })
         p = Product()
         p.contenttype = ContentType()
+        # TODO: assertion ??
         
 class InheritTest6(testbase.AssertMixin):
     """tests eager load/lazy load of child items off inheritance mappers, tests that
index 0fc3ca60f0b5c762efeb621d0605663d665376b0..19d39e41f11ed6dd2bf8b320dbd285918a42bb69 100644 (file)
@@ -3,6 +3,7 @@ from sqlalchemy import *
 from sqlalchemy.databases import sqlite, postgres, mysql, oracle
 from testbase import PersistTest
 import unittest, re
+import testbase
 
 # the select test now tests almost completely with TableClause/ColumnClause objects,
 # which are free-roaming table/column objects not attached to any database.  
@@ -645,4 +646,4 @@ class SchemaTest(SQLTest):
         self.runtest(table4.insert(values=(2, 5, 'test')), "INSERT INTO remote_owner.remotetable (rem_id, datatype_id, value) VALUES (:rem_id, :datatype_id, :value)")
         
 if __name__ == "__main__":
-    unittest.main()        
+    testbase.main()