summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/query.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/orm/query.py')
-rw-r--r--lib/sqlalchemy/orm/query.py1805
1 files changed, 850 insertions, 955 deletions
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index 8996a758e..dfa24efee 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -15,35 +15,54 @@ operations at the SQL (non-ORM) level. ``Query`` differs from ``Select`` in
that it returns ORM-mapped objects and interacts with an ORM session, whereas
the ``Select`` construct interacts directly with the database to return
iterable result sets.
+
"""
from itertools import chain
-from sqlalchemy import sql, util, exceptions, logging
+
+from sqlalchemy import sql, util, log
+from sqlalchemy import exc as sa_exc
+from sqlalchemy.orm import exc as orm_exc
from sqlalchemy.sql import util as sql_util
from sqlalchemy.sql import expression, visitors, operators
-from sqlalchemy.orm import mapper, object_mapper
+from sqlalchemy.orm import attributes, interfaces, mapper, object_mapper
+from sqlalchemy.orm.util import _state_mapper, _is_mapped_class, \
+ _is_aliased_class, _entity_descriptor, _entity_info, _class_to_mapper, \
+ _orm_columns, AliasedClass, _orm_selectable, join as orm_join, ORMAdapter
-from sqlalchemy.orm.util import _state_mapper, _class_to_mapper, _is_mapped_class, _is_aliased_class
-from sqlalchemy.orm import util as mapperutil
-from sqlalchemy.orm import interfaces
-from sqlalchemy.orm import attributes
-from sqlalchemy.orm.util import AliasedClass
+__all__ = ['Query', 'QueryContext', 'aliased']
-aliased = AliasedClass
-__all__ = ['Query', 'QueryContext', 'aliased']
+aliased = AliasedClass
+def _generative(*assertions):
+ """mark a method as generative."""
+
+ def decorate(fn):
+ argspec = util.format_argspec_plus(fn)
+ run_assertions = assertions
+ code = "\n".join([
+ "def %s%s:",
+ " %r",
+ " self = self._clone()",
+ " for a in run_assertions:",
+ " a(self, %r)",
+ " fn%s",
+ " return self"
+ ]) % (fn.__name__, argspec['args'], fn.__doc__, fn.__name__, argspec['apply_pos'])
+ env = locals().copy()
+ exec code in env
+ return env[fn.__name__]
+ return decorate
class Query(object):
"""Encapsulates the object-fetching operations provided by Mappers."""
- def __init__(self, class_or_mapper, session=None, entity_name=None):
- self._session = session
-
+ def __init__(self, entities, session=None, entity_name=None):
+ self.session = session
+
self._with_options = []
self._lockmode = None
-
- self._entities = []
self._order_by = False
self._group_by = False
self._distinct = False
@@ -53,51 +72,53 @@ class Query(object):
self._params = {}
self._yield_per = None
self._criterion = None
+ self._correlate = util.Set()
+ self._joinpoint = None
+ self._with_labels = False
self.__joinable_tables = None
self._having = None
- self._column_aggregate = None
self._populate_existing = False
self._version_check = False
self._autoflush = True
-
self._attributes = {}
self._current_path = ()
self._only_load_props = None
self._refresh_instance = None
-
- self.__init_mapper(_class_to_mapper(class_or_mapper, entity_name=entity_name))
-
- def __init_mapper(self, mapper):
- """populate all instance variables derived from this Query's mapper."""
-
- self.mapper = mapper
- self.table = self._from_obj = self.mapper.mapped_table
- self._eager_loaders = util.Set(chain(*[mp._eager_loaders for mp in [m for m in self.mapper.iterate_to_root()]]))
- self._extension = self.mapper.extension
- self._aliases_head = self._aliases_tail = None
- self._alias_ids = {}
- self._joinpoint = self.mapper
- self._entities.append(_PrimaryMapperEntity(self.mapper))
- if self.mapper.with_polymorphic:
- self.__set_with_polymorphic(*self.mapper.with_polymorphic)
- else:
- self._with_polymorphic = []
-
- def __generate_alias_ids(self):
- self._alias_ids = dict([
- (k, list(v)) for k, v in self._alias_ids.iteritems()
- ])
+ self._from_obj = None
+ self._entities = []
+ self._polymorphic_adapters = {}
+ self._filter_aliases = None
+ self._from_obj_alias = None
+ self.__currenttables = util.Set()
+
+ for ent in util.to_list(entities):
+ _QueryEntity(self, ent, entity_name=entity_name)
+
+ self.__setup_aliasizers(self._entities)
+
+ def __setup_aliasizers(self, entities):
+ d = {}
+ for ent in entities:
+ for entity in ent.entities:
+ if entity not in d:
+ mapper, selectable, is_aliased_class = _entity_info(entity, ent.entity_name)
+ if not is_aliased_class and mapper.with_polymorphic:
+ with_polymorphic = mapper._with_polymorphic_mappers
+ self.__mapper_loads_polymorphically_with(mapper, sql_util.ColumnAdapter(selectable, mapper._equivalent_columns))
+ adapter = None
+ elif is_aliased_class:
+ adapter = sql_util.ColumnAdapter(selectable, mapper._equivalent_columns)
+ with_polymorphic = None
+ else:
+ with_polymorphic = adapter = None
- def __no_criterion(self, meth):
- return self.__conditional_clone(meth, [self.__no_criterion_condition])
+ d[entity] = (mapper, adapter, selectable, is_aliased_class, with_polymorphic)
+ ent.setup_entity(entity, *d[entity])
- def __no_statement(self, meth):
- return self.__conditional_clone(meth, [self.__no_statement_condition])
-
- def __reset_all(self, mapper, meth):
- q = self.__conditional_clone(meth, [self.__no_criterion_condition])
- q.__init_mapper(mapper, mapper)
- return q
+ def __mapper_loads_polymorphically_with(self, mapper, adapter):
+ for m2 in mapper._with_polymorphic_mappers:
+ for m in m2.iterate_to_root():
+ self._polymorphic_adapters[m.mapped_table] = self._polymorphic_adapters[m.local_table] = adapter
def __set_select_from(self, from_obj):
if isinstance(from_obj, expression._SelectBaseMixin):
@@ -105,54 +126,168 @@ class Query(object):
from_obj = from_obj.alias()
self._from_obj = from_obj
- self._alias_ids = {}
+ equivs = self.__all_equivs()
+
+ if isinstance(from_obj, expression.Alias):
+ # dont alias a regular join (since its not an alias itself)
+ self._from_obj_alias = sql_util.ColumnAdapter(self._from_obj, equivs)
+
+ def _get_polymorphic_adapter(self, entity, selectable):
+ self.__mapper_loads_polymorphically_with(entity.mapper, sql_util.ColumnAdapter(selectable, entity.mapper._equivalent_columns))
+
+ def _reset_polymorphic_adapter(self, mapper):
+ for m2 in mapper._with_polymorphic_mappers:
+ for m in m2.iterate_to_root():
+ self._polymorphic_adapters.pop(m.mapped_table, None)
+ self._polymorphic_adapters.pop(m.local_table, None)
+
+ def __reset_joinpoint(self):
+ self._joinpoint = None
+ self._filter_aliases = None
+
+ def __adapt_polymorphic_element(self, element):
+ if isinstance(element, expression.FromClause):
+ search = element
+ elif hasattr(element, 'table'):
+ search = element.table
+ else:
+ search = None
+
+ if search:
+ alias = self._polymorphic_adapters.get(search, None)
+ if alias:
+ return alias.adapt_clause(element)
+
+ def __replace_element(self, adapters):
+ def replace(elem):
+ if '_halt_adapt' in elem._annotations:
+ return elem
+
+ for adapter in adapters:
+ e = adapter(elem)
+ if e:
+ return e
+ return replace
+
+ def __replace_orm_element(self, adapters):
+ def replace(elem):
+ if '_halt_adapt' in elem._annotations:
+ return elem
+
+ if "_orm_adapt" in elem._annotations or "parententity" in elem._annotations:
+ for adapter in adapters:
+ e = adapter(elem)
+ if e:
+ return e
+ return replace
+
+ def _adapt_all_clauses(self):
+ self._disable_orm_filtering = True
+ _adapt_all_clauses = _generative()(_adapt_all_clauses)
+
+ def _adapt_clause(self, clause, as_filter, orm_only):
+ adapters = []
+ if as_filter and self._filter_aliases:
+ adapters.append(self._filter_aliases.replace)
+
+ if self._polymorphic_adapters:
+ adapters.append(self.__adapt_polymorphic_element)
+
+ if self._from_obj_alias:
+ adapters.append(self._from_obj_alias.replace)
+
+ if not adapters:
+ return clause
+
+ if getattr(self, '_disable_orm_filtering', not orm_only):
+ return visitors.replacement_traverse(clause, {'column_collections':False}, self.__replace_element(adapters))
+ else:
+ return visitors.replacement_traverse(clause, {'column_collections':False}, self.__replace_orm_element(adapters))
- if self.table not in self._get_joinable_tables():
- self._aliases_head = self._aliases_tail = mapperutil.AliasedClauses(self._from_obj, equivalents=self.mapper._equivalent_columns)
- self._alias_ids.setdefault(self.table, []).append(self._aliases_head)
+ def _entity_zero(self):
+ return self._entities[0]
+
+ def _mapper_zero(self):
+ return self._entity_zero().entity_zero
+
+ def _extension_zero(self):
+ ent = self._entity_zero()
+ return getattr(ent, 'extension', ent.mapper.extension)
+
+ def _mapper_entities(self):
+ for ent in self._entities:
+ if hasattr(ent, 'primary_entity'):
+ yield ent
+ _mapper_entities = property(_mapper_entities)
+
+ def _joinpoint_zero(self):
+ return self._joinpoint or self._entity_zero().entity_zero
+
+ def _mapper_zero_or_none(self):
+ if not getattr(self._entities[0], 'primary_entity', False):
+ return None
+ return self._entities[0].mapper
+
+ def _only_mapper_zero(self):
+ if len(self._entities) > 1:
+ raise sa_exc.InvalidRequestError("This operation requires a Query against a single mapper.")
+ return self._mapper_zero()
+
+ def _only_entity_zero(self):
+ if len(self._entities) > 1:
+ raise sa_exc.InvalidRequestError("This operation requires a Query against a single mapper.")
+ return self._entity_zero()
+
+ def _generate_mapper_zero(self):
+ if not getattr(self._entities[0], 'primary_entity', False):
+ raise sa_exc.InvalidRequestError("No primary mapper set up for this Query.")
+ entity = self._entities[0]._clone()
+ self._entities = [entity] + self._entities[1:]
+ return entity
+
+ def __mapper_zero_from_obj(self):
+ if self._from_obj:
+ return self._from_obj
else:
- self._aliases_head = self._aliases_tail = None
+ return self._entity_zero().selectable
- def __set_with_polymorphic(self, cls_or_mappers, selectable=None):
- mappers, from_obj = self.mapper._with_polymorphic_args(cls_or_mappers, selectable)
- self._with_polymorphic = mappers
- self.__set_select_from(from_obj)
+ def __all_equivs(self):
+ equivs = {}
+ for ent in self._mapper_entities:
+ equivs.update(ent.mapper._equivalent_columns)
+ return equivs
- def __no_criterion_condition(self, q, meth):
- if q._criterion or q._statement:
+ def __no_criterion_condition(self, meth):
+ if self._criterion or self._statement or self._from_obj:
util.warn(
("Query.%s() being called on a Query with existing criterion; "
- "criterion is being ignored.") % meth)
-
- q._joinpoint = self.mapper
- q._statement = q._criterion = None
- q._order_by = q._group_by = q._distinct = False
- q._aliases_tail = q._aliases_head
- q.table = q._from_obj = q.mapper.mapped_table
- if q.mapper.with_polymorphic:
- q.__set_with_polymorphic(*q.mapper.with_polymorphic)
-
- def __no_entities(self, meth):
- q = self.__no_statement(meth)
- if len(q._entities) > 1 and not isinstance(q._entities[0], _PrimaryMapperEntity):
- raise exceptions.InvalidRequestError(
- ("Query.%s() being called on a Query with existing "
- "additional entities or columns - can't replace columns") % meth)
- q._entities = []
- return q
+ "criterion is being ignored. This usage is deprecated.") % meth)
- def __no_statement_condition(self, q, meth):
- if q._statement:
- raise exceptions.InvalidRequestError(
+ self._statement = self._criterion = self._from_obj = None
+ self._order_by = self._group_by = self._distinct = False
+ self.__joined_tables = {}
+
+ def __no_from_condition(self, meth):
+ if self._from_obj:
+ raise sa_exc.InvalidRequestError("Query.%s() being called on a Query which already has a FROM clause established. This usage is deprecated." % meth)
+
+ def __no_statement_condition(self, meth):
+ if self._statement:
+ raise sa_exc.InvalidRequestError(
("Query.%s() being called on a Query with an existing full "
"statement - can't apply criterion.") % meth)
- def __conditional_clone(self, methname=None, conditions=None):
- q = self._clone()
- if conditions:
- for condition in conditions:
- condition(q, methname)
- return q
+ def __no_limit_offset(self, meth):
+ if self._limit or self._offset:
+ util.warn("Query.%s() being called on a Query which already has LIMIT or OFFSET applied. "
+ "This usage is deprecated. Apply filtering and joins before LIMIT or OFFSET are applied, "
+ "or to filter/join to the row-limited results of the query, call from_self() first."
+ "In release 0.5, from_self() will be called automatically in this scenario."
+ )
+
+ def __no_criterion(self):
+ """generate a Query with no criterion, warn if criterion was present"""
+ __no_criterion = _generative(__no_criterion_condition)(__no_criterion)
def __get_options(self, populate_existing=None, version_check=None, only_load_props=None, refresh_instance=None):
if populate_existing:
@@ -170,18 +305,27 @@ class Query(object):
q.__dict__ = self.__dict__.copy()
return q
- def session(self):
- if self._session is None:
- return self.mapper.get_session()
- else:
- return self._session
- session = property(session)
-
def statement(self):
"""return the full SELECT statement represented by this Query."""
- return self._compile_context().statement
+ return self._compile_context(labels=self._with_labels).statement
statement = property(statement)
+ def with_labels(self):
+ """Apply column labels to the return value of Query.statement.
+
+ Indicates that this Query's `statement` accessor should return a SELECT statement
+ that applies labels to all columns in the form <tablename>_<columnname>; this
+ is commonly used to disambiguate columns from multiple tables which have the
+ same name.
+
+ When the `Query` actually issues SQL to load rows, it always uses
+ column labeling.
+
+ """
+ self._with_labels = True
+ with_labels = _generative()(with_labels)
+
+
def whereclause(self):
"""return the WHERE criterion for this Query."""
return self._criterion
@@ -189,48 +333,44 @@ class Query(object):
def _with_current_path(self, path):
"""indicate that this query applies to objects loaded within a certain path.
-
- Used by deferred loaders (see strategies.py) which transfer query
+
+ Used by deferred loaders (see strategies.py) which transfer query
options from an originating query to a newly generated query intended
for the deferred load.
-
+
"""
- q = self._clone()
- q._current_path = path
- return q
+ self._current_path = path
+ _with_current_path = _generative()(_with_current_path)
def with_polymorphic(self, cls_or_mappers, selectable=None):
"""Load columns for descendant mappers of this Query's mapper.
-
+
Using this method will ensure that each descendant mapper's
- tables are included in the FROM clause, and will allow filter()
- criterion to be used against those tables. The resulting
+ tables are included in the FROM clause, and will allow filter()
+ criterion to be used against those tables. The resulting
instances will also have those columns already loaded so that
no "post fetch" of those columns will be required.
-
+
``cls_or_mappers`` is a single class or mapper, or list of class/mappers,
which inherit from this Query's mapper. Alternatively, it
- may also be the string ``'*'``, in which case all descending
+ may also be the string ``'*'``, in which case all descending
mappers will be added to the FROM clause.
-
- ``selectable`` is a table or select() statement that will
+
+ ``selectable`` is a table or select() statement that will
be used in place of the generated FROM clause. This argument
- is required if any of the desired mappers use concrete table
- inheritance, since SQLAlchemy currently cannot generate UNIONs
- among tables automatically. If used, the ``selectable``
- argument must represent the full set of tables and columns mapped
+ is required if any of the desired mappers use concrete table
+ inheritance, since SQLAlchemy currently cannot generate UNIONs
+ among tables automatically. If used, the ``selectable``
+ argument must represent the full set of tables and columns mapped
by every desired mapper. Otherwise, the unaccounted mapped columns
- will result in their table being appended directly to the FROM
+ will result in their table being appended directly to the FROM
clause which will usually lead to incorrect results.
"""
- q = self.__no_criterion('with_polymorphic')
-
- q.__set_with_polymorphic(cls_or_mappers, selectable=selectable)
+ entity = self._generate_mapper_zero()
+ entity.set_with_polymorphic(self, cls_or_mappers, selectable=selectable)
+ with_polymorphic = _generative(__no_from_condition, __no_criterion_condition)(with_polymorphic)
- return q
-
-
def yield_per(self, count):
"""Yield only ``count`` rows at a time.
@@ -242,30 +382,28 @@ class Query(object):
eagerly loaded collections (i.e. any lazy=False) since those
collections will be cleared for a new load when encountered in a
subsequent result batch.
- """
- q = self._clone()
- q._yield_per = count
- return q
+ """
+ self._yield_per = count
+ yield_per = _generative()(yield_per)
def get(self, ident, **kwargs):
"""Return an instance of the object based on the given identifier, or None if not found.
The `ident` argument is a scalar or tuple of primary key column values
in the order of the table def's primary key columns.
+
"""
- ret = self._extension.get(self, ident, **kwargs)
+ ret = self._extension_zero().get(self, ident, **kwargs)
if ret is not mapper.EXT_CONTINUE:
return ret
# convert composite types to individual args
- # TODO: account for the order of columns in the
- # ColumnProperty it corresponds to
if hasattr(ident, '__composite_values__'):
ident = ident.__composite_values__()
- key = self.mapper.identity_key_from_primary_key(ident)
+ key = self._only_mapper_zero().identity_key_from_primary_key(ident)
return self._get(key, ident, **kwargs)
def load(self, ident, raiseerr=True, **kwargs):
@@ -275,15 +413,20 @@ class Query(object):
pending changes** to the object already existing in the Session. The
`ident` argument is a scalar or tuple of primary key column values in
the order of the table def's primary key columns.
- """
- ret = self._extension.load(self, ident, **kwargs)
+ """
+ ret = self._extension_zero().load(self, ident, **kwargs)
if ret is not mapper.EXT_CONTINUE:
return ret
- key = self.mapper.identity_key_from_primary_key(ident)
+
+ # convert composite types to individual args
+ if hasattr(ident, '__composite_values__'):
+ ident = ident.__composite_values__()
+
+ key = self._only_mapper_zero().identity_key_from_primary_key(ident)
instance = self.populate_existing()._get(key, ident, **kwargs)
if instance is None and raiseerr:
- raise exceptions.InvalidRequestError("No instance found for identity %s" % repr(ident))
+ raise sa_exc.InvalidRequestError("No instance found for identity %s" % repr(ident))
return instance
def query_from_parent(cls, instance, property, **kwargs):
@@ -303,27 +446,33 @@ class Query(object):
\**kwargs
all extra keyword arguments are propagated to the constructor of
Query.
- """
+ deprecated. use sqlalchemy.orm.with_parent in conjunction with
+ filter().
+
+ """
mapper = object_mapper(instance)
prop = mapper.get_property(property, resolve_synonyms=True)
target = prop.mapper
criterion = prop.compare(operators.eq, instance, value_is_parent=True)
return Query(target, **kwargs).filter(criterion)
- query_from_parent = classmethod(query_from_parent)
+ query_from_parent = classmethod(util.deprecated(None, False)(query_from_parent))
+
+ def correlate(self, *args):
+ self._correlate = self._correlate.union([_orm_selectable(s) for s in args])
+ correlate = _generative()(correlate)
def autoflush(self, setting):
"""Return a Query with a specific 'autoflush' setting.
Note that a Session with autoflush=False will
- not autoflush, even if this flag is set to True at the
+ not autoflush, even if this flag is set to True at the
Query level. Therefore this flag is usually used only
to disable autoflush for a specific Query.
-
+
"""
- q = self._clone()
- q._autoflush = setting
- return q
+ self._autoflush = setting
+ autoflush = _generative()(autoflush)
def populate_existing(self):
"""Return a Query that will refresh all instances loaded.
@@ -336,11 +485,10 @@ class Query(object):
An alternative to populate_existing() is to expire the Session
fully using session.expire_all().
-
+
"""
- q = self._clone()
- q._populate_existing = True
- return q
+ self._populate_existing = True
+ populate_existing = _generative()(populate_existing)
def with_parent(self, instance, property=None):
"""add a join criterion corresponding to a relationship to the given parent instance.
@@ -361,140 +509,98 @@ class Query(object):
mapper = object_mapper(instance)
if property is None:
for prop in mapper.iterate_properties:
- if isinstance(prop, properties.PropertyLoader) and prop.mapper is self.mapper:
+ if isinstance(prop, properties.PropertyLoader) and prop.mapper is self._mapper_zero():
break
else:
- raise exceptions.InvalidRequestError("Could not locate a property which relates instances of class '%s' to instances of class '%s'" % (self.mapper.class_.__name__, instance.__class__.__name__))
+ raise sa_exc.InvalidRequestError("Could not locate a property which relates instances of class '%s' to instances of class '%s'" % (self._mapper_zero().class_.__name__, instance.__class__.__name__))
else:
prop = mapper.get_property(property, resolve_synonyms=True)
return self.filter(prop.compare(operators.eq, instance, value_is_parent=True))
- def add_entity(self, entity, alias=None, id=None):
- """add a mapped entity to the list of result columns to be returned.
-
- This will have the effect of all result-returning methods returning a tuple
- of results, the first element being an instance of the primary class for this
- Query, and subsequent elements matching columns or entities which were
- specified via add_column or add_entity.
-
- When adding entities to the result, its generally desirable to add
- limiting criterion to the query which can associate the primary entity
- of this Query along with the additional entities. The Query selects
- from all tables with no joining criterion by default.
+ def add_entity(self, entity, alias=None):
+ """add a mapped entity to the list of result columns to be returned."""
- entity
- a class or mapper which will be added to the results.
+ if alias:
+ entity = aliased(entity, alias)
- alias
- a sqlalchemy.sql.Alias object which will be used to select rows. this
- will match the usage of the given Alias in filter(), order_by(), etc. expressions
+ self._entities = list(self._entities)
+ m = _MapperEntity(self, entity)
+ self.__setup_aliasizers([m])
+ add_entity = _generative()(add_entity)
- id
- a string ID matching that given to query.join() or query.outerjoin(); rows will be
- selected from the aliased join created via those methods.
+ def from_self(self, *entities):
+ """return a Query that selects from this Query's SELECT statement.
+ \*entities - optional list of entities which will replace
+ those being selected.
"""
- q = self._clone()
-
- if not alias and _is_aliased_class(entity):
- alias = entity.alias
- if isinstance(entity, type):
- entity = mapper.class_mapper(entity)
+ fromclause = self.compile().correlate(None)
+ self._statement = self._criterion = None
+ self._order_by = self._group_by = self._distinct = False
+ self._limit = self._offset = None
+ self.__set_select_from(fromclause)
+ if entities:
+ self._entities = []
+ for ent in entities:
+ _QueryEntity(self, ent)
+ self.__setup_aliasizers(self._entities)
- if alias is not None:
- alias = mapperutil.AliasedClauses(alias)
+ from_self = _generative()(from_self)
+ _from_self = from_self
- q._entities = q._entities + [_MapperEntity(mapper=entity, alias=alias, id=id)]
- return q
-
- def _from_self(self):
- """return a Query that selects from this Query's SELECT statement.
-
- The API for this method hasn't been decided yet and is subject to change.
-
- """
- q = self._clone()
- q._eager_loaders = util.Set()
- fromclause = q.compile().correlate(None)
- return Query(self.mapper, self.session).select_from(fromclause)
-
def values(self, *columns):
"""Return an iterator yielding result tuples corresponding to the given list of columns"""
-
- q = self.__no_entities('_values')
- q._only_load_props = q._eager_loaders = util.Set()
- q._no_filters = True
+
+ if not columns:
+ return iter(())
+ q = self._clone()
+ q._entities = []
for column in columns:
- q._entities.append(self._add_column(column, None, False))
+ _ColumnEntity(q, column)
+ q.__setup_aliasizers(q._entities)
if not q._yield_per:
- q = q.yield_per(10)
+ q._yield_per = 10
return iter(q)
_values = values
-
- def add_column(self, column, id=None):
- """Add a SQL ColumnElement to the list of result columns to be returned.
- This will have the effect of all result-returning methods returning a
- tuple of results, the first element being an instance of the primary
- class for this Query, and subsequent elements matching columns or
- entities which were specified via add_column or add_entity.
+ def add_column(self, column):
+ """Add a SQL ColumnElement to the list of result columns to be returned."""
- When adding columns to the result, its generally desirable to add
- limiting criterion to the query which can associate the primary entity
- of this Query along with the additional columns, if the column is
- based on a table or selectable that is not the primary mapped
- selectable. The Query selects from all tables with no joining
- criterion by default.
+ self._entities = list(self._entities)
+ c = _ColumnEntity(self, column)
+ self.__setup_aliasizers([c])
+ add_column = _generative()(add_column)
- column
- a string column name or sql.ColumnElement to be added to the results.
-
- """
- q = self._clone()
- q._entities = q._entities + [self._add_column(column, id, True)]
- return q
-
- def _add_column(self, column, id, looks_for_aliases):
- if isinstance(column, interfaces.PropComparator):
- column = column.clause_element()
-
- elif not isinstance(column, (sql.ColumnElement, basestring)):
- raise exceptions.InvalidRequestError("Invalid column expression '%r'" % column)
-
- return _ColumnEntity(column, id)
-
def options(self, *args):
"""Return a new Query object, applying the given list of
MapperOptions.
"""
- return self._options(False, *args)
+ return self.__options(False, *args)
def _conditional_options(self, *args):
- return self._options(True, *args)
+ return self.__options(True, *args)
- def _options(self, conditional, *args):
- q = self._clone()
+ def __options(self, conditional, *args):
# most MapperOptions write to the '_attributes' dictionary,
# so copy that as well
- q._attributes = q._attributes.copy()
+ self._attributes = self._attributes.copy()
opts = [o for o in util.flatten_iterator(args)]
- q._with_options = q._with_options + opts
+ self._with_options = self._with_options + opts
if conditional:
for opt in opts:
- opt.process_query_conditionally(q)
+ opt.process_query_conditionally(self)
else:
for opt in opts:
- opt.process_query(q)
- return q
+ opt.process_query(self)
+ __options = _generative()(__options)
def with_lockmode(self, mode):
"""Return a new Query object with the specified locking mode."""
-
- q = self._clone()
- q._lockmode = mode
- return q
+
+ self._lockmode = mode
+ with_lockmode = _generative()(with_lockmode)
def params(self, *args, **kwargs):
"""add values for bind parameters which may have been specified in filter().
@@ -505,14 +611,13 @@ class Query(object):
\**kwargs cannot be used.
"""
- q = self._clone()
if len(args) == 1:
kwargs.update(args[0])
elif len(args) > 0:
- raise exceptions.ArgumentError("params() takes zero or one positional argument, which is a dictionary.")
- q._params = q._params.copy()
- q._params.update(kwargs)
- return q
+ raise sa_exc.ArgumentError("params() takes zero or one positional argument, which is a dictionary.")
+ self._params = self._params.copy()
+ self._params.update(kwargs)
+ params = _generative()(params)
def filter(self, criterion):
"""apply the given filtering criterion to the query and return the newly resulting ``Query``
@@ -524,22 +629,20 @@ class Query(object):
criterion = sql.text(criterion)
if criterion is not None and not isinstance(criterion, sql.ClauseElement):
- raise exceptions.ArgumentError("filter() argument must be of type sqlalchemy.sql.ClauseElement or string")
+ raise sa_exc.ArgumentError("filter() argument must be of type sqlalchemy.sql.ClauseElement or string")
- if self._aliases_tail:
- criterion = self._aliases_tail.adapt_clause(criterion)
+ criterion = self._adapt_clause(criterion, True, True)
- q = self.__no_statement("filter")
- if q._criterion is not None:
- q._criterion = q._criterion & criterion
+ if self._criterion is not None:
+ self._criterion = self._criterion & criterion
else:
- q._criterion = criterion
- return q
+ self._criterion = criterion
+ filter = _generative(__no_statement_condition, __no_limit_offset)(filter)
def filter_by(self, **kwargs):
"""apply the given filtering criterion to the query and return the newly resulting ``Query``."""
- clauses = [self._joinpoint.get_property(key, resolve_synonyms=True).compare(operators.eq, value)
+ clauses = [_entity_descriptor(self._joinpoint_zero(), key)[0] == value
for key, value in kwargs.iteritems()]
return self.filter(sql.and_(*clauses))
@@ -568,31 +671,27 @@ class Query(object):
def order_by(self, *criterion):
"""apply one or more ORDER BY criterion to the query and return the newly resulting ``Query``"""
- q = self.__no_statement("order_by")
+ criterion = [self._adapt_clause(expression._literal_as_text(o), True, True) for o in criterion]
- if self._aliases_tail:
- criterion = tuple(self._aliases_tail.adapt_list(
- [expression._literal_as_text(o) for o in criterion]
- ))
-
- if q._order_by is False:
- q._order_by = criterion
+ if self._order_by is False:
+ self._order_by = criterion
else:
- q._order_by = q._order_by + criterion
- return q
+ self._order_by = self._order_by + criterion
order_by = util.array_as_starargs_decorator(order_by)
-
+ order_by = _generative(__no_statement_condition)(order_by)
+
def group_by(self, *criterion):
"""apply one or more GROUP BY criterion to the query and return the newly resulting ``Query``"""
- q = self.__no_statement("group_by")
- if q._group_by is False:
- q._group_by = criterion
+ criterion = list(chain(*[_orm_columns(c) for c in criterion]))
+
+ if self._group_by is False:
+ self._group_by = criterion
else:
- q._group_by = q._group_by + criterion
- return q
+ self._group_by = self._group_by + criterion
group_by = util.array_as_starargs_decorator(group_by)
-
+ group_by = _generative(__no_statement_condition)(group_by)
+
def having(self, criterion):
"""apply a HAVING criterion to the query and return the newly resulting ``Query``."""
@@ -600,190 +699,225 @@ class Query(object):
criterion = sql.text(criterion)
if criterion is not None and not isinstance(criterion, sql.ClauseElement):
- raise exceptions.ArgumentError("having() argument must be of type sqlalchemy.sql.ClauseElement or string")
+ raise sa_exc.ArgumentError("having() argument must be of type sqlalchemy.sql.ClauseElement or string")
- if self._aliases_tail:
- criterion = self._aliases_tail.adapt_clause(criterion)
+ criterion = self._adapt_clause(criterion, True, True)
- q = self.__no_statement("having")
- if q._having is not None:
- q._having = q._having & criterion
+ if self._having is not None:
+ self._having = self._having & criterion
else:
- q._having = criterion
- return q
+ self._having = criterion
+ having = _generative(__no_statement_condition)(having)
- def join(self, prop, id=None, aliased=False, from_joinpoint=False):
+ def join(self, *props, **kwargs):
"""Create a join against this ``Query`` object's criterion
- and apply generatively, retunring the newly resulting ``Query``.
-
- 'prop' may be one of:
- * a string property name, i.e. "rooms"
- * a class-mapped attribute, i.e. Houses.rooms
- * a 2-tuple containing one of the above, combined with a selectable
- which derives from the properties' mapped table
- * a list (not a tuple) containing a combination of any of the above.
+ and apply generatively, returning the newly resulting ``Query``.
+ each element in \*props may be:
+
+ * a string property name, i.e. "rooms". This will join along
+ the relation of the same name from this Query's "primary"
+ mapper, if one is present.
+
+ * a class-mapped attribute, i.e. Houses.rooms. This will create a
+ join from "Houses" table to that of the "rooms" relation.
+
+ * a 2-tuple containing a target class or selectable, and
+ an "ON" clause. The ON clause can be the property name/
+ attribute like above, or a SQL expression.
+
+
e.g.::
+ # join along string attribute names
session.query(Company).join('employees')
- session.query(Company).join(['employees', 'tasks'])
- session.query(Houses).join([Colonials.rooms, Room.closets])
- session.query(Company).join([('employees', people.join(engineers)), Engineer.computers])
+ session.query(Company).join('employees', 'tasks')
+
+ # join the Person entity to an alias of itself,
+ # along the "friends" relation
+ PAlias = aliased(Person)
+ session.query(Person).join((Palias, Person.friends))
+
+ # join from Houses to the "rooms" attribute on the
+ # "Colonials" subclass of Houses, then join to the
+ # "closets" relation on Room
+ session.query(Houses).join(Colonials.rooms, Room.closets)
+
+ # join from Company entities to the "employees" collection,
+ # using "people JOIN engineers" as the target. Then join
+ # to the "computers" collection on the Engineer entity.
+ session.query(Company).join((people.join(engineers), 'employees'), Engineer.computers)
+
+ # join from Articles to Keywords, using the "keywords" attribute.
+ # assume this is a many-to-many relation.
+ session.query(Article).join(Article.keywords)
+
+ # same thing, but spelled out entirely explicitly
+ # including the association table.
+ session.query(Article).join(
+ (article_keywords, Articles.id==article_keywords.c.article_id),
+ (Keyword, Keyword.id==article_keywords.c.keyword_id)
+ )
+
+ \**kwargs include:
+
+ aliased - when joining, create anonymous aliases of each table. This is
+ used for self-referential joins or multiple joins to the same table.
+ Consider usage of the aliased(SomeClass) construct as a more explicit
+ approach to this.
+
+ from_joinpoint - when joins are specified using string property names,
+ locate the property from the mapper found in the most recent previous
+ join() call, instead of from the root entity.
"""
- return self._join(prop, id=id, outerjoin=False, aliased=aliased, from_joinpoint=from_joinpoint)
+ aliased, from_joinpoint = kwargs.pop('aliased', False), kwargs.pop('from_joinpoint', False)
+ if kwargs:
+ raise TypeError("unknown arguments: %s" % ','.join(kwargs.keys()))
+ return self.__join(props, outerjoin=False, create_aliases=aliased, from_joinpoint=from_joinpoint)
+ join = util.array_as_starargs_decorator(join)
- def outerjoin(self, prop, id=None, aliased=False, from_joinpoint=False):
+ def outerjoin(self, *props, **kwargs):
"""Create a left outer join against this ``Query`` object's criterion
and apply generatively, retunring the newly resulting ``Query``.
+
+ Usage is the same as the ``join()`` method.
- 'prop' may be one of:
- * a string property name, i.e. "rooms"
- * a class-mapped attribute, i.e. Houses.rooms
- * a 2-tuple containing one of the above, combined with a selectable
- which derives from the properties' mapped table
- * a list (not a tuple) containing a combination of any of the above.
+ """
+ aliased, from_joinpoint = kwargs.pop('aliased', False), kwargs.pop('from_joinpoint', False)
+ if kwargs:
+ raise TypeError("unknown arguments: %s" % ','.join(kwargs.keys()))
+ return self.__join(props, outerjoin=True, create_aliases=aliased, from_joinpoint=from_joinpoint)
+ outerjoin = util.array_as_starargs_decorator(outerjoin)
- e.g.::
+ def __join(self, keys, outerjoin, create_aliases, from_joinpoint):
+ self.__currenttables = util.Set(self.__currenttables)
+ self._polymorphic_adapters = self._polymorphic_adapters.copy()
- session.query(Company).outerjoin('employees')
- session.query(Company).outerjoin(['employees', 'tasks'])
- session.query(Houses).outerjoin([Colonials.rooms, Room.closets])
- session.query(Company).join([('employees', people.join(engineers)), Engineer.computers])
+ if not from_joinpoint:
+ self.__reset_joinpoint()
- """
- return self._join(prop, id=id, outerjoin=True, aliased=aliased, from_joinpoint=from_joinpoint)
-
- def _join(self, prop, id, outerjoin, aliased, from_joinpoint):
- (clause, mapper, aliases) = self._join_to(prop, outerjoin=outerjoin, start=from_joinpoint and self._joinpoint or self.mapper, create_aliases=aliased)
- # TODO: improve the generative check here to look for primary mapped entity, etc.
- q = self.__no_statement("join")
- q._from_obj = clause
- q._joinpoint = mapper
- q._aliases = aliases
- q.__generate_alias_ids()
-
- if aliases:
- q._aliases_tail = aliases
-
- a = aliases
- while a is not None:
- if isinstance(a, mapperutil.PropertyAliasedClauses):
- q._alias_ids.setdefault(a.mapper, []).append(a)
- q._alias_ids.setdefault(a.table, []).append(a)
- a = a.parentclauses
+ clause = self._from_obj
+ right_entity = None
+
+ for arg1 in util.to_list(keys):
+ prop = None
+ aliased_entity = False
+ alias_criterion = False
+ left_entity = right_entity
+ right_entity = right_mapper = None
+
+ if isinstance(arg1, tuple):
+ arg1, arg2 = arg1
else:
- break
+ arg2 = None
+
+ if isinstance(arg2, (interfaces.PropComparator, basestring)):
+ onclause = arg2
+ right_entity = arg1
+ elif isinstance(arg1, (interfaces.PropComparator, basestring)):
+ onclause = arg1
+ right_entity = arg2
+ else:
+ onclause = arg2
+ right_entity = arg1
- if id:
- q._alias_ids[id] = [aliases]
- return q
+ if isinstance(onclause, interfaces.PropComparator):
+ of_type = getattr(onclause, '_of_type', None)
+ prop = onclause.property
+ descriptor = onclause
+
+ if not left_entity:
+ left_entity = onclause.parententity
+
+ if of_type:
+ right_mapper = of_type
+ else:
+ right_mapper = prop.mapper
+
+ if not right_entity:
+ right_entity = right_mapper
+
+ elif isinstance(onclause, basestring):
+ if not left_entity:
+ left_entity = self._joinpoint_zero()
+
+ descriptor, prop = _entity_descriptor(left_entity, onclause)
+ right_mapper = prop.mapper
+ if not right_entity:
+ right_entity = right_mapper
+ elif onclause is None:
+ if not left_entity:
+ left_entity = self._joinpoint_zero()
+ else:
+ if not left_entity:
+ left_entity = self._joinpoint_zero()
+
+ if not clause:
+ if isinstance(onclause, interfaces.PropComparator):
+ clause = onclause.__clause_element__()
- def _get_joinable_tables(self):
- if not self.__joinable_tables or self.__joinable_tables[0] is not self._from_obj:
- currenttables = [self._from_obj]
- def visit_join(join):
- currenttables.append(join.left)
- currenttables.append(join.right)
- visitors.traverse(self._from_obj, visit_join=visit_join, traverse_options={'column_collections':False, 'aliased_selectables':False})
- self.__joinable_tables = (self._from_obj, currenttables)
- return currenttables
- else:
- return self.__joinable_tables[1]
+ for ent in self._mapper_entities:
+ if ent.corresponds_to(left_entity):
+ clause = ent.selectable
+ break
- def _join_to(self, keys, outerjoin=False, start=None, create_aliases=True):
- if start is None:
- start = self._joinpoint
+ if not clause:
+ raise exc.InvalidRequestError("Could not find a FROM clause to join from")
- clause = self._from_obj
+ bogus, right_selectable, is_aliased_class = _entity_info(right_entity)
- currenttables = self._get_joinable_tables()
+ if right_mapper and not is_aliased_class:
+ if right_entity is right_selectable:
- # determine if generated joins need to be aliased on the left
- # hand side.
- if self._aliases_head is self._aliases_tail is not None:
- adapt_against = self._aliases_tail.alias
- elif start is not self.mapper and self._aliases_tail:
- adapt_against = self._aliases_tail.alias
- else:
- adapt_against = None
+ if not right_selectable.is_derived_from(right_mapper.mapped_table):
+ raise sa_exc.InvalidRequestError("Selectable '%s' is not derived from '%s'" % (right_selectable.description, right_mapper.mapped_table.description))
- mapper = start
- alias = self._aliases_tail
+ if not isinstance(right_selectable, expression.Alias):
+ right_selectable = right_selectable.alias()
- if not isinstance(keys, list):
- keys = [keys]
-
- for key in keys:
- use_selectable = None
- of_type = None
- is_aliased_class = False
-
- if isinstance(key, tuple):
- key, use_selectable = key
-
- if isinstance(key, interfaces.PropComparator):
- prop = key.property
- if getattr(key, '_of_type', None):
- of_type = key._of_type
- if not use_selectable:
- use_selectable = key._of_type.mapped_table
- else:
- prop = mapper.get_property(key, resolve_synonyms=True)
-
- if use_selectable:
- if _is_aliased_class(use_selectable):
- use_selectable = use_selectable.alias
- is_aliased_class = True
- if not use_selectable.is_derived_from(prop.mapper.mapped_table):
- raise exceptions.InvalidRequestError("Selectable '%s' is not derived from '%s'" % (use_selectable.description, prop.mapper.mapped_table.description))
- if not isinstance(use_selectable, expression.Alias):
- use_selectable = use_selectable.alias()
- elif prop.mapper.with_polymorphic:
- use_selectable = prop.mapper._with_polymorphic_selectable()
- if not isinstance(use_selectable, expression.Alias):
- use_selectable = use_selectable.alias()
-
- if prop._is_self_referential() and not create_aliases and not use_selectable:
- raise exceptions.InvalidRequestError("Self-referential query on '%s' property requires aliased=True argument." % str(prop))
-
- if prop.table not in currenttables or create_aliases or use_selectable:
+ right_entity = aliased(right_mapper, right_selectable)
+ alias_criterion = True
+
+ elif right_mapper.with_polymorphic or isinstance(right_mapper.mapped_table, expression.Join):
+ aliased_entity = True
+ right_entity = aliased(right_mapper)
+ alias_criterion = True
- if use_selectable or create_aliases:
- alias = mapperutil.PropertyAliasedClauses(prop,
- prop.primaryjoin,
- prop.secondaryjoin,
- alias,
- alias=use_selectable,
- should_adapt=not is_aliased_class
- )
- crit = alias.primaryjoin
+ elif create_aliases:
+ right_entity = aliased(right_mapper)
+ alias_criterion = True
+
+ elif prop:
+ if prop.table in self.__currenttables:
+ if prop.secondary is not None and prop.secondary not in self.__currenttables:
+ # TODO: this check is not strong enough for different paths to the same endpoint which
+ # does not use secondary tables
+ raise sa_exc.InvalidRequestError("Can't join to property '%s'; a path to this table along a different secondary table already exists. Use the `alias=True` argument to `join()`." % descriptor)
+
+ continue
+
if prop.secondary:
- clause = clause.join(alias.secondary, crit, isouter=outerjoin)
- clause = clause.join(alias.alias, alias.secondaryjoin, isouter=outerjoin)
- else:
- clause = clause.join(alias.alias, crit, isouter=outerjoin)
- else:
- assert not prop.mapper.with_polymorphic
- pj, sj, source, dest, target_adapter = prop._create_joins(source_selectable=adapt_against)
- if sj:
- clause = clause.join(prop.secondary, pj, isouter=outerjoin)
- clause = clause.join(prop.table, sj, isouter=outerjoin)
- else:
- clause = clause.join(prop.table, pj, isouter=outerjoin)
-
- elif not create_aliases and prop.secondary is not None and prop.secondary not in currenttables:
- # TODO: this check is not strong enough for different paths to the same endpoint which
- # does not use secondary tables
- raise exceptions.InvalidRequestError("Can't join to property '%s'; a path to this table along a different secondary table already exists. Use the `alias=True` argument to `join()`." % prop.key)
+ self.__currenttables.add(prop.secondary)
+ self.__currenttables.add(prop.table)
- mapper = of_type or prop.mapper
+ right_entity = prop.mapper
- if use_selectable:
- adapt_against = use_selectable
-
- return (clause, mapper, alias)
+ if prop:
+ onclause = prop
+
+ clause = orm_join(clause, right_entity, onclause, isouter=outerjoin)
+ if alias_criterion:
+ self._filter_aliases = ORMAdapter(right_entity,
+ equivalents=right_mapper._equivalent_columns, chain_to=self._filter_aliases)
+
+ if aliased_entity:
+ self.__mapper_loads_polymorphically_with(right_mapper, ORMAdapter(right_entity, equivalents=right_mapper._equivalent_columns))
+
+ self._from_obj = clause
+ self._joinpoint = right_entity
+ __join = _generative(__no_statement_condition, __no_limit_offset)(__join)
def reset_joinpoint(self):
"""return a new Query reset the 'joinpoint' of this Query reset
@@ -794,13 +928,8 @@ class Query(object):
the root.
"""
- q = self.__no_statement("reset_joinpoint")
- q._joinpoint = q.mapper
- if q.table not in q._get_joinable_tables():
- q._aliases_head = q._aliases_tail = mapperutil.AliasedClauses(q._from_obj, equivalents=q.mapper._equivalent_columns)
- else:
- q._aliases_head = q._aliases_tail = None
- return q
+ self.__reset_joinpoint()
+ reset_joinpoint = _generative(__no_statement_condition)(reset_joinpoint)
def select_from(self, from_obj):
"""Set the `from_obj` parameter of the query and return the newly
@@ -811,14 +940,13 @@ class Query(object):
`from_obj` is a single table or selectable.
"""
- new = self.__no_criterion('select_from')
if isinstance(from_obj, (tuple, list)):
util.warn_deprecated("select_from() now accepts a single Selectable as its argument, which replaces any existing FROM criterion.")
from_obj = from_obj[-1]
+
+ self.__set_select_from(from_obj)
+ select_from = _generative(__no_from_condition, __no_criterion_condition)(select_from)
- new.__set_select_from(from_obj)
- return new
-
def __getitem__(self, item):
if isinstance(item, slice):
start = item.start
@@ -863,9 +991,8 @@ class Query(object):
``Query``.
"""
- new = self.__no_statement("distinct")
- new._distinct = True
- return new
+ self._distinct = True
+ distinct = _generative(__no_statement_condition)(distinct)
def all(self):
"""Return the results represented by this ``Query`` as a list.
@@ -875,7 +1002,6 @@ class Query(object):
"""
return list(self)
-
def from_statement(self, statement):
"""Execute the given SELECT statement and return results.
@@ -891,9 +1017,8 @@ class Query(object):
"""
if isinstance(statement, basestring):
statement = sql.text(statement)
- q = self.__no_criterion('from_statement')
- q._statement = statement
- return q
+ self._statement = statement
+ from_statement = _generative(__no_criterion_condition)(from_statement)
def first(self):
"""Return the first result of this ``Query`` or None if the result doesn't contain any row.
@@ -901,9 +1026,6 @@ class Query(object):
This results in an execution of the underlying query.
"""
- if self._column_aggregate is not None:
- return self._col_aggregate(*self._column_aggregate)
-
ret = list(self[0:1])
if len(ret) > 0:
return ret[0]
@@ -916,17 +1038,14 @@ class Query(object):
This results in an execution of the underlying query.
"""
- if self._column_aggregate is not None:
- return self._col_aggregate(*self._column_aggregate)
-
ret = list(self[0:2])
if len(ret) == 1:
return ret[0]
elif len(ret) == 0:
- raise exceptions.InvalidRequestError('No rows returned for one()')
+ raise sa_exc.InvalidRequestError('No rows returned for one()')
else:
- raise exceptions.InvalidRequestError('Multiple rows returned for one()')
+ raise sa_exc.InvalidRequestError('Multiple rows returned for one()')
def __iter__(self):
context = self._compile_context()
@@ -936,37 +1055,41 @@ class Query(object):
return self._execute_and_instances(context)
def _execute_and_instances(self, querycontext):
- result = self.session.execute(querycontext.statement, params=self._params, mapper=self.mapper, instance=self._refresh_instance)
- return self.iterate_instances(result, querycontext=querycontext)
+ result = self.session.execute(querycontext.statement, params=self._params, mapper=self._mapper_zero_or_none(), instance=self._refresh_instance)
+ return self.iterate_instances(result, querycontext)
- def instances(self, cursor, *mappers_or_columns, **kwargs):
- return list(self.iterate_instances(cursor, *mappers_or_columns, **kwargs))
+ def instances(self, cursor, __context=None):
+ return list(self.iterate_instances(cursor, __context))
- def iterate_instances(self, cursor, *mappers_or_columns, **kwargs):
+ def iterate_instances(self, cursor, __context=None):
session = self.session
- context = kwargs.pop('querycontext', None)
+ context = __context
if context is None:
context = QueryContext(self)
context.runid = _new_runid()
- entities = self._entities + [_QueryEntity.legacy_guess_type(mc) for mc in mappers_or_columns]
-
- if getattr(self, '_no_filters', False):
- filter = None
- single_entity = custom_rows = False
- else:
- single_entity = isinstance(entities[0], _PrimaryMapperEntity) and len(entities) == 1
- custom_rows = single_entity and 'append_result' in context.extension.methods
-
+ filtered = bool(list(self._mapper_entities))
+ single_entity = filtered and len(self._entities) == 1
+
+ if filtered:
if single_entity:
filter = util.OrderedIdentitySet
else:
filter = util.OrderedSet
-
- process = [query_entity.row_processor(self, context, single_entity) for query_entity in entities]
+ else:
+ filter = None
+
+ custom_rows = single_entity and 'append_result' in self._entities[0].extension.methods
+ (process, labels) = zip(*[query_entity.row_processor(self, context, custom_rows) for query_entity in self._entities])
+
+ if not single_entity:
+ labels = dict([(label, property(util.itemgetter(i))) for i, label in enumerate(labels) if label])
+ rowtuple = type.__new__(type, "RowTuple", (tuple,), labels)
+ rowtuple.keys = labels.keys
+
while True:
context.progress = util.Set()
context.partials = {}
@@ -974,7 +1097,7 @@ class Query(object):
if self._yield_per:
fetch = cursor.fetchmany(self._yield_per)
if not fetch:
- return
+ break
else:
fetch = cursor.fetchall()
@@ -985,23 +1108,20 @@ class Query(object):
elif single_entity:
rows = [process[0](context, row) for row in fetch]
else:
- rows = [tuple([proc(context, row) for proc in process]) for row in fetch]
+ rows = [rowtuple([proc(context, row) for proc in process]) for row in fetch]
if filter:
rows = filter(rows)
- if context.refresh_instance and context.only_load_props and context.refresh_instance in context.progress:
- context.refresh_instance.commit(context.only_load_props)
+ if context.refresh_instance and self._only_load_props and context.refresh_instance in context.progress:
+ context.refresh_instance.commit(self._only_load_props)
context.progress.remove(context.refresh_instance)
- for ii in context.progress:
- context.attributes.get(('populating_mapper', ii), _state_mapper(ii))._post_instance(context, ii)
- ii.commit_all()
-
+ session._finalize_loaded(context.progress)
+
for ii, attrs in context.partials.items():
- context.attributes.get(('populating_mapper', ii), _state_mapper(ii))._post_instance(context, ii, only_load_props=attrs)
ii.commit(attrs)
-
+
for row in rows:
yield row
@@ -1010,9 +1130,18 @@ class Query(object):
def _get(self, key=None, ident=None, refresh_instance=None, lockmode=None, only_load_props=None):
lockmode = lockmode or self._lockmode
- if not self._populate_existing and not refresh_instance and not self.mapper.always_refresh and lockmode is None:
+ if not self._populate_existing and not refresh_instance and not self._mapper_zero().always_refresh and lockmode is None:
try:
- return self.session.identity_map[key]
+ instance = self.session.identity_map[key]
+ state = attributes.instance_state(instance)
+ if state.expired:
+ try:
+ state()
+ except orm_exc.ObjectDeletedError:
+ # TODO: should we expunge ? if so, should we expunge here ? or in mapper._load_scalar_attributes ?
+ self.session.expunge(instance)
+ return None
+ return instance
except KeyError:
pass
@@ -1022,27 +1151,29 @@ class Query(object):
else:
ident = util.to_list(ident)
- q = self
-
- # dont use 'polymorphic' mapper if we are refreshing an instance
- if refresh_instance and q.mapper is not q.mapper:
- q = q.__reset_all(q.mapper, '_get')
+ if refresh_instance is None:
+ q = self.__no_criterion()
+ else:
+ q = self._clone()
if ident is not None:
- q = q.__no_criterion('get')
+ mapper = q._mapper_zero()
params = {}
- (_get_clause, _get_params) = q.mapper._get_clause
- q = q.filter(_get_clause)
- for i, primary_key in enumerate(q.mapper.primary_key):
+ (_get_clause, _get_params) = mapper._get_clause
+
+ _get_clause = q._adapt_clause(_get_clause, True, False)
+ q._criterion = _get_clause
+
+ for i, primary_key in enumerate(mapper.primary_key):
try:
params[_get_params[primary_key].key] = ident[i]
except IndexError:
- raise exceptions.InvalidRequestError("Could not find enough values to formulate primary key for query.get(); primary key columns are %s" % ', '.join(["'%s'" % str(c) for c in q.mapper.primary_key]))
- q = q.params(params)
+ raise sa_exc.InvalidRequestError("Could not find enough values to formulate primary key for query.get(); primary key columns are %s" % ', '.join(["'%s'" % str(c) for c in q.mapper.primary_key]))
+ q._params = params
if lockmode is not None:
- q = q.with_lockmode(lockmode)
- q = q.__get_options(populate_existing=bool(refresh_instance), version_check=(lockmode is not None), only_load_props=only_load_props, refresh_instance=refresh_instance)
+ q._lockmode = lockmode
+ q.__get_options(populate_existing=bool(refresh_instance), version_check=(lockmode is not None), only_load_props=only_load_props, refresh_instance=refresh_instance)
q._order_by = None
try:
# call using all() to avoid LIMIT compilation complexity
@@ -1053,41 +1184,26 @@ class Query(object):
def _select_args(self):
return {'limit':self._limit, 'offset':self._offset, 'distinct':self._distinct, 'group_by':self._group_by or None, 'having':self._having or None}
_select_args = property(_select_args)
-
+
def _should_nest_selectable(self):
kwargs = self._select_args
return (kwargs.get('limit') is not None or kwargs.get('offset') is not None or kwargs.get('distinct', False))
_should_nest_selectable = property(_should_nest_selectable)
- def count(self, whereclause=None, params=None, **kwargs):
- """Apply this query's criterion to a SELECT COUNT statement.
-
- the whereclause, params and \**kwargs arguments are deprecated. use filter()
- and other generative methods to establish modifiers.
-
- """
- q = self
- if whereclause is not None:
- q = q.filter(whereclause)
- if params is not None:
- q = q.params(params)
- q = q._legacy_select_kwargs(**kwargs)
- return q._count()
-
- def _count(self):
+ def count(self):
"""Apply this query's criterion to a SELECT COUNT statement.
this is the purely generative version which will become
the public method in version 0.5.
"""
- return self._col_aggregate(sql.literal_column('1'), sql.func.count, nested_cols=list(self.mapper.primary_key))
+ return self._col_aggregate(sql.literal_column('1'), sql.func.count, nested_cols=list(self._mapper_zero().primary_key))
def _col_aggregate(self, col, func, nested_cols=None):
whereclause = self._criterion
-
+
context = QueryContext(self)
- from_obj = self._from_obj
+ from_obj = self.__mapper_zero_from_obj()
if self._should_nest_selectable:
if not nested_cols:
@@ -1097,113 +1213,97 @@ class Query(object):
s = sql.select([func(s.corresponding_column(col) or col)]).select_from(s)
else:
s = sql.select([func(col)], whereclause, from_obj=from_obj, **self._select_args)
-
+
if self._autoflush and not self._populate_existing:
self.session._autoflush()
- return self.session.scalar(s, params=self._params, mapper=self.mapper)
+ return self.session.scalar(s, params=self._params, mapper=self._mapper_zero())
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):
-
+ def _compile_context(self, labels=True):
context = QueryContext(self)
- if self._statement:
- self._statement.use_labels = True
- context.statement = self._statement
+ if context.statement:
return context
- from_obj = self._from_obj
- adapter = self._aliases_head
-
if self._lockmode:
try:
- for_update = {'read':'read','update':True,'update_nowait':'nowait',None:False}[self._lockmode]
+ for_update = {'read': 'read',
+ 'update': True,
+ 'update_nowait': 'nowait',
+ None: False}[self._lockmode]
except KeyError:
- raise exceptions.ArgumentError("Unknown lockmode '%s'" % self._lockmode)
+ raise sa_exc.ArgumentError("Unknown lockmode '%s'" % self._lockmode)
else:
for_update = False
-
- context.from_clause = from_obj
- context.whereclause = self._criterion
- context.order_by = self._order_by
-
+
for entity in self._entities:
entity.setup_context(self, context)
-
- if self._eager_loaders and self._should_nest_selectable:
- # eager loaders are present, and the SELECT has limiting criterion
- # produce a "wrapped" selectable.
-
+
+ eager_joins = context.eager_joins.values()
+
+ if context.from_clause:
+ froms = [context.from_clause] # "load from a single FROM" mode, i.e. when select_from() or join() is used
+ else:
+ froms = context.froms # "load from discrete FROMs" mode, i.e. when each _MappedEntity has its own FROM
+
+ if eager_joins and self._should_nest_selectable:
+ # for eager joins present and LIMIT/OFFSET/DISTINCT, wrap the query inside a select,
+ # then append eager joins onto that
+
if context.order_by:
- context.order_by = [expression._literal_as_text(o) for o in util.to_list(context.order_by) or []]
- if adapter:
- context.order_by = adapter.adapt_list(context.order_by)
- # locate all embedded Column clauses so they can be added to the
- # "inner" select statement where they'll be available to the enclosing
- # statement's "order by"
- # TODO: this likely doesn't work with very involved ORDER BY expressions,
- # such as those including subqueries
order_by_col_expr = list(chain(*[sql_util.find_columns(o) for o in context.order_by]))
else:
context.order_by = None
order_by_col_expr = []
-
- if adapter:
- context.primary_columns = adapter.adapt_list(context.primary_columns)
-
- inner = sql.select(context.primary_columns + order_by_col_expr, context.whereclause, from_obj=context.from_clause, use_labels=True, correlate=False, order_by=context.order_by, **self._select_args).alias()
- local_adapter = sql_util.ClauseAdapter(inner)
- context.row_adapter = mapperutil.create_row_adapter(inner, equivalent_columns=self.mapper._equivalent_columns)
+ inner = sql.select(context.primary_columns + order_by_col_expr, context.whereclause, from_obj=froms, use_labels=labels, correlate=False, order_by=context.order_by, **self._select_args)
+
+ if self._correlate:
+ inner = inner.correlate(*self._correlate)
+
+ inner = inner.alias()
- statement = sql.select([inner] + context.secondary_columns, for_update=for_update, use_labels=True)
+ equivs = self.__all_equivs()
- if context.eager_joins:
- eager_joins = local_adapter.traverse(context.eager_joins)
- statement.append_from(eager_joins)
+ context.adapter = sql_util.ColumnAdapter(inner, equivs)
+
+ statement = sql.select([inner] + context.secondary_columns, for_update=for_update, use_labels=labels)
+
+ from_clause = inner
+ for eager_join in eager_joins:
+ # EagerLoader places a 'stop_on' attribute on the join,
+ # giving us a marker as to where the "splice point" of the join should be
+ from_clause = sql_util.splice_joins(from_clause, eager_join, eager_join.stop_on)
+
+ statement.append_from(from_clause)
if context.order_by:
+ local_adapter = sql_util.ClauseAdapter(inner)
statement.append_order_by(*local_adapter.copy_and_process(context.order_by))
statement.append_order_by(*context.eager_order_by)
else:
- if context.order_by:
- context.order_by = [expression._literal_as_text(o) for o in util.to_list(context.order_by) or []]
- if adapter:
- context.order_by = adapter.adapt_list(context.order_by)
- else:
+ if not context.order_by:
context.order_by = None
-
- if adapter:
- context.primary_columns = adapter.adapt_list(context.primary_columns)
- context.row_adapter = mapperutil.create_row_adapter(adapter.alias, equivalent_columns=self.mapper._equivalent_columns)
-
+
if self._distinct and context.order_by:
order_by_col_expr = list(chain(*[sql_util.find_columns(o) for o in context.order_by]))
context.primary_columns += order_by_col_expr
- statement = sql.select(context.primary_columns + context.secondary_columns, context.whereclause, from_obj=from_obj, use_labels=True, for_update=for_update, order_by=context.order_by, **self._select_args)
+ froms += context.eager_joins.values()
- if context.eager_joins:
- if adapter:
- context.eager_joins = adapter.adapt_clause(context.eager_joins)
- statement.append_from(context.eager_joins)
+ statement = sql.select(context.primary_columns + context.secondary_columns, context.whereclause, from_obj=froms, use_labels=labels, for_update=for_update, correlate=False, order_by=context.order_by, **self._select_args)
+ if self._correlate:
+ statement = statement.correlate(*self._correlate)
if context.eager_order_by:
- if adapter:
- context.eager_order_by = adapter.adapt_list(context.eager_order_by)
statement.append_order_by(*context.eager_order_by)
- # polymorphic mappers which have concrete tables in their hierarchy usually
- # require row aliasing unconditionally.
- if not context.row_adapter and self.mapper._requires_row_aliasing:
- context.row_adapter = mapperutil.create_row_adapter(self.table, equivalent_columns=self.mapper._equivalent_columns)
-
- context.statement = statement
+ context.statement = statement._annotate({'_halt_adapt': True})
return context
@@ -1213,462 +1313,257 @@ class Query(object):
def __str__(self):
return str(self.compile())
- # DEPRECATED LAND !
-
- def _generative_col_aggregate(self, col, func):
- """apply the given aggregate function to the query and return the newly
- resulting ``Query``. (deprecated)
- """
- if self._column_aggregate is not None:
- raise exceptions.InvalidRequestError("Query already contains an aggregate column or function")
- q = self.__no_statement("aggregate")
- q._column_aggregate = (col, func)
- return q
-
- def apply_min(self, col):
- """apply the SQL ``min()`` function against the given column to the
- query and return the newly resulting ``Query``.
-
- DEPRECATED.
- """
- return self._generative_col_aggregate(col, sql.func.min)
-
- def apply_max(self, col):
- """apply the SQL ``max()`` function against the given column to the
- query and return the newly resulting ``Query``.
-
- DEPRECATED.
- """
- return self._generative_col_aggregate(col, sql.func.max)
-
- def apply_sum(self, col):
- """apply the SQL ``sum()`` function against the given column to the
- query and return the newly resulting ``Query``.
-
- DEPRECATED.
- """
- return self._generative_col_aggregate(col, sql.func.sum)
-
- def apply_avg(self, col):
- """apply the SQL ``avg()`` function against the given column to the
- query and return the newly resulting ``Query``.
-
- DEPRECATED.
- """
- return self._generative_col_aggregate(col, sql.func.avg)
-
- def list(self): #pragma: no cover
- """DEPRECATED. use all()"""
-
- return list(self)
-
- def scalar(self): #pragma: no cover
- """DEPRECATED. use first()"""
-
- return self.first()
-
- def _legacy_filter_by(self, *args, **kwargs): #pragma: no cover
- return self.filter(self._legacy_join_by(args, kwargs, start=self._joinpoint))
-
- def count_by(self, *args, **params): #pragma: no cover
- """DEPRECATED. use query.filter_by(\**params).count()"""
-
- return self.count(self.join_by(*args, **params))
-
- def select_whereclause(self, whereclause=None, params=None, **kwargs): #pragma: no cover
- """DEPRECATED. use query.filter(whereclause).all()"""
-
- q = self.filter(whereclause)._legacy_select_kwargs(**kwargs)
- if params is not None:
- q = q.params(params)
- return list(q)
+class _QueryEntity(object):
+ """represent an entity column returned within a Query result."""
- def _legacy_select_from(self, from_obj):
- q = self._clone()
- if len(from_obj) > 1:
- raise exceptions.ArgumentError("Multiple-entry from_obj parameter no longer supported")
- q._from_obj = from_obj[0]
- return q
+ def __new__(cls, *args, **kwargs):
+ if cls is _QueryEntity:
+ entity = args[1]
+ if _is_mapped_class(entity):
+ cls = _MapperEntity
+ else:
+ cls = _ColumnEntity
+ return object.__new__(cls)
- def _legacy_select_kwargs(self, **kwargs): #pragma: no cover
- q = self
- if "order_by" in kwargs and kwargs['order_by']:
- q = q.order_by(kwargs['order_by'])
- if "group_by" in kwargs:
- q = q.group_by(kwargs['group_by'])
- if "from_obj" in kwargs:
- q = q._legacy_select_from(kwargs['from_obj'])
- if "lockmode" in kwargs:
- q = q.with_lockmode(kwargs['lockmode'])
- if "distinct" in kwargs:
- q = q.distinct()
- if "limit" in kwargs:
- q = q.limit(kwargs['limit'])
- if "offset" in kwargs:
- q = q.offset(kwargs['offset'])
+ def _clone(self):
+ q = self.__class__.__new__(self.__class__)
+ q.__dict__ = self.__dict__.copy()
return q
+class _MapperEntity(_QueryEntity):
+ """mapper/class/AliasedClass entity"""
- def get_by(self, *args, **params): #pragma: no cover
- """DEPRECATED. use query.filter_by(\**params).first()"""
-
- ret = self._extension.get_by(self, *args, **params)
- if ret is not mapper.EXT_CONTINUE:
- return ret
-
- return self._legacy_filter_by(*args, **params).first()
-
- def select_by(self, *args, **params): #pragma: no cover
- """DEPRECATED. use use query.filter_by(\**params).all()."""
-
- ret = self._extension.select_by(self, *args, **params)
- if ret is not mapper.EXT_CONTINUE:
- return ret
-
- return self._legacy_filter_by(*args, **params).list()
-
- def join_by(self, *args, **params): #pragma: no cover
- """DEPRECATED. use join() to construct joins based on attribute names."""
+ def __init__(self, query, entity, entity_name=None):
+ self.primary_entity = not query._entities
+ query._entities.append(self)
- return self._legacy_join_by(args, params, start=self._joinpoint)
+ self.entities = [entity]
+ self.entity_zero = entity
+ self.entity_name = entity_name
- def _build_select(self, arg=None, params=None, **kwargs): #pragma: no cover
- if isinstance(arg, sql.FromClause) and arg.supports_execution():
- return self.from_statement(arg)
- elif arg is not None:
- return self.filter(arg)._legacy_select_kwargs(**kwargs)
+ def setup_entity(self, entity, mapper, adapter, from_obj, is_aliased_class, with_polymorphic):
+ self.mapper = mapper
+ self.extension = self.mapper.extension
+ self.adapter = adapter
+ self.selectable = from_obj
+ self._with_polymorphic = with_polymorphic
+ self.is_aliased_class = is_aliased_class
+ if is_aliased_class:
+ self.path_entity = self.entity = self.entity_zero = entity
else:
- return self._legacy_select_kwargs(**kwargs)
-
- def selectfirst(self, arg=None, **kwargs): #pragma: no cover
- """DEPRECATED. use query.filter(whereclause).first()"""
-
- return self._build_select(arg, **kwargs).first()
-
- def selectone(self, arg=None, **kwargs): #pragma: no cover
- """DEPRECATED. use query.filter(whereclause).one()"""
-
- return self._build_select(arg, **kwargs).one()
-
- def select(self, arg=None, **kwargs): #pragma: no cover
- """DEPRECATED. use query.filter(whereclause).all(), or query.from_statement(statement).all()"""
+ self.path_entity = mapper.base_mapper
+ self.entity = self.entity_zero = mapper
- ret = self._extension.select(self, arg=arg, **kwargs)
- if ret is not mapper.EXT_CONTINUE:
- return ret
- return self._build_select(arg, **kwargs).all()
-
- def execute(self, clauseelement, params=None, *args, **kwargs): #pragma: no cover
- """DEPRECATED. use query.from_statement().all()"""
-
- return self._select_statement(clauseelement, params, **kwargs)
-
- def select_statement(self, statement, **params): #pragma: no cover
- """DEPRECATED. Use query.from_statement(statement)"""
-
- return self._select_statement(statement, params)
-
- def select_text(self, text, **params): #pragma: no cover
- """DEPRECATED. Use query.from_statement(statement)"""
+ def set_with_polymorphic(self, query, cls_or_mappers, selectable):
+ if cls_or_mappers is None:
+ query._reset_polymorphic_adapter(self.mapper)
+ return
- return self._select_statement(text, params)
-
- def _select_statement(self, statement, params=None, **kwargs): #pragma: no cover
- q = self.from_statement(statement)
- if params is not None:
- q = q.params(params)
- q.__get_options(**kwargs)
- return list(q)
-
- def join_to(self, key): #pragma: no cover
- """DEPRECATED. use join() to create joins based on property names."""
-
- [keys, p] = self._locate_prop(key)
- return self.join_via(keys)
+ mappers, from_obj = self.mapper._with_polymorphic_args(cls_or_mappers, selectable)
+ self._with_polymorphic = mappers
- def join_via(self, keys): #pragma: no cover
- """DEPRECATED. use join() to create joins based on property names."""
+ # TODO: do the wrapped thing here too so that with_polymorphic() can be
+ # applied to aliases
+ if not self.is_aliased_class:
+ self.selectable = from_obj
+ self.adapter = query._get_polymorphic_adapter(self, from_obj)
- mapper = self._joinpoint
- clause = None
- for key in keys:
- prop = mapper.get_property(key, resolve_synonyms=True)
- if clause is None:
- clause = prop._get_join(mapper)
- else:
- clause &= prop._get_join(mapper)
- mapper = prop.mapper
+ def corresponds_to(self, entity):
+ if _is_aliased_class(entity):
+ return entity is self.path_entity
+ else:
+ return entity.base_mapper is self.path_entity
- return clause
+ def _get_entity_clauses(self, query, context):
- def _legacy_join_by(self, args, params, start=None): #pragma: no cover
- import properties
+ adapter = None
+ if not self.is_aliased_class and query._polymorphic_adapters:
+ for mapper in self.mapper.iterate_to_root():
+ adapter = query._polymorphic_adapters.get(mapper.mapped_table, None)
+ if adapter:
+ break
- clause = None
- for arg in args:
- if clause is None:
- clause = arg
- else:
- clause &= arg
+ if not adapter and self.adapter:
+ adapter = self.adapter
- for key, value in params.iteritems():
- (keys, prop) = self._locate_prop(key, start=start)
- if isinstance(prop, properties.PropertyLoader):
- c = prop.compare(operators.eq, value) & self.join_via(keys[:-1])
+ if adapter:
+ if query._from_obj_alias:
+ ret = adapter.wrap(query._from_obj_alias)
else:
- c = prop.compare(operators.eq, value) & self.join_via(keys)
- if clause is None:
- clause = c
- else:
- clause &= c
- return clause
-
- def _locate_prop(self, key, start=None): #pragma: no cover
- import properties
- keys = []
- seen = util.Set()
- def search_for_prop(mapper_):
- if mapper_ in seen:
- return None
- seen.add(mapper_)
-
- prop = mapper_.get_property(key, resolve_synonyms=True, raiseerr=False)
- if prop is not None:
- if isinstance(prop, properties.PropertyLoader):
- keys.insert(0, prop.key)
- return prop
- else:
- for prop in mapper_.iterate_properties:
- if not isinstance(prop, properties.PropertyLoader):
- continue
- x = search_for_prop(prop.mapper)
- if x:
- keys.insert(0, prop.key)
- return x
- else:
- return None
- p = search_for_prop(start or self.mapper)
- if p is None:
- raise exceptions.InvalidRequestError("Can't locate property named '%s'" % key)
- return [keys, p]
-
- def selectfirst_by(self, *args, **params): #pragma: no cover
- """DEPRECATED. Use query.filter_by(\**kwargs).first()"""
-
- return self._legacy_filter_by(*args, **params).first()
-
- def selectone_by(self, *args, **params): #pragma: no cover
- """DEPRECATED. Use query.filter_by(\**kwargs).one()"""
-
- return self._legacy_filter_by(*args, **params).one()
-
- for deprecated_method in ('list', 'scalar', 'count_by',
- 'select_whereclause', 'get_by', 'select_by',
- 'join_by', 'selectfirst', 'selectone', 'select',
- 'execute', 'select_statement', 'select_text',
- 'join_to', 'join_via', 'selectfirst_by',
- 'selectone_by', 'apply_max', 'apply_min',
- 'apply_avg', 'apply_sum'):
- locals()[deprecated_method] = \
- util.deprecated(None, False)(locals()[deprecated_method])
-
-class _QueryEntity(object):
- """represent an entity column returned within a Query result."""
-
- def legacy_guess_type(self, e):
- if isinstance(e, type):
- return _MapperEntity(mapper=mapper.class_mapper(e))
- elif isinstance(e, mapper.Mapper):
- return _MapperEntity(mapper=e)
+ ret = adapter
else:
- return _ColumnEntity(column=e)
- legacy_guess_type=classmethod(legacy_guess_type)
+ ret = query._from_obj_alias
-class _MapperEntity(_QueryEntity):
- """entity column corresponding to mapped ORM instances."""
-
- def __init__(self, mapper, alias=None, id=None):
- self.mapper = mapper
- self.alias = alias
- self.alias_id = id
-
- def _get_entity_clauses(self, query):
- if self.alias:
- return self.alias
- elif self.alias_id:
- try:
- return query._alias_ids[self.alias_id][0]
- except KeyError:
- raise exceptions.InvalidRequestError("Query has no alias identified by '%s'" % self.alias_id)
-
- l = query._alias_ids.get(self.mapper)
- if l:
- if len(l) > 1:
- raise exceptions.InvalidRequestError("Ambiguous join for entity '%s'; specify id=<someid> to query.join()/query.add_entity()" % str(self.mapper))
- return l[0]
- else:
- return None
-
- def row_processor(self, query, context, single_entity):
- clauses = self._get_entity_clauses(query)
- if clauses:
- def proc(context, row):
- return self.mapper._instance(context, clauses.row_decorator(row), None)
- else:
- def proc(context, row):
- return self.mapper._instance(context, row, None)
-
- return proc
-
- def setup_context(self, query, context):
- clauses = self._get_entity_clauses(query)
- for value in self.mapper.iterate_properties:
- context.exec_with_path(self.mapper, value.key, value.setup, context, parentclauses=clauses)
+ return ret
- def __str__(self):
- return str(self.mapper)
+ def row_processor(self, query, context, custom_rows):
+ adapter = self._get_entity_clauses(query, context)
-class _PrimaryMapperEntity(_MapperEntity):
- """entity column corresponding to the 'primary' (first) mapped ORM instance."""
+ if context.adapter and adapter:
+ adapter = adapter.wrap(context.adapter)
+ elif not adapter:
+ adapter = context.adapter
- def row_processor(self, query, context, single_entity):
- if single_entity and 'append_result' in context.extension.methods:
+ # polymorphic mappers which have concrete tables in their hierarchy usually
+ # require row aliasing unconditionally.
+ if not adapter and self.mapper._requires_row_aliasing:
+ adapter = sql_util.ColumnAdapter(self.selectable, self.mapper._equivalent_columns)
+
+ if self.primary_entity:
+ _instance = self.mapper._instance_processor(context, (self.path_entity,), adapter,
+ extension=self.extension, only_load_props=query._only_load_props, refresh_instance=context.refresh_instance
+ )
+ else:
+ _instance = self.mapper._instance_processor(context, (self.path_entity,), adapter)
+
+ if custom_rows:
def main(context, row, result):
- if context.row_adapter:
- row = context.row_adapter(row)
- self.mapper._instance(context, row, result,
- extension=context.extension, only_load_props=context.only_load_props, refresh_instance=context.refresh_instance
- )
- elif context.row_adapter:
- def main(context, row):
- return self.mapper._instance(context, context.row_adapter(row), None,
- extension=context.extension, only_load_props=context.only_load_props, refresh_instance=context.refresh_instance
- )
+ _instance(row, result)
else:
def main(context, row):
- return self.mapper._instance(context, row, None,
- extension=context.extension, only_load_props=context.only_load_props, refresh_instance=context.refresh_instance
- )
+ return _instance(row, None)
- return main
+ if self.is_aliased_class:
+ entname = self.entity._sa_label_name
+ else:
+ entname = self.mapper.class_.__name__
+
+ return main, entname
def setup_context(self, query, context):
# if single-table inheritance mapper, add "typecol IN (polymorphic)" criterion so
# that we only load the appropriate types
if self.mapper.single and self.mapper.inherits is not None and self.mapper.polymorphic_on is not None and self.mapper.polymorphic_identity is not None:
context.whereclause = sql.and_(context.whereclause, self.mapper.polymorphic_on.in_([m.polymorphic_identity for m in self.mapper.polymorphic_iterator()]))
-
- if context.order_by is False:
- if self.mapper.order_by:
- context.order_by = self.mapper.order_by
- elif context.from_clause.default_order_by():
- context.order_by = context.from_clause.default_order_by()
-
- for value in self.mapper._iterate_polymorphic_properties(query._with_polymorphic, context.from_clause):
+
+ context.froms.append(self.selectable)
+
+ adapter = self._get_entity_clauses(query, context)
+
+ if self.primary_entity:
+ if context.order_by is False:
+ # the "default" ORDER BY use case applies only to "mapper zero". the "from clause" default should
+ # go away in 0.5 (or...maybe 0.6).
+ if self.mapper.order_by:
+ context.order_by = self.mapper.order_by
+ elif context.from_clause:
+ context.order_by = context.from_clause.default_order_by()
+ else:
+ context.order_by = self.selectable.default_order_by()
+ if context.order_by and adapter:
+ context.order_by = adapter.adapt_list(util.to_list(context.order_by))
+
+ for value in self.mapper._iterate_polymorphic_properties(self._with_polymorphic):
if query._only_load_props and value.key not in query._only_load_props:
continue
- context.exec_with_path(self.mapper, value.key, value.setup, context, only_load_props=query._only_load_props)
+ value.setup(context, self, (self.path_entity,), adapter, only_load_props=query._only_load_props, column_collection=context.primary_columns)
+
+ def __str__(self):
+ return str(self.mapper)
+
class _ColumnEntity(_QueryEntity):
- """entity column corresponding to Table or selectable columns."""
+ """Column/expression based entity."""
+
+ def __init__(self, query, column, entity_name=None):
+ if isinstance(column, expression.FromClause) and not isinstance(column, expression.ColumnElement):
+ for c in column.c:
+ _ColumnEntity(query, c)
+ return
+
+ query._entities.append(self)
- def __init__(self, column, id):
if isinstance(column, basestring):
column = sql.literal_column(column)
-
- if column and isinstance(column, sql.ColumnElement) and not hasattr(column, '_label'):
+ elif isinstance(column, (attributes.QueryableAttribute, mapper.Mapper._CompileOnAttr)):
+ column = column.__clause_element__()
+ elif not isinstance(column, sql.ColumnElement):
+ raise sa_exc.InvalidRequestError("Invalid column expression '%r'" % column)
+
+ if not hasattr(column, '_label'):
column = column.label(None)
+
self.column = column
- self.alias_id = id
+ self.entity_name = None
+ self.froms = util.Set()
+ self.entities = util.OrderedSet([elem._annotations['parententity'] for elem in visitors.iterate(column, {}) if 'parententity' in elem._annotations])
+ if self.entities:
+ self.entity_zero = list(self.entities)[0]
+ else:
+ self.entity_zero = None
+
+ def setup_entity(self, entity, mapper, adapter, from_obj, is_aliased_class, with_polymorphic):
+ self.froms.add(from_obj)
def __resolve_expr_against_query_aliases(self, query, expr, context):
- if not query._alias_ids:
- return expr
-
- if ('_ColumnEntity', expr) in context.attributes:
- return context.attributes[('_ColumnEntity', expr)]
-
- if self.alias_id:
- try:
- aliases = query._alias_ids[self.alias_id][0]
- except KeyError:
- raise exceptions.InvalidRequestError("Query has no alias identified by '%s'" % self.alias_id)
+ return query._adapt_clause(expr, False, True)
- def _locate_aliased(element):
- if element in query._alias_ids:
- return aliases
- else:
- def _locate_aliased(element):
- if element in query._alias_ids:
- aliases = query._alias_ids[element]
- if len(aliases) > 1:
- raise exceptions.InvalidRequestError("Ambiguous join for entity '%s'; specify id=<someid> to query.join()/query.add_column(), or use the aliased() function to use explicit class aliases." % expr)
- return aliases[0]
- return None
-
- class Adapter(visitors.ClauseVisitor):
- def before_clone(self, element):
- if isinstance(element, expression.FromClause):
- alias = _locate_aliased(element)
- if alias:
- return alias.alias
-
- if hasattr(element, 'table'):
- alias = _locate_aliased(element.table)
- if alias:
- return alias.aliased_column(element)
+ def row_processor(self, query, context, custom_rows):
+ column = self.__resolve_expr_against_query_aliases(query, self.column, context)
- return None
+ if context.adapter:
+ column = context.adapter.columns[column]
- context.attributes[('_ColumnEntity', expr)] = ret = Adapter().traverse(expr, clone=True)
- return ret
-
- def row_processor(self, query, context, single_entity):
- column = self.__resolve_expr_against_query_aliases(query, self.column, context)
def proc(context, row):
return row[column]
- return proc
-
+
+ return (proc, getattr(column, 'name', None))
+
def setup_context(self, query, context):
column = self.__resolve_expr_against_query_aliases(query, self.column, context)
- context.secondary_columns.append(column)
-
+ context.froms += list(self.froms)
+ context.primary_columns.append(column)
+
def __str__(self):
return str(self.column)
-
-Query.logger = logging.class_logger(Query)
+Query.logger = log.class_logger(Query)
class QueryContext(object):
def __init__(self, query):
+
+ if query._statement:
+ if isinstance(query._statement, expression._SelectBaseMixin) and not query._statement.use_labels:
+ self.statement = query._statement.apply_labels()
+ else:
+ self.statement = query._statement
+ else:
+ self.statement = None
+ self.from_clause = query._from_obj
+ self.whereclause = query._criterion
+ self.order_by = query._order_by
+ if self.order_by:
+ self.order_by = [expression._literal_as_text(o) for o in util.to_list(self.order_by)]
+
self.query = query
- self.mapper = query.mapper
self.session = query.session
- self.extension = query._extension
- self.statement = None
- self.row_adapter = None
self.populate_existing = query._populate_existing
self.version_check = query._version_check
- self.only_load_props = query._only_load_props
self.refresh_instance = query._refresh_instance
- self.path = ()
self.primary_columns = []
self.secondary_columns = []
self.eager_order_by = []
- self.eager_joins = None
+
+ self.eager_joins = {}
+ self.froms = []
+ self.adapter = None
+
self.options = query._with_options
self.attributes = query._attributes.copy()
- def exec_with_path(self, mapper, propkey, fn, *args, **kwargs):
- oldpath = self.path
- self.path += (mapper.base_mapper, propkey)
- try:
- return fn(*args, **kwargs)
- finally:
- self.path = oldpath
+class AliasOption(interfaces.MapperOption):
+ def __init__(self, alias):
+ self.alias = alias
+ def process_query(self, query):
+ if isinstance(self.alias, basestring):
+ alias = query._mapper_zero().mapped_table.alias(self.alias)
+ else:
+ alias = self.alias
+ query._from_obj_alias = sql_util.ColumnAdapter(alias)
+
_runid = 1L
_id_lock = util.threading.Lock()