summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2007-09-26 17:08:19 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2007-09-26 17:08:19 +0000
commit3e672bbfc8d284b58208987d5fb858e424d02111 (patch)
treef0ddef9742f0a25f93922cdb429dec68e3139e10 /lib
parent9d16ae440b416358b469e6881f1203095233c37c (diff)
downloadsqlalchemy-3e672bbfc8d284b58208987d5fb858e424d02111.tar.gz
- created a link between QueryContext and SelectionContext; the attribute
dictionary of QueryContext is now passed to SelectionContext inside of Query.instances(), allowing messages to be passed between the two stages. - removed the recent "exact match" behavior of Alias objects, they're back to their usual behavior. - tightened up the relationship between the Query's generation of "eager load" aliases, and Query.instances() which actually grabs the eagerly loaded rows. If the aliases were not specifically generated for that statement by EagerLoader, the EagerLoader will not take effect when the rows are fetched. This prevents columns from being grabbed accidentally as being part of an eager load when they were not meant for such, which can happen with textual SQL as well as some inheritance situations. It's particularly important since the "anonymous aliasing" of columns uses simple integer counts now to generate labels.
Diffstat (limited to 'lib')
-rw-r--r--lib/sqlalchemy/engine/base.py16
-rw-r--r--lib/sqlalchemy/orm/interfaces.py4
-rw-r--r--lib/sqlalchemy/orm/query.py35
-rw-r--r--lib/sqlalchemy/orm/shard.py10
-rw-r--r--lib/sqlalchemy/orm/strategies.py26
-rw-r--r--lib/sqlalchemy/sql/expression.py8
6 files changed, 50 insertions, 49 deletions
diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py
index 7e9d57d97..ad2b5df96 100644
--- a/lib/sqlalchemy/engine/base.py
+++ b/lib/sqlalchemy/engine/base.py
@@ -1287,19 +1287,9 @@ class ResultProxy(object):
elif isinstance(key, basestring) and key.lower() in props:
rec = props[key.lower()]
elif isinstance(key, expression.ColumnElement):
- try:
- if getattr(key, '_exact_match', False):
- # exact match flag means the label must be present in the
- # generated column_labels
- label = context.column_labels[key._label].lower()
- else:
- # otherwise, fall back to the straight name of the column
- # if not in generated labels
- label = context.column_labels.get(key._label, key.name).lower()
- if label in props:
- rec = props[label]
- except KeyError:
- pass
+ label = context.column_labels.get(key._label, key.name).lower()
+ if label in props:
+ rec = props[label]
if not "rec" in locals():
raise exceptions.NoSuchColumnError("Could not locate column in row for column '%s'" % (str(key)))
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py
index e0f7799b7..8da7d917c 100644
--- a/lib/sqlalchemy/orm/interfaces.py
+++ b/lib/sqlalchemy/orm/interfaces.py
@@ -493,10 +493,10 @@ class OperationContext(object):
Accept ``MapperOption`` objects which may modify its state before proceeding.
"""
- def __init__(self, mapper, options):
+ def __init__(self, mapper, options, attributes=None):
self.mapper = mapper
self.options = options
- self.attributes = {}
+ self.attributes = attributes or {}
self.recursion_stack = util.Set()
for opt in util.flatten_iterator(options):
self.accept_option(opt)
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index cb30eafcf..0c62737cb 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -612,16 +612,16 @@ class Query(object):
raise exceptions.InvalidRequestError('Multiple rows returned for one()')
def __iter__(self):
- statement = self.compile()
- statement.use_labels = True
+ context = self._compile_context()
+ context.statement.use_labels = True
if self._autoflush and not self._populate_existing:
self.session._autoflush()
- return self._execute_and_instances(statement)
+ return self._execute_and_instances(context)
- def _execute_and_instances(self, statement):
- result = self.session.execute(statement, params=self._params, mapper=self.mapper)
+ def _execute_and_instances(self, querycontext):
+ result = self.session.execute(querycontext.statement, params=self._params, mapper=self.mapper)
try:
- return iter(self.instances(result))
+ return iter(self.instances(result, querycontext=querycontext))
finally:
result.close()
@@ -784,10 +784,16 @@ class Query(object):
def compile(self):
"""compiles and returns a SQL statement based on the criterion and conditions within this Query."""
+ return self._compile_context().statement
+ def _compile_context(self):
+
+ context = QueryContext(self)
+
if self._statement:
self._statement.use_labels = True
- return self._statement
+ context.statement = self._statement
+ return context
whereclause = self._criterion
@@ -807,7 +813,6 @@ class Query(object):
# get/create query context. get the ultimate compile arguments
# from there
- context = QueryContext(self)
order_by = context.order_by
from_obj = context.from_obj
lockmode = context.lockmode
@@ -887,7 +892,7 @@ class Query(object):
m = clauses.adapt_clause(m)
statement.append_column(m)
- return statement
+ return context
def _get_entity_clauses(self, m):
"""for tuples added via add_entity() or add_column(), attempt to locate
@@ -1209,17 +1214,27 @@ class SelectionContext(OperationContext):
Indicates if mappers that have version_id columns should verify
that instances existing already within the Session should have
this attribute compared to the freshly loaded value.
+
+ querycontext
+ the QueryContext, if any, used to generate the executed statement.
+ If present, the attribute dictionary from this Context will be used
+ as the basis for this SelectionContext's attribute dictionary. This
+ allows query-compile-time operations to send messages to the
+ result-processing-time operations.
"""
def __init__(self, mapper, session, extension, **kwargs):
self.populate_existing = kwargs.pop('populate_existing', False)
self.version_check = kwargs.pop('version_check', False)
+ querycontext = kwargs.pop('querycontext', None)
+ if querycontext:
+ kwargs['attributes'] = querycontext.attributes
self.session = session
self.extension = extension
self.identity_map = {}
self.stack = LoaderStack()
super(SelectionContext, self).__init__(mapper, kwargs.pop('with_options', []), **kwargs)
-
+
def accept_option(self, opt):
"""Accept a MapperOption which will process (modify) the state
of this SelectionContext.
diff --git a/lib/sqlalchemy/orm/shard.py b/lib/sqlalchemy/orm/shard.py
index 48e057966..c38bcdd96 100644
--- a/lib/sqlalchemy/orm/shard.py
+++ b/lib/sqlalchemy/orm/shard.py
@@ -78,19 +78,19 @@ class ShardedQuery(Query):
q._shard_id = shard_id
return q
- def _execute_and_instances(self, statement):
+ def _execute_and_instances(self, context):
if self._shard_id is not None:
- result = self.session.connection(mapper=self.mapper, shard_id=self._shard_id).execute(statement, **self._params)
+ result = self.session.connection(mapper=self.mapper, shard_id=self._shard_id).execute(context.statement, **self._params)
try:
- return iter(self.instances(result))
+ return iter(self.instances(result, querycontext=context))
finally:
result.close()
else:
partial = []
for shard_id in self.query_chooser(self):
- result = self.session.connection(mapper=self.mapper, shard_id=shard_id).execute(statement, **self._params)
+ result = self.session.connection(mapper=self.mapper, shard_id=shard_id).execute(context.statement, **self._params)
try:
- partial = partial + list(self.instances(result))
+ partial = partial + list(self.instances(result, querycontext=context))
finally:
result.close()
# if some kind of in memory 'sorting' were done, this is where it would happen
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index f3a0029f5..b93993af8 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -506,12 +506,17 @@ class EagerLoader(AbstractRelationLoader):
break
else:
raise exceptions.InvalidRequestError("EagerLoader cannot locate a clause with which to outer join to, in query '%s' %s" % (str(statement), localparent.mapped_table))
-
+
+ # create AliasedClauses object to build up the eager query. this is cached after 1st creation.
try:
clauses = self.clauses[path]
except KeyError:
clauses = mapperutil.PropertyAliasedClauses(self.parent_property, self.parent_property.polymorphic_primaryjoin, self.parent_property.polymorphic_secondaryjoin, parentclauses)
self.clauses[path] = clauses
+
+ # place the "row_decorator" from the AliasedClauses into the QueryContext, where it will
+ # be picked up in create_row_processor() when results are fetched
+ context.attributes[("eager_row_processor", path)] = clauses.row_decorator
if self.secondaryjoin is not None:
statement._outerjoin = sql.outerjoin(towrap, clauses.secondary, clauses.primaryjoin).outerjoin(clauses.alias, clauses.secondaryjoin)
@@ -545,19 +550,18 @@ class EagerLoader(AbstractRelationLoader):
# custom row decoration function, placed in the selectcontext by the
# contains_eager() mapper option
decorator = selectcontext.attributes[("eager_row_processor", self.parent_property)]
+ # key was present, but no decorator; therefore just use the row as is
if decorator is None:
decorator = lambda row: row
+ # check for an AliasedClauses row decorator that was set up by query._compile_context().
+ # a further refactoring (described in [ticket:777]) will simplify this so that the
+ # contains_eager() option generates the same key as this one
+ elif ("eager_row_processor", path) in selectcontext.attributes:
+ decorator = selectcontext.attributes[("eager_row_processor", path)]
else:
- try:
- # decorate the row according to the stored AliasedClauses for this eager load
- clauses = self.clauses[path]
- decorator = clauses.row_decorator
- except KeyError, k:
- # no stored AliasedClauses: eager loading was not set up in the query and
- # AliasedClauses never got initialized
- if self._should_log_debug:
- self.logger.debug("Could not locate aliased clauses for key: " + str(path))
- return None
+ if self._should_log_debug:
+ self.logger.debug("Could not locate aliased clauses for key: " + str(path))
+ return None
try:
decorated_row = decorator(row)
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index d649fc0ff..4bacf4be3 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -2425,14 +2425,6 @@ class Alias(FromClause):
def _get_from_objects(self, **modifiers):
return [self]
- def _proxy_column(self, column):
- c = column._make_proxy(self)
- # send a note to ResultProxy to not "approximate"
- # this column based on its name when targeting result columns
- # see test/sql/query.py QueryTest.test_exact_match
- c._exact_match = True
- return c
-
bind = property(lambda s: s.selectable.bind)
class _ColumnElementAdapter(ColumnElement):