summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2015-02-25 10:54:14 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2015-02-25 10:54:14 -0500
commit734876180845878e704c5abb6dbd9f15667bd034 (patch)
tree18b988c7b031727388f73165c99771300a771373
parent305ea84004fe604f461cd3c9438fbc84e3d790b2 (diff)
downloadsqlalchemy-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.py31
-rw-r--r--lib/sqlalchemy/orm/loading.py48
-rw-r--r--lib/sqlalchemy/orm/query.py85
-rw-r--r--lib/sqlalchemy/orm/strategies.py32
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)