From: Mike Bayer Date: Sat, 27 May 2006 00:13:12 +0000 (+0000) Subject: added has_key to RowProxy, + caching of key lookups X-Git-Tag: rel_0_2_0~11 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3b5ba3870ee5a27d10422b29befebc1143a7f571;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git added has_key to RowProxy, + caching of key lookups 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 --- diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py index bf7b1c20dd..83dfad04fc 100644 --- a/lib/sqlalchemy/engine/base.py +++ b/lib/sqlalchemy/engine/base.py @@ -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): diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 4f1309fdfb..9b636f244e 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -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): diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 7b15aa7734..00e8e2a67e 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -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] diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index c6c4055f84..7103264739 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -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), diff --git a/test/inheritance.py b/test/inheritance.py index 265134173f..0957638676 100644 --- a/test/inheritance.py +++ b/test/inheritance.py @@ -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 diff --git a/test/select.py b/test/select.py index 0fc3ca60f0..19d39e41f1 100644 --- a/test/select.py +++ b/test/select.py @@ -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()