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.py1236
1 files changed, 580 insertions, 656 deletions
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index d51fd75c3..284653b5c 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -4,89 +4,49 @@
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
-from sqlalchemy import sql, util, exceptions, sql_util, logging, schema
-from sqlalchemy.orm import mapper, class_mapper, object_mapper
-from sqlalchemy.orm.interfaces import OperationContext, SynonymProperty
-import random
+from sqlalchemy import sql, util, exceptions, sql_util, logging
+from sqlalchemy.orm import mapper, object_mapper
+from sqlalchemy.orm import util as mapperutil
+from sqlalchemy.orm.interfaces import OperationContext, LoaderStack
+import operator
__all__ = ['Query', 'QueryContext', 'SelectionContext']
class Query(object):
- """Encapsulates the object-fetching operations provided by Mappers.
+ """Encapsulates the object-fetching operations provided by Mappers."""
- Note that this particular version of Query contains the 0.3 API as well as most of the
- 0.4 API for forwards compatibility. A large part of the API here is deprecated (but still present)
- in the 0.4 series.
- """
-
- def __init__(self, class_or_mapper, session=None, entity_name=None, lockmode=None, with_options=None, extension=None, **kwargs):
+ def __init__(self, class_or_mapper, session=None, entity_name=None):
if isinstance(class_or_mapper, type):
self.mapper = mapper.class_mapper(class_or_mapper, entity_name=entity_name)
else:
self.mapper = class_or_mapper.compile()
- self.with_options = with_options or []
self.select_mapper = self.mapper.get_select_mapper().compile()
- self.always_refresh = kwargs.pop('always_refresh', self.mapper.always_refresh)
- self.lockmode = lockmode
- self.extension = mapper._ExtensionCarrier()
- if extension is not None:
- self.extension.append(extension)
- self.extension.append(self.mapper.extension)
- self.is_polymorphic = self.mapper is not self.select_mapper
+
self._session = session
- if not hasattr(self.mapper, '_get_clause'):
- _get_clause = sql.and_()
- for primary_key in self.primary_key_columns:
- _get_clause.clauses.append(primary_key == sql.bindparam(primary_key._label, type=primary_key.type, unique=True))
- self.mapper._get_clause = _get_clause
+ self._with_options = []
+ self._lockmode = None
+ self._extension = self.mapper.extension.copy()
self._entities = []
- self._get_clause = self.mapper._get_clause
-
- self._order_by = kwargs.pop('order_by', False)
- self._group_by = kwargs.pop('group_by', False)
- self._distinct = kwargs.pop('distinct', False)
- self._offset = kwargs.pop('offset', None)
- self._limit = kwargs.pop('limit', None)
- self._criterion = None
+ self._order_by = False
+ self._group_by = False
+ self._distinct = False
+ self._offset = None
+ self._limit = None
+ self._statement = None
self._params = {}
- self._col = None
- self._func = None
+ self._criterion = None
+ self._column_aggregate = None
self._joinpoint = self.mapper
+ self._aliases = None
+ self._alias_ids = {}
self._from_obj = [self.table]
- self._statement = None
-
- for opt in util.flatten_iterator(self.with_options):
- opt.process_query(self)
+ self._populate_existing = False
+ self._version_check = False
def _clone(self):
- # yes, a little embarassing here.
- # go look at 0.4 for the simple version.
q = Query.__new__(Query)
- q.mapper = self.mapper
- q.select_mapper = self.select_mapper
- q._order_by = self._order_by
- q._distinct = self._distinct
- q._entities = list(self._entities)
- q.always_refresh = self.always_refresh
- q.with_options = list(self.with_options)
- q._session = self.session
- q.is_polymorphic = self.is_polymorphic
- q.lockmode = self.lockmode
- q.extension = mapper._ExtensionCarrier()
- for ext in self.extension:
- q.extension.append(ext)
- q._offset = self._offset
- q._limit = self._limit
- q._params = self._params
- q._group_by = self._group_by
- q._get_clause = self._get_clause
- q._from_obj = list(self._from_obj)
- q._joinpoint = self._joinpoint
- q._criterion = self._criterion
- q._statement = self._statement
- q._col = self._col
- q._func = self._func
+ q.__dict__ = self.__dict__.copy()
return q
def _get_session(self):
@@ -96,7 +56,7 @@ class Query(object):
return self._session
table = property(lambda s:s.select_mapper.mapped_table)
- primary_key_columns = property(lambda s:s.select_mapper.pks_by_table[s.select_mapper.mapped_table])
+ primary_key_columns = property(lambda s:s.select_mapper.primary_key)
session = property(_get_session)
def get(self, ident, **kwargs):
@@ -108,13 +68,20 @@ class Query(object):
columns.
"""
- ret = self.extension.get(self, ident, **kwargs)
+ ret = self._extension.get(self, ident, **kwargs)
if ret is not mapper.EXT_PASS:
return ret
- key = self.mapper.identity_key(ident)
+
+ # convert composite types to individual args
+ # TODO: account for the order of columns in the
+ # ColumnProperty it corresponds to
+ if hasattr(ident, '__colset__'):
+ ident = ident.__colset__()
+
+ key = self.mapper.identity_key_from_primary_key(ident)
return self._get(key, ident, **kwargs)
- def load(self, ident, **kwargs):
+ def load(self, ident, raiseerr=True, **kwargs):
"""Return an instance of the object based on the given
identifier.
@@ -125,304 +92,14 @@ class Query(object):
columns.
"""
- ret = self.extension.load(self, ident, **kwargs)
+ ret = self._extension.load(self, ident, **kwargs)
if ret is not mapper.EXT_PASS:
return ret
- key = self.mapper.identity_key(ident)
+ key = self.mapper.identity_key_from_primary_key(ident)
instance = self._get(key, ident, reload=True, **kwargs)
- if instance is None:
+ if instance is None and raiseerr:
raise exceptions.InvalidRequestError("No instance found for identity %s" % repr(ident))
return instance
-
- def get_by(self, *args, **params):
- """Like ``select_by()``, but only return the first
- as a scalar, or None if no object found.
- Synonymous with ``selectfirst_by()``.
-
- The criterion is constructed in the same way as the
- ``select_by()`` method.
-
- this method is deprecated in 0.4.
- """
-
- ret = self.extension.get_by(self, *args, **params)
- if ret is not mapper.EXT_PASS:
- return ret
- x = self.select_whereclause(self.join_by(*args, **params), limit=1)
- if x:
- return x[0]
- else:
- return None
-
- def select_by(self, *args, **params):
- """Return an array of object instances based on the given
- clauses and key/value criterion.
-
- \*args
- a list of zero or more ``ClauseElements`` which will be
- connected by ``AND`` operators.
-
- \**params
- a set of zero or more key/value parameters which
- are converted into ``ClauseElements``. the keys are mapped to
- property or column names mapped by this mapper's Table, and
- the values are coerced into a ``WHERE`` clause separated by
- ``AND`` operators. If the local property/column names dont
- contain the key, a search will be performed against this
- mapper's immediate list of relations as well, forming the
- appropriate join conditions if a matching property is located.
-
- if the located property is a column-based property, the comparison
- value should be a scalar with an appropriate type. If the
- property is a relationship-bound property, the comparison value
- should be an instance of the related class.
-
- E.g.::
-
- result = usermapper.select_by(user_name = 'fred')
-
- this method is deprecated in 0.4.
- """
-
- ret = self.extension.select_by(self, *args, **params)
- if ret is not mapper.EXT_PASS:
- return ret
- return self.select_whereclause(self.join_by(*args, **params))
-
- def join_by(self, *args, **params):
- """Return a ``ClauseElement`` representing the ``WHERE``
- clause that would normally be sent to ``select_whereclause()``
- by ``select_by()``.
-
- The criterion is constructed in the same way as the
- ``select_by()`` method.
-
- this method is deprecated in 0.4.
- """
-
- return self._join_by(args, params)
-
-
- def join_to(self, key):
- """Given the key name of a property, will recursively descend
- through all child properties from this Query's mapper to
- locate the property, and will return a ClauseElement
- representing a join from this Query's mapper to the endmost
- mapper.
-
- this method is deprecated in 0.4.
- """
-
- [keys, p] = self._locate_prop(key)
- return self.join_via(keys)
-
- def join_via(self, keys):
- """Given a list of keys that represents a path from this
- Query's mapper to a related mapper based on names of relations
- from one mapper to the next, return a ClauseElement
- representing a join from this Query's mapper to the endmost
- mapper.
-
- this method is deprecated in 0.4.
- """
-
- mapper = self.mapper
- 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
-
- return clause
-
- def selectfirst_by(self, *args, **params):
- """Like ``select_by()``, but only return the first
- as a scalar, or None if no object found.
- Synonymous with ``get_by()``.
-
- The criterion is constructed in the same way as the
- ``select_by()`` method.
-
- this method is deprecated in 0.4.
- """
-
- return self.get_by(*args, **params)
-
- def selectone_by(self, *args, **params):
- """Like ``selectfirst_by()``, but throws an error if not
- exactly one result was returned.
-
- The criterion is constructed in the same way as the
- ``select_by()`` method.
-
- this method is deprecated in 0.4.
- """
-
- ret = self.select_whereclause(self.join_by(*args, **params), limit=2)
- if len(ret) == 1:
- return ret[0]
- elif len(ret) == 0:
- raise exceptions.InvalidRequestError('No rows returned for selectone_by')
- else:
- raise exceptions.InvalidRequestError('Multiple rows returned for selectone_by')
-
- def count_by(self, *args, **params):
- """Return the count of instances based on the given clauses
- and key/value criterion.
-
- The criterion is constructed in the same way as the
- ``select_by()`` method.
-
- this method is deprecated in 0.4.
- """
-
- return self.count(self.join_by(*args, **params))
-
- def selectfirst(self, arg=None, **kwargs):
- """Query for a single instance using the given criterion.
-
- Arguments are the same as ``select()``. In the case that
- the given criterion represents ``WHERE`` criterion only,
- LIMIT 1 is applied to the fully generated statement.
-
- this method is deprecated in 0.4.
- """
-
- if isinstance(arg, sql.FromClause) and arg.supports_execution():
- ret = self.select_statement(arg, **kwargs)
- else:
- kwargs['limit'] = 1
- ret = self.select_whereclause(whereclause=arg, **kwargs)
- if ret:
- return ret[0]
- else:
- return None
-
- def selectone(self, arg=None, **kwargs):
- """Query for a single instance using the given criterion.
-
- Unlike ``selectfirst``, this method asserts that only one
- row exists. In the case that the given criterion represents
- ``WHERE`` criterion only, LIMIT 2 is applied to the fully
- generated statement.
-
- this method is deprecated in 0.4.
- """
-
- if isinstance(arg, sql.FromClause) and arg.supports_execution():
- ret = self.select_statement(arg, **kwargs)
- else:
- kwargs['limit'] = 2
- ret = self.select_whereclause(whereclause=arg, **kwargs)
- if len(ret) == 1:
- return ret[0]
- elif len(ret) == 0:
- raise exceptions.InvalidRequestError('No rows returned for selectone_by')
- else:
- raise exceptions.InvalidRequestError('Multiple rows returned for selectone')
-
- def select(self, arg=None, **kwargs):
- """Select instances of the object from the database.
-
- `arg` can be any ClauseElement, which will form the criterion
- with which to load the objects.
-
- For more advanced usage, arg can also be a Select statement
- object, which will be executed and its resulting rowset used
- to build new object instances.
-
- In this case, the developer must ensure that an adequate set
- of columns exists in the rowset with which to build new object
- instances.
-
- this method is deprecated in 0.4.
- """
-
- ret = self.extension.select(self, arg=arg, **kwargs)
- if ret is not mapper.EXT_PASS:
- return ret
- if isinstance(arg, sql.FromClause) and arg.supports_execution():
- return self.select_statement(arg, **kwargs)
- else:
- return self.select_whereclause(whereclause=arg, **kwargs)
-
- def select_whereclause(self, whereclause=None, params=None, **kwargs):
- """Given a ``WHERE`` criterion, create a ``SELECT`` statement,
- execute and return the resulting instances.
-
- this method is deprecated in 0.4.
- """
- statement = self.compile(whereclause, **kwargs)
- return self._select_statement(statement, params=params)
-
- def count(self, whereclause=None, params=None, **kwargs):
- """Given a ``WHERE`` criterion, create a ``SELECT COUNT``
- statement, execute and return the resulting count value.
-
- the additional arguments to this method are is deprecated in 0.4.
-
- """
- if self._criterion:
- if whereclause is not None:
- whereclause = sql.and_(self._criterion, whereclause)
- else:
- whereclause = self._criterion
- from_obj = kwargs.pop('from_obj', self._from_obj)
- kwargs.setdefault('distinct', self._distinct)
-
- alltables = []
- for l in [sql_util.TableFinder(x) for x in from_obj]:
- alltables += l
-
- if self.table not in alltables:
- from_obj.append(self.table)
- if self._nestable(**kwargs):
- s = sql.select([self.table], whereclause, from_obj=from_obj, **kwargs).alias('getcount').count()
- else:
- primary_key = self.primary_key_columns
- s = sql.select([sql.func.count(list(primary_key)[0])], whereclause, from_obj=from_obj, **kwargs)
- return self.session.scalar(self.mapper, s, params=params)
-
- def select_statement(self, statement, **params):
- """Given a ``ClauseElement``-based statement, execute and
- return the resulting instances.
-
- this method is deprecated in 0.4.
- """
-
- return self._select_statement(statement, params=params)
-
- def select_text(self, text, **params):
- """Given a literal string-based statement, execute and return
- the resulting instances.
-
- this method is deprecated in 0.4. use from_statement() instead.
- """
-
- t = sql.text(text)
- return self.execute(t, params=params)
-
- def _with_lazy_criterion(cls, instance, prop, reverse=False):
- """extract query criterion from a LazyLoader strategy given a Mapper,
- source persisted/detached instance and PropertyLoader.
-
- """
-
- from sqlalchemy.orm import strategies
- (criterion, lazybinds, rev) = strategies.LazyLoader._create_lazy_clause(prop, reverse_direction=reverse)
- bind_to_col = dict([(lazybinds[col].key, col) for col in lazybinds])
-
- class Visitor(sql.ClauseVisitor):
- def visit_bindparam(self, bindparam):
- mapper = reverse and prop.mapper or prop.parent
- bindparam.value = mapper.get_attr_by_column(instance, bind_to_col[bindparam.key])
- Visitor().traverse(criterion)
- return criterion
- _with_lazy_criterion = classmethod(_with_lazy_criterion)
-
def query_from_parent(cls, instance, property, **kwargs):
"""return a newly constructed Query object, with criterion corresponding to
@@ -445,9 +122,25 @@ class Query(object):
mapper = object_mapper(instance)
prop = mapper.get_property(property, resolve_synonyms=True)
target = prop.mapper
- criterion = cls._with_lazy_criterion(instance, prop)
+ criterion = prop.compare(operator.eq, instance, value_is_parent=True)
return Query(target, **kwargs).filter(criterion)
query_from_parent = classmethod(query_from_parent)
+
+ def populate_existing(self):
+ """return a Query that will refresh all instances loaded.
+
+ this includes all entities accessed from the database, including
+ secondary entities, eagerly-loaded collection items.
+
+ All changes present on entities which are already present in the session will
+ be reset and the entities will all be marked "clean".
+
+ This is essentially the en-masse version of load().
+ """
+
+ q = self._clone()
+ q._populate_existing = True
+ return q
def with_parent(self, instance, property=None):
"""add a join criterion corresponding to a relationship to the given parent instance.
@@ -474,9 +167,9 @@ class Query(object):
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__))
else:
prop = mapper.get_property(property, resolve_synonyms=True)
- return self.filter(Query._with_lazy_criterion(instance, prop))
+ return self.filter(prop.compare(operator.eq, instance, value_is_parent=True))
- def add_entity(self, entity):
+ 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
@@ -492,12 +185,25 @@ class Query(object):
entity
a class or mapper which will be added to the results.
+ 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
+
+ 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.
"""
q = self._clone()
- q._entities.append(entity)
+
+ if isinstance(entity, type):
+ entity = mapper.class_mapper(entity)
+ if alias is not None:
+ alias = mapperutil.AliasedClauses(entity.mapped_table, alias=alias)
+
+ q._entities = q._entities + [(entity, alias, id)]
return q
- def add_column(self, column):
+ 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
@@ -517,51 +223,56 @@ class Query(object):
"""
q = self._clone()
-
+
# alias non-labeled column elements.
- # TODO: make the generation deterministic
if isinstance(column, sql.ColumnElement) and not hasattr(column, '_label'):
- column = column.label("anon_" + hex(random.randint(0, 65535))[2:])
-
- q._entities.append(column)
+ column = column.label(None)
+
+ q._entities = q._entities + [(column, None, id)]
return q
- def options(self, *args, **kwargs):
+ def options(self, *args):
"""Return a new Query object, applying the given list of
MapperOptions.
"""
+
q = self._clone()
- for opt in util.flatten_iterator(args):
- q.with_options.append(opt)
+ opts = [o for o in util.flatten_iterator(args)]
+ q._with_options = q._with_options + opts
+ for opt in opts:
opt.process_query(q)
return q
def with_lockmode(self, mode):
"""Return a new Query object with the specified locking mode."""
q = self._clone()
- q.lockmode = mode
+ q._lockmode = mode
return q
def params(self, **kwargs):
"""add values for bind parameters which may have been specified in filter()."""
-
+
q = self._clone()
q._params = q._params.copy()
q._params.update(kwargs)
return q
-
+
def filter(self, criterion):
"""apply the given filtering criterion to the query and return the newly resulting ``Query``
the criterion is any sql.ClauseElement applicable to the WHERE clause of a select.
"""
-
+
if isinstance(criterion, basestring):
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")
-
+
+
+ if self._aliases is not None:
+ criterion = self._aliases.adapt_clause(criterion)
+
q = self._clone()
if q._criterion is not None:
q._criterion = q._criterion & criterion
@@ -569,23 +280,36 @@ class Query(object):
q._criterion = criterion
return q
- def filter_by(self, *args, **kwargs):
- """apply the given filtering criterion to the query and return the newly resulting ``Query``
+ def filter_by(self, **kwargs):
+ """apply the given filtering criterion to the query and return the newly resulting ``Query``."""
- The criterion is constructed in the same way as the
- ``select_by()`` method.
- """
- return self.filter(self._join_by(args, kwargs, start=self._joinpoint))
+ #import properties
+
+ alias = None
+ join = None
+ clause = None
+ joinpoint = self._joinpoint
- def _join_to(self, prop, outerjoin=False, start=None):
- if start is None:
- start = self._joinpoint
+ for key, value in kwargs.iteritems():
+ prop = joinpoint.get_property(key, resolve_synonyms=True)
+ c = prop.compare(operator.eq, value)
- if isinstance(prop, list):
- keys = prop
+ if alias is not None:
+ sql_util.ClauseAdapter(alias).traverse(c)
+ if clause is None:
+ clause = c
+ else:
+ clause &= c
+
+ if join is not None:
+ return self.select_from(join).filter(clause)
else:
- [keys,p] = self._locate_prop(prop, start=start)
+ return self.filter(clause)
+ def _join_to(self, keys, outerjoin=False, start=None, create_aliases=True):
+ if start is None:
+ start = self._joinpoint
+
clause = self._from_obj[-1]
currenttables = [clause]
@@ -594,101 +318,56 @@ class Query(object):
currenttables.append(join.left)
currenttables.append(join.right)
FindJoinedTables().traverse(clause)
-
+
mapper = start
- for key in keys:
+ alias = self._aliases
+ for key in util.to_list(keys):
prop = mapper.get_property(key, resolve_synonyms=True)
- if prop._is_self_referential():
- raise exceptions.InvalidRequestError("Self-referential query on '%s' property must be constructed manually using an Alias object for the related table." % str(prop))
- # dont re-join to a table already in our from objects
- if prop.select_table not in currenttables:
- if outerjoin:
- if prop.secondary:
- clause = clause.outerjoin(prop.secondary, prop.get_join(mapper, primary=True, secondary=False))
- clause = clause.outerjoin(prop.select_table, prop.get_join(mapper, primary=False))
+ if prop._is_self_referential() and not create_aliases:
+ raise exceptions.InvalidRequestError("Self-referential query on '%s' property requires create_aliases=True argument." % str(prop))
+
+ if prop.select_table not in currenttables or create_aliases:
+ if prop.secondary:
+ if create_aliases:
+ alias = mapperutil.PropertyAliasedClauses(prop,
+ prop.get_join(mapper, primary=True, secondary=False),
+ prop.get_join(mapper, primary=False, secondary=True),
+ alias
+ )
+ clause = clause.join(alias.secondary, alias.primaryjoin, isouter=outerjoin).join(alias.alias, alias.secondaryjoin, isouter=outerjoin)
else:
- clause = clause.outerjoin(prop.select_table, prop.get_join(mapper))
+ clause = clause.join(prop.secondary, prop.get_join(mapper, primary=True, secondary=False), isouter=outerjoin)
+ clause = clause.join(prop.select_table, prop.get_join(mapper, primary=False), isouter=outerjoin)
else:
- if prop.secondary:
- clause = clause.join(prop.secondary, prop.get_join(mapper, primary=True, secondary=False))
- clause = clause.join(prop.select_table, prop.get_join(mapper, primary=False))
+ if create_aliases:
+ alias = mapperutil.PropertyAliasedClauses(prop,
+ prop.get_join(mapper, primary=True, secondary=False),
+ None,
+ alias
+ )
+ clause = clause.join(alias.alias, alias.primaryjoin, isouter=outerjoin)
else:
- clause = clause.join(prop.select_table, prop.get_join(mapper))
- elif prop.secondary is not None and prop.secondary not in currenttables:
+ clause = clause.join(prop.select_table, prop.get_join(mapper), 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 explicit `Alias` objects." % prop.key)
+ 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)
mapper = prop.mapper
- return (clause, mapper)
-
- def _join_by(self, args, params, start=None):
- """Return a ``ClauseElement`` representing the ``WHERE``
- clause that would normally be sent to ``select_whereclause()``
- by ``select_by()``.
-
- The criterion is constructed in the same way as the
- ``select_by()`` method.
- """
- import properties
-
- clause = None
- for arg in args:
- if clause is None:
- clause = arg
- else:
- clause &= arg
-
- for key, value in params.iteritems():
- (keys, prop) = self._locate_prop(key, start=start)
- if isinstance(prop, properties.PropertyLoader):
- c = self._with_lazy_criterion(value, prop, True) & self.join_via(keys[:-1])
- else:
- c = prop.compare(value) & self.join_via(keys)
- if clause is None:
- clause = c
- else:
- clause &= c
- return clause
-
- def _locate_prop(self, key, start=None):
- import properties
- keys = []
- seen = util.Set()
- def search_for_prop(mapper_):
- if mapper_ in seen:
- return None
- seen.add(mapper_)
- if mapper_.props.has_key(key):
- prop = mapper_.get_property(key, resolve_synonyms=True)
- 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]
+ if create_aliases:
+ return (clause, mapper, alias)
+ else:
+ return (clause, mapper, None)
def _generative_col_aggregate(self, col, func):
"""apply the given aggregate function to the query and return the newly
resulting ``Query``.
"""
- if self._col is not None or self._func is not None:
+ if self._column_aggregate is not None:
raise exceptions.InvalidRequestError("Query already contains an aggregate column or function")
q = self._clone()
- q._col = col
- q._func = func
+ q._column_aggregate = (col, func)
return q
def apply_min(self, col):
@@ -721,13 +400,13 @@ class Query(object):
For performance, only use subselect if `order_by` attribute is set.
"""
- ops = {'distinct':self._distinct, 'order_by':self._order_by, 'from_obj':self._from_obj}
+ ops = {'distinct':self._distinct, 'order_by':self._order_by or None, 'from_obj':self._from_obj}
if self._order_by is not False:
s1 = sql.select([col], self._criterion, **ops).alias('u')
- return sql.select([func(s1.corresponding_column(col))]).scalar()
+ return self.session.execute(sql.select([func(s1.corresponding_column(col))]), mapper=self.mapper).scalar()
else:
- return sql.select([func(col)], self._criterion, **ops).scalar()
+ return self.session.execute(sql.select([func(col)], self._criterion, **ops), mapper=self.mapper).scalar()
def min(self, col):
"""Execute the SQL ``min()`` function against the given column."""
@@ -756,7 +435,7 @@ class Query(object):
if q._order_by is False:
q._order_by = util.to_list(criterion)
else:
- q._order_by.extend(util.to_list(criterion))
+ q._order_by = q._order_by + util.to_list(criterion)
return q
def group_by(self, criterion):
@@ -766,52 +445,62 @@ class Query(object):
if q._group_by is False:
q._group_by = util.to_list(criterion)
else:
- q._group_by.extend(util.to_list(criterion))
+ q._group_by = q._group_by + util.to_list(criterion)
return q
-
- def join(self, prop):
+
+ def join(self, prop, id=None, aliased=False, from_joinpoint=False):
"""create a join of this ``Query`` object's criterion
to a relationship and return the newly resulting ``Query``.
-
- 'prop' may be a string property name in which it is located
- in the same manner as keyword arguments in ``select_by``, or
- it may be a list of strings in which case the property is located
- by direct traversal of each keyname (i.e. like join_via()).
+
+ 'prop' may be a string property name or a list of string
+ property names.
"""
-
- q = self._clone()
- (clause, mapper) = self._join_to(prop, outerjoin=False)
- q._from_obj = [clause]
- q._joinpoint = mapper
- return q
- def outerjoin(self, prop):
+ return self._join(prop, id=id, outerjoin=False, aliased=aliased, from_joinpoint=from_joinpoint)
+
+ def outerjoin(self, prop, id=None, aliased=False, from_joinpoint=False):
"""create a left outer join of this ``Query`` object's criterion
to a relationship and return the newly resulting ``Query``.
- 'prop' may be a string property name in which it is located
- in the same manner as keyword arguments in ``select_by``, or
- it may be a list of strings in which case the property is located
- by direct traversal of each keyname (i.e. like join_via()).
+ 'prop' may be a string property name or a list of string
+ property names.
"""
+
+ 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)
q = self._clone()
- (clause, mapper) = self._join_to(prop, outerjoin=True)
q._from_obj = [clause]
q._joinpoint = mapper
+ q._aliases = aliases
+
+ a = aliases
+ while a is not None:
+ q._alias_ids.setdefault(a.mapper, []).append(a)
+ q._alias_ids.setdefault(a.table, []).append(a)
+ q._alias_ids.setdefault(a.alias, []).append(a)
+ a = a.parentclauses
+
+ if id:
+ q._alias_ids[id] = aliases
return q
def reset_joinpoint(self):
"""return a new Query reset the 'joinpoint' of this Query reset
back to the starting mapper. Subsequent generative calls will
be constructed from the new joinpoint.
-
- This is an interim method which will not be needed with new behavior
- to be released in 0.4."""
-
+
+ Note that each call to join() or outerjoin() also starts from
+ the root.
+ """
+
q = self._clone()
q._joinpoint = q.mapper
+ q._aliases = None
return q
+
def select_from(self, from_obj):
"""Set the `from_obj` parameter of the query and return the newly
resulting ``Query``.
@@ -823,20 +512,6 @@ class Query(object):
new._from_obj = list(new._from_obj) + util.to_list(from_obj)
return new
- def __getattr__(self, key):
- if (key.startswith('select_by_')):
- key = key[10:]
- def foo(arg):
- return self.select_by(**{key:arg})
- return foo
- elif (key.startswith('get_by_')):
- key = key[7:]
- def foo(arg):
- return self.get_by(**{key:arg})
- return foo
- else:
- raise AttributeError(key)
-
def __getitem__(self, item):
if isinstance(item, slice):
start = item.start
@@ -884,99 +559,65 @@ class Query(object):
new._distinct = True
return new
- def list(self):
+ def all(self):
"""Return the results represented by this ``Query`` as a list.
This results in an execution of the underlying query.
-
- this method is deprecated in 0.4. use all() instead.
"""
-
return list(self)
-
- def one(self):
- """Return the first result of this ``Query``, raising an exception if more than one row exists.
-
- This results in an execution of the underlying query.
- this method is for forwards-compatibility with 0.4.
- """
-
- if self._col is None or self._func is None:
- ret = list(self[0:2])
-
- if len(ret) == 1:
- return ret[0]
- elif len(ret) == 0:
- raise exceptions.InvalidRequestError('No rows returned for one()')
- else:
- raise exceptions.InvalidRequestError('Multiple rows returned for one()')
- else:
- return self._col_aggregate(self._col, self._func)
-
+
+ def from_statement(self, statement):
+ if isinstance(statement, basestring):
+ statement = sql.text(statement)
+ q = self._clone()
+ q._statement = statement
+ return q
+
def first(self):
"""Return the first result of this ``Query``.
This results in an execution of the underlying query.
-
- this method is for forwards-compatibility with 0.4.
"""
- if self._col is None or self._func is None:
- ret = list(self[0:1])
- if len(ret) > 0:
- return ret[0]
- else:
- return None
+ 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]
else:
- return self._col_aggregate(self._col, self._func)
+ return None
- def all(self):
- """Return the results represented by this ``Query`` as a list.
+ def one(self):
+ """Return the first result of this ``Query``, raising an exception if more than one row exists.
This results in an execution of the underlying query.
"""
- return self.list()
-
- def from_statement(self, statement):
- """execute a full select() statement, or literal textual string as a SELECT statement.
-
- this method is for forwards compatibility with 0.4.
- """
- if isinstance(statement, basestring):
- statement = sql.text(statement)
- q = self._clone()
- q._statement = statement
- return q
- def scalar(self):
- """Return the first result of this ``Query``.
+ if self._column_aggregate is not None:
+ return self._col_aggregate(*self._column_aggregate)
- This results in an execution of the underlying query.
-
- this method will be deprecated in 0.4; first() is added for
- forwards-compatibility.
- """
+ ret = list(self[0:2])
- return self.first()
+ if len(ret) == 1:
+ return ret[0]
+ elif len(ret) == 0:
+ raise exceptions.InvalidRequestError('No rows returned for one()')
+ else:
+ raise exceptions.InvalidRequestError('Multiple rows returned for one()')
def __iter__(self):
- return iter(self.select_whereclause())
-
- def execute(self, clauseelement, params=None, *args, **kwargs):
- """Execute the given ClauseElement-based statement against
- this Query's session/mapper, return the resulting list of
- instances.
-
- this method is deprecated in 0.4. Use from_statement() instead.
- """
-
- p = self._params
- if params is not None:
- p.update(params)
- result = self.session.execute(self.mapper, clauseelement, params=p)
+ statement = self.compile()
+ statement.use_labels = True
+ if self.session.autoflush:
+ self.session.flush()
+ return self._execute_and_instances(statement)
+
+ def _execute_and_instances(self, statement):
+ result = self.session.execute(statement, params=self._params, mapper=self.mapper)
try:
- return self.instances(result, **kwargs)
+ return iter(self.instances(result))
finally:
result.close()
@@ -984,50 +625,47 @@ class Query(object):
"""Return a list of mapped instances corresponding to the rows
in a given *cursor* (i.e. ``ResultProxy``).
- \*mappers_or_columns is an optional list containing one or more of
- classes, mappers, strings or sql.ColumnElements which will be
- applied to each row and added horizontally to the result set,
- which becomes a list of tuples. The first element in each tuple
- is the usual result based on the mapper represented by this
- ``Query``. Each additional element in the tuple corresponds to an
- entry in the \*mappers_or_columns list.
-
- For each element in \*mappers_or_columns, if the element is
- a mapper or mapped class, an additional class instance will be
- present in the tuple. If the element is a string or sql.ColumnElement,
- the corresponding result column from each row will be present in the tuple.
-
- Note that when \*mappers_or_columns is present, "uniquing" for the result set
- is *disabled*, so that the resulting tuples contain entities as they actually
- correspond. this indicates that multiple results may be present if this
- option is used.
+ The \*mappers_or_columns and \**kwargs arguments are deprecated.
+ To add instances or columns to the results, use add_entity()
+ and add_column().
"""
self.__log_debug("instances()")
session = self.session
- context = SelectionContext(self.select_mapper, session, self.extension, with_options=self.with_options, **kwargs)
+ kwargs.setdefault('populate_existing', self._populate_existing)
+ kwargs.setdefault('version_check', self._version_check)
+
+ context = SelectionContext(self.select_mapper, session, self._extension, with_options=self._with_options, **kwargs)
process = []
mappers_or_columns = tuple(self._entities) + mappers_or_columns
if mappers_or_columns:
- for m in mappers_or_columns:
+ for tup in mappers_or_columns:
+ if isinstance(tup, tuple):
+ (m, alias, alias_id) = tup
+ clauses = self._get_entity_clauses(tup)
+ else:
+ clauses = alias = alias_id = None
+ m = tup
if isinstance(m, type):
m = mapper.class_mapper(m)
if isinstance(m, mapper.Mapper):
def x(m):
+ row_adapter = clauses is not None and clauses.row_decorator or (lambda row: row)
appender = []
def proc(context, row):
- if not m._instance(context, row, appender):
+ if not m._instance(context, row_adapter(row), appender):
appender.append(None)
process.append((proc, appender))
x(m)
- elif isinstance(m, sql.ColumnElement) or isinstance(m, basestring):
+ elif isinstance(m, (sql.ColumnElement, basestring)):
def y(m):
+ row_adapter = clauses is not None and clauses.row_decorator or (lambda row: row)
res = []
def proc(context, row):
- res.append(row[m])
+ res.append(row_adapter(row)[m])
process.append((proc, res))
y(m)
result = []
@@ -1039,9 +677,12 @@ class Query(object):
for proc in process:
proc[0](context, row)
+ for instance in context.identity_map.values():
+ context.attributes.get(('populating_mapper', instance), object_mapper(instance))._post_instance(context, instance)
+
# store new stuff in the identity map
- for value in context.identity_map.values():
- session._register_persistent(value)
+ for instance in context.identity_map.values():
+ session._register_persistent(instance)
if mappers_or_columns:
return list(util.OrderedSet(zip(*([result] + [o[1] for o in process]))))
@@ -1050,8 +691,8 @@ class Query(object):
def _get(self, key, ident=None, reload=False, lockmode=None):
- lockmode = lockmode or self.lockmode
- if not reload and not self.always_refresh and lockmode is None:
+ lockmode = lockmode or self._lockmode
+ if not reload and not self.mapper.always_refresh and lockmode is None:
try:
return self.session._get(key)
except KeyError:
@@ -1062,21 +703,22 @@ class Query(object):
else:
ident = util.to_list(ident)
params = {}
- try:
- for i, primary_key in enumerate(self.primary_key_columns):
+
+ for i, primary_key in enumerate(self.primary_key_columns):
+ try:
params[primary_key._label] = 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 self.primary_key_columns]))
+ 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 self.primary_key_columns]))
try:
- statement = self.compile(self._get_clause, lockmode=lockmode)
- return self._select_statement(statement, params=params, populate_existing=reload, version_check=(lockmode is not None))[0]
+ q = self
+ if lockmode is not None:
+ q = q.with_lockmode(lockmode)
+ q = q.filter(self.select_mapper._get_clause)
+ q = q.params(**params)._select_context_options(populate_existing=reload, version_check=(lockmode is not None))
+ return q.first()
except IndexError:
return None
- def _select_statement(self, statement, params=None, **kwargs):
- statement.use_labels = True
- return self.execute(statement, params=params, **kwargs)
-
def _should_nest(self, querycontext):
"""Return True if the given statement options indicate that we
should *nest* the generated query as a subquery inside of a
@@ -1094,21 +736,56 @@ class Query(object):
return (kwargs.get('limit') is not None or kwargs.get('offset') is not None or kwargs.get('distinct', False))
- def compile(self, whereclause = None, **kwargs):
- """Given a WHERE criterion, produce a ClauseElement-based
- statement suitable for usage in the execute() method.
+ 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()
- the arguments to this function are deprecated and are removed in version 0.4.
+ 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.
"""
+ whereclause = self._criterion
+
+ context = QueryContext(self)
+ from_obj = context.from_obj
+
+ alltables = []
+ for l in [sql_util.TableFinder(x) for x in from_obj]:
+ alltables += l
+
+ if self.table not in alltables:
+ from_obj.append(self.table)
+ if self._nestable(**context.select_args()):
+ s = sql.select([self.table], whereclause, from_obj=from_obj, **context.select_args()).alias('getcount').count()
+ else:
+ primary_key = self.primary_key_columns
+ s = sql.select([sql.func.count(list(primary_key)[0])], whereclause, from_obj=from_obj, **context.select_args())
+ return self.session.scalar(s, params=self._params, mapper=self.mapper)
+
+ def compile(self):
+ """compiles and returns a SQL statement based on the criterion and conditions within this Query."""
+
if self._statement:
self._statement.use_labels = True
return self._statement
+
+ whereclause = self._criterion
- if self._criterion:
- whereclause = sql.and_(self._criterion, whereclause)
-
- if whereclause is not None and self.is_polymorphic:
+ if whereclause is not None and (self.mapper is not self.select_mapper):
# adapt the given WHERECLAUSE to adjust instances of this query's mapped
# table to be that of our select_table,
# which may be the "polymorphic" selectable used by our mapper.
@@ -1124,16 +801,10 @@ class Query(object):
# get/create query context. get the ultimate compile arguments
# from there
- context = kwargs.pop('query_context', None)
- if context is None:
- context = QueryContext(self, kwargs)
+ context = QueryContext(self)
order_by = context.order_by
- group_by = context.group_by
from_obj = context.from_obj
lockmode = context.lockmode
- distinct = context.distinct
- limit = context.limit
- offset = context.offset
if order_by is False:
order_by = self.mapper.order_by
if order_by is False:
@@ -1161,31 +832,33 @@ class Query(object):
# if theres an order by, add those columns to the column list
# of the "rowcount" query we're going to make
if order_by:
- order_by = util.to_list(order_by) or []
+ order_by = [sql._literal_as_text(o) for o in util.to_list(order_by) or []]
cf = sql_util.ColumnFinder()
for o in order_by:
cf.traverse(o)
else:
cf = []
- s2 = sql.select(self.table.primary_key + list(cf), whereclause, use_labels=True, from_obj=from_obj, **context.select_args())
+ s2 = sql.select(self.primary_key_columns + list(cf), whereclause, use_labels=True, from_obj=from_obj, correlate=False, **context.select_args())
if order_by:
- s2.order_by(*util.to_list(order_by))
+ s2 = s2.order_by(*util.to_list(order_by))
s3 = s2.alias('tbl_row_count')
- crit = s3.primary_key==self.table.primary_key
+ crit = s3.primary_key==self.primary_key_columns
statement = sql.select([], crit, use_labels=True, for_update=for_update)
# now for the order by, convert the columns to their corresponding columns
# in the "rowcount" query, and tack that new order by onto the "rowcount" query
if order_by:
- statement.order_by(*sql_util.ClauseAdapter(s3).copy_and_process(order_by))
+ statement.append_order_by(*sql_util.ClauseAdapter(s3).copy_and_process(order_by))
else:
statement = sql.select([], whereclause, from_obj=from_obj, use_labels=True, for_update=for_update, **context.select_args())
if order_by:
- statement.order_by(*util.to_list(order_by))
+ statement.append_order_by(*util.to_list(order_by))
+
# for a DISTINCT query, you need the columns explicitly specified in order
# to use it in "order_by". ensure they are in the column criterion (particularly oid).
# TODO: this should be done at the SQL level not the mapper level
- if kwargs.get('distinct', False) and order_by:
+ # TODO: need test coverage for this
+ if context.distinct and order_by:
[statement.append_column(c) for c in util.to_list(order_by)]
context.statement = statement
@@ -1197,20 +870,268 @@ class Query(object):
value.setup(context)
# additional entities/columns, add those to selection criterion
- for m in self._entities:
- if isinstance(m, type):
- m = mapper.class_mapper(m)
+ for tup in self._entities:
+ (m, alias, alias_id) = tup
+ clauses = self._get_entity_clauses(tup)
if isinstance(m, mapper.Mapper):
for value in m.iterate_properties:
- value.setup(context)
+ value.setup(context, parentclauses=clauses)
elif isinstance(m, sql.ColumnElement):
+ if clauses is not None:
+ m = clauses.adapt_clause(m)
statement.append_column(m)
return statement
+ def _get_entity_clauses(self, m):
+ """for tuples added via add_entity() or add_column(), attempt to locate
+ an AliasedClauses object which should be used to formulate the query as well
+ as to process result rows."""
+ (m, alias, alias_id) = m
+ if alias is not None:
+ return alias
+ if alias_id is not None:
+ try:
+ return self._alias_ids[alias_id]
+ except KeyError:
+ raise exceptions.InvalidRequestError("Query has no alias identified by '%s'" % alias_id)
+ if isinstance(m, type):
+ m = mapper.class_mapper(m)
+ if isinstance(m, mapper.Mapper):
+ l = self._alias_ids.get(m)
+ if l:
+ if len(l) > 1:
+ raise exceptions.InvalidRequestError("Ambiguous join for entity '%s'; specify id=<someid> to query.join()/query.add_entity()" % str(m))
+ else:
+ return l[0]
+ else:
+ return None
+ elif isinstance(m, sql.ColumnElement):
+ aliases = []
+ for table in sql_util.TableFinder(m, check_columns=True):
+ for a in self._alias_ids.get(table, []):
+ aliases.append(a)
+ if len(aliases) > 1:
+ raise exceptions.InvalidRequestError("Ambiguous join for entity '%s'; specify id=<someid> to query.join()/query.add_column()" % str(m))
+ elif len(aliases) == 1:
+ return aliases[0]
+ else:
+ return None
+
def __log_debug(self, msg):
self.logger.debug(msg)
+ def __str__(self):
+ return str(self.compile())
+
+ # DEPRECATED LAND !
+
+ def list(self):
+ """DEPRECATED. use all()"""
+
+ return list(self)
+
+ def scalar(self):
+ """DEPRECATED. use first()"""
+
+ return self.first()
+
+ def _legacy_filter_by(self, *args, **kwargs):
+ return self.filter(self._legacy_join_by(args, kwargs, start=self._joinpoint))
+
+ def count_by(self, *args, **params):
+ """DEPRECATED. use query.filter_by(\**params).count()"""
+
+ return self.count(self.join_by(*args, **params))
+
+
+ def select_whereclause(self, whereclause=None, params=None, **kwargs):
+ """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)
+
+ def _legacy_select_kwargs(self, **kwargs):
+ 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.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'])
+ return q
+
+
+ def get_by(self, *args, **params):
+ """DEPRECATED. use query.filter_by(\**params).first()"""
+
+ ret = self._extension.get_by(self, *args, **params)
+ if ret is not mapper.EXT_PASS:
+ return ret
+
+ return self._legacy_filter_by(*args, **params).first()
+
+ def select_by(self, *args, **params):
+ """DEPRECATED. use use query.filter_by(\**params).all()."""
+
+ ret = self._extension.select_by(self, *args, **params)
+ if ret is not mapper.EXT_PASS:
+ return ret
+
+ return self._legacy_filter_by(*args, **params).list()
+
+ def join_by(self, *args, **params):
+ """DEPRECATED. use join() to construct joins based on attribute names."""
+
+ return self._legacy_join_by(args, params, start=self._joinpoint)
+
+ def _build_select(self, arg=None, params=None, **kwargs):
+ if isinstance(arg, sql.FromClause) and arg.supports_execution():
+ return self.from_statement(arg)
+ else:
+ return self.filter(arg)._legacy_select_kwargs(**kwargs)
+
+ def selectfirst(self, arg=None, **kwargs):
+ """DEPRECATED. use query.filter(whereclause).first()"""
+
+ return self._build_select(arg, **kwargs).first()
+
+ def selectone(self, arg=None, **kwargs):
+ """DEPRECATED. use query.filter(whereclause).one()"""
+
+ return self._build_select(arg, **kwargs).one()
+
+ def select(self, arg=None, **kwargs):
+ """DEPRECATED. use query.filter(whereclause).all(), or query.from_statement(statement).all()"""
+
+ ret = self._extension.select(self, arg=arg, **kwargs)
+ if ret is not mapper.EXT_PASS:
+ return ret
+ return self._build_select(arg, **kwargs).all()
+
+ def execute(self, clauseelement, params=None, *args, **kwargs):
+ """DEPRECATED. use query.from_statement().all()"""
+
+ return self._select_statement(clauseelement, params, **kwargs)
+
+ def select_statement(self, statement, **params):
+ """DEPRECATED. Use query.from_statement(statement)"""
+
+ return self._select_statement(statement, params)
+
+ def select_text(self, text, **params):
+ """DEPRECATED. Use query.from_statement(statement)"""
+
+ return self._select_statement(text, params)
+
+ def _select_statement(self, statement, params=None, **kwargs):
+ q = self.from_statement(statement)
+ if params is not None:
+ q = q.params(**params)
+ q._select_context_options(**kwargs)
+ return list(q)
+
+ def _select_context_options(self, populate_existing=None, version_check=None):
+ if populate_existing is not None:
+ self._populate_existing = populate_existing
+ if version_check is not None:
+ self._version_check = version_check
+ return self
+
+ def join_to(self, key):
+ """DEPRECATED. use join() to create joins based on property names."""
+
+ [keys, p] = self._locate_prop(key)
+ return self.join_via(keys)
+
+ def join_via(self, keys):
+ """DEPRECATED. use join() to create joins based on property names."""
+
+ 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
+
+ return clause
+
+ def _legacy_join_by(self, args, params, start=None):
+ import properties
+
+ clause = None
+ for arg in args:
+ if clause is None:
+ clause = arg
+ else:
+ clause &= arg
+
+ for key, value in params.iteritems():
+ (keys, prop) = self._locate_prop(key, start=start)
+ if isinstance(prop, properties.PropertyLoader):
+ c = prop.compare(operator.eq, value) & self.join_via(keys[:-1])
+ else:
+ c = prop.compare(operator.eq, value) & self.join_via(keys)
+ if clause is None:
+ clause = c
+ else:
+ clause &= c
+ return clause
+
+ def _locate_prop(self, key, start=None):
+ 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):
+ """DEPRECATED. Use query.filter_by(\**kwargs).first()"""
+
+ return self._legacy_filter_by(*args, **params).first()
+
+ def selectone_by(self, *args, **params):
+ """DEPRECATED. Use query.filter_by(\**kwargs).one()"""
+
+ return self._legacy_filter_by(*args, **params).one()
+
+
+
Query.logger = logging.class_logger(Query)
class QueryContext(OperationContext):
@@ -1219,25 +1140,25 @@ class QueryContext(OperationContext):
in a query construction.
"""
- def __init__(self, query, kwargs):
+ def __init__(self, query):
self.query = query
- self.order_by = kwargs.pop('order_by', query._order_by)
- self.group_by = kwargs.pop('group_by', query._group_by)
- self.from_obj = kwargs.pop('from_obj', query._from_obj)
- self.lockmode = kwargs.pop('lockmode', query.lockmode)
- self.distinct = kwargs.pop('distinct', query._distinct)
- self.limit = kwargs.pop('limit', query._limit)
- self.offset = kwargs.pop('offset', query._offset)
+ self.order_by = query._order_by
+ self.group_by = query._group_by
+ self.from_obj = query._from_obj
+ self.lockmode = query._lockmode
+ self.distinct = query._distinct
+ self.limit = query._limit
+ self.offset = query._offset
self.eager_loaders = util.Set([x for x in query.mapper._eager_loaders])
self.statement = None
- super(QueryContext, self).__init__(query.mapper, query.with_options, **kwargs)
+ super(QueryContext, self).__init__(query.mapper, query._with_options)
def select_args(self):
"""Return a dictionary of attributes from this
``QueryContext`` that can be applied to a ``sql.Select``
statement.
"""
- return {'limit':self.limit, 'offset':self.offset, 'distinct':self.distinct, 'group_by':self.group_by}
+ return {'limit':self.limit, 'offset':self.offset, 'distinct':self.distinct, 'group_by':self.group_by or None}
def accept_option(self, opt):
"""Accept a ``MapperOption`` which will process (modify) the
@@ -1265,8 +1186,10 @@ class SelectionContext(OperationContext):
yet been added as persistent to the Session.
attributes
- A dictionary to store arbitrary data; eager loaders use it to
- store additional result lists.
+ A dictionary to store arbitrary data; mappers, strategies, and
+ options all store various state information here in order
+ to communicate with each other and to themselves.
+
populate_existing
Indicates if its OK to overwrite the attributes of instances
@@ -1284,6 +1207,7 @@ class SelectionContext(OperationContext):
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):