summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/orm/query.py137
-rw-r--r--lib/sqlalchemy/orm/strategies.py5
-rw-r--r--lib/sqlalchemy/orm/util.py4
-rw-r--r--lib/sqlalchemy/sql/expression.py6
4 files changed, 139 insertions, 13 deletions
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index 3e2063f76..4fa68379d 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -73,6 +73,7 @@ class Query(object):
self._correlate = set()
self._joinpoint = None
self._with_labels = False
+ self._enable_eagerloads = True
self.__joinable_tables = None
self._having = None
self._populate_existing = False
@@ -131,16 +132,14 @@ class Query(object):
def __set_select_from(self, from_obj):
if isinstance(from_obj, expression._SelectBaseMixin):
- # alias SELECTs and unions
from_obj = from_obj.alias()
self._from_obj = from_obj
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))
@@ -318,14 +317,38 @@ class Query(object):
@property
def statement(self):
"""The full SELECT statement represented by this Query."""
+
return self._compile_context(labels=self._with_labels).statement._annotate({'_halt_adapt': True})
+ @property
+ def _nested_statement(self):
+ return self.with_labels().enable_eagerloads(False).statement.correlate(None)
+
def subquery(self):
- """return the full SELECT statement represented by this Query, embedded within an Alias."""
+ """return the full SELECT statement represented by this Query, embedded within an Alias.
+
+ Eager JOIN generation within the query is disabled.
+
+ """
- return self.statement.alias()
+ return self.enable_eagerloads(False).statement.alias()
@_generative()
+ def enable_eagerloads(self, value):
+ """Control whether or not eager joins are rendered.
+
+ When set to False, the returned Query will not render
+ eager joins regardless of eagerload() options
+ or mapper-level lazy=False configurations.
+
+ This is used primarily when nesting the Query's
+ statement into a subquery or other
+ selectable.
+
+ """
+ self._enable_eagerloads = value
+
+ @_generative()
def with_labels(self):
"""Apply column labels to the return value of Query.statement.
@@ -524,23 +547,27 @@ class Query(object):
m = _MapperEntity(self, entity)
self.__setup_aliasizers([m])
- @_generative()
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.
+
"""
+ fromclause = self._nested_statement
+ q = self._from_selectable(fromclause)
+ if entities:
+ q._set_entities(entities)
+ return q
+
+ _from_self = from_self
- fromclause = self.with_labels().statement.correlate(None)
+ @_generative()
+ def _from_selectable(self, fromclause):
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._set_entities(entities)
-
- _from_self = from_self
def values(self, *columns):
"""Return an iterator yielding result tuples corresponding to the given list of columns"""
@@ -692,6 +719,92 @@ class Query(object):
else:
self._having = criterion
+ def union(self, *q):
+ """Produce a UNION of this Query against one or more queries.
+
+ e.g.::
+
+ q1 = sess.query(SomeClass).filter(SomeClass.foo=='bar')
+ q2 = sess.query(SomeClass).filter(SomeClass.bar=='foo')
+
+ q3 = q1.union(q2)
+
+ The method accepts multiple Query objects so as to control
+ the level of nesting. A series of ``union()`` calls such as::
+
+ x.union(y).union(z).all()
+
+ will nest on each ``union()``, and produces::
+
+ SELECT * FROM (SELECT * FROM (SELECT * FROM X UNION SELECT * FROM y) UNION SELECT * FROM Z)
+
+ Whereas::
+
+ x.union(y, z).all()
+
+ produces::
+
+ SELECT * FROM (SELECT * FROM X UNION SELECT * FROM y UNION SELECT * FROM Z)
+
+ """
+ return self._from_selectable(
+ expression.union(*([self._nested_statement]+ [x._nested_statement for x in q])))
+
+ def union_all(self, *q):
+ """Produce a UNION ALL of this Query against one or more queries.
+
+ Works the same way as :method:`union`. See that
+ method for usage examples.
+
+ """
+ return self._from_selectable(
+ expression.union_all(*([self._nested_statement]+ [x._nested_statement for x in q]))
+ )
+
+ def intersect(self, *q):
+ """Produce an INTERSECT of this Query against one or more queries.
+
+ Works the same way as :method:`union`. See that
+ method for usage examples.
+
+ """
+ return self._from_selectable(
+ expression.intersect(*([self._nested_statement]+ [x._nested_statement for x in q]))
+ )
+
+ def intersect_all(self, *q):
+ """Produce an INTERSECT ALL of this Query against one or more queries.
+
+ Works the same way as :method:`union`. See that
+ method for usage examples.
+
+ """
+ return self._from_selectable(
+ expression.intersect_all(*([self._nested_statement]+ [x._nested_statement for x in q]))
+ )
+
+ def except_(self, *q):
+ """Produce an EXCEPT of this Query against one or more queries.
+
+ Works the same way as :method:`union`. See that
+ method for usage examples.
+
+ """
+ return self._from_selectable(
+ expression.except_(*([self._nested_statement]+ [x._nested_statement for x in q]))
+ )
+
+ def except_all(self, *q):
+ """Produce an EXCEPT ALL of this Query against one or more queries.
+
+ Works the same way as :method:`union`. See that
+ method for usage examples.
+
+ """
+ return self._from_selectable(
+ expression.except_all(*([self._nested_statement]+ [x._nested_statement for x in q]))
+ )
+
@util.accepts_a_list_as_starargs(list_deprecation='pending')
def join(self, *props, **kwargs):
"""Create a join against this ``Query`` object's criterion
@@ -1929,7 +2042,7 @@ class QueryContext(object):
self.primary_columns = []
self.secondary_columns = []
self.eager_order_by = []
-
+ self.enable_eagerloads = query._enable_eagerloads
self.eager_joins = {}
self.froms = []
self.adapter = None
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index 68c46f09e..5f820565f 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -603,6 +603,9 @@ class EagerLoader(AbstractRelationLoader):
def setup_query(self, context, entity, path, adapter, column_collection=None, parentmapper=None, **kwargs):
"""Add a left outer join to the statement thats being constructed."""
+ if not context.enable_eagerloads:
+ return
+
path = path + (self.key,)
# check for user-defined eager alias
@@ -649,7 +652,7 @@ class EagerLoader(AbstractRelationLoader):
# whether or not the Query will wrap the selectable in a subquery,
# and then attach eager load joins to that (i.e., in the case of LIMIT/OFFSET etc.)
should_nest_selectable = context.query._should_nest_selectable
-
+
if entity in context.eager_joins:
entity_key, default_towrap = entity, entity.selectable
elif should_nest_selectable or not context.from_clause or not sql_util.search(context.from_clause, entity.selectable):
diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py
index c44583d33..0288f9964 100644
--- a/lib/sqlalchemy/orm/util.py
+++ b/lib/sqlalchemy/orm/util.py
@@ -378,6 +378,10 @@ class _ORMJoin(expression.Join):
if isinstance(onclause, basestring):
prop = left_mapper.get_property(onclause)
elif isinstance(onclause, attributes.QueryableAttribute):
+ # TODO: we might want to honor the current adapt_from,
+ # if already set. we would need to adjust how we calculate
+ # adapt_from though since it is present in too many cases
+ # at the moment (query tests illustrate that).
adapt_from = onclause.__clause_element__()
prop = onclause.property
elif isinstance(onclause, MapperProperty):
diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py
index 48b1d0687..65cc903dd 100644
--- a/lib/sqlalchemy/sql/expression.py
+++ b/lib/sqlalchemy/sql/expression.py
@@ -3123,6 +3123,12 @@ class CompoundSelect(_SelectBaseMixin, FromClause):
def self_group(self, against=None):
return _FromGrouping(self)
+ def is_derived_from(self, fromclause):
+ for s in self.selects:
+ if s.is_derived_from(fromclause):
+ return True
+ return False
+
def _populate_column_collection(self):
for cols in zip(*[s.c for s in self.selects]):
proxy = cols[0]._make_proxy(self, name=self.use_labels and cols[0]._label or None)