From: Mike Bayer Date: Sun, 28 Oct 2007 04:36:36 +0000 (+0000) Subject: - a little cleanup to deferred inheritance loading X-Git-Tag: rel_0_4_1~102 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=103c867cf38a36db076eb1a841548638cbca0336;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - a little cleanup to deferred inheritance loading --- diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 757021cd0d..1c18133116 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -16,6 +16,8 @@ from sqlalchemy.orm import util as mapperutil class ColumnLoader(LoaderStrategy): + """Default column loader.""" + def init(self): super(ColumnLoader, self).init() self.columns = self.parent_property.columns @@ -82,16 +84,42 @@ class ColumnLoader(LoaderStrategy): # our mapped column is not present in the row. check if we need to initialize a polymorphic # row fetcher used by inheritance. (hosted_mapper, needs_tables) = selectcontext.attributes.get(('polymorphic_fetch', mapper), (None, None)) + if hosted_mapper is None: return (None, None, None) if hosted_mapper.polymorphic_fetch == 'deferred': # 'deferred' polymorphic row fetcher, put a callable on the property. + # create a deferred column loader which will query the remaining not-yet-loaded tables in an inheritance load. + # the mapper for the object creates the WHERE criterion using the mapper who originally + # "hosted" the query and the list of tables which are unloaded between the "hosted" mapper + # and this mapper. (i.e. A->B->C, the query used mapper A. therefore will need B's and C's tables + # in the query). + + # deferred loader strategy + strategy = self.parent_property._get_strategy(DeferredColumnLoader) + + # full list of ColumnProperty objects to be loaded in the deferred fetch + props = [p for p in mapper.iterate_properties if isinstance(p.strategy, ColumnLoader) and p.columns[0].table in needs_tables] + + # TODO: we are somewhat duplicating efforts from mapper._get_poly_select_loader + # and should look for ways to simplify. + cond, param_names = mapper._deferred_inheritance_condition(hosted_mapper, needs_tables) + statement = sql.select(needs_tables, cond, use_labels=True) + def create_statement(instance): + params = {} + for c in param_names: + params[c.name] = mapper.get_attr_by_column(instance, c) + return (statement, params) + def new_execute(instance, row, isnew, **flags): if isnew: - sessionlib.attribute_manager.init_instance_attribute(instance, self.key, callable_=self._get_deferred_inheritance_loader(instance, mapper, hosted_mapper, needs_tables)) + loader = strategy.setup_loader(instance, props=props, create_statement=create_statement) + sessionlib.attribute_manager.init_instance_attribute(instance, self.key, callable_=loader) + if self._should_log_debug: self.logger.debug("Returning deferred column fetcher for %s %s" % (mapper, self.key)) + return (new_execute, None, None) else: # immediate polymorphic row fetcher. no processing needed for this row. @@ -99,41 +127,11 @@ class ColumnLoader(LoaderStrategy): self.logger.debug("Returning no column fetcher for %s %s" % (mapper, self.key)) return (None, None, None) - def _get_deferred_inheritance_loader(self, instance, mapper, hosted_mapper, needs_tables): - # create a deferred column loader which will query the remaining not-yet-loaded tables in an inheritance load. - # the mapper for the object creates the WHERE criterion using the mapper who originally - # "hosted" the query and the list of tables which are unloaded between the "hosted" mapper - # and this mapper. (i.e. A->B->C, the query used mapper A. therefore will need B's and C's tables - # in the query). - def create_statement(): - # TODO: the SELECT statement here should be cached in the selectcontext. we are somewhat duplicating - # efforts from mapper._get_poly_select_loader as well and should look - # for ways to simplify. - cond, param_names = mapper._deferred_inheritance_condition(hosted_mapper, needs_tables) - statement = sql.select(needs_tables, cond, use_labels=True) - params = {} - for c in param_names: - params[c.name] = mapper.get_attr_by_column(instance, c) - return (statement, params) - - # install the create_statement() callable using the deferred loading strategy - strategy = self.parent_property._get_strategy(DeferredColumnLoader) - - # assemble list of all ColumnProperties which will need to be loaded - props = [p for p in mapper.iterate_properties if isinstance(p.strategy, ColumnLoader) and p.columns[0].table in needs_tables] - - # set the deferred loader on the instance attribute - return strategy.setup_loader(instance, props=props, create_statement=create_statement) - ColumnLoader.logger = logging.class_logger(ColumnLoader) class DeferredColumnLoader(LoaderStrategy): - """Describes an object attribute that corresponds to a table - column, which also will *lazy load* its value from the table. - - This is per-column lazy loading. - """ + """Deferred column loader, a per-column or per-column-group lazy loader.""" def create_row_processor(self, selectcontext, mapper, row): if self.group is not None and selectcontext.attributes.get(('undefer', self.group), False): @@ -207,7 +205,7 @@ class DeferredColumnLoader(LoaderStrategy): params[param_map[primary_key].key] = ident[i] statement = sql.select([p.columns[0] for p in group], clause, from_obj=[localparent.mapped_table], use_labels=True) else: - statement, params = create_statement() + statement, params = create_statement(instance) # TODO: have the "fetch of one row" operation go through the same channels as a query._get() # deferred load of several attributes should be a specialized case of a query refresh operation