summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql/selectable.py
diff options
context:
space:
mode:
authormike bayer <mike_mp@zzzcomputing.com>2020-03-11 18:08:03 +0000
committerGerrit Code Review <gerrit@bbpush.zzzcomputing.com>2020-03-11 18:08:03 +0000
commit2aa9a8043b4982d4d7b53e8b11371ea27fccd09c (patch)
tree0fc1f0ddd3a6defdda5888ee48bd4e69bd162c4c /lib/sqlalchemy/sql/selectable.py
parent59ca6e5fcc6974ea1fac82d05157aa58e550b332 (diff)
parent693938dd6fb2f3ee3e031aed4c62355ac97f3ceb (diff)
downloadsqlalchemy-2aa9a8043b4982d4d7b53e8b11371ea27fccd09c.tar.gz
Merge "Rework select(), CompoundSelect() in terms of CompileState"
Diffstat (limited to 'lib/sqlalchemy/sql/selectable.py')
-rw-r--r--lib/sqlalchemy/sql/selectable.py706
1 files changed, 274 insertions, 432 deletions
diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py
index 5536b27bc..45b9e7f9d 100644
--- a/lib/sqlalchemy/sql/selectable.py
+++ b/lib/sqlalchemy/sql/selectable.py
@@ -26,16 +26,18 @@ from .base import _cloned_intersection
from .base import _expand_cloned
from .base import _from_objects
from .base import _generative
+from .base import _select_iterables
from .base import ColumnCollection
from .base import ColumnSet
+from .base import CompileState
from .base import DedupeColumnCollection
from .base import Executable
from .base import Generative
+from .base import HasCompileState
from .base import HasMemoized
from .base import Immutable
from .coercions import _document_text_coercion
from .elements import _anonymous_label
-from .elements import _select_iterables
from .elements import and_
from .elements import BindParameter
from .elements import ClauseElement
@@ -44,7 +46,6 @@ from .elements import ColumnClause
from .elements import GroupedElement
from .elements import Grouping
from .elements import literal_column
-from .elements import True_
from .elements import UnaryExpression
from .visitors import InternalTraversal
from .. import exc
@@ -1994,54 +1995,6 @@ class ForUpdateArg(ClauseElement):
("skip_locked", InternalTraversal.dp_boolean),
]
- @classmethod
- def parse_legacy_select(self, arg):
- """Parse the for_update argument of :func:`.select`.
-
- :param mode: Defines the lockmode to use.
-
- ``None`` - translates to no lockmode
-
- ``'update'`` - translates to ``FOR UPDATE``
- (standard SQL, supported by most dialects)
-
- ``'nowait'`` - translates to ``FOR UPDATE NOWAIT``
- (supported by Oracle, PostgreSQL 8.1 upwards)
-
- ``'read'`` - translates to ``LOCK IN SHARE MODE`` (for MySQL),
- and ``FOR SHARE`` (for PostgreSQL)
-
- ``'read_nowait'`` - translates to ``FOR SHARE NOWAIT``
- (supported by PostgreSQL). ``FOR SHARE`` and
- ``FOR SHARE NOWAIT`` (PostgreSQL).
-
- """
- if arg in (None, False):
- return None
-
- nowait = read = False
- if arg == "nowait":
- nowait = True
- elif arg == "read":
- read = True
- elif arg == "read_nowait":
- read = nowait = True
- elif arg is not True:
- raise exc.ArgumentError("Unknown for_update argument: %r" % arg)
-
- return ForUpdateArg(read=read, nowait=nowait)
-
- @property
- def legacy_for_update_value(self):
- if self.read and not self.nowait:
- return "read"
- elif self.read and self.nowait:
- return "read_nowait"
- elif self.nowait:
- return "nowait"
- else:
- return True
-
def __eq__(self, other):
return (
isinstance(other, ForUpdateArg)
@@ -2261,24 +2214,6 @@ class SelectBase(
"""
return Lateral._factory(self, name)
- @_generative
- @util.deprecated(
- "0.6",
- message="The :meth:`.SelectBase.autocommit` method is deprecated, "
- "and will be removed in a future release. Please use the "
- "the :paramref:`.Connection.execution_options.autocommit` "
- "parameter in conjunction with the "
- ":meth:`.Executable.execution_options` method.",
- )
- def autocommit(self):
- """return a new selectable with the 'autocommit' flag set to
- True.
- """
-
- self._execution_options = self._execution_options.union(
- {"autocommit": True}
- )
-
def _generate(self):
"""Override the default _generate() method to also clear out
exported collections."""
@@ -2458,8 +2393,8 @@ class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
"""
- _order_by_clause = ClauseList()
- _group_by_clause = ClauseList()
+ _order_by_clauses = ()
+ _group_by_clauses = ()
_limit_clause = None
_offset_clause = None
_for_update_arg = None
@@ -2467,56 +2402,25 @@ class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
def __init__(
self,
use_labels=False,
- for_update=False,
limit=None,
offset=None,
order_by=None,
group_by=None,
bind=None,
- autocommit=None,
):
self.use_labels = use_labels
- if for_update is not False:
- self._for_update_arg = ForUpdateArg.parse_legacy_select(for_update)
-
- if autocommit is not None:
- util.warn_deprecated(
- "The select.autocommit parameter is deprecated and will be "
- "removed in a future release. Please refer to the "
- "Select.execution_options.autocommit` parameter."
- )
- self._execution_options = self._execution_options.union(
- {"autocommit": autocommit}
- )
if limit is not None:
- self._limit_clause = self._offset_or_limit_clause(limit)
+ self.limit.non_generative(self, limit)
if offset is not None:
- self._offset_clause = self._offset_or_limit_clause(offset)
- self._bind = bind
+ self.offset.non_generative(self, offset)
if order_by is not None:
- self._order_by_clause = ClauseList(
- *util.to_list(order_by),
- _literal_as_text_role=roles.OrderByRole
- )
+ self.order_by.non_generative(self, *util.to_list(order_by))
if group_by is not None:
- self._group_by_clause = ClauseList(
- *util.to_list(group_by), _literal_as_text_role=roles.ByOfRole
- )
+ self.group_by.non_generative(self, *util.to_list(group_by))
- @property
- def for_update(self):
- """Provide legacy dialect support for the ``for_update`` attribute.
- """
- if self._for_update_arg is not None:
- return self._for_update_arg.legacy_for_update_value
- else:
- return None
-
- @for_update.setter
- def for_update(self, value):
- self._for_update_arg = ForUpdateArg.parse_legacy_select(value)
+ self._bind = bind
@_generative
def with_for_update(
@@ -2565,14 +2469,10 @@ class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
on Oracle and PostgreSQL dialects or ``FOR SHARE SKIP LOCKED`` if
``read=True`` is also specified.
- .. versionadded:: 1.1.0
-
:param key_share: boolean, will render ``FOR NO KEY UPDATE``,
or if combined with ``read=True`` will render ``FOR KEY SHARE``,
on the PostgreSQL dialect.
- .. versionadded:: 1.1.0
-
"""
self._for_update_arg = ForUpdateArg(
nowait=nowait,
@@ -2596,6 +2496,20 @@ class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
"""
self.use_labels = True
+ @property
+ def _group_by_clause(self):
+ """ClauseList access to group_by_clauses for legacy dialects"""
+ return ClauseList._construct_raw(
+ operators.comma_op, self._group_by_clauses
+ )
+
+ @property
+ def _order_by_clause(self):
+ """ClauseList access to order_by_clauses for legacy dialects"""
+ return ClauseList._construct_raw(
+ operators.comma_op, self._order_by_clauses
+ )
+
def _offset_or_limit_clause(self, element, name=None, type_=None):
"""Convert the given value to an "offset or limit" clause.
@@ -2727,12 +2641,11 @@ class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
"""
if len(clauses) == 1 and clauses[0] is None:
- self._order_by_clause = ClauseList()
+ self._order_by_clauses = ()
else:
- if getattr(self, "_order_by_clause", None) is not None:
- clauses = list(self._order_by_clause) + list(clauses)
- self._order_by_clause = ClauseList(
- *clauses, _literal_as_text_role=roles.OrderByRole
+ self._order_by_clauses += tuple(
+ coercions.expect(roles.OrderByRole, clause)
+ for clause in clauses
)
@_generative
@@ -2755,20 +2668,24 @@ class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
"""
if len(clauses) == 1 and clauses[0] is None:
- self._group_by_clause = ClauseList()
+ self._group_by_clauses = ()
else:
- if getattr(self, "_group_by_clause", None) is not None:
- clauses = list(self._group_by_clause) + list(clauses)
- self._group_by_clause = ClauseList(
- *clauses, _literal_as_text_role=roles.ByOfRole
+ self._group_by_clauses += tuple(
+ coercions.expect(roles.ByOfRole, clause) for clause in clauses
)
- @property
+
+class CompoundSelectState(CompileState):
+ @util.memoized_property
def _label_resolve_dict(self):
- raise NotImplementedError()
+ # TODO: this is hacky and slow
+ hacky_subquery = self.statement.subquery()
+ hacky_subquery.named_with_column = False
+ d = dict((c.key, c) for c in hacky_subquery.c)
+ return d, d, d
-class CompoundSelect(GenerativeSelect):
+class CompoundSelect(HasCompileState, GenerativeSelect):
"""Forms the basis of ``UNION``, ``UNION ALL``, and other
SELECT-based set operations.
@@ -2790,13 +2707,14 @@ class CompoundSelect(GenerativeSelect):
"""
__visit_name__ = "compound_select"
+ _compile_state_factory = CompoundSelectState._create
_traverse_internals = [
("selects", InternalTraversal.dp_clauseelement_list),
("_limit_clause", InternalTraversal.dp_clauseelement),
("_offset_clause", InternalTraversal.dp_clauseelement),
- ("_order_by_clause", InternalTraversal.dp_clauseelement),
- ("_group_by_clause", InternalTraversal.dp_clauseelement),
+ ("_order_by_clauses", InternalTraversal.dp_clauseelement_list),
+ ("_group_by_clauses", InternalTraversal.dp_clauseelement_list),
("_for_update_arg", InternalTraversal.dp_clauseelement),
("keyword", InternalTraversal.dp_string),
] + SupportsCloneAnnotations._clone_annotations_traverse_internals
@@ -2822,14 +2740,6 @@ class CompoundSelect(GenerativeSelect):
GenerativeSelect.__init__(self, **kwargs)
- @SelectBase._memoized_property
- def _label_resolve_dict(self):
- # TODO: this is hacky and slow
- hacky_subquery = self.subquery()
- hacky_subquery.named_with_column = False
- d = dict((c.key, c) for c in hacky_subquery.c)
- return d, d, d
-
@classmethod
def _create_union(cls, *selects, **kwargs):
r"""Return a ``UNION`` of multiple selectables.
@@ -3001,6 +2911,7 @@ class CompoundSelect(GenerativeSelect):
"""
return self.selects[0].selected_columns
+ @property
def bind(self):
if self._bind:
return self._bind
@@ -3011,11 +2922,10 @@ class CompoundSelect(GenerativeSelect):
else:
return None
- def _set_bind(self, bind):
+ @bind.setter
+ def bind(self, bind):
self._bind = bind
- bind = property(bind, _set_bind)
-
class DeprecatedSelectGenerations(object):
@util.deprecated(
@@ -3135,8 +3045,130 @@ class DeprecatedSelectGenerations(object):
self.select_from.non_generative(self, fromclause)
+class SelectState(CompileState):
+ def __init__(self, statement, compiler, **kw):
+ self.statement = statement
+ self.froms = self._get_froms(statement)
+
+ self.columns_plus_names = statement._generate_columns_plus_names(True)
+
+ def _get_froms(self, statement):
+ froms = []
+ seen = set()
+
+ for item in statement._iterate_from_elements():
+ if item._is_subquery and item.element is statement:
+ raise exc.InvalidRequestError(
+ "select() construct refers to itself as a FROM"
+ )
+ if not seen.intersection(item._cloned_set):
+ froms.append(item)
+ seen.update(item._cloned_set)
+
+ return froms
+
+ def _get_display_froms(
+ self, explicit_correlate_froms=None, implicit_correlate_froms=None
+ ):
+ """Return the full list of 'from' clauses to be displayed.
+
+ Takes into account a set of existing froms which may be
+ rendered in the FROM clause of enclosing selects; this Select
+ may want to leave those absent if it is automatically
+ correlating.
+
+ """
+ froms = self.froms
+
+ toremove = set(
+ itertools.chain.from_iterable(
+ [_expand_cloned(f._hide_froms) for f in froms]
+ )
+ )
+ if toremove:
+ # filter out to FROM clauses not in the list,
+ # using a list to maintain ordering
+ froms = [f for f in froms if f not in toremove]
+
+ if self.statement._correlate:
+ to_correlate = self.statement._correlate
+ if to_correlate:
+ froms = [
+ f
+ for f in froms
+ if f
+ not in _cloned_intersection(
+ _cloned_intersection(
+ froms, explicit_correlate_froms or ()
+ ),
+ to_correlate,
+ )
+ ]
+
+ if self.statement._correlate_except is not None:
+
+ froms = [
+ f
+ for f in froms
+ if f
+ not in _cloned_difference(
+ _cloned_intersection(
+ froms, explicit_correlate_froms or ()
+ ),
+ self.statement._correlate_except,
+ )
+ ]
+
+ if (
+ self.statement._auto_correlate
+ and implicit_correlate_froms
+ and len(froms) > 1
+ ):
+
+ froms = [
+ f
+ for f in froms
+ if f
+ not in _cloned_intersection(froms, implicit_correlate_froms)
+ ]
+
+ if not len(froms):
+ raise exc.InvalidRequestError(
+ "Select statement '%s"
+ "' returned no FROM clauses "
+ "due to auto-correlation; "
+ "specify correlate(<tables>) "
+ "to control correlation "
+ "manually." % self
+ )
+
+ return froms
+
+ @util.memoized_property
+ def _label_resolve_dict(self):
+ with_cols = dict(
+ (c._resolve_label or c._label or c.key, c)
+ for c in _select_iterables(self.statement._raw_columns)
+ if c._allow_label_resolve
+ )
+ only_froms = dict(
+ (c.key, c)
+ for c in _select_iterables(self.froms)
+ if c._allow_label_resolve
+ )
+ only_cols = with_cols.copy()
+ for key, value in only_froms.items():
+ with_cols.setdefault(key, value)
+
+ return with_cols, only_froms, only_cols
+
+
class Select(
- HasPrefixes, HasSuffixes, DeprecatedSelectGenerations, GenerativeSelect
+ HasPrefixes,
+ HasSuffixes,
+ HasCompileState,
+ DeprecatedSelectGenerations,
+ GenerativeSelect,
):
"""Represents a ``SELECT`` statement.
@@ -3144,28 +3176,29 @@ class Select(
__visit_name__ = "select"
+ _compile_state_factory = SelectState._create
+
_hints = util.immutabledict()
_statement_hints = ()
_distinct = False
_distinct_on = ()
_correlate = ()
_correlate_except = None
+ _where_criteria = ()
+ _having_criteria = ()
+ _from_obj = ()
+ _auto_correlate = True
+
_memoized_property = SelectBase._memoized_property
_traverse_internals = (
[
- ("_from_obj", InternalTraversal.dp_fromclause_ordered_set),
+ ("_from_obj", InternalTraversal.dp_clauseelement_list),
("_raw_columns", InternalTraversal.dp_clauseelement_list),
- ("_whereclause", InternalTraversal.dp_clauseelement),
- ("_having", InternalTraversal.dp_clauseelement),
- (
- "_order_by_clause.clauses",
- InternalTraversal.dp_clauseelement_list,
- ),
- (
- "_group_by_clause.clauses",
- InternalTraversal.dp_clauseelement_list,
- ),
+ ("_where_criteria", InternalTraversal.dp_clauseelement_list),
+ ("_having_criteria", InternalTraversal.dp_clauseelement_list),
+ ("_order_by_clauses", InternalTraversal.dp_clauseelement_list,),
+ ("_group_by_clauses", InternalTraversal.dp_clauseelement_list,),
("_correlate", InternalTraversal.dp_clauseelement_unordered_set),
(
"_correlate_except",
@@ -3220,13 +3253,6 @@ class Select(
for ent in util.to_list(entities)
]
- # this should all go away once Select is converted to have
- # default state at the class level
- self._auto_correlate = True
- self._from_obj = util.OrderedSet()
- self._whereclause = None
- self._having = None
-
GenerativeSelect.__init__(self)
return self
@@ -3244,24 +3270,6 @@ class Select(
else:
return Select._create_select(*entities)
- @util.deprecated_params(
- autocommit=(
- "0.6",
- "The :paramref:`.select.autocommit` parameter is deprecated "
- "and will be removed in a future release. Please refer to "
- "the :paramref:`.Connection.execution_options.autocommit` "
- "parameter in conjunction with the the "
- ":meth:`.Executable.execution_options` method in order to "
- "affect the autocommit behavior for a statement.",
- ),
- for_update=(
- "0.9",
- "The :paramref:`.select.for_update` parameter is deprecated and "
- "will be removed in a future release. Please refer to the "
- ":meth:`.Select.with_for_update` to specify the "
- "structure of the ``FOR UPDATE`` clause.",
- ),
- )
def __init__(
self,
columns=None,
@@ -3332,8 +3340,6 @@ class Select(
:meth:`.Select.select_from` - full description of explicit
FROM clause specification.
- :param autocommit: legacy autocommit parameter.
-
:param bind=None:
an :class:`~.Engine` or :class:`~.Connection` instance
to which the
@@ -3369,25 +3375,6 @@ class Select(
:meth:`.Select.distinct`
- :param for_update=False:
- when ``True``, applies ``FOR UPDATE`` to the end of the
- resulting statement.
-
- ``for_update`` accepts various string values interpreted by
- specific backends, including:
-
- * ``"read"`` - on MySQL, translates to ``LOCK IN SHARE MODE``;
- on PostgreSQL, translates to ``FOR SHARE``.
- * ``"nowait"`` - on PostgreSQL and Oracle, translates to
- ``FOR UPDATE NOWAIT``.
- * ``"read_nowait"`` - on PostgreSQL, translates to
- ``FOR SHARE NOWAIT``.
-
- .. seealso::
-
- :meth:`.Select.with_for_update` - improved API for
- specifying the ``FOR UPDATE`` clause.
-
:param group_by:
a list of :class:`.ClauseElement` objects which will comprise the
``GROUP BY`` clause of the resulting select. This parameter
@@ -3469,23 +3456,15 @@ class Select(
)
self._auto_correlate = correlate
+
if distinct is not False:
- self._distinct = True
- if not isinstance(distinct, bool):
- self._distinct_on = tuple(
- [
- coercions.expect(roles.ByOfRole, e)
- for e in util.to_list(distinct)
- ]
- )
+ if distinct is True:
+ self.distinct.non_generative(self)
+ else:
+ self.distinct.non_generative(self, *util.to_list(distinct))
if from_obj is not None:
- self._from_obj = util.OrderedSet(
- coercions.expect(roles.FromClauseRole, f)
- for f in util.to_list(from_obj)
- )
- else:
- self._from_obj = util.OrderedSet()
+ self.select_from.non_generative(self, *util.to_list(from_obj))
try:
cols_present = bool(columns)
@@ -3499,26 +3478,17 @@ class Select(
)
if cols_present:
- self._raw_columns = []
- for c in columns:
- c = coercions.expect(roles.ColumnsClauseRole, c)
- self._raw_columns.append(c)
+ self._raw_columns = [
+ coercions.expect(roles.ColumnsClauseRole, c,) for c in columns
+ ]
else:
self._raw_columns = []
if whereclause is not None:
- self._whereclause = coercions.expect(
- roles.WhereHavingRole, whereclause
- ).self_group(against=operators._asbool)
- else:
- self._whereclause = None
+ self.where.non_generative(self, whereclause)
if having is not None:
- self._having = coercions.expect(
- roles.WhereHavingRole, having
- ).self_group(against=operators._asbool)
- else:
- self._having = None
+ self.having.non_generative(self, having)
if prefixes:
self._setup_prefixes(prefixes)
@@ -3528,119 +3498,27 @@ class Select(
GenerativeSelect.__init__(self, **kwargs)
- @property
- def _froms(self):
- # current roadblock to caching is two tests that test that the
- # SELECT can be compiled to a string, then a Table is created against
- # columns, then it can be compiled again and works. this is somewhat
- # valid as people make select() against declarative class where
- # columns don't have their Table yet and perhaps some operations
- # call upon _froms and cache it too soon.
- froms = []
- seen = set()
-
- for item in itertools.chain(
- _from_objects(*self._raw_columns),
- _from_objects(self._whereclause)
- if self._whereclause is not None
- else (),
- self._from_obj,
- ):
- if item._is_subquery and item.element is self:
- raise exc.InvalidRequestError(
- "select() construct refers to itself as a FROM"
- )
- if not seen.intersection(item._cloned_set):
- froms.append(item)
- seen.update(item._cloned_set)
-
- return froms
-
- def _get_display_froms(
- self, explicit_correlate_froms=None, implicit_correlate_froms=None
- ):
- """Return the full list of 'from' clauses to be displayed.
-
- Takes into account a set of existing froms which may be
- rendered in the FROM clause of enclosing selects; this Select
- may want to leave those absent if it is automatically
- correlating.
-
- """
- froms = self._froms
-
- toremove = set(
- itertools.chain(*[_expand_cloned(f._hide_froms) for f in froms])
- )
- if toremove:
- # filter out to FROM clauses not in the list,
- # using a list to maintain ordering
- froms = [f for f in froms if f not in toremove]
-
- if self._correlate:
- to_correlate = self._correlate
- if to_correlate:
- froms = [
- f
- for f in froms
- if f
- not in _cloned_intersection(
- _cloned_intersection(
- froms, explicit_correlate_froms or ()
- ),
- to_correlate,
- )
- ]
-
- if self._correlate_except is not None:
-
- froms = [
- f
- for f in froms
- if f
- not in _cloned_difference(
- _cloned_intersection(
- froms, explicit_correlate_froms or ()
- ),
- self._correlate_except,
- )
- ]
-
- if (
- self._auto_correlate
- and implicit_correlate_froms
- and len(froms) > 1
- ):
-
- froms = [
- f
- for f in froms
- if f
- not in _cloned_intersection(froms, implicit_correlate_froms)
- ]
-
- if not len(froms):
- raise exc.InvalidRequestError(
- "Select statement '%s"
- "' returned no FROM clauses "
- "due to auto-correlation; "
- "specify correlate(<tables>) "
- "to control correlation "
- "manually." % self
- )
-
- return froms
-
def _scalar_type(self):
elem = self._raw_columns[0]
cols = list(elem._select_iterable)
return cols[0].type
+ def _iterate_from_elements(self):
+ return itertools.chain(
+ itertools.chain.from_iterable(
+ [element._from_objects for element in self._raw_columns]
+ ),
+ itertools.chain.from_iterable(
+ [element._from_objects for element in self._where_criteria]
+ ),
+ self._from_obj,
+ )
+
@property
def froms(self):
"""Return the displayed list of FromClause elements."""
- return self._get_display_froms()
+ return self._compile_state_factory(self, None)._get_display_froms()
def with_statement_hint(self, text, dialect_name="*"):
"""add a statement hint to this :class:`.Select`.
@@ -3705,18 +3583,6 @@ class Select(
else:
self._hints = self._hints.union({(selectable, dialect_name): text})
- @_memoized_property.method
- def locate_all_froms(self):
- """return a Set of all FromClause elements referenced by this Select.
-
- This set is a superset of that returned by the ``froms`` property,
- which is specifically for those FromClause elements that would
- actually be rendered.
-
- """
- froms = self._froms
- return froms + list(_from_objects(*froms))
-
@property
def inner_columns(self):
"""an iterator of all ColumnElement expressions which would
@@ -3725,29 +3591,11 @@ class Select(
"""
return _select_iterables(self._raw_columns)
- @_memoized_property
- def _label_resolve_dict(self):
- with_cols = dict(
- (c._resolve_label or c._label or c.key, c)
- for c in _select_iterables(self._raw_columns)
- if c._allow_label_resolve
- )
- only_froms = dict(
- (c.key, c)
- for c in _select_iterables(self.froms)
- if c._allow_label_resolve
- )
- only_cols = with_cols.copy()
- for key, value in only_froms.items():
- with_cols.setdefault(key, value)
-
- return with_cols, only_froms, only_cols
-
def is_derived_from(self, fromclause):
if self in fromclause._cloned_set:
return True
- for f in self.locate_all_froms():
+ for f in self._iterate_from_elements():
if f.is_derived_from(fromclause):
return True
return False
@@ -3758,40 +3606,24 @@ class Select(
# objects
# 1. keep a dictionary of the froms we've cloned, and what
- # they've become. This is consulted later when we derive
- # additional froms from "whereclause" and the columns clause,
- # which may still reference the uncloned parent table.
- # as of 0.7.4 we also put the current version of _froms, which
- # gets cleared on each generation. previously we were "baking"
- # _froms into self._from_obj.
+ # they've become. This allows us to ensure the same cloned from
+ # is used when other items such as columns are "cloned"
all_the_froms = list(
itertools.chain(
_from_objects(*self._raw_columns),
- _from_objects(self._whereclause)
- if self._whereclause is not None
- else (),
+ _from_objects(*self._where_criteria),
)
)
new_froms = {f: clone(f, **kw) for f in all_the_froms}
- # copy FROM collections
- self._from_obj = util.OrderedSet(
- clone(f, **kw) for f in self._from_obj
- ).union(f for f in new_froms.values() if isinstance(f, Join))
-
- self._correlate = set(clone(f, **kw) for f in self._correlate)
- if self._correlate_except:
- self._correlate_except = set(
- clone(f, **kw) for f in self._correlate_except
- )
+ # 2. copy FROM collections.
+ self._from_obj = tuple(clone(f, **kw) for f in self._from_obj) + tuple(
+ f for f in new_froms.values() if isinstance(f, Join)
+ )
- # 4. clone other things. The difficulty here is that Column
- # objects are usually not altered by a straight clone because they
- # are dependent on the FROM cloning we just did above in order to
- # be targeted correctly, or a new FROM we have might be a JOIN
- # object which doesn't have its own columns. so give the cloner a
- # hint.
+ # 3. clone everything else, making sure we use columns
+ # corresponding to the froms we just made.
def replace(obj, **kw):
if isinstance(obj, ColumnClause) and obj.table in new_froms:
newelem = new_froms[obj.table].corresponding_column(obj)
@@ -3799,25 +3631,16 @@ class Select(
kw["replace"] = replace
- # TODO: I'd still like to try to leverage the traversal data
- self._raw_columns = [clone(c, **kw) for c in self._raw_columns]
- for attr in (
- "_limit_clause",
- "_offset_clause",
- "_whereclause",
- "_having",
- "_order_by_clause",
- "_group_by_clause",
- "_for_update_arg",
- ):
- if getattr(self, attr) is not None:
- setattr(self, attr, clone(getattr(self, attr), **kw))
+ super(Select, self)._copy_internals(
+ clone=clone, omit_attrs=("_from_obj",), **kw
+ )
self._reset_memoizations()
def get_children(self, **kwargs):
- # TODO: define "get_children" traversal items separately?
- return self._froms + super(Select, self).get_children(
+ return list(set(self._iterate_from_elements())) + super(
+ Select, self
+ ).get_children(
omit_attrs=["_from_obj", "_correlate", "_correlate_except"]
)
@@ -3838,7 +3661,7 @@ class Select(
self._reset_memoizations()
self._raw_columns = self._raw_columns + [
- coercions.expect(roles.ColumnsClauseRole, column)
+ coercions.expect(roles.ColumnsClauseRole, column,)
for column in columns
]
@@ -3889,7 +3712,7 @@ class Select(
util.preloaded.sql_util.reduce_columns(
self.inner_columns,
only_synonyms=only_synonyms,
- *(self._whereclause,) + tuple(self._from_obj)
+ *(self._where_criteria + self._from_obj)
)
)
@@ -3965,12 +3788,18 @@ class Select(
self._reset_memoizations()
rc = []
for c in columns:
- c = coercions.expect(roles.ColumnsClauseRole, c)
+ c = coercions.expect(roles.ColumnsClauseRole, c,)
if isinstance(c, ScalarSelect):
c = c.self_group(against=operators.comma_op)
rc.append(c)
self._raw_columns = rc
+ @property
+ def _whereclause(self):
+ """Legacy, return the WHERE clause as a :class:`.BooleanClauseList`"""
+
+ return and_(*self._where_criteria)
+
@_generative
def where(self, whereclause):
"""return a new select() construct with the given expression added to
@@ -3978,8 +3807,9 @@ class Select(
"""
- self._reset_memoizations()
- self._whereclause = and_(True_._ifnone(self._whereclause), whereclause)
+ self._where_criteria += (
+ coercions.expect(roles.WhereHavingRole, whereclause),
+ )
@_generative
def having(self, having):
@@ -3987,8 +3817,9 @@ class Select(
its HAVING clause, joined to the existing clause via AND, if any.
"""
- self._reset_memoizations()
- self._having = and_(True_._ifnone(self._having), having)
+ self._having_criteria += (
+ coercions.expect(roles.WhereHavingRole, having),
+ )
@_generative
def distinct(self, *expr):
@@ -4001,16 +3832,17 @@ class Select(
"""
if expr:
- expr = [coercions.expect(roles.ByOfRole, e) for e in expr]
self._distinct = True
- self._distinct_on = self._distinct_on + tuple(expr)
+ self._distinct_on = self._distinct_on + tuple(
+ coercions.expect(roles.ByOfRole, e) for e in expr
+ )
else:
self._distinct = True
@_generative
- def select_from(self, fromclause):
+ def select_from(self, *froms):
r"""return a new :func:`.select` construct with the
- given FROM expression
+ given FROM expression(s)
merged into its list of FROM objects.
E.g.::
@@ -4039,9 +3871,11 @@ class Select(
select([func.count('*')]).select_from(table1)
"""
- self._reset_memoizations()
- fromclause = coercions.expect(roles.FromClauseRole, fromclause)
- self._from_obj = self._from_obj.union([fromclause])
+
+ self._from_obj += tuple(
+ coercions.expect(roles.FromClauseRole, fromclause)
+ for fromclause in froms
+ )
@_generative
def correlate(self, *fromclauses):
@@ -4198,12 +4032,17 @@ class Select(
names = {}
def name_for_col(c):
- if c._label is None or not c._render_label_in_columns_clause:
+ if not c._render_label_in_columns_clause:
return (None, c, False)
+ elif c._label is None:
+ repeated = c.anon_label in names
+ names[c.anon_label] = c
+ return (None, c, repeated)
- repeated = False
name = c._label
+ repeated = False
+
if name in names:
# when looking to see if names[name] is the same column as
# c, use hash(), so that an annotated version of the column
@@ -4242,13 +4081,11 @@ class Select(
return [name_for_col(c) for c in cols]
else:
# repeated name logic only for use labels at the moment
- return [(None, c, False) for c in cols]
-
- @_memoized_property
- def _columns_plus_names(self):
- """generate label names plus columns to render in a SELECT."""
+ same_cols = set()
- return self._generate_columns_plus_names(True)
+ return [
+ (None, c, c in same_cols or same_cols.add(c)) for c in cols
+ ]
def _generate_fromclause_column_proxies(self, subquery):
"""generate column proxies to place in the exported .c collection
@@ -4340,29 +4177,34 @@ class Select(
"""
return CompoundSelect._create_intersect_all(self, other, **kwargs)
+ @property
def bind(self):
if self._bind:
return self._bind
- froms = self._froms
- if not froms:
- for c in self._raw_columns:
- e = c.bind
- if e:
- self._bind = e
- return e
- else:
- e = list(froms)[0].bind
+
+ for item in self._iterate_from_elements():
+ if item._is_subquery and item.element is self:
+ raise exc.InvalidRequestError(
+ "select() construct refers to itself as a FROM"
+ )
+
+ e = item.bind
if e:
self._bind = e
return e
+ else:
+ break
- return None
+ for c in self._raw_columns:
+ e = c.bind
+ if e:
+ self._bind = e
+ return e
- def _set_bind(self, bind):
+ @bind.setter
+ def bind(self, bind):
self._bind = bind
- bind = property(bind, _set_bind)
-
class ScalarSelect(roles.InElementRole, Generative, Grouping):
_from_objects = []