diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-02-25 10:54:14 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-02-25 10:54:14 -0500 |
commit | 734876180845878e704c5abb6dbd9f15667bd034 (patch) | |
tree | 18b988c7b031727388f73165c99771300a771373 | |
parent | 305ea84004fe604f461cd3c9438fbc84e3d790b2 (diff) | |
download | sqlalchemy-734876180845878e704c5abb6dbd9f15667bd034.tar.gz |
- the "hello world" of the new approach. this is strictly
load a plain entity with plain columns.
-rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 31 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/loading.py | 48 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/query.py | 85 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 32 |
4 files changed, 101 insertions, 95 deletions
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index b3b8d612d..d12c20a2c 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -116,13 +116,6 @@ class MapperProperty(_MappedAttribute, InspectionAttr, util.MemoizedSlots): """ - def create_row_processor(self, context, path, - mapper, result, adapter, populators): - """Produce row processing functions and append to the given - set of populators lists. - - """ - def cascade_iterator(self, type_, state, visited_instances=None, halt_on=None): """Iterate through instances related to the given instance for @@ -496,18 +489,6 @@ class StrategizedProperty(MapperProperty): strat = self.strategy strat.setup_query(context, entity, path, loader, adapter, **kwargs) - def create_row_processor( - self, context, path, mapper, - result, adapter, populators): - loader = self._get_context_loader(context, path) - if loader and loader.strategy: - strat = self._get_strategy(loader.strategy) - else: - strat = self.strategy - strat.create_row_processor( - context, path, loader, - mapper, result, adapter, populators) - def do_init(self): self._strategies = {} self.strategy = self._get_strategy_by_cls(self.strategy_class) @@ -614,17 +595,5 @@ class LoaderStrategy(object): """ - def create_row_processor(self, context, path, loadopt, mapper, - result, adapter, populators): - """Establish row processing functions for a given QueryContext. - - This method fulfills the contract specified by - MapperProperty.create_row_processor(). - - StrategizedProperty delegates its create_row_processor() method - directly to this method. - - """ - def __str__(self): return str(self.parent_property) diff --git a/lib/sqlalchemy/orm/loading.py b/lib/sqlalchemy/orm/loading.py index c59257039..6046436c8 100644 --- a/lib/sqlalchemy/orm/loading.py +++ b/lib/sqlalchemy/orm/loading.py @@ -43,14 +43,10 @@ def instances(query, cursor, context): return tuple(fn(x) for x, fn in zip(row, filter_fns)) try: - (process, labels) = \ - list(zip(*[ - query_entity.row_processor(query, - context, cursor) - for query_entity in query._entities - ])) + (labels, process) = list(zip(*context.loaders)) if not single_entity: + # TODO: this should be in context, so it can also be cached. keyed_tuple = util.lightweight_named_tuple('result', labels) while True: @@ -218,18 +214,26 @@ def load_on_ident(query, key, return None -def instance_processor(mapper, context, result, path, adapter, +def instance_processor(mapper, props_toload, context, column_collection, + query_entity, path, adapter, only_load_props=None, refresh_state=None, polymorphic_discriminator=None, _polymorphic_from=None): """Produce a mapper level row processor callable which processes rows into mapped instances.""" - # note that this method, most of which exists in a closure - # called _instance(), resists being broken out, as - # attempts to do so tend to add significant function - # call overhead. _instance() is the most - # performance-critical section in the whole ORM. + populators = collections.defaultdict(list) + + for prop in props_toload: + prop.setup( + context, + query_entity, + path, + adapter, + only_load_props=only_load_props, + column_collection=column_collection, + populators=populators + ) pk_cols = mapper.primary_key @@ -238,16 +242,10 @@ def instance_processor(mapper, context, result, path, adapter, identity_class = mapper._identity_class - populators = collections.defaultdict(list) - props = mapper._props.values() if only_load_props is not None: props = (p for p in props if p.key in only_load_props) - for prop in props: - prop.create_row_processor( - context, path, mapper, result, adapter, populators) - propagate_options = context.propagate_options if propagate_options: load_path = context.query._current_path + path \ @@ -262,7 +260,6 @@ def instance_processor(mapper, context, result, path, adapter, instance_dict = attributes.instance_dict session_id = context.session.hash_key version_check = context.version_check - runid = context.runid if refresh_state: refresh_identity_key = refresh_state.key @@ -288,7 +285,7 @@ def instance_processor(mapper, context, result, path, adapter, state = refresh_state instance = state.obj() dict_ = instance_dict(instance) - isnew = state.runid != runid + isnew = state.runid != context.runid currentload = True loaded_instance = False else: @@ -306,7 +303,7 @@ def instance_processor(mapper, context, result, path, adapter, state = instance_state(instance) dict_ = instance_dict(instance) - isnew = state.runid != runid + isnew = state.runid != context.runid currentload = not isnew loaded_instance = False @@ -388,12 +385,13 @@ def instance_processor(mapper, context, result, path, adapter, return instance - if not _polymorphic_from and not refresh_state: + # TODO: this has to be reworked (again) + # if not _polymorphic_from and not refresh_state: # if we are doing polymorphic, dispatch to a different _instance() # method specific to the subclass mapper - _instance = _decorate_polymorphic_switch( - _instance, context, mapper, result, path, - polymorphic_discriminator, adapter) + # _instance = _decorate_polymorphic_switch( + # _instance, context, mapper, path, + # polymorphic_discriminator, adapter) return _instance diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 205a5539f..76471889f 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -3015,7 +3015,7 @@ class Query(object): equivs = self.__all_equivs() - context.adapter = sql_util.ColumnAdapter(inner, equivs) + outer_adapter = sql_util.ColumnAdapter(inner, equivs) statement = sql.select( [inner] + context.secondary_columns, @@ -3036,12 +3036,21 @@ class Query(object): if context.order_by: statement.append_order_by( - *context.adapter.copy_and_process( + *outer_adapter.copy_and_process( context.order_by ) ) statement.append_order_by(*context.eager_order_by) + + for idx, col in enumerate(context.primary_columns, 0): + context.column_processors[col](idx) + + for idx, col in enumerate( + context.secondary_columns, + len(context.primary_columns) + len(order_by_col_expr)): + context.column_processors[col](idx) + return statement def _simple_statement(self, context): @@ -3078,6 +3087,13 @@ class Query(object): if context.eager_order_by: statement.append_order_by(*context.eager_order_by) + + # initiate indexes for column processor functions + # that have been established + for idx, col in enumerate( + context.primary_columns + context.secondary_columns): + context.column_processors[col](idx) + return statement def _adjust_for_single_inheritance(self, context): @@ -3297,6 +3313,13 @@ class _MapperEntity(_QueryEntity): def setup_context(self, query, context): adapter = self._get_entity_clauses(query, context) + # TODO: this was part of concrete, how does this apply + # now? At least for textual, something is needed. + # if not adapter and self.mapper._requires_row_aliasing: + # adapter = sql_util.ColumnAdapter( + # self.selectable, + # self.mapper._equivalent_columns) + # if self._adapted_selectable is None: context.froms += (self.selectable,) @@ -3317,28 +3340,44 @@ class _MapperEntity(_QueryEntity): else: poly_properties = self.mapper._polymorphic_properties - for value in poly_properties: - if query._only_load_props and \ - value.key not in query._only_load_props: - continue - value.setup( - context, - self, - self.path, - adapter, - only_load_props=query._only_load_props, - column_collection=context.primary_columns - ) + props_toload = [ + prop for prop in poly_properties + if not query._only_load_props + or prop.key in query._only_load_props + ] - if self._polymorphic_discriminator is not None and \ - self._polymorphic_discriminator \ - is not self.mapper.polymorphic_on: + if query._primary_entity is self: + only_load_props = query._only_load_props + refresh_state = context.refresh_state + else: + only_load_props = refresh_state = None + + _instance = loading.instance_processor( + self.mapper, + props_toload, + context, + context.primary_columns, + self, + self.path, + adapter, + only_load_props=only_load_props, + refresh_state=refresh_state, + polymorphic_discriminator=self._polymorphic_discriminator + ) - if adapter: - pd = adapter.columns[self._polymorphic_discriminator] - else: - pd = self._polymorphic_discriminator - context.primary_columns.append(pd) + context.loaders.append((self._label_name, _instance)) + # TODO: this needs to be in instance_processor() + # and needs a getter fn. a special entry in + # populators should be used here + # if self._polymorphic_discriminator is not None and \ + # self._polymorphic_discriminator \ + # is not self.mapper.polymorphic_on: + # + # if adapter: + # pd = adapter.columns[self._polymorphic_discriminator] + # else: + # pd = self._polymorphic_discriminator + # context.primary_columns.append(pd) def __str__(self): return str(self.mapper) @@ -3708,12 +3747,14 @@ class QueryContext(object): self.refresh_state = query._refresh_state self.primary_columns = [] self.secondary_columns = [] + self.column_processors = {} self.eager_order_by = [] self.eager_joins = {} self.create_eager_joins = [] self.propagate_options = set(o for o in query._with_options if o.propagate_to_loaders) self.attributes = query._attributes.copy() + self.loaders = [] class AliasOption(interfaces.MapperOption): diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 0444c63ae..b7c5dc213 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -24,7 +24,7 @@ from .interfaces import ( ) from .session import _state_session import itertools - +import operator def _register_attribute( strategy, mapper, useobject, @@ -139,11 +139,24 @@ class ColumnLoader(LoaderStrategy): def setup_query( self, context, entity, path, loadopt, - adapter, column_collection, **kwargs): + adapter, column_collection, populators, **kwargs): for c in self.columns: if adapter: c = adapter.columns[c] column_collection.append(c) + # here, we can normally break out. The exception is + # when the mapper needs to see a particular column here, + # which most often occurs when the mapper.primary_key + # attribute points a column other than the first one, e.g. + # the column on the base table. More specific logic + # should be added here so that we need not bother + # querying out every column. + + def quick_populate(index): + populators["quick"].append( + (self.key, operator.itemgetter(index)) + ) + context.column_processors[self.columns[0]] = quick_populate def init_class_attribute(self, mapper): self.is_class_level = True @@ -159,21 +172,6 @@ class ColumnLoader(LoaderStrategy): active_history=active_history ) - def create_row_processor( - self, context, path, - loadopt, mapper, result, adapter, populators): - # look through list of columns represented here - # to see which, if any, is present in the row. - for col in self.columns: - if adapter: - col = adapter.columns[col] - getter = result._getter(col) - if getter: - populators["quick"].append((self.key, getter)) - break - else: - populators["expire"].append((self.key, True)) - @log.class_logger @properties.ColumnProperty.strategy_for(deferred=True, instrument=True) |