You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6914 lines
227 KiB
6914 lines
227 KiB
# sql/selectable.py
|
|
# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
|
|
# <see AUTHORS file>
|
|
#
|
|
# This module is part of SQLAlchemy and is released under
|
|
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
|
|
"""The :class:`_expression.FromClause` class of SQL expression elements,
|
|
representing
|
|
SQL tables and derived rowsets.
|
|
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import collections
|
|
from enum import Enum
|
|
import itertools
|
|
from typing import AbstractSet
|
|
from typing import Any as TODO_Any
|
|
from typing import Any
|
|
from typing import Callable
|
|
from typing import cast
|
|
from typing import Dict
|
|
from typing import Generic
|
|
from typing import Iterable
|
|
from typing import Iterator
|
|
from typing import List
|
|
from typing import NamedTuple
|
|
from typing import NoReturn
|
|
from typing import Optional
|
|
from typing import overload
|
|
from typing import Sequence
|
|
from typing import Set
|
|
from typing import Tuple
|
|
from typing import Type
|
|
from typing import TYPE_CHECKING
|
|
from typing import TypeVar
|
|
from typing import Union
|
|
|
|
from . import cache_key
|
|
from . import coercions
|
|
from . import operators
|
|
from . import roles
|
|
from . import traversals
|
|
from . import type_api
|
|
from . import visitors
|
|
from ._typing import _ColumnsClauseArgument
|
|
from ._typing import _no_kw
|
|
from ._typing import _TP
|
|
from ._typing import is_column_element
|
|
from ._typing import is_select_statement
|
|
from ._typing import is_subquery
|
|
from ._typing import is_table
|
|
from ._typing import is_text_clause
|
|
from .annotation import Annotated
|
|
from .annotation import SupportsCloneAnnotations
|
|
from .base import _clone
|
|
from .base import _cloned_difference
|
|
from .base import _cloned_intersection
|
|
from .base import _entity_namespace_key
|
|
from .base import _EntityNamespace
|
|
from .base import _expand_cloned
|
|
from .base import _from_objects
|
|
from .base import _generative
|
|
from .base import _never_select_column
|
|
from .base import _NoArg
|
|
from .base import _select_iterables
|
|
from .base import CacheableOptions
|
|
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 BindParameter
|
|
from .elements import BooleanClauseList
|
|
from .elements import ClauseElement
|
|
from .elements import ClauseList
|
|
from .elements import ColumnClause
|
|
from .elements import ColumnElement
|
|
from .elements import DQLDMLClauseElement
|
|
from .elements import GroupedElement
|
|
from .elements import literal_column
|
|
from .elements import TableValuedColumn
|
|
from .elements import UnaryExpression
|
|
from .operators import OperatorType
|
|
from .sqltypes import NULLTYPE
|
|
from .visitors import _TraverseInternalsType
|
|
from .visitors import InternalTraversal
|
|
from .visitors import prefix_anon_map
|
|
from .. import exc
|
|
from .. import util
|
|
from ..util import HasMemoized_ro_memoized_attribute
|
|
from ..util.typing import Literal
|
|
from ..util.typing import Protocol
|
|
from ..util.typing import Self
|
|
|
|
and_ = BooleanClauseList.and_
|
|
|
|
_T = TypeVar("_T", bound=Any)
|
|
|
|
if TYPE_CHECKING:
|
|
from ._typing import _ColumnExpressionArgument
|
|
from ._typing import _ColumnExpressionOrStrLabelArgument
|
|
from ._typing import _FromClauseArgument
|
|
from ._typing import _JoinTargetArgument
|
|
from ._typing import _LimitOffsetType
|
|
from ._typing import _MAYBE_ENTITY
|
|
from ._typing import _NOT_ENTITY
|
|
from ._typing import _OnClauseArgument
|
|
from ._typing import _SelectStatementForCompoundArgument
|
|
from ._typing import _T0
|
|
from ._typing import _T1
|
|
from ._typing import _T2
|
|
from ._typing import _T3
|
|
from ._typing import _T4
|
|
from ._typing import _T5
|
|
from ._typing import _T6
|
|
from ._typing import _T7
|
|
from ._typing import _TextCoercedExpressionArgument
|
|
from ._typing import _TypedColumnClauseArgument as _TCCA
|
|
from ._typing import _TypeEngineArgument
|
|
from .base import _AmbiguousTableNameMap
|
|
from .base import ExecutableOption
|
|
from .base import ReadOnlyColumnCollection
|
|
from .cache_key import _CacheKeyTraversalType
|
|
from .compiler import SQLCompiler
|
|
from .dml import Delete
|
|
from .dml import Update
|
|
from .elements import BinaryExpression
|
|
from .elements import KeyedColumnElement
|
|
from .elements import Label
|
|
from .elements import NamedColumn
|
|
from .elements import TextClause
|
|
from .functions import Function
|
|
from .schema import ForeignKey
|
|
from .schema import ForeignKeyConstraint
|
|
from .sqltypes import TableValueType
|
|
from .type_api import TypeEngine
|
|
from .visitors import _CloneCallableType
|
|
|
|
|
|
_ColumnsClauseElement = Union["FromClause", ColumnElement[Any], "TextClause"]
|
|
_LabelConventionCallable = Callable[
|
|
[Union["ColumnElement[Any]", "TextClause"]], Optional[str]
|
|
]
|
|
|
|
|
|
class _JoinTargetProtocol(Protocol):
|
|
@util.ro_non_memoized_property
|
|
def _from_objects(self) -> List[FromClause]: ...
|
|
|
|
@util.ro_non_memoized_property
|
|
def entity_namespace(self) -> _EntityNamespace: ...
|
|
|
|
|
|
_JoinTargetElement = Union["FromClause", _JoinTargetProtocol]
|
|
_OnClauseElement = Union["ColumnElement[bool]", _JoinTargetProtocol]
|
|
|
|
_ForUpdateOfArgument = Union[
|
|
# single column, Table, ORM Entity
|
|
Union[
|
|
"_ColumnExpressionArgument[Any]",
|
|
"_FromClauseArgument",
|
|
],
|
|
# or sequence of single column elements
|
|
Sequence["_ColumnExpressionArgument[Any]"],
|
|
]
|
|
|
|
|
|
_SetupJoinsElement = Tuple[
|
|
_JoinTargetElement,
|
|
Optional[_OnClauseElement],
|
|
Optional["FromClause"],
|
|
Dict[str, Any],
|
|
]
|
|
|
|
|
|
_SelectIterable = Iterable[Union["ColumnElement[Any]", "TextClause"]]
|
|
|
|
|
|
class _OffsetLimitParam(BindParameter[int]):
|
|
inherit_cache = True
|
|
|
|
@property
|
|
def _limit_offset_value(self) -> Optional[int]:
|
|
return self.effective_value
|
|
|
|
|
|
class ReturnsRows(roles.ReturnsRowsRole, DQLDMLClauseElement):
|
|
"""The base-most class for Core constructs that have some concept of
|
|
columns that can represent rows.
|
|
|
|
While the SELECT statement and TABLE are the primary things we think
|
|
of in this category, DML like INSERT, UPDATE and DELETE can also specify
|
|
RETURNING which means they can be used in CTEs and other forms, and
|
|
PostgreSQL has functions that return rows also.
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
"""
|
|
|
|
_is_returns_rows = True
|
|
|
|
# sub-elements of returns_rows
|
|
_is_from_clause = False
|
|
_is_select_base = False
|
|
_is_select_statement = False
|
|
_is_lateral = False
|
|
|
|
@property
|
|
def selectable(self) -> ReturnsRows:
|
|
return self
|
|
|
|
@util.ro_non_memoized_property
|
|
def _all_selected_columns(self) -> _SelectIterable:
|
|
"""A sequence of column expression objects that represents the
|
|
"selected" columns of this :class:`_expression.ReturnsRows`.
|
|
|
|
This is typically equivalent to .exported_columns except it is
|
|
delivered in the form of a straight sequence and not keyed
|
|
:class:`_expression.ColumnCollection`.
|
|
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
|
|
"""Return ``True`` if this :class:`.ReturnsRows` is
|
|
'derived' from the given :class:`.FromClause`.
|
|
|
|
An example would be an Alias of a Table is derived from that Table.
|
|
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def _generate_fromclause_column_proxies(
|
|
self, fromclause: FromClause
|
|
) -> None:
|
|
"""Populate columns into an :class:`.AliasedReturnsRows` object."""
|
|
|
|
raise NotImplementedError()
|
|
|
|
def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
|
|
"""reset internal collections for an incoming column being added."""
|
|
raise NotImplementedError()
|
|
|
|
@property
|
|
def exported_columns(self) -> ReadOnlyColumnCollection[Any, Any]:
|
|
"""A :class:`_expression.ColumnCollection`
|
|
that represents the "exported"
|
|
columns of this :class:`_expression.ReturnsRows`.
|
|
|
|
The "exported" columns represent the collection of
|
|
:class:`_expression.ColumnElement`
|
|
expressions that are rendered by this SQL
|
|
construct. There are primary varieties which are the
|
|
"FROM clause columns" of a FROM clause, such as a table, join,
|
|
or subquery, the "SELECTed columns", which are the columns in
|
|
the "columns clause" of a SELECT statement, and the RETURNING
|
|
columns in a DML statement..
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
.. seealso::
|
|
|
|
:attr:`_expression.FromClause.exported_columns`
|
|
|
|
:attr:`_expression.SelectBase.exported_columns`
|
|
"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
class ExecutableReturnsRows(Executable, ReturnsRows):
|
|
"""base for executable statements that return rows."""
|
|
|
|
|
|
class TypedReturnsRows(ExecutableReturnsRows, Generic[_TP]):
|
|
"""base for executable statements that return rows."""
|
|
|
|
|
|
class Selectable(ReturnsRows):
|
|
"""Mark a class as being selectable."""
|
|
|
|
__visit_name__ = "selectable"
|
|
|
|
is_selectable = True
|
|
|
|
def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
|
|
raise NotImplementedError()
|
|
|
|
def lateral(self, name: Optional[str] = None) -> LateralFromClause:
|
|
"""Return a LATERAL alias of this :class:`_expression.Selectable`.
|
|
|
|
The return value is the :class:`_expression.Lateral` construct also
|
|
provided by the top-level :func:`_expression.lateral` function.
|
|
|
|
.. seealso::
|
|
|
|
:ref:`tutorial_lateral_correlation` - overview of usage.
|
|
|
|
"""
|
|
return Lateral._construct(self, name=name)
|
|
|
|
@util.deprecated(
|
|
"1.4",
|
|
message="The :meth:`.Selectable.replace_selectable` method is "
|
|
"deprecated, and will be removed in a future release. Similar "
|
|
"functionality is available via the sqlalchemy.sql.visitors module.",
|
|
)
|
|
@util.preload_module("sqlalchemy.sql.util")
|
|
def replace_selectable(self, old: FromClause, alias: Alias) -> Self:
|
|
"""Replace all occurrences of :class:`_expression.FromClause`
|
|
'old' with the given :class:`_expression.Alias`
|
|
object, returning a copy of this :class:`_expression.FromClause`.
|
|
|
|
"""
|
|
return util.preloaded.sql_util.ClauseAdapter(alias).traverse(self)
|
|
|
|
def corresponding_column(
|
|
self, column: KeyedColumnElement[Any], require_embedded: bool = False
|
|
) -> Optional[KeyedColumnElement[Any]]:
|
|
"""Given a :class:`_expression.ColumnElement`, return the exported
|
|
:class:`_expression.ColumnElement` object from the
|
|
:attr:`_expression.Selectable.exported_columns`
|
|
collection of this :class:`_expression.Selectable`
|
|
which corresponds to that
|
|
original :class:`_expression.ColumnElement` via a common ancestor
|
|
column.
|
|
|
|
:param column: the target :class:`_expression.ColumnElement`
|
|
to be matched.
|
|
|
|
:param require_embedded: only return corresponding columns for
|
|
the given :class:`_expression.ColumnElement`, if the given
|
|
:class:`_expression.ColumnElement`
|
|
is actually present within a sub-element
|
|
of this :class:`_expression.Selectable`.
|
|
Normally the column will match if
|
|
it merely shares a common ancestor with one of the exported
|
|
columns of this :class:`_expression.Selectable`.
|
|
|
|
.. seealso::
|
|
|
|
:attr:`_expression.Selectable.exported_columns` - the
|
|
:class:`_expression.ColumnCollection`
|
|
that is used for the operation.
|
|
|
|
:meth:`_expression.ColumnCollection.corresponding_column`
|
|
- implementation
|
|
method.
|
|
|
|
"""
|
|
|
|
return self.exported_columns.corresponding_column(
|
|
column, require_embedded
|
|
)
|
|
|
|
|
|
class HasPrefixes:
|
|
_prefixes: Tuple[Tuple[DQLDMLClauseElement, str], ...] = ()
|
|
|
|
_has_prefixes_traverse_internals: _TraverseInternalsType = [
|
|
("_prefixes", InternalTraversal.dp_prefix_sequence)
|
|
]
|
|
|
|
@_generative
|
|
@_document_text_coercion(
|
|
"prefixes",
|
|
":meth:`_expression.HasPrefixes.prefix_with`",
|
|
":paramref:`.HasPrefixes.prefix_with.*prefixes`",
|
|
)
|
|
def prefix_with(
|
|
self,
|
|
*prefixes: _TextCoercedExpressionArgument[Any],
|
|
dialect: str = "*",
|
|
) -> Self:
|
|
r"""Add one or more expressions following the statement keyword, i.e.
|
|
SELECT, INSERT, UPDATE, or DELETE. Generative.
|
|
|
|
This is used to support backend-specific prefix keywords such as those
|
|
provided by MySQL.
|
|
|
|
E.g.::
|
|
|
|
stmt = table.insert().prefix_with("LOW_PRIORITY", dialect="mysql")
|
|
|
|
# MySQL 5.7 optimizer hints
|
|
stmt = select(table).prefix_with(
|
|
"/*+ BKA(t1) */", dialect="mysql")
|
|
|
|
Multiple prefixes can be specified by multiple calls
|
|
to :meth:`_expression.HasPrefixes.prefix_with`.
|
|
|
|
:param \*prefixes: textual or :class:`_expression.ClauseElement`
|
|
construct which
|
|
will be rendered following the INSERT, UPDATE, or DELETE
|
|
keyword.
|
|
:param dialect: optional string dialect name which will
|
|
limit rendering of this prefix to only that dialect.
|
|
|
|
"""
|
|
self._prefixes = self._prefixes + tuple(
|
|
[
|
|
(coercions.expect(roles.StatementOptionRole, p), dialect)
|
|
for p in prefixes
|
|
]
|
|
)
|
|
return self
|
|
|
|
|
|
class HasSuffixes:
|
|
_suffixes: Tuple[Tuple[DQLDMLClauseElement, str], ...] = ()
|
|
|
|
_has_suffixes_traverse_internals: _TraverseInternalsType = [
|
|
("_suffixes", InternalTraversal.dp_prefix_sequence)
|
|
]
|
|
|
|
@_generative
|
|
@_document_text_coercion(
|
|
"suffixes",
|
|
":meth:`_expression.HasSuffixes.suffix_with`",
|
|
":paramref:`.HasSuffixes.suffix_with.*suffixes`",
|
|
)
|
|
def suffix_with(
|
|
self,
|
|
*suffixes: _TextCoercedExpressionArgument[Any],
|
|
dialect: str = "*",
|
|
) -> Self:
|
|
r"""Add one or more expressions following the statement as a whole.
|
|
|
|
This is used to support backend-specific suffix keywords on
|
|
certain constructs.
|
|
|
|
E.g.::
|
|
|
|
stmt = select(col1, col2).cte().suffix_with(
|
|
"cycle empno set y_cycle to 1 default 0", dialect="oracle")
|
|
|
|
Multiple suffixes can be specified by multiple calls
|
|
to :meth:`_expression.HasSuffixes.suffix_with`.
|
|
|
|
:param \*suffixes: textual or :class:`_expression.ClauseElement`
|
|
construct which
|
|
will be rendered following the target clause.
|
|
:param dialect: Optional string dialect name which will
|
|
limit rendering of this suffix to only that dialect.
|
|
|
|
"""
|
|
self._suffixes = self._suffixes + tuple(
|
|
[
|
|
(coercions.expect(roles.StatementOptionRole, p), dialect)
|
|
for p in suffixes
|
|
]
|
|
)
|
|
return self
|
|
|
|
|
|
class HasHints:
|
|
_hints: util.immutabledict[Tuple[FromClause, str], str] = (
|
|
util.immutabledict()
|
|
)
|
|
_statement_hints: Tuple[Tuple[str, str], ...] = ()
|
|
|
|
_has_hints_traverse_internals: _TraverseInternalsType = [
|
|
("_statement_hints", InternalTraversal.dp_statement_hint_list),
|
|
("_hints", InternalTraversal.dp_table_hint_list),
|
|
]
|
|
|
|
def with_statement_hint(self, text: str, dialect_name: str = "*") -> Self:
|
|
"""Add a statement hint to this :class:`_expression.Select` or
|
|
other selectable object.
|
|
|
|
This method is similar to :meth:`_expression.Select.with_hint`
|
|
except that
|
|
it does not require an individual table, and instead applies to the
|
|
statement as a whole.
|
|
|
|
Hints here are specific to the backend database and may include
|
|
directives such as isolation levels, file directives, fetch directives,
|
|
etc.
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_expression.Select.with_hint`
|
|
|
|
:meth:`_expression.Select.prefix_with` - generic SELECT prefixing
|
|
which also can suit some database-specific HINT syntaxes such as
|
|
MySQL optimizer hints
|
|
|
|
"""
|
|
return self._with_hint(None, text, dialect_name)
|
|
|
|
@_generative
|
|
def with_hint(
|
|
self,
|
|
selectable: _FromClauseArgument,
|
|
text: str,
|
|
dialect_name: str = "*",
|
|
) -> Self:
|
|
r"""Add an indexing or other executional context hint for the given
|
|
selectable to this :class:`_expression.Select` or other selectable
|
|
object.
|
|
|
|
The text of the hint is rendered in the appropriate
|
|
location for the database backend in use, relative
|
|
to the given :class:`_schema.Table` or :class:`_expression.Alias`
|
|
passed as the
|
|
``selectable`` argument. The dialect implementation
|
|
typically uses Python string substitution syntax
|
|
with the token ``%(name)s`` to render the name of
|
|
the table or alias. E.g. when using Oracle, the
|
|
following::
|
|
|
|
select(mytable).\
|
|
with_hint(mytable, "index(%(name)s ix_mytable)")
|
|
|
|
Would render SQL as::
|
|
|
|
select /*+ index(mytable ix_mytable) */ ... from mytable
|
|
|
|
The ``dialect_name`` option will limit the rendering of a particular
|
|
hint to a particular backend. Such as, to add hints for both Oracle
|
|
and Sybase simultaneously::
|
|
|
|
select(mytable).\
|
|
with_hint(mytable, "index(%(name)s ix_mytable)", 'oracle').\
|
|
with_hint(mytable, "WITH INDEX ix_mytable", 'mssql')
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_expression.Select.with_statement_hint`
|
|
|
|
"""
|
|
|
|
return self._with_hint(selectable, text, dialect_name)
|
|
|
|
def _with_hint(
|
|
self,
|
|
selectable: Optional[_FromClauseArgument],
|
|
text: str,
|
|
dialect_name: str,
|
|
) -> Self:
|
|
if selectable is None:
|
|
self._statement_hints += ((dialect_name, text),)
|
|
else:
|
|
self._hints = self._hints.union(
|
|
{
|
|
(
|
|
coercions.expect(roles.FromClauseRole, selectable),
|
|
dialect_name,
|
|
): text
|
|
}
|
|
)
|
|
return self
|
|
|
|
|
|
class FromClause(roles.AnonymizedFromClauseRole, Selectable):
|
|
"""Represent an element that can be used within the ``FROM``
|
|
clause of a ``SELECT`` statement.
|
|
|
|
The most common forms of :class:`_expression.FromClause` are the
|
|
:class:`_schema.Table` and the :func:`_expression.select` constructs. Key
|
|
features common to all :class:`_expression.FromClause` objects include:
|
|
|
|
* a :attr:`.c` collection, which provides per-name access to a collection
|
|
of :class:`_expression.ColumnElement` objects.
|
|
* a :attr:`.primary_key` attribute, which is a collection of all those
|
|
:class:`_expression.ColumnElement`
|
|
objects that indicate the ``primary_key`` flag.
|
|
* Methods to generate various derivations of a "from" clause, including
|
|
:meth:`_expression.FromClause.alias`,
|
|
:meth:`_expression.FromClause.join`,
|
|
:meth:`_expression.FromClause.select`.
|
|
|
|
|
|
"""
|
|
|
|
__visit_name__ = "fromclause"
|
|
named_with_column = False
|
|
|
|
@util.ro_non_memoized_property
|
|
def _hide_froms(self) -> Iterable[FromClause]:
|
|
return ()
|
|
|
|
_is_clone_of: Optional[FromClause]
|
|
|
|
_columns: ColumnCollection[Any, Any]
|
|
|
|
schema: Optional[str] = None
|
|
"""Define the 'schema' attribute for this :class:`_expression.FromClause`.
|
|
|
|
This is typically ``None`` for most objects except that of
|
|
:class:`_schema.Table`, where it is taken as the value of the
|
|
:paramref:`_schema.Table.schema` argument.
|
|
|
|
"""
|
|
|
|
is_selectable = True
|
|
_is_from_clause = True
|
|
_is_join = False
|
|
|
|
_use_schema_map = False
|
|
|
|
def select(self) -> Select[Any]:
|
|
r"""Return a SELECT of this :class:`_expression.FromClause`.
|
|
|
|
|
|
e.g.::
|
|
|
|
stmt = some_table.select().where(some_table.c.id == 5)
|
|
|
|
.. seealso::
|
|
|
|
:func:`_expression.select` - general purpose
|
|
method which allows for arbitrary column lists.
|
|
|
|
"""
|
|
return Select(self)
|
|
|
|
def join(
|
|
self,
|
|
right: _FromClauseArgument,
|
|
onclause: Optional[_ColumnExpressionArgument[bool]] = None,
|
|
isouter: bool = False,
|
|
full: bool = False,
|
|
) -> Join:
|
|
"""Return a :class:`_expression.Join` from this
|
|
:class:`_expression.FromClause`
|
|
to another :class:`FromClause`.
|
|
|
|
E.g.::
|
|
|
|
from sqlalchemy import join
|
|
|
|
j = user_table.join(address_table,
|
|
user_table.c.id == address_table.c.user_id)
|
|
stmt = select(user_table).select_from(j)
|
|
|
|
would emit SQL along the lines of::
|
|
|
|
SELECT user.id, user.name FROM user
|
|
JOIN address ON user.id = address.user_id
|
|
|
|
:param right: the right side of the join; this is any
|
|
:class:`_expression.FromClause` object such as a
|
|
:class:`_schema.Table` object, and
|
|
may also be a selectable-compatible object such as an ORM-mapped
|
|
class.
|
|
|
|
:param onclause: a SQL expression representing the ON clause of the
|
|
join. If left at ``None``, :meth:`_expression.FromClause.join`
|
|
will attempt to
|
|
join the two tables based on a foreign key relationship.
|
|
|
|
:param isouter: if True, render a LEFT OUTER JOIN, instead of JOIN.
|
|
|
|
:param full: if True, render a FULL OUTER JOIN, instead of LEFT OUTER
|
|
JOIN. Implies :paramref:`.FromClause.join.isouter`.
|
|
|
|
.. seealso::
|
|
|
|
:func:`_expression.join` - standalone function
|
|
|
|
:class:`_expression.Join` - the type of object produced
|
|
|
|
"""
|
|
|
|
return Join(self, right, onclause, isouter, full)
|
|
|
|
def outerjoin(
|
|
self,
|
|
right: _FromClauseArgument,
|
|
onclause: Optional[_ColumnExpressionArgument[bool]] = None,
|
|
full: bool = False,
|
|
) -> Join:
|
|
"""Return a :class:`_expression.Join` from this
|
|
:class:`_expression.FromClause`
|
|
to another :class:`FromClause`, with the "isouter" flag set to
|
|
True.
|
|
|
|
E.g.::
|
|
|
|
from sqlalchemy import outerjoin
|
|
|
|
j = user_table.outerjoin(address_table,
|
|
user_table.c.id == address_table.c.user_id)
|
|
|
|
The above is equivalent to::
|
|
|
|
j = user_table.join(
|
|
address_table,
|
|
user_table.c.id == address_table.c.user_id,
|
|
isouter=True)
|
|
|
|
:param right: the right side of the join; this is any
|
|
:class:`_expression.FromClause` object such as a
|
|
:class:`_schema.Table` object, and
|
|
may also be a selectable-compatible object such as an ORM-mapped
|
|
class.
|
|
|
|
:param onclause: a SQL expression representing the ON clause of the
|
|
join. If left at ``None``, :meth:`_expression.FromClause.join`
|
|
will attempt to
|
|
join the two tables based on a foreign key relationship.
|
|
|
|
:param full: if True, render a FULL OUTER JOIN, instead of
|
|
LEFT OUTER JOIN.
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_expression.FromClause.join`
|
|
|
|
:class:`_expression.Join`
|
|
|
|
"""
|
|
|
|
return Join(self, right, onclause, True, full)
|
|
|
|
def alias(
|
|
self, name: Optional[str] = None, flat: bool = False
|
|
) -> NamedFromClause:
|
|
"""Return an alias of this :class:`_expression.FromClause`.
|
|
|
|
E.g.::
|
|
|
|
a2 = some_table.alias('a2')
|
|
|
|
The above code creates an :class:`_expression.Alias`
|
|
object which can be used
|
|
as a FROM clause in any SELECT statement.
|
|
|
|
.. seealso::
|
|
|
|
:ref:`tutorial_using_aliases`
|
|
|
|
:func:`_expression.alias`
|
|
|
|
"""
|
|
|
|
return Alias._construct(self, name=name)
|
|
|
|
def tablesample(
|
|
self,
|
|
sampling: Union[float, Function[Any]],
|
|
name: Optional[str] = None,
|
|
seed: Optional[roles.ExpressionElementRole[Any]] = None,
|
|
) -> TableSample:
|
|
"""Return a TABLESAMPLE alias of this :class:`_expression.FromClause`.
|
|
|
|
The return value is the :class:`_expression.TableSample`
|
|
construct also
|
|
provided by the top-level :func:`_expression.tablesample` function.
|
|
|
|
.. seealso::
|
|
|
|
:func:`_expression.tablesample` - usage guidelines and parameters
|
|
|
|
"""
|
|
return TableSample._construct(
|
|
self, sampling=sampling, name=name, seed=seed
|
|
)
|
|
|
|
def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
|
|
"""Return ``True`` if this :class:`_expression.FromClause` is
|
|
'derived' from the given ``FromClause``.
|
|
|
|
An example would be an Alias of a Table is derived from that Table.
|
|
|
|
"""
|
|
# this is essentially an "identity" check in the base class.
|
|
# Other constructs override this to traverse through
|
|
# contained elements.
|
|
return fromclause in self._cloned_set
|
|
|
|
def _is_lexical_equivalent(self, other: FromClause) -> bool:
|
|
"""Return ``True`` if this :class:`_expression.FromClause` and
|
|
the other represent the same lexical identity.
|
|
|
|
This tests if either one is a copy of the other, or
|
|
if they are the same via annotation identity.
|
|
|
|
"""
|
|
return bool(self._cloned_set.intersection(other._cloned_set))
|
|
|
|
@util.ro_non_memoized_property
|
|
def description(self) -> str:
|
|
"""A brief description of this :class:`_expression.FromClause`.
|
|
|
|
Used primarily for error message formatting.
|
|
|
|
"""
|
|
return getattr(self, "name", self.__class__.__name__ + " object")
|
|
|
|
def _generate_fromclause_column_proxies(
|
|
self, fromclause: FromClause
|
|
) -> None:
|
|
fromclause._columns._populate_separate_keys(
|
|
col._make_proxy(fromclause) for col in self.c
|
|
)
|
|
|
|
@util.ro_non_memoized_property
|
|
def exported_columns(
|
|
self,
|
|
) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
|
|
"""A :class:`_expression.ColumnCollection`
|
|
that represents the "exported"
|
|
columns of this :class:`_expression.Selectable`.
|
|
|
|
The "exported" columns for a :class:`_expression.FromClause`
|
|
object are synonymous
|
|
with the :attr:`_expression.FromClause.columns` collection.
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
.. seealso::
|
|
|
|
:attr:`_expression.Selectable.exported_columns`
|
|
|
|
:attr:`_expression.SelectBase.exported_columns`
|
|
|
|
|
|
"""
|
|
return self.c
|
|
|
|
@util.ro_non_memoized_property
|
|
def columns(
|
|
self,
|
|
) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
|
|
"""A named-based collection of :class:`_expression.ColumnElement`
|
|
objects maintained by this :class:`_expression.FromClause`.
|
|
|
|
The :attr:`.columns`, or :attr:`.c` collection, is the gateway
|
|
to the construction of SQL expressions using table-bound or
|
|
other selectable-bound columns::
|
|
|
|
select(mytable).where(mytable.c.somecolumn == 5)
|
|
|
|
:return: a :class:`.ColumnCollection` object.
|
|
|
|
"""
|
|
return self.c
|
|
|
|
@util.ro_memoized_property
|
|
def c(self) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
|
|
"""
|
|
A synonym for :attr:`.FromClause.columns`
|
|
|
|
:return: a :class:`.ColumnCollection`
|
|
|
|
"""
|
|
if "_columns" not in self.__dict__:
|
|
self._init_collections()
|
|
self._populate_column_collection()
|
|
return self._columns.as_readonly()
|
|
|
|
@util.ro_non_memoized_property
|
|
def entity_namespace(self) -> _EntityNamespace:
|
|
"""Return a namespace used for name-based access in SQL expressions.
|
|
|
|
This is the namespace that is used to resolve "filter_by()" type
|
|
expressions, such as::
|
|
|
|
stmt.filter_by(address='some address')
|
|
|
|
It defaults to the ``.c`` collection, however internally it can
|
|
be overridden using the "entity_namespace" annotation to deliver
|
|
alternative results.
|
|
|
|
"""
|
|
return self.c
|
|
|
|
@util.ro_memoized_property
|
|
def primary_key(self) -> Iterable[NamedColumn[Any]]:
|
|
"""Return the iterable collection of :class:`_schema.Column` objects
|
|
which comprise the primary key of this :class:`_selectable.FromClause`.
|
|
|
|
For a :class:`_schema.Table` object, this collection is represented
|
|
by the :class:`_schema.PrimaryKeyConstraint` which itself is an
|
|
iterable collection of :class:`_schema.Column` objects.
|
|
|
|
"""
|
|
self._init_collections()
|
|
self._populate_column_collection()
|
|
return self.primary_key
|
|
|
|
@util.ro_memoized_property
|
|
def foreign_keys(self) -> Iterable[ForeignKey]:
|
|
"""Return the collection of :class:`_schema.ForeignKey` marker objects
|
|
which this FromClause references.
|
|
|
|
Each :class:`_schema.ForeignKey` is a member of a
|
|
:class:`_schema.Table`-wide
|
|
:class:`_schema.ForeignKeyConstraint`.
|
|
|
|
.. seealso::
|
|
|
|
:attr:`_schema.Table.foreign_key_constraints`
|
|
|
|
"""
|
|
self._init_collections()
|
|
self._populate_column_collection()
|
|
return self.foreign_keys
|
|
|
|
def _reset_column_collection(self) -> None:
|
|
"""Reset the attributes linked to the ``FromClause.c`` attribute.
|
|
|
|
This collection is separate from all the other memoized things
|
|
as it has shown to be sensitive to being cleared out in situations
|
|
where enclosing code, typically in a replacement traversal scenario,
|
|
has already established strong relationships
|
|
with the exported columns.
|
|
|
|
The collection is cleared for the case where a table is having a
|
|
column added to it as well as within a Join during copy internals.
|
|
|
|
"""
|
|
|
|
for key in ["_columns", "columns", "c", "primary_key", "foreign_keys"]:
|
|
self.__dict__.pop(key, None)
|
|
|
|
@util.ro_non_memoized_property
|
|
def _select_iterable(self) -> _SelectIterable:
|
|
return (c for c in self.c if not _never_select_column(c))
|
|
|
|
def _init_collections(self) -> None:
|
|
assert "_columns" not in self.__dict__
|
|
assert "primary_key" not in self.__dict__
|
|
assert "foreign_keys" not in self.__dict__
|
|
|
|
self._columns = ColumnCollection()
|
|
self.primary_key = ColumnSet() # type: ignore
|
|
self.foreign_keys = set() # type: ignore
|
|
|
|
@property
|
|
def _cols_populated(self) -> bool:
|
|
return "_columns" in self.__dict__
|
|
|
|
def _populate_column_collection(self) -> None:
|
|
"""Called on subclasses to establish the .c collection.
|
|
|
|
Each implementation has a different way of establishing
|
|
this collection.
|
|
|
|
"""
|
|
|
|
def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
|
|
"""Given a column added to the .c collection of an underlying
|
|
selectable, produce the local version of that column, assuming this
|
|
selectable ultimately should proxy this column.
|
|
|
|
this is used to "ping" a derived selectable to add a new column
|
|
to its .c. collection when a Column has been added to one of the
|
|
Table objects it ultimately derives from.
|
|
|
|
If the given selectable hasn't populated its .c. collection yet,
|
|
it should at least pass on the message to the contained selectables,
|
|
but it will return None.
|
|
|
|
This method is currently used by Declarative to allow Table
|
|
columns to be added to a partially constructed inheritance
|
|
mapping that may have already produced joins. The method
|
|
isn't public right now, as the full span of implications
|
|
and/or caveats aren't yet clear.
|
|
|
|
It's also possible that this functionality could be invoked by
|
|
default via an event, which would require that
|
|
selectables maintain a weak referencing collection of all
|
|
derivations.
|
|
|
|
"""
|
|
self._reset_column_collection()
|
|
|
|
def _anonymous_fromclause(
|
|
self, *, name: Optional[str] = None, flat: bool = False
|
|
) -> FromClause:
|
|
return self.alias(name=name)
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
def self_group(
|
|
self, against: Optional[OperatorType] = None
|
|
) -> Union[FromGrouping, Self]: ...
|
|
|
|
|
|
class NamedFromClause(FromClause):
|
|
"""A :class:`.FromClause` that has a name.
|
|
|
|
Examples include tables, subqueries, CTEs, aliased tables.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
"""
|
|
|
|
named_with_column = True
|
|
|
|
name: str
|
|
|
|
@util.preload_module("sqlalchemy.sql.sqltypes")
|
|
def table_valued(self) -> TableValuedColumn[Any]:
|
|
"""Return a :class:`_sql.TableValuedColumn` object for this
|
|
:class:`_expression.FromClause`.
|
|
|
|
A :class:`_sql.TableValuedColumn` is a :class:`_sql.ColumnElement` that
|
|
represents a complete row in a table. Support for this construct is
|
|
backend dependent, and is supported in various forms by backends
|
|
such as PostgreSQL, Oracle and SQL Server.
|
|
|
|
E.g.:
|
|
|
|
.. sourcecode:: pycon+sql
|
|
|
|
>>> from sqlalchemy import select, column, func, table
|
|
>>> a = table("a", column("id"), column("x"), column("y"))
|
|
>>> stmt = select(func.row_to_json(a.table_valued()))
|
|
>>> print(stmt)
|
|
{printsql}SELECT row_to_json(a) AS row_to_json_1
|
|
FROM a
|
|
|
|
.. versionadded:: 1.4.0b2
|
|
|
|
.. seealso::
|
|
|
|
:ref:`tutorial_functions` - in the :ref:`unified_tutorial`
|
|
|
|
"""
|
|
return TableValuedColumn(self, type_api.TABLEVALUE)
|
|
|
|
|
|
class SelectLabelStyle(Enum):
|
|
"""Label style constants that may be passed to
|
|
:meth:`_sql.Select.set_label_style`."""
|
|
|
|
LABEL_STYLE_NONE = 0
|
|
"""Label style indicating no automatic labeling should be applied to the
|
|
columns clause of a SELECT statement.
|
|
|
|
Below, the columns named ``columna`` are both rendered as is, meaning that
|
|
the name ``columna`` can only refer to the first occurrence of this name
|
|
within a result set, as well as if the statement were used as a subquery:
|
|
|
|
.. sourcecode:: pycon+sql
|
|
|
|
>>> from sqlalchemy import table, column, select, true, LABEL_STYLE_NONE
|
|
>>> table1 = table("table1", column("columna"), column("columnb"))
|
|
>>> table2 = table("table2", column("columna"), column("columnc"))
|
|
>>> print(select(table1, table2).join(table2, true()).set_label_style(LABEL_STYLE_NONE))
|
|
{printsql}SELECT table1.columna, table1.columnb, table2.columna, table2.columnc
|
|
FROM table1 JOIN table2 ON true
|
|
|
|
Used with the :meth:`_sql.Select.set_label_style` method.
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
""" # noqa: E501
|
|
|
|
LABEL_STYLE_TABLENAME_PLUS_COL = 1
|
|
"""Label style indicating all columns should be labeled as
|
|
``<tablename>_<columnname>`` when generating the columns clause of a SELECT
|
|
statement, to disambiguate same-named columns referenced from different
|
|
tables, aliases, or subqueries.
|
|
|
|
Below, all column names are given a label so that the two same-named
|
|
columns ``columna`` are disambiguated as ``table1_columna`` and
|
|
``table2_columna``:
|
|
|
|
.. sourcecode:: pycon+sql
|
|
|
|
>>> from sqlalchemy import table, column, select, true, LABEL_STYLE_TABLENAME_PLUS_COL
|
|
>>> table1 = table("table1", column("columna"), column("columnb"))
|
|
>>> table2 = table("table2", column("columna"), column("columnc"))
|
|
>>> print(select(table1, table2).join(table2, true()).set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL))
|
|
{printsql}SELECT table1.columna AS table1_columna, table1.columnb AS table1_columnb, table2.columna AS table2_columna, table2.columnc AS table2_columnc
|
|
FROM table1 JOIN table2 ON true
|
|
|
|
Used with the :meth:`_sql.GenerativeSelect.set_label_style` method.
|
|
Equivalent to the legacy method ``Select.apply_labels()``;
|
|
:data:`_sql.LABEL_STYLE_TABLENAME_PLUS_COL` is SQLAlchemy's legacy
|
|
auto-labeling style. :data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY` provides a
|
|
less intrusive approach to disambiguation of same-named column expressions.
|
|
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
""" # noqa: E501
|
|
|
|
LABEL_STYLE_DISAMBIGUATE_ONLY = 2
|
|
"""Label style indicating that columns with a name that conflicts with
|
|
an existing name should be labeled with a semi-anonymizing label
|
|
when generating the columns clause of a SELECT statement.
|
|
|
|
Below, most column names are left unaffected, except for the second
|
|
occurrence of the name ``columna``, which is labeled using the
|
|
label ``columna_1`` to disambiguate it from that of ``tablea.columna``:
|
|
|
|
.. sourcecode:: pycon+sql
|
|
|
|
>>> from sqlalchemy import table, column, select, true, LABEL_STYLE_DISAMBIGUATE_ONLY
|
|
>>> table1 = table("table1", column("columna"), column("columnb"))
|
|
>>> table2 = table("table2", column("columna"), column("columnc"))
|
|
>>> print(select(table1, table2).join(table2, true()).set_label_style(LABEL_STYLE_DISAMBIGUATE_ONLY))
|
|
{printsql}SELECT table1.columna, table1.columnb, table2.columna AS columna_1, table2.columnc
|
|
FROM table1 JOIN table2 ON true
|
|
|
|
Used with the :meth:`_sql.GenerativeSelect.set_label_style` method,
|
|
:data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY` is the default labeling style
|
|
for all SELECT statements outside of :term:`1.x style` ORM queries.
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
""" # noqa: E501
|
|
|
|
LABEL_STYLE_DEFAULT = LABEL_STYLE_DISAMBIGUATE_ONLY
|
|
"""The default label style, refers to
|
|
:data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY`.
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
"""
|
|
|
|
LABEL_STYLE_LEGACY_ORM = 3
|
|
|
|
|
|
(
|
|
LABEL_STYLE_NONE,
|
|
LABEL_STYLE_TABLENAME_PLUS_COL,
|
|
LABEL_STYLE_DISAMBIGUATE_ONLY,
|
|
_,
|
|
) = list(SelectLabelStyle)
|
|
|
|
LABEL_STYLE_DEFAULT = LABEL_STYLE_DISAMBIGUATE_ONLY
|
|
|
|
|
|
class Join(roles.DMLTableRole, FromClause):
|
|
"""Represent a ``JOIN`` construct between two
|
|
:class:`_expression.FromClause`
|
|
elements.
|
|
|
|
The public constructor function for :class:`_expression.Join`
|
|
is the module-level
|
|
:func:`_expression.join()` function, as well as the
|
|
:meth:`_expression.FromClause.join` method
|
|
of any :class:`_expression.FromClause` (e.g. such as
|
|
:class:`_schema.Table`).
|
|
|
|
.. seealso::
|
|
|
|
:func:`_expression.join`
|
|
|
|
:meth:`_expression.FromClause.join`
|
|
|
|
"""
|
|
|
|
__visit_name__ = "join"
|
|
|
|
_traverse_internals: _TraverseInternalsType = [
|
|
("left", InternalTraversal.dp_clauseelement),
|
|
("right", InternalTraversal.dp_clauseelement),
|
|
("onclause", InternalTraversal.dp_clauseelement),
|
|
("isouter", InternalTraversal.dp_boolean),
|
|
("full", InternalTraversal.dp_boolean),
|
|
]
|
|
|
|
_is_join = True
|
|
|
|
left: FromClause
|
|
right: FromClause
|
|
onclause: Optional[ColumnElement[bool]]
|
|
isouter: bool
|
|
full: bool
|
|
|
|
def __init__(
|
|
self,
|
|
left: _FromClauseArgument,
|
|
right: _FromClauseArgument,
|
|
onclause: Optional[_OnClauseArgument] = None,
|
|
isouter: bool = False,
|
|
full: bool = False,
|
|
):
|
|
"""Construct a new :class:`_expression.Join`.
|
|
|
|
The usual entrypoint here is the :func:`_expression.join`
|
|
function or the :meth:`_expression.FromClause.join` method of any
|
|
:class:`_expression.FromClause` object.
|
|
|
|
"""
|
|
|
|
# when deannotate was removed here, callcounts went up for ORM
|
|
# compilation of eager joins, since there were more comparisons of
|
|
# annotated objects. test_orm.py -> test_fetch_results
|
|
# was therefore changed to show a more real-world use case, where the
|
|
# compilation is cached; there's no change in post-cache callcounts.
|
|
# callcounts for a single compilation in that particular test
|
|
# that includes about eight joins about 1100 extra fn calls, from
|
|
# 29200 -> 30373
|
|
|
|
self.left = coercions.expect(
|
|
roles.FromClauseRole,
|
|
left,
|
|
)
|
|
self.right = coercions.expect(
|
|
roles.FromClauseRole,
|
|
right,
|
|
).self_group()
|
|
|
|
if onclause is None:
|
|
self.onclause = self._match_primaries(self.left, self.right)
|
|
else:
|
|
# note: taken from If91f61527236fd4d7ae3cad1f24c38be921c90ba
|
|
# not merged yet
|
|
self.onclause = coercions.expect(
|
|
roles.OnClauseRole, onclause
|
|
).self_group(against=operators._asbool)
|
|
|
|
self.isouter = isouter
|
|
self.full = full
|
|
|
|
@util.ro_non_memoized_property
|
|
def description(self) -> str:
|
|
return "Join object on %s(%d) and %s(%d)" % (
|
|
self.left.description,
|
|
id(self.left),
|
|
self.right.description,
|
|
id(self.right),
|
|
)
|
|
|
|
def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
|
|
return (
|
|
# use hash() to ensure direct comparison to annotated works
|
|
# as well
|
|
hash(fromclause) == hash(self)
|
|
or self.left.is_derived_from(fromclause)
|
|
or self.right.is_derived_from(fromclause)
|
|
)
|
|
|
|
def self_group(
|
|
self, against: Optional[OperatorType] = None
|
|
) -> FromGrouping:
|
|
...
|
|
return FromGrouping(self)
|
|
|
|
@util.preload_module("sqlalchemy.sql.util")
|
|
def _populate_column_collection(self) -> None:
|
|
sqlutil = util.preloaded.sql_util
|
|
columns: List[KeyedColumnElement[Any]] = [c for c in self.left.c] + [
|
|
c for c in self.right.c
|
|
]
|
|
|
|
self.primary_key.extend( # type: ignore
|
|
sqlutil.reduce_columns(
|
|
(c for c in columns if c.primary_key), self.onclause
|
|
)
|
|
)
|
|
self._columns._populate_separate_keys(
|
|
(col._tq_key_label, col) for col in columns
|
|
)
|
|
self.foreign_keys.update( # type: ignore
|
|
itertools.chain(*[col.foreign_keys for col in columns])
|
|
)
|
|
|
|
def _copy_internals(
|
|
self, clone: _CloneCallableType = _clone, **kw: Any
|
|
) -> None:
|
|
# see Select._copy_internals() for similar concept
|
|
|
|
# here we pre-clone "left" and "right" so that we can
|
|
# determine the new FROM clauses
|
|
all_the_froms = set(
|
|
itertools.chain(
|
|
_from_objects(self.left),
|
|
_from_objects(self.right),
|
|
)
|
|
)
|
|
|
|
# run the clone on those. these will be placed in the
|
|
# cache used by the clone function
|
|
new_froms = {f: clone(f, **kw) for f in all_the_froms}
|
|
|
|
# set up a special replace function that will replace for
|
|
# ColumnClause with parent table referring to those
|
|
# replaced FromClause objects
|
|
def replace(
|
|
obj: Union[BinaryExpression[Any], ColumnClause[Any]],
|
|
**kw: Any,
|
|
) -> Optional[KeyedColumnElement[ColumnElement[Any]]]:
|
|
if isinstance(obj, ColumnClause) and obj.table in new_froms:
|
|
newelem = new_froms[obj.table].corresponding_column(obj)
|
|
return newelem
|
|
return None
|
|
|
|
kw["replace"] = replace
|
|
|
|
# run normal _copy_internals. the clones for
|
|
# left and right will come from the clone function's
|
|
# cache
|
|
super()._copy_internals(clone=clone, **kw)
|
|
|
|
self._reset_memoizations()
|
|
|
|
def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
|
|
super()._refresh_for_new_column(column)
|
|
self.left._refresh_for_new_column(column)
|
|
self.right._refresh_for_new_column(column)
|
|
|
|
def _match_primaries(
|
|
self,
|
|
left: FromClause,
|
|
right: FromClause,
|
|
) -> ColumnElement[bool]:
|
|
if isinstance(left, Join):
|
|
left_right = left.right
|
|
else:
|
|
left_right = None
|
|
return self._join_condition(left, right, a_subset=left_right)
|
|
|
|
@classmethod
|
|
def _join_condition(
|
|
cls,
|
|
a: FromClause,
|
|
b: FromClause,
|
|
*,
|
|
a_subset: Optional[FromClause] = None,
|
|
consider_as_foreign_keys: Optional[
|
|
AbstractSet[ColumnClause[Any]]
|
|
] = None,
|
|
) -> ColumnElement[bool]:
|
|
"""Create a join condition between two tables or selectables.
|
|
|
|
See sqlalchemy.sql.util.join_condition() for full docs.
|
|
|
|
"""
|
|
constraints = cls._joincond_scan_left_right(
|
|
a, a_subset, b, consider_as_foreign_keys
|
|
)
|
|
|
|
if len(constraints) > 1:
|
|
cls._joincond_trim_constraints(
|
|
a, b, constraints, consider_as_foreign_keys
|
|
)
|
|
|
|
if len(constraints) == 0:
|
|
if isinstance(b, FromGrouping):
|
|
hint = (
|
|
" Perhaps you meant to convert the right side to a "
|
|
"subquery using alias()?"
|
|
)
|
|
else:
|
|
hint = ""
|
|
raise exc.NoForeignKeysError(
|
|
"Can't find any foreign key relationships "
|
|
"between '%s' and '%s'.%s"
|
|
% (a.description, b.description, hint)
|
|
)
|
|
|
|
crit = [(x == y) for x, y in list(constraints.values())[0]]
|
|
if len(crit) == 1:
|
|
return crit[0]
|
|
else:
|
|
return and_(*crit)
|
|
|
|
@classmethod
|
|
def _can_join(
|
|
cls,
|
|
left: FromClause,
|
|
right: FromClause,
|
|
*,
|
|
consider_as_foreign_keys: Optional[
|
|
AbstractSet[ColumnClause[Any]]
|
|
] = None,
|
|
) -> bool:
|
|
if isinstance(left, Join):
|
|
left_right = left.right
|
|
else:
|
|
left_right = None
|
|
|
|
constraints = cls._joincond_scan_left_right(
|
|
a=left,
|
|
b=right,
|
|
a_subset=left_right,
|
|
consider_as_foreign_keys=consider_as_foreign_keys,
|
|
)
|
|
|
|
return bool(constraints)
|
|
|
|
@classmethod
|
|
@util.preload_module("sqlalchemy.sql.util")
|
|
def _joincond_scan_left_right(
|
|
cls,
|
|
a: FromClause,
|
|
a_subset: Optional[FromClause],
|
|
b: FromClause,
|
|
consider_as_foreign_keys: Optional[AbstractSet[ColumnClause[Any]]],
|
|
) -> collections.defaultdict[
|
|
Optional[ForeignKeyConstraint],
|
|
List[Tuple[ColumnClause[Any], ColumnClause[Any]]],
|
|
]:
|
|
sql_util = util.preloaded.sql_util
|
|
|
|
a = coercions.expect(roles.FromClauseRole, a)
|
|
b = coercions.expect(roles.FromClauseRole, b)
|
|
|
|
constraints: collections.defaultdict[
|
|
Optional[ForeignKeyConstraint],
|
|
List[Tuple[ColumnClause[Any], ColumnClause[Any]]],
|
|
] = collections.defaultdict(list)
|
|
|
|
for left in (a_subset, a):
|
|
if left is None:
|
|
continue
|
|
for fk in sorted(
|
|
b.foreign_keys,
|
|
key=lambda fk: fk.parent._creation_order,
|
|
):
|
|
if (
|
|
consider_as_foreign_keys is not None
|
|
and fk.parent not in consider_as_foreign_keys
|
|
):
|
|
continue
|
|
try:
|
|
col = fk.get_referent(left)
|
|
except exc.NoReferenceError as nrte:
|
|
table_names = {t.name for t in sql_util.find_tables(left)}
|
|
if nrte.table_name in table_names:
|
|
raise
|
|
else:
|
|
continue
|
|
|
|
if col is not None:
|
|
constraints[fk.constraint].append((col, fk.parent))
|
|
if left is not b:
|
|
for fk in sorted(
|
|
left.foreign_keys,
|
|
key=lambda fk: fk.parent._creation_order,
|
|
):
|
|
if (
|
|
consider_as_foreign_keys is not None
|
|
and fk.parent not in consider_as_foreign_keys
|
|
):
|
|
continue
|
|
try:
|
|
col = fk.get_referent(b)
|
|
except exc.NoReferenceError as nrte:
|
|
table_names = {t.name for t in sql_util.find_tables(b)}
|
|
if nrte.table_name in table_names:
|
|
raise
|
|
else:
|
|
continue
|
|
|
|
if col is not None:
|
|
constraints[fk.constraint].append((col, fk.parent))
|
|
if constraints:
|
|
break
|
|
return constraints
|
|
|
|
@classmethod
|
|
def _joincond_trim_constraints(
|
|
cls,
|
|
a: FromClause,
|
|
b: FromClause,
|
|
constraints: Dict[Any, Any],
|
|
consider_as_foreign_keys: Optional[Any],
|
|
) -> None:
|
|
# more than one constraint matched. narrow down the list
|
|
# to include just those FKCs that match exactly to
|
|
# "consider_as_foreign_keys".
|
|
if consider_as_foreign_keys:
|
|
for const in list(constraints):
|
|
if {f.parent for f in const.elements} != set(
|
|
consider_as_foreign_keys
|
|
):
|
|
del constraints[const]
|
|
|
|
# if still multiple constraints, but
|
|
# they all refer to the exact same end result, use it.
|
|
if len(constraints) > 1:
|
|
dedupe = {tuple(crit) for crit in constraints.values()}
|
|
if len(dedupe) == 1:
|
|
key = list(constraints)[0]
|
|
constraints = {key: constraints[key]}
|
|
|
|
if len(constraints) != 1:
|
|
raise exc.AmbiguousForeignKeysError(
|
|
"Can't determine join between '%s' and '%s'; "
|
|
"tables have more than one foreign key "
|
|
"constraint relationship between them. "
|
|
"Please specify the 'onclause' of this "
|
|
"join explicitly." % (a.description, b.description)
|
|
)
|
|
|
|
def select(self) -> Select[Any]:
|
|
r"""Create a :class:`_expression.Select` from this
|
|
:class:`_expression.Join`.
|
|
|
|
E.g.::
|
|
|
|
stmt = table_a.join(table_b, table_a.c.id == table_b.c.a_id)
|
|
|
|
stmt = stmt.select()
|
|
|
|
The above will produce a SQL string resembling::
|
|
|
|
SELECT table_a.id, table_a.col, table_b.id, table_b.a_id
|
|
FROM table_a JOIN table_b ON table_a.id = table_b.a_id
|
|
|
|
"""
|
|
return Select(self.left, self.right).select_from(self)
|
|
|
|
@util.preload_module("sqlalchemy.sql.util")
|
|
def _anonymous_fromclause(
|
|
self, name: Optional[str] = None, flat: bool = False
|
|
) -> TODO_Any:
|
|
sqlutil = util.preloaded.sql_util
|
|
if flat:
|
|
if name is not None:
|
|
raise exc.ArgumentError("Can't send name argument with flat")
|
|
left_a, right_a = (
|
|
self.left._anonymous_fromclause(flat=True),
|
|
self.right._anonymous_fromclause(flat=True),
|
|
)
|
|
adapter = sqlutil.ClauseAdapter(left_a).chain(
|
|
sqlutil.ClauseAdapter(right_a)
|
|
)
|
|
|
|
return left_a.join(
|
|
right_a,
|
|
adapter.traverse(self.onclause),
|
|
isouter=self.isouter,
|
|
full=self.full,
|
|
)
|
|
else:
|
|
return (
|
|
self.select()
|
|
.set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
|
|
.correlate(None)
|
|
.alias(name)
|
|
)
|
|
|
|
@util.ro_non_memoized_property
|
|
def _hide_froms(self) -> Iterable[FromClause]:
|
|
return itertools.chain(
|
|
*[_from_objects(x.left, x.right) for x in self._cloned_set]
|
|
)
|
|
|
|
@util.ro_non_memoized_property
|
|
def _from_objects(self) -> List[FromClause]:
|
|
self_list: List[FromClause] = [self]
|
|
return self_list + self.left._from_objects + self.right._from_objects
|
|
|
|
|
|
class NoInit:
|
|
def __init__(self, *arg: Any, **kw: Any):
|
|
raise NotImplementedError(
|
|
"The %s class is not intended to be constructed "
|
|
"directly. Please use the %s() standalone "
|
|
"function or the %s() method available from appropriate "
|
|
"selectable objects."
|
|
% (
|
|
self.__class__.__name__,
|
|
self.__class__.__name__.lower(),
|
|
self.__class__.__name__.lower(),
|
|
)
|
|
)
|
|
|
|
|
|
class LateralFromClause(NamedFromClause):
|
|
"""mark a FROM clause as being able to render directly as LATERAL"""
|
|
|
|
|
|
# FromClause ->
|
|
# AliasedReturnsRows
|
|
# -> Alias only for FromClause
|
|
# -> Subquery only for SelectBase
|
|
# -> CTE only for HasCTE -> SelectBase, DML
|
|
# -> Lateral -> FromClause, but we accept SelectBase
|
|
# w/ non-deprecated coercion
|
|
# -> TableSample -> only for FromClause
|
|
|
|
|
|
class AliasedReturnsRows(NoInit, NamedFromClause):
|
|
"""Base class of aliases against tables, subqueries, and other
|
|
selectables."""
|
|
|
|
_is_from_container = True
|
|
|
|
_supports_derived_columns = False
|
|
|
|
element: ReturnsRows
|
|
|
|
_traverse_internals: _TraverseInternalsType = [
|
|
("element", InternalTraversal.dp_clauseelement),
|
|
("name", InternalTraversal.dp_anon_name),
|
|
]
|
|
|
|
@classmethod
|
|
def _construct(
|
|
cls,
|
|
selectable: Any,
|
|
*,
|
|
name: Optional[str] = None,
|
|
**kw: Any,
|
|
) -> Self:
|
|
obj = cls.__new__(cls)
|
|
obj._init(selectable, name=name, **kw)
|
|
return obj
|
|
|
|
def _init(self, selectable: Any, *, name: Optional[str] = None) -> None:
|
|
self.element = coercions.expect(
|
|
roles.ReturnsRowsRole, selectable, apply_propagate_attrs=self
|
|
)
|
|
self.element = selectable
|
|
self._orig_name = name
|
|
if name is None:
|
|
if (
|
|
isinstance(selectable, FromClause)
|
|
and selectable.named_with_column
|
|
):
|
|
name = getattr(selectable, "name", None)
|
|
if isinstance(name, _anonymous_label):
|
|
name = None
|
|
name = _anonymous_label.safe_construct(id(self), name or "anon")
|
|
self.name = name
|
|
|
|
def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
|
|
super()._refresh_for_new_column(column)
|
|
self.element._refresh_for_new_column(column)
|
|
|
|
def _populate_column_collection(self) -> None:
|
|
self.element._generate_fromclause_column_proxies(self)
|
|
|
|
@util.ro_non_memoized_property
|
|
def description(self) -> str:
|
|
name = self.name
|
|
if isinstance(name, _anonymous_label):
|
|
name = "anon_1"
|
|
|
|
return name
|
|
|
|
@util.ro_non_memoized_property
|
|
def implicit_returning(self) -> bool:
|
|
return self.element.implicit_returning # type: ignore
|
|
|
|
@property
|
|
def original(self) -> ReturnsRows:
|
|
"""Legacy for dialects that are referring to Alias.original."""
|
|
return self.element
|
|
|
|
def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
|
|
if fromclause in self._cloned_set:
|
|
return True
|
|
return self.element.is_derived_from(fromclause)
|
|
|
|
def _copy_internals(
|
|
self, clone: _CloneCallableType = _clone, **kw: Any
|
|
) -> None:
|
|
existing_element = self.element
|
|
|
|
super()._copy_internals(clone=clone, **kw)
|
|
|
|
# the element clone is usually against a Table that returns the
|
|
# same object. don't reset exported .c. collections and other
|
|
# memoized details if it was not changed. this saves a lot on
|
|
# performance.
|
|
if existing_element is not self.element:
|
|
self._reset_column_collection()
|
|
|
|
@property
|
|
def _from_objects(self) -> List[FromClause]:
|
|
return [self]
|
|
|
|
|
|
class FromClauseAlias(AliasedReturnsRows):
|
|
element: FromClause
|
|
|
|
|
|
class Alias(roles.DMLTableRole, FromClauseAlias):
|
|
"""Represents an table or selectable alias (AS).
|
|
|
|
Represents an alias, as typically applied to any table or
|
|
sub-select within a SQL statement using the ``AS`` keyword (or
|
|
without the keyword on certain databases such as Oracle).
|
|
|
|
This object is constructed from the :func:`_expression.alias` module
|
|
level function as well as the :meth:`_expression.FromClause.alias`
|
|
method available
|
|
on all :class:`_expression.FromClause` subclasses.
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_expression.FromClause.alias`
|
|
|
|
"""
|
|
|
|
__visit_name__ = "alias"
|
|
|
|
inherit_cache = True
|
|
|
|
element: FromClause
|
|
|
|
@classmethod
|
|
def _factory(
|
|
cls,
|
|
selectable: FromClause,
|
|
name: Optional[str] = None,
|
|
flat: bool = False,
|
|
) -> NamedFromClause:
|
|
return coercions.expect(
|
|
roles.FromClauseRole, selectable, allow_select=True
|
|
).alias(name=name, flat=flat)
|
|
|
|
|
|
class TableValuedAlias(LateralFromClause, Alias):
|
|
"""An alias against a "table valued" SQL function.
|
|
|
|
This construct provides for a SQL function that returns columns
|
|
to be used in the FROM clause of a SELECT statement. The
|
|
object is generated using the :meth:`_functions.FunctionElement.table_valued`
|
|
method, e.g.:
|
|
|
|
.. sourcecode:: pycon+sql
|
|
|
|
>>> from sqlalchemy import select, func
|
|
>>> fn = func.json_array_elements_text('["one", "two", "three"]').table_valued("value")
|
|
>>> print(select(fn.c.value))
|
|
{printsql}SELECT anon_1.value
|
|
FROM json_array_elements_text(:json_array_elements_text_1) AS anon_1
|
|
|
|
.. versionadded:: 1.4.0b2
|
|
|
|
.. seealso::
|
|
|
|
:ref:`tutorial_functions_table_valued` - in the :ref:`unified_tutorial`
|
|
|
|
""" # noqa: E501
|
|
|
|
__visit_name__ = "table_valued_alias"
|
|
|
|
_supports_derived_columns = True
|
|
_render_derived = False
|
|
_render_derived_w_types = False
|
|
joins_implicitly = False
|
|
|
|
_traverse_internals: _TraverseInternalsType = [
|
|
("element", InternalTraversal.dp_clauseelement),
|
|
("name", InternalTraversal.dp_anon_name),
|
|
("_tableval_type", InternalTraversal.dp_type),
|
|
("_render_derived", InternalTraversal.dp_boolean),
|
|
("_render_derived_w_types", InternalTraversal.dp_boolean),
|
|
]
|
|
|
|
def _init(
|
|
self,
|
|
selectable: Any,
|
|
*,
|
|
name: Optional[str] = None,
|
|
table_value_type: Optional[TableValueType] = None,
|
|
joins_implicitly: bool = False,
|
|
) -> None:
|
|
super()._init(selectable, name=name)
|
|
|
|
self.joins_implicitly = joins_implicitly
|
|
self._tableval_type = (
|
|
type_api.TABLEVALUE
|
|
if table_value_type is None
|
|
else table_value_type
|
|
)
|
|
|
|
@HasMemoized.memoized_attribute
|
|
def column(self) -> TableValuedColumn[Any]:
|
|
"""Return a column expression representing this
|
|
:class:`_sql.TableValuedAlias`.
|
|
|
|
This accessor is used to implement the
|
|
:meth:`_functions.FunctionElement.column_valued` method. See that
|
|
method for further details.
|
|
|
|
E.g.:
|
|
|
|
.. sourcecode:: pycon+sql
|
|
|
|
>>> print(select(func.some_func().table_valued("value").column))
|
|
{printsql}SELECT anon_1 FROM some_func() AS anon_1
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_functions.FunctionElement.column_valued`
|
|
|
|
"""
|
|
|
|
return TableValuedColumn(self, self._tableval_type)
|
|
|
|
def alias(
|
|
self, name: Optional[str] = None, flat: bool = False
|
|
) -> TableValuedAlias:
|
|
"""Return a new alias of this :class:`_sql.TableValuedAlias`.
|
|
|
|
This creates a distinct FROM object that will be distinguished
|
|
from the original one when used in a SQL statement.
|
|
|
|
"""
|
|
|
|
tva: TableValuedAlias = TableValuedAlias._construct(
|
|
self,
|
|
name=name,
|
|
table_value_type=self._tableval_type,
|
|
joins_implicitly=self.joins_implicitly,
|
|
)
|
|
|
|
if self._render_derived:
|
|
tva._render_derived = True
|
|
tva._render_derived_w_types = self._render_derived_w_types
|
|
|
|
return tva
|
|
|
|
def lateral(self, name: Optional[str] = None) -> LateralFromClause:
|
|
"""Return a new :class:`_sql.TableValuedAlias` with the lateral flag
|
|
set, so that it renders as LATERAL.
|
|
|
|
.. seealso::
|
|
|
|
:func:`_expression.lateral`
|
|
|
|
"""
|
|
tva = self.alias(name=name)
|
|
tva._is_lateral = True
|
|
return tva
|
|
|
|
def render_derived(
|
|
self,
|
|
name: Optional[str] = None,
|
|
with_types: bool = False,
|
|
) -> TableValuedAlias:
|
|
"""Apply "render derived" to this :class:`_sql.TableValuedAlias`.
|
|
|
|
This has the effect of the individual column names listed out
|
|
after the alias name in the "AS" sequence, e.g.:
|
|
|
|
.. sourcecode:: pycon+sql
|
|
|
|
>>> print(
|
|
... select(
|
|
... func.unnest(array(["one", "two", "three"])).
|
|
table_valued("x", with_ordinality="o").render_derived()
|
|
... )
|
|
... )
|
|
{printsql}SELECT anon_1.x, anon_1.o
|
|
FROM unnest(ARRAY[%(param_1)s, %(param_2)s, %(param_3)s]) WITH ORDINALITY AS anon_1(x, o)
|
|
|
|
The ``with_types`` keyword will render column types inline within
|
|
the alias expression (this syntax currently applies to the
|
|
PostgreSQL database):
|
|
|
|
.. sourcecode:: pycon+sql
|
|
|
|
>>> print(
|
|
... select(
|
|
... func.json_to_recordset(
|
|
... '[{"a":1,"b":"foo"},{"a":"2","c":"bar"}]'
|
|
... )
|
|
... .table_valued(column("a", Integer), column("b", String))
|
|
... .render_derived(with_types=True)
|
|
... )
|
|
... )
|
|
{printsql}SELECT anon_1.a, anon_1.b FROM json_to_recordset(:json_to_recordset_1)
|
|
AS anon_1(a INTEGER, b VARCHAR)
|
|
|
|
:param name: optional string name that will be applied to the alias
|
|
generated. If left as None, a unique anonymizing name will be used.
|
|
|
|
:param with_types: if True, the derived columns will include the
|
|
datatype specification with each column. This is a special syntax
|
|
currently known to be required by PostgreSQL for some SQL functions.
|
|
|
|
""" # noqa: E501
|
|
|
|
# note: don't use the @_generative system here, keep a reference
|
|
# to the original object. otherwise you can have re-use of the
|
|
# python id() of the original which can cause name conflicts if
|
|
# a new anon-name grabs the same identifier as the local anon-name
|
|
# (just saw it happen on CI)
|
|
|
|
# construct against original to prevent memory growth
|
|
# for repeated generations
|
|
new_alias: TableValuedAlias = TableValuedAlias._construct(
|
|
self.element,
|
|
name=name,
|
|
table_value_type=self._tableval_type,
|
|
joins_implicitly=self.joins_implicitly,
|
|
)
|
|
new_alias._render_derived = True
|
|
new_alias._render_derived_w_types = with_types
|
|
return new_alias
|
|
|
|
|
|
class Lateral(FromClauseAlias, LateralFromClause):
|
|
"""Represent a LATERAL subquery.
|
|
|
|
This object is constructed from the :func:`_expression.lateral` module
|
|
level function as well as the :meth:`_expression.FromClause.lateral`
|
|
method available
|
|
on all :class:`_expression.FromClause` subclasses.
|
|
|
|
While LATERAL is part of the SQL standard, currently only more recent
|
|
PostgreSQL versions provide support for this keyword.
|
|
|
|
.. seealso::
|
|
|
|
:ref:`tutorial_lateral_correlation` - overview of usage.
|
|
|
|
"""
|
|
|
|
__visit_name__ = "lateral"
|
|
_is_lateral = True
|
|
|
|
inherit_cache = True
|
|
|
|
@classmethod
|
|
def _factory(
|
|
cls,
|
|
selectable: Union[SelectBase, _FromClauseArgument],
|
|
name: Optional[str] = None,
|
|
) -> LateralFromClause:
|
|
return coercions.expect(
|
|
roles.FromClauseRole, selectable, explicit_subquery=True
|
|
).lateral(name=name)
|
|
|
|
|
|
class TableSample(FromClauseAlias):
|
|
"""Represent a TABLESAMPLE clause.
|
|
|
|
This object is constructed from the :func:`_expression.tablesample` module
|
|
level function as well as the :meth:`_expression.FromClause.tablesample`
|
|
method
|
|
available on all :class:`_expression.FromClause` subclasses.
|
|
|
|
.. seealso::
|
|
|
|
:func:`_expression.tablesample`
|
|
|
|
"""
|
|
|
|
__visit_name__ = "tablesample"
|
|
|
|
_traverse_internals: _TraverseInternalsType = (
|
|
AliasedReturnsRows._traverse_internals
|
|
+ [
|
|
("sampling", InternalTraversal.dp_clauseelement),
|
|
("seed", InternalTraversal.dp_clauseelement),
|
|
]
|
|
)
|
|
|
|
@classmethod
|
|
def _factory(
|
|
cls,
|
|
selectable: _FromClauseArgument,
|
|
sampling: Union[float, Function[Any]],
|
|
name: Optional[str] = None,
|
|
seed: Optional[roles.ExpressionElementRole[Any]] = None,
|
|
) -> TableSample:
|
|
return coercions.expect(roles.FromClauseRole, selectable).tablesample(
|
|
sampling, name=name, seed=seed
|
|
)
|
|
|
|
@util.preload_module("sqlalchemy.sql.functions")
|
|
def _init( # type: ignore[override]
|
|
self,
|
|
selectable: Any,
|
|
*,
|
|
name: Optional[str] = None,
|
|
sampling: Union[float, Function[Any]],
|
|
seed: Optional[roles.ExpressionElementRole[Any]] = None,
|
|
) -> None:
|
|
assert sampling is not None
|
|
functions = util.preloaded.sql_functions
|
|
if not isinstance(sampling, functions.Function):
|
|
sampling = functions.func.system(sampling)
|
|
|
|
self.sampling: Function[Any] = sampling
|
|
self.seed = seed
|
|
super()._init(selectable, name=name)
|
|
|
|
def _get_method(self) -> Function[Any]:
|
|
return self.sampling
|
|
|
|
|
|
class CTE(
|
|
roles.DMLTableRole,
|
|
roles.IsCTERole,
|
|
Generative,
|
|
HasPrefixes,
|
|
HasSuffixes,
|
|
AliasedReturnsRows,
|
|
):
|
|
"""Represent a Common Table Expression.
|
|
|
|
The :class:`_expression.CTE` object is obtained using the
|
|
:meth:`_sql.SelectBase.cte` method from any SELECT statement. A less often
|
|
available syntax also allows use of the :meth:`_sql.HasCTE.cte` method
|
|
present on :term:`DML` constructs such as :class:`_sql.Insert`,
|
|
:class:`_sql.Update` and
|
|
:class:`_sql.Delete`. See the :meth:`_sql.HasCTE.cte` method for
|
|
usage details on CTEs.
|
|
|
|
.. seealso::
|
|
|
|
:ref:`tutorial_subqueries_ctes` - in the 2.0 tutorial
|
|
|
|
:meth:`_sql.HasCTE.cte` - examples of calling styles
|
|
|
|
"""
|
|
|
|
__visit_name__ = "cte"
|
|
|
|
_traverse_internals: _TraverseInternalsType = (
|
|
AliasedReturnsRows._traverse_internals
|
|
+ [
|
|
("_cte_alias", InternalTraversal.dp_clauseelement),
|
|
("_restates", InternalTraversal.dp_clauseelement),
|
|
("recursive", InternalTraversal.dp_boolean),
|
|
("nesting", InternalTraversal.dp_boolean),
|
|
]
|
|
+ HasPrefixes._has_prefixes_traverse_internals
|
|
+ HasSuffixes._has_suffixes_traverse_internals
|
|
)
|
|
|
|
element: HasCTE
|
|
|
|
@classmethod
|
|
def _factory(
|
|
cls,
|
|
selectable: HasCTE,
|
|
name: Optional[str] = None,
|
|
recursive: bool = False,
|
|
) -> CTE:
|
|
r"""Return a new :class:`_expression.CTE`,
|
|
or Common Table Expression instance.
|
|
|
|
Please see :meth:`_expression.HasCTE.cte` for detail on CTE usage.
|
|
|
|
"""
|
|
return coercions.expect(roles.HasCTERole, selectable).cte(
|
|
name=name, recursive=recursive
|
|
)
|
|
|
|
def _init(
|
|
self,
|
|
selectable: Select[Any],
|
|
*,
|
|
name: Optional[str] = None,
|
|
recursive: bool = False,
|
|
nesting: bool = False,
|
|
_cte_alias: Optional[CTE] = None,
|
|
_restates: Optional[CTE] = None,
|
|
_prefixes: Optional[Tuple[()]] = None,
|
|
_suffixes: Optional[Tuple[()]] = None,
|
|
) -> None:
|
|
self.recursive = recursive
|
|
self.nesting = nesting
|
|
self._cte_alias = _cte_alias
|
|
# Keep recursivity reference with union/union_all
|
|
self._restates = _restates
|
|
if _prefixes:
|
|
self._prefixes = _prefixes
|
|
if _suffixes:
|
|
self._suffixes = _suffixes
|
|
super()._init(selectable, name=name)
|
|
|
|
def _populate_column_collection(self) -> None:
|
|
if self._cte_alias is not None:
|
|
self._cte_alias._generate_fromclause_column_proxies(self)
|
|
else:
|
|
self.element._generate_fromclause_column_proxies(self)
|
|
|
|
def alias(self, name: Optional[str] = None, flat: bool = False) -> CTE:
|
|
"""Return an :class:`_expression.Alias` of this
|
|
:class:`_expression.CTE`.
|
|
|
|
This method is a CTE-specific specialization of the
|
|
:meth:`_expression.FromClause.alias` method.
|
|
|
|
.. seealso::
|
|
|
|
:ref:`tutorial_using_aliases`
|
|
|
|
:func:`_expression.alias`
|
|
|
|
"""
|
|
return CTE._construct(
|
|
self.element,
|
|
name=name,
|
|
recursive=self.recursive,
|
|
nesting=self.nesting,
|
|
_cte_alias=self,
|
|
_prefixes=self._prefixes,
|
|
_suffixes=self._suffixes,
|
|
)
|
|
|
|
def union(self, *other: _SelectStatementForCompoundArgument) -> CTE:
|
|
r"""Return a new :class:`_expression.CTE` with a SQL ``UNION``
|
|
of the original CTE against the given selectables provided
|
|
as positional arguments.
|
|
|
|
:param \*other: one or more elements with which to create a
|
|
UNION.
|
|
|
|
.. versionchanged:: 1.4.28 multiple elements are now accepted.
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_sql.HasCTE.cte` - examples of calling styles
|
|
|
|
"""
|
|
assert is_select_statement(
|
|
self.element
|
|
), f"CTE element f{self.element} does not support union()"
|
|
|
|
return CTE._construct(
|
|
self.element.union(*other),
|
|
name=self.name,
|
|
recursive=self.recursive,
|
|
nesting=self.nesting,
|
|
_restates=self,
|
|
_prefixes=self._prefixes,
|
|
_suffixes=self._suffixes,
|
|
)
|
|
|
|
def union_all(self, *other: _SelectStatementForCompoundArgument) -> CTE:
|
|
r"""Return a new :class:`_expression.CTE` with a SQL ``UNION ALL``
|
|
of the original CTE against the given selectables provided
|
|
as positional arguments.
|
|
|
|
:param \*other: one or more elements with which to create a
|
|
UNION.
|
|
|
|
.. versionchanged:: 1.4.28 multiple elements are now accepted.
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_sql.HasCTE.cte` - examples of calling styles
|
|
|
|
"""
|
|
|
|
assert is_select_statement(
|
|
self.element
|
|
), f"CTE element f{self.element} does not support union_all()"
|
|
|
|
return CTE._construct(
|
|
self.element.union_all(*other),
|
|
name=self.name,
|
|
recursive=self.recursive,
|
|
nesting=self.nesting,
|
|
_restates=self,
|
|
_prefixes=self._prefixes,
|
|
_suffixes=self._suffixes,
|
|
)
|
|
|
|
def _get_reference_cte(self) -> CTE:
|
|
"""
|
|
A recursive CTE is updated to attach the recursive part.
|
|
Updated CTEs should still refer to the original CTE.
|
|
This function returns this reference identifier.
|
|
"""
|
|
return self._restates if self._restates is not None else self
|
|
|
|
|
|
class _CTEOpts(NamedTuple):
|
|
nesting: bool
|
|
|
|
|
|
class _ColumnsPlusNames(NamedTuple):
|
|
required_label_name: Optional[str]
|
|
"""
|
|
string label name, if non-None, must be rendered as a
|
|
label, i.e. "AS <name>"
|
|
"""
|
|
|
|
proxy_key: Optional[str]
|
|
"""
|
|
proxy_key that is to be part of the result map for this
|
|
col. this is also the key in a fromclause.c or
|
|
select.selected_columns collection
|
|
"""
|
|
|
|
fallback_label_name: Optional[str]
|
|
"""
|
|
name that can be used to render an "AS <name>" when
|
|
we have to render a label even though
|
|
required_label_name was not given
|
|
"""
|
|
|
|
column: Union[ColumnElement[Any], TextClause]
|
|
"""
|
|
the ColumnElement itself
|
|
"""
|
|
|
|
repeated: bool
|
|
"""
|
|
True if this is a duplicate of a previous column
|
|
in the list of columns
|
|
"""
|
|
|
|
|
|
class SelectsRows(ReturnsRows):
|
|
"""Sub-base of ReturnsRows for elements that deliver rows
|
|
directly, namely SELECT and INSERT/UPDATE/DELETE..RETURNING"""
|
|
|
|
_label_style: SelectLabelStyle = LABEL_STYLE_NONE
|
|
|
|
def _generate_columns_plus_names(
|
|
self,
|
|
anon_for_dupe_key: bool,
|
|
cols: Optional[_SelectIterable] = None,
|
|
) -> List[_ColumnsPlusNames]:
|
|
"""Generate column names as rendered in a SELECT statement by
|
|
the compiler.
|
|
|
|
This is distinct from the _column_naming_convention generator that's
|
|
intended for population of .c collections and similar, which has
|
|
different rules. the collection returned here calls upon the
|
|
_column_naming_convention as well.
|
|
|
|
"""
|
|
|
|
if cols is None:
|
|
cols = self._all_selected_columns
|
|
|
|
key_naming_convention = SelectState._column_naming_convention(
|
|
self._label_style
|
|
)
|
|
|
|
names = {}
|
|
|
|
result: List[_ColumnsPlusNames] = []
|
|
result_append = result.append
|
|
|
|
table_qualified = self._label_style is LABEL_STYLE_TABLENAME_PLUS_COL
|
|
label_style_none = self._label_style is LABEL_STYLE_NONE
|
|
|
|
# a counter used for "dedupe" labels, which have double underscores
|
|
# in them and are never referred by name; they only act
|
|
# as positional placeholders. they need only be unique within
|
|
# the single columns clause they're rendered within (required by
|
|
# some dbs such as mysql). So their anon identity is tracked against
|
|
# a fixed counter rather than hash() identity.
|
|
dedupe_hash = 1
|
|
|
|
for c in cols:
|
|
repeated = False
|
|
|
|
if not c._render_label_in_columns_clause:
|
|
effective_name = required_label_name = fallback_label_name = (
|
|
None
|
|
)
|
|
elif label_style_none:
|
|
if TYPE_CHECKING:
|
|
assert is_column_element(c)
|
|
|
|
effective_name = required_label_name = None
|
|
fallback_label_name = c._non_anon_label or c._anon_name_label
|
|
else:
|
|
if TYPE_CHECKING:
|
|
assert is_column_element(c)
|
|
|
|
if table_qualified:
|
|
required_label_name = effective_name = (
|
|
fallback_label_name
|
|
) = c._tq_label
|
|
else:
|
|
effective_name = fallback_label_name = c._non_anon_label
|
|
required_label_name = None
|
|
|
|
if effective_name is None:
|
|
# it seems like this could be _proxy_key and we would
|
|
# not need _expression_label but it isn't
|
|
# giving us a clue when to use anon_label instead
|
|
expr_label = c._expression_label
|
|
if expr_label is None:
|
|
repeated = c._anon_name_label in names
|
|
names[c._anon_name_label] = c
|
|
effective_name = required_label_name = None
|
|
|
|
if repeated:
|
|
# here, "required_label_name" is sent as
|
|
# "None" and "fallback_label_name" is sent.
|
|
if table_qualified:
|
|
fallback_label_name = (
|
|
c._dedupe_anon_tq_label_idx(dedupe_hash)
|
|
)
|
|
dedupe_hash += 1
|
|
else:
|
|
fallback_label_name = c._dedupe_anon_label_idx(
|
|
dedupe_hash
|
|
)
|
|
dedupe_hash += 1
|
|
else:
|
|
fallback_label_name = c._anon_name_label
|
|
else:
|
|
required_label_name = effective_name = (
|
|
fallback_label_name
|
|
) = expr_label
|
|
|
|
if effective_name is not None:
|
|
if TYPE_CHECKING:
|
|
assert is_column_element(c)
|
|
|
|
if effective_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
|
|
# is seen as the same as the non-annotated
|
|
if hash(names[effective_name]) != hash(c):
|
|
# different column under the same name. apply
|
|
# disambiguating label
|
|
if table_qualified:
|
|
required_label_name = fallback_label_name = (
|
|
c._anon_tq_label
|
|
)
|
|
else:
|
|
required_label_name = fallback_label_name = (
|
|
c._anon_name_label
|
|
)
|
|
|
|
if anon_for_dupe_key and required_label_name in names:
|
|
# here, c._anon_tq_label is definitely unique to
|
|
# that column identity (or annotated version), so
|
|
# this should always be true.
|
|
# this is also an infrequent codepath because
|
|
# you need two levels of duplication to be here
|
|
assert hash(names[required_label_name]) == hash(c)
|
|
|
|
# the column under the disambiguating label is
|
|
# already present. apply the "dedupe" label to
|
|
# subsequent occurrences of the column so that the
|
|
# original stays non-ambiguous
|
|
if table_qualified:
|
|
required_label_name = fallback_label_name = (
|
|
c._dedupe_anon_tq_label_idx(dedupe_hash)
|
|
)
|
|
dedupe_hash += 1
|
|
else:
|
|
required_label_name = fallback_label_name = (
|
|
c._dedupe_anon_label_idx(dedupe_hash)
|
|
)
|
|
dedupe_hash += 1
|
|
repeated = True
|
|
else:
|
|
names[required_label_name] = c
|
|
elif anon_for_dupe_key:
|
|
# same column under the same name. apply the "dedupe"
|
|
# label so that the original stays non-ambiguous
|
|
if table_qualified:
|
|
required_label_name = fallback_label_name = (
|
|
c._dedupe_anon_tq_label_idx(dedupe_hash)
|
|
)
|
|
dedupe_hash += 1
|
|
else:
|
|
required_label_name = fallback_label_name = (
|
|
c._dedupe_anon_label_idx(dedupe_hash)
|
|
)
|
|
dedupe_hash += 1
|
|
repeated = True
|
|
else:
|
|
names[effective_name] = c
|
|
|
|
result_append(
|
|
_ColumnsPlusNames(
|
|
required_label_name,
|
|
key_naming_convention(c),
|
|
fallback_label_name,
|
|
c,
|
|
repeated,
|
|
)
|
|
)
|
|
|
|
return result
|
|
|
|
|
|
class HasCTE(roles.HasCTERole, SelectsRows):
|
|
"""Mixin that declares a class to include CTE support."""
|
|
|
|
_has_ctes_traverse_internals: _TraverseInternalsType = [
|
|
("_independent_ctes", InternalTraversal.dp_clauseelement_list),
|
|
("_independent_ctes_opts", InternalTraversal.dp_plain_obj),
|
|
]
|
|
|
|
_independent_ctes: Tuple[CTE, ...] = ()
|
|
_independent_ctes_opts: Tuple[_CTEOpts, ...] = ()
|
|
|
|
@_generative
|
|
def add_cte(self, *ctes: CTE, nest_here: bool = False) -> Self:
|
|
r"""Add one or more :class:`_sql.CTE` constructs to this statement.
|
|
|
|
This method will associate the given :class:`_sql.CTE` constructs with
|
|
the parent statement such that they will each be unconditionally
|
|
rendered in the WITH clause of the final statement, even if not
|
|
referenced elsewhere within the statement or any sub-selects.
|
|
|
|
The optional :paramref:`.HasCTE.add_cte.nest_here` parameter when set
|
|
to True will have the effect that each given :class:`_sql.CTE` will
|
|
render in a WITH clause rendered directly along with this statement,
|
|
rather than being moved to the top of the ultimate rendered statement,
|
|
even if this statement is rendered as a subquery within a larger
|
|
statement.
|
|
|
|
This method has two general uses. One is to embed CTE statements that
|
|
serve some purpose without being referenced explicitly, such as the use
|
|
case of embedding a DML statement such as an INSERT or UPDATE as a CTE
|
|
inline with a primary statement that may draw from its results
|
|
indirectly. The other is to provide control over the exact placement
|
|
of a particular series of CTE constructs that should remain rendered
|
|
directly in terms of a particular statement that may be nested in a
|
|
larger statement.
|
|
|
|
E.g.::
|
|
|
|
from sqlalchemy import table, column, select
|
|
t = table('t', column('c1'), column('c2'))
|
|
|
|
ins = t.insert().values({"c1": "x", "c2": "y"}).cte()
|
|
|
|
stmt = select(t).add_cte(ins)
|
|
|
|
Would render::
|
|
|
|
WITH anon_1 AS
|
|
(INSERT INTO t (c1, c2) VALUES (:param_1, :param_2))
|
|
SELECT t.c1, t.c2
|
|
FROM t
|
|
|
|
Above, the "anon_1" CTE is not referenced in the SELECT
|
|
statement, however still accomplishes the task of running an INSERT
|
|
statement.
|
|
|
|
Similarly in a DML-related context, using the PostgreSQL
|
|
:class:`_postgresql.Insert` construct to generate an "upsert"::
|
|
|
|
from sqlalchemy import table, column
|
|
from sqlalchemy.dialects.postgresql import insert
|
|
|
|
t = table("t", column("c1"), column("c2"))
|
|
|
|
delete_statement_cte = (
|
|
t.delete().where(t.c.c1 < 1).cte("deletions")
|
|
)
|
|
|
|
insert_stmt = insert(t).values({"c1": 1, "c2": 2})
|
|
update_statement = insert_stmt.on_conflict_do_update(
|
|
index_elements=[t.c.c1],
|
|
set_={
|
|
"c1": insert_stmt.excluded.c1,
|
|
"c2": insert_stmt.excluded.c2,
|
|
},
|
|
).add_cte(delete_statement_cte)
|
|
|
|
print(update_statement)
|
|
|
|
The above statement renders as::
|
|
|
|
WITH deletions AS
|
|
(DELETE FROM t WHERE t.c1 < %(c1_1)s)
|
|
INSERT INTO t (c1, c2) VALUES (%(c1)s, %(c2)s)
|
|
ON CONFLICT (c1) DO UPDATE SET c1 = excluded.c1, c2 = excluded.c2
|
|
|
|
.. versionadded:: 1.4.21
|
|
|
|
:param \*ctes: zero or more :class:`.CTE` constructs.
|
|
|
|
.. versionchanged:: 2.0 Multiple CTE instances are accepted
|
|
|
|
:param nest_here: if True, the given CTE or CTEs will be rendered
|
|
as though they specified the :paramref:`.HasCTE.cte.nesting` flag
|
|
to ``True`` when they were added to this :class:`.HasCTE`.
|
|
Assuming the given CTEs are not referenced in an outer-enclosing
|
|
statement as well, the CTEs given should render at the level of
|
|
this statement when this flag is given.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
.. seealso::
|
|
|
|
:paramref:`.HasCTE.cte.nesting`
|
|
|
|
|
|
"""
|
|
opt = _CTEOpts(
|
|
nest_here,
|
|
)
|
|
for cte in ctes:
|
|
cte = coercions.expect(roles.IsCTERole, cte)
|
|
self._independent_ctes += (cte,)
|
|
self._independent_ctes_opts += (opt,)
|
|
return self
|
|
|
|
def cte(
|
|
self,
|
|
name: Optional[str] = None,
|
|
recursive: bool = False,
|
|
nesting: bool = False,
|
|
) -> CTE:
|
|
r"""Return a new :class:`_expression.CTE`,
|
|
or Common Table Expression instance.
|
|
|
|
Common table expressions are a SQL standard whereby SELECT
|
|
statements can draw upon secondary statements specified along
|
|
with the primary statement, using a clause called "WITH".
|
|
Special semantics regarding UNION can also be employed to
|
|
allow "recursive" queries, where a SELECT statement can draw
|
|
upon the set of rows that have previously been selected.
|
|
|
|
CTEs can also be applied to DML constructs UPDATE, INSERT
|
|
and DELETE on some databases, both as a source of CTE rows
|
|
when combined with RETURNING, as well as a consumer of
|
|
CTE rows.
|
|
|
|
SQLAlchemy detects :class:`_expression.CTE` objects, which are treated
|
|
similarly to :class:`_expression.Alias` objects, as special elements
|
|
to be delivered to the FROM clause of the statement as well
|
|
as to a WITH clause at the top of the statement.
|
|
|
|
For special prefixes such as PostgreSQL "MATERIALIZED" and
|
|
"NOT MATERIALIZED", the :meth:`_expression.CTE.prefix_with`
|
|
method may be
|
|
used to establish these.
|
|
|
|
.. versionchanged:: 1.3.13 Added support for prefixes.
|
|
In particular - MATERIALIZED and NOT MATERIALIZED.
|
|
|
|
:param name: name given to the common table expression. Like
|
|
:meth:`_expression.FromClause.alias`, the name can be left as
|
|
``None`` in which case an anonymous symbol will be used at query
|
|
compile time.
|
|
:param recursive: if ``True``, will render ``WITH RECURSIVE``.
|
|
A recursive common table expression is intended to be used in
|
|
conjunction with UNION ALL in order to derive rows
|
|
from those already selected.
|
|
:param nesting: if ``True``, will render the CTE locally to the
|
|
statement in which it is referenced. For more complex scenarios,
|
|
the :meth:`.HasCTE.add_cte` method using the
|
|
:paramref:`.HasCTE.add_cte.nest_here`
|
|
parameter may also be used to more carefully
|
|
control the exact placement of a particular CTE.
|
|
|
|
.. versionadded:: 1.4.24
|
|
|
|
.. seealso::
|
|
|
|
:meth:`.HasCTE.add_cte`
|
|
|
|
The following examples include two from PostgreSQL's documentation at
|
|
https://www.postgresql.org/docs/current/static/queries-with.html,
|
|
as well as additional examples.
|
|
|
|
Example 1, non recursive::
|
|
|
|
from sqlalchemy import (Table, Column, String, Integer,
|
|
MetaData, select, func)
|
|
|
|
metadata = MetaData()
|
|
|
|
orders = Table('orders', metadata,
|
|
Column('region', String),
|
|
Column('amount', Integer),
|
|
Column('product', String),
|
|
Column('quantity', Integer)
|
|
)
|
|
|
|
regional_sales = select(
|
|
orders.c.region,
|
|
func.sum(orders.c.amount).label('total_sales')
|
|
).group_by(orders.c.region).cte("regional_sales")
|
|
|
|
|
|
top_regions = select(regional_sales.c.region).\
|
|
where(
|
|
regional_sales.c.total_sales >
|
|
select(
|
|
func.sum(regional_sales.c.total_sales) / 10
|
|
)
|
|
).cte("top_regions")
|
|
|
|
statement = select(
|
|
orders.c.region,
|
|
orders.c.product,
|
|
func.sum(orders.c.quantity).label("product_units"),
|
|
func.sum(orders.c.amount).label("product_sales")
|
|
).where(orders.c.region.in_(
|
|
select(top_regions.c.region)
|
|
)).group_by(orders.c.region, orders.c.product)
|
|
|
|
result = conn.execute(statement).fetchall()
|
|
|
|
Example 2, WITH RECURSIVE::
|
|
|
|
from sqlalchemy import (Table, Column, String, Integer,
|
|
MetaData, select, func)
|
|
|
|
metadata = MetaData()
|
|
|
|
parts = Table('parts', metadata,
|
|
Column('part', String),
|
|
Column('sub_part', String),
|
|
Column('quantity', Integer),
|
|
)
|
|
|
|
included_parts = select(\
|
|
parts.c.sub_part, parts.c.part, parts.c.quantity\
|
|
).\
|
|
where(parts.c.part=='our part').\
|
|
cte(recursive=True)
|
|
|
|
|
|
incl_alias = included_parts.alias()
|
|
parts_alias = parts.alias()
|
|
included_parts = included_parts.union_all(
|
|
select(
|
|
parts_alias.c.sub_part,
|
|
parts_alias.c.part,
|
|
parts_alias.c.quantity
|
|
).\
|
|
where(parts_alias.c.part==incl_alias.c.sub_part)
|
|
)
|
|
|
|
statement = select(
|
|
included_parts.c.sub_part,
|
|
func.sum(included_parts.c.quantity).
|
|
label('total_quantity')
|
|
).\
|
|
group_by(included_parts.c.sub_part)
|
|
|
|
result = conn.execute(statement).fetchall()
|
|
|
|
Example 3, an upsert using UPDATE and INSERT with CTEs::
|
|
|
|
from datetime import date
|
|
from sqlalchemy import (MetaData, Table, Column, Integer,
|
|
Date, select, literal, and_, exists)
|
|
|
|
metadata = MetaData()
|
|
|
|
visitors = Table('visitors', metadata,
|
|
Column('product_id', Integer, primary_key=True),
|
|
Column('date', Date, primary_key=True),
|
|
Column('count', Integer),
|
|
)
|
|
|
|
# add 5 visitors for the product_id == 1
|
|
product_id = 1
|
|
day = date.today()
|
|
count = 5
|
|
|
|
update_cte = (
|
|
visitors.update()
|
|
.where(and_(visitors.c.product_id == product_id,
|
|
visitors.c.date == day))
|
|
.values(count=visitors.c.count + count)
|
|
.returning(literal(1))
|
|
.cte('update_cte')
|
|
)
|
|
|
|
upsert = visitors.insert().from_select(
|
|
[visitors.c.product_id, visitors.c.date, visitors.c.count],
|
|
select(literal(product_id), literal(day), literal(count))
|
|
.where(~exists(update_cte.select()))
|
|
)
|
|
|
|
connection.execute(upsert)
|
|
|
|
Example 4, Nesting CTE (SQLAlchemy 1.4.24 and above)::
|
|
|
|
value_a = select(
|
|
literal("root").label("n")
|
|
).cte("value_a")
|
|
|
|
# A nested CTE with the same name as the root one
|
|
value_a_nested = select(
|
|
literal("nesting").label("n")
|
|
).cte("value_a", nesting=True)
|
|
|
|
# Nesting CTEs takes ascendency locally
|
|
# over the CTEs at a higher level
|
|
value_b = select(value_a_nested.c.n).cte("value_b")
|
|
|
|
value_ab = select(value_a.c.n.label("a"), value_b.c.n.label("b"))
|
|
|
|
The above query will render the second CTE nested inside the first,
|
|
shown with inline parameters below as::
|
|
|
|
WITH
|
|
value_a AS
|
|
(SELECT 'root' AS n),
|
|
value_b AS
|
|
(WITH value_a AS
|
|
(SELECT 'nesting' AS n)
|
|
SELECT value_a.n AS n FROM value_a)
|
|
SELECT value_a.n AS a, value_b.n AS b
|
|
FROM value_a, value_b
|
|
|
|
The same CTE can be set up using the :meth:`.HasCTE.add_cte` method
|
|
as follows (SQLAlchemy 2.0 and above)::
|
|
|
|
value_a = select(
|
|
literal("root").label("n")
|
|
).cte("value_a")
|
|
|
|
# A nested CTE with the same name as the root one
|
|
value_a_nested = select(
|
|
literal("nesting").label("n")
|
|
).cte("value_a")
|
|
|
|
# Nesting CTEs takes ascendency locally
|
|
# over the CTEs at a higher level
|
|
value_b = (
|
|
select(value_a_nested.c.n).
|
|
add_cte(value_a_nested, nest_here=True).
|
|
cte("value_b")
|
|
)
|
|
|
|
value_ab = select(value_a.c.n.label("a"), value_b.c.n.label("b"))
|
|
|
|
Example 5, Non-Linear CTE (SQLAlchemy 1.4.28 and above)::
|
|
|
|
edge = Table(
|
|
"edge",
|
|
metadata,
|
|
Column("id", Integer, primary_key=True),
|
|
Column("left", Integer),
|
|
Column("right", Integer),
|
|
)
|
|
|
|
root_node = select(literal(1).label("node")).cte(
|
|
"nodes", recursive=True
|
|
)
|
|
|
|
left_edge = select(edge.c.left).join(
|
|
root_node, edge.c.right == root_node.c.node
|
|
)
|
|
right_edge = select(edge.c.right).join(
|
|
root_node, edge.c.left == root_node.c.node
|
|
)
|
|
|
|
subgraph_cte = root_node.union(left_edge, right_edge)
|
|
|
|
subgraph = select(subgraph_cte)
|
|
|
|
The above query will render 2 UNIONs inside the recursive CTE::
|
|
|
|
WITH RECURSIVE nodes(node) AS (
|
|
SELECT 1 AS node
|
|
UNION
|
|
SELECT edge."left" AS "left"
|
|
FROM edge JOIN nodes ON edge."right" = nodes.node
|
|
UNION
|
|
SELECT edge."right" AS "right"
|
|
FROM edge JOIN nodes ON edge."left" = nodes.node
|
|
)
|
|
SELECT nodes.node FROM nodes
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_orm.Query.cte` - ORM version of
|
|
:meth:`_expression.HasCTE.cte`.
|
|
|
|
"""
|
|
return CTE._construct(
|
|
self, name=name, recursive=recursive, nesting=nesting
|
|
)
|
|
|
|
|
|
class Subquery(AliasedReturnsRows):
|
|
"""Represent a subquery of a SELECT.
|
|
|
|
A :class:`.Subquery` is created by invoking the
|
|
:meth:`_expression.SelectBase.subquery` method, or for convenience the
|
|
:meth:`_expression.SelectBase.alias` method, on any
|
|
:class:`_expression.SelectBase` subclass
|
|
which includes :class:`_expression.Select`,
|
|
:class:`_expression.CompoundSelect`, and
|
|
:class:`_expression.TextualSelect`. As rendered in a FROM clause,
|
|
it represents the
|
|
body of the SELECT statement inside of parenthesis, followed by the usual
|
|
"AS <somename>" that defines all "alias" objects.
|
|
|
|
The :class:`.Subquery` object is very similar to the
|
|
:class:`_expression.Alias`
|
|
object and can be used in an equivalent way. The difference between
|
|
:class:`_expression.Alias` and :class:`.Subquery` is that
|
|
:class:`_expression.Alias` always
|
|
contains a :class:`_expression.FromClause` object whereas
|
|
:class:`.Subquery`
|
|
always contains a :class:`_expression.SelectBase` object.
|
|
|
|
.. versionadded:: 1.4 The :class:`.Subquery` class was added which now
|
|
serves the purpose of providing an aliased version of a SELECT
|
|
statement.
|
|
|
|
"""
|
|
|
|
__visit_name__ = "subquery"
|
|
|
|
_is_subquery = True
|
|
|
|
inherit_cache = True
|
|
|
|
element: SelectBase
|
|
|
|
@classmethod
|
|
def _factory(
|
|
cls, selectable: SelectBase, name: Optional[str] = None
|
|
) -> Subquery:
|
|
"""Return a :class:`.Subquery` object."""
|
|
|
|
return coercions.expect(
|
|
roles.SelectStatementRole, selectable
|
|
).subquery(name=name)
|
|
|
|
@util.deprecated(
|
|
"1.4",
|
|
"The :meth:`.Subquery.as_scalar` method, which was previously "
|
|
"``Alias.as_scalar()`` prior to version 1.4, is deprecated and "
|
|
"will be removed in a future release; Please use the "
|
|
":meth:`_expression.Select.scalar_subquery` method of the "
|
|
":func:`_expression.select` "
|
|
"construct before constructing a subquery object, or with the ORM "
|
|
"use the :meth:`_query.Query.scalar_subquery` method.",
|
|
)
|
|
def as_scalar(self) -> ScalarSelect[Any]:
|
|
return self.element.set_label_style(LABEL_STYLE_NONE).scalar_subquery()
|
|
|
|
|
|
class FromGrouping(GroupedElement, FromClause):
|
|
"""Represent a grouping of a FROM clause"""
|
|
|
|
_traverse_internals: _TraverseInternalsType = [
|
|
("element", InternalTraversal.dp_clauseelement)
|
|
]
|
|
|
|
element: FromClause
|
|
|
|
def __init__(self, element: FromClause):
|
|
self.element = coercions.expect(roles.FromClauseRole, element)
|
|
|
|
def _init_collections(self) -> None:
|
|
pass
|
|
|
|
@util.ro_non_memoized_property
|
|
def columns(
|
|
self,
|
|
) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
|
|
return self.element.columns
|
|
|
|
@util.ro_non_memoized_property
|
|
def c(self) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
|
|
return self.element.columns
|
|
|
|
@property
|
|
def primary_key(self) -> Iterable[NamedColumn[Any]]:
|
|
return self.element.primary_key
|
|
|
|
@property
|
|
def foreign_keys(self) -> Iterable[ForeignKey]:
|
|
return self.element.foreign_keys
|
|
|
|
def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
|
|
return self.element.is_derived_from(fromclause)
|
|
|
|
def alias(
|
|
self, name: Optional[str] = None, flat: bool = False
|
|
) -> NamedFromGrouping:
|
|
return NamedFromGrouping(self.element.alias(name=name, flat=flat))
|
|
|
|
def _anonymous_fromclause(self, **kw: Any) -> FromGrouping:
|
|
return FromGrouping(self.element._anonymous_fromclause(**kw))
|
|
|
|
@util.ro_non_memoized_property
|
|
def _hide_froms(self) -> Iterable[FromClause]:
|
|
return self.element._hide_froms
|
|
|
|
@util.ro_non_memoized_property
|
|
def _from_objects(self) -> List[FromClause]:
|
|
return self.element._from_objects
|
|
|
|
def __getstate__(self) -> Dict[str, FromClause]:
|
|
return {"element": self.element}
|
|
|
|
def __setstate__(self, state: Dict[str, FromClause]) -> None:
|
|
self.element = state["element"]
|
|
|
|
|
|
class NamedFromGrouping(FromGrouping, NamedFromClause):
|
|
"""represent a grouping of a named FROM clause
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
"""
|
|
|
|
inherit_cache = True
|
|
|
|
|
|
class TableClause(roles.DMLTableRole, Immutable, NamedFromClause):
|
|
"""Represents a minimal "table" construct.
|
|
|
|
This is a lightweight table object that has only a name, a
|
|
collection of columns, which are typically produced
|
|
by the :func:`_expression.column` function, and a schema::
|
|
|
|
from sqlalchemy import table, column
|
|
|
|
user = table("user",
|
|
column("id"),
|
|
column("name"),
|
|
column("description"),
|
|
)
|
|
|
|
The :class:`_expression.TableClause` construct serves as the base for
|
|
the more commonly used :class:`_schema.Table` object, providing
|
|
the usual set of :class:`_expression.FromClause` services including
|
|
the ``.c.`` collection and statement generation methods.
|
|
|
|
It does **not** provide all the additional schema-level services
|
|
of :class:`_schema.Table`, including constraints, references to other
|
|
tables, or support for :class:`_schema.MetaData`-level services.
|
|
It's useful
|
|
on its own as an ad-hoc construct used to generate quick SQL
|
|
statements when a more fully fledged :class:`_schema.Table`
|
|
is not on hand.
|
|
|
|
"""
|
|
|
|
__visit_name__ = "table"
|
|
|
|
_traverse_internals: _TraverseInternalsType = [
|
|
(
|
|
"columns",
|
|
InternalTraversal.dp_fromclause_canonical_column_collection,
|
|
),
|
|
("name", InternalTraversal.dp_string),
|
|
("schema", InternalTraversal.dp_string),
|
|
]
|
|
|
|
_is_table = True
|
|
|
|
fullname: str
|
|
|
|
implicit_returning = False
|
|
""":class:`_expression.TableClause`
|
|
doesn't support having a primary key or column
|
|
-level defaults, so implicit returning doesn't apply."""
|
|
|
|
@util.ro_memoized_property
|
|
def _autoincrement_column(self) -> Optional[ColumnClause[Any]]:
|
|
"""No PK or default support so no autoincrement column."""
|
|
return None
|
|
|
|
def __init__(self, name: str, *columns: ColumnClause[Any], **kw: Any):
|
|
super().__init__()
|
|
self.name = name
|
|
self._columns = DedupeColumnCollection()
|
|
self.primary_key = ColumnSet() # type: ignore
|
|
self.foreign_keys = set() # type: ignore
|
|
for c in columns:
|
|
self.append_column(c)
|
|
|
|
schema = kw.pop("schema", None)
|
|
if schema is not None:
|
|
self.schema = schema
|
|
if self.schema is not None:
|
|
self.fullname = "%s.%s" % (self.schema, self.name)
|
|
else:
|
|
self.fullname = self.name
|
|
if kw:
|
|
raise exc.ArgumentError("Unsupported argument(s): %s" % list(kw))
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
@util.ro_non_memoized_property
|
|
def columns(
|
|
self,
|
|
) -> ReadOnlyColumnCollection[str, ColumnClause[Any]]: ...
|
|
|
|
@util.ro_non_memoized_property
|
|
def c(self) -> ReadOnlyColumnCollection[str, ColumnClause[Any]]: ...
|
|
|
|
def __str__(self) -> str:
|
|
if self.schema is not None:
|
|
return self.schema + "." + self.name
|
|
else:
|
|
return self.name
|
|
|
|
def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
|
|
pass
|
|
|
|
def _init_collections(self) -> None:
|
|
pass
|
|
|
|
@util.ro_memoized_property
|
|
def description(self) -> str:
|
|
return self.name
|
|
|
|
def append_column(self, c: ColumnClause[Any]) -> None:
|
|
existing = c.table
|
|
if existing is not None and existing is not self:
|
|
raise exc.ArgumentError(
|
|
"column object '%s' already assigned to table '%s'"
|
|
% (c.key, existing)
|
|
)
|
|
|
|
self._columns.add(c)
|
|
c.table = self
|
|
|
|
@util.preload_module("sqlalchemy.sql.dml")
|
|
def insert(self) -> util.preloaded.sql_dml.Insert:
|
|
"""Generate an :class:`_sql.Insert` construct against this
|
|
:class:`_expression.TableClause`.
|
|
|
|
E.g.::
|
|
|
|
table.insert().values(name='foo')
|
|
|
|
See :func:`_expression.insert` for argument and usage information.
|
|
|
|
"""
|
|
|
|
return util.preloaded.sql_dml.Insert(self)
|
|
|
|
@util.preload_module("sqlalchemy.sql.dml")
|
|
def update(self) -> Update:
|
|
"""Generate an :func:`_expression.update` construct against this
|
|
:class:`_expression.TableClause`.
|
|
|
|
E.g.::
|
|
|
|
table.update().where(table.c.id==7).values(name='foo')
|
|
|
|
See :func:`_expression.update` for argument and usage information.
|
|
|
|
"""
|
|
return util.preloaded.sql_dml.Update(
|
|
self,
|
|
)
|
|
|
|
@util.preload_module("sqlalchemy.sql.dml")
|
|
def delete(self) -> Delete:
|
|
"""Generate a :func:`_expression.delete` construct against this
|
|
:class:`_expression.TableClause`.
|
|
|
|
E.g.::
|
|
|
|
table.delete().where(table.c.id==7)
|
|
|
|
See :func:`_expression.delete` for argument and usage information.
|
|
|
|
"""
|
|
return util.preloaded.sql_dml.Delete(self)
|
|
|
|
@util.ro_non_memoized_property
|
|
def _from_objects(self) -> List[FromClause]:
|
|
return [self]
|
|
|
|
|
|
ForUpdateParameter = Union["ForUpdateArg", None, bool, Dict[str, Any]]
|
|
|
|
|
|
class ForUpdateArg(ClauseElement):
|
|
_traverse_internals: _TraverseInternalsType = [
|
|
("of", InternalTraversal.dp_clauseelement_list),
|
|
("nowait", InternalTraversal.dp_boolean),
|
|
("read", InternalTraversal.dp_boolean),
|
|
("skip_locked", InternalTraversal.dp_boolean),
|
|
]
|
|
|
|
of: Optional[Sequence[ClauseElement]]
|
|
nowait: bool
|
|
read: bool
|
|
skip_locked: bool
|
|
|
|
@classmethod
|
|
def _from_argument(
|
|
cls, with_for_update: ForUpdateParameter
|
|
) -> Optional[ForUpdateArg]:
|
|
if isinstance(with_for_update, ForUpdateArg):
|
|
return with_for_update
|
|
elif with_for_update in (None, False):
|
|
return None
|
|
elif with_for_update is True:
|
|
return ForUpdateArg()
|
|
else:
|
|
return ForUpdateArg(**cast("Dict[str, Any]", with_for_update))
|
|
|
|
def __eq__(self, other: Any) -> bool:
|
|
return (
|
|
isinstance(other, ForUpdateArg)
|
|
and other.nowait == self.nowait
|
|
and other.read == self.read
|
|
and other.skip_locked == self.skip_locked
|
|
and other.key_share == self.key_share
|
|
and other.of is self.of
|
|
)
|
|
|
|
def __ne__(self, other: Any) -> bool:
|
|
return not self.__eq__(other)
|
|
|
|
def __hash__(self) -> int:
|
|
return id(self)
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
nowait: bool = False,
|
|
read: bool = False,
|
|
of: Optional[_ForUpdateOfArgument] = None,
|
|
skip_locked: bool = False,
|
|
key_share: bool = False,
|
|
):
|
|
"""Represents arguments specified to
|
|
:meth:`_expression.Select.for_update`.
|
|
|
|
"""
|
|
|
|
self.nowait = nowait
|
|
self.read = read
|
|
self.skip_locked = skip_locked
|
|
self.key_share = key_share
|
|
if of is not None:
|
|
self.of = [
|
|
coercions.expect(roles.ColumnsClauseRole, elem)
|
|
for elem in util.to_list(of)
|
|
]
|
|
else:
|
|
self.of = None
|
|
|
|
|
|
class Values(roles.InElementRole, Generative, LateralFromClause):
|
|
"""Represent a ``VALUES`` construct that can be used as a FROM element
|
|
in a statement.
|
|
|
|
The :class:`_expression.Values` object is created from the
|
|
:func:`_expression.values` function.
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
"""
|
|
|
|
__visit_name__ = "values"
|
|
|
|
_data: Tuple[Sequence[Tuple[Any, ...]], ...] = ()
|
|
|
|
_unnamed: bool
|
|
_traverse_internals: _TraverseInternalsType = [
|
|
("_column_args", InternalTraversal.dp_clauseelement_list),
|
|
("_data", InternalTraversal.dp_dml_multi_values),
|
|
("name", InternalTraversal.dp_string),
|
|
("literal_binds", InternalTraversal.dp_boolean),
|
|
]
|
|
|
|
def __init__(
|
|
self,
|
|
*columns: ColumnClause[Any],
|
|
name: Optional[str] = None,
|
|
literal_binds: bool = False,
|
|
):
|
|
super().__init__()
|
|
self._column_args = columns
|
|
|
|
if name is None:
|
|
self._unnamed = True
|
|
self.name = _anonymous_label.safe_construct(id(self), "anon")
|
|
else:
|
|
self._unnamed = False
|
|
self.name = name
|
|
self.literal_binds = literal_binds
|
|
self.named_with_column = not self._unnamed
|
|
|
|
@property
|
|
def _column_types(self) -> List[TypeEngine[Any]]:
|
|
return [col.type for col in self._column_args]
|
|
|
|
@_generative
|
|
def alias(self, name: Optional[str] = None, flat: bool = False) -> Self:
|
|
"""Return a new :class:`_expression.Values`
|
|
construct that is a copy of this
|
|
one with the given name.
|
|
|
|
This method is a VALUES-specific specialization of the
|
|
:meth:`_expression.FromClause.alias` method.
|
|
|
|
.. seealso::
|
|
|
|
:ref:`tutorial_using_aliases`
|
|
|
|
:func:`_expression.alias`
|
|
|
|
"""
|
|
non_none_name: str
|
|
|
|
if name is None:
|
|
non_none_name = _anonymous_label.safe_construct(id(self), "anon")
|
|
else:
|
|
non_none_name = name
|
|
|
|
self.name = non_none_name
|
|
self.named_with_column = True
|
|
self._unnamed = False
|
|
return self
|
|
|
|
@_generative
|
|
def lateral(self, name: Optional[str] = None) -> LateralFromClause:
|
|
"""Return a new :class:`_expression.Values` with the lateral flag set,
|
|
so that
|
|
it renders as LATERAL.
|
|
|
|
.. seealso::
|
|
|
|
:func:`_expression.lateral`
|
|
|
|
"""
|
|
non_none_name: str
|
|
|
|
if name is None:
|
|
non_none_name = self.name
|
|
else:
|
|
non_none_name = name
|
|
|
|
self._is_lateral = True
|
|
self.name = non_none_name
|
|
self._unnamed = False
|
|
return self
|
|
|
|
@_generative
|
|
def data(self, values: Sequence[Tuple[Any, ...]]) -> Self:
|
|
"""Return a new :class:`_expression.Values` construct,
|
|
adding the given data to the data list.
|
|
|
|
E.g.::
|
|
|
|
my_values = my_values.data([(1, 'value 1'), (2, 'value2')])
|
|
|
|
:param values: a sequence (i.e. list) of tuples that map to the
|
|
column expressions given in the :class:`_expression.Values`
|
|
constructor.
|
|
|
|
"""
|
|
|
|
self._data += (values,)
|
|
return self
|
|
|
|
def scalar_values(self) -> ScalarValues:
|
|
"""Returns a scalar ``VALUES`` construct that can be used as a
|
|
COLUMN element in a statement.
|
|
|
|
.. versionadded:: 2.0.0b4
|
|
|
|
"""
|
|
return ScalarValues(self._column_args, self._data, self.literal_binds)
|
|
|
|
def _populate_column_collection(self) -> None:
|
|
for c in self._column_args:
|
|
if c.table is not None and c.table is not self:
|
|
_, c = c._make_proxy(self)
|
|
else:
|
|
# if the column was used in other contexts, ensure
|
|
# no memoizations of other FROM clauses.
|
|
# see test_values.py -> test_auto_proxy_select_direct_col
|
|
c._reset_memoizations()
|
|
self._columns.add(c)
|
|
c.table = self
|
|
|
|
@util.ro_non_memoized_property
|
|
def _from_objects(self) -> List[FromClause]:
|
|
return [self]
|
|
|
|
|
|
class ScalarValues(roles.InElementRole, GroupedElement, ColumnElement[Any]):
|
|
"""Represent a scalar ``VALUES`` construct that can be used as a
|
|
COLUMN element in a statement.
|
|
|
|
The :class:`_expression.ScalarValues` object is created from the
|
|
:meth:`_expression.Values.scalar_values` method. It's also
|
|
automatically generated when a :class:`_expression.Values` is used in
|
|
an ``IN`` or ``NOT IN`` condition.
|
|
|
|
.. versionadded:: 2.0.0b4
|
|
|
|
"""
|
|
|
|
__visit_name__ = "scalar_values"
|
|
|
|
_traverse_internals: _TraverseInternalsType = [
|
|
("_column_args", InternalTraversal.dp_clauseelement_list),
|
|
("_data", InternalTraversal.dp_dml_multi_values),
|
|
("literal_binds", InternalTraversal.dp_boolean),
|
|
]
|
|
|
|
def __init__(
|
|
self,
|
|
columns: Sequence[ColumnClause[Any]],
|
|
data: Tuple[Sequence[Tuple[Any, ...]], ...],
|
|
literal_binds: bool,
|
|
):
|
|
super().__init__()
|
|
self._column_args = columns
|
|
self._data = data
|
|
self.literal_binds = literal_binds
|
|
|
|
@property
|
|
def _column_types(self) -> List[TypeEngine[Any]]:
|
|
return [col.type for col in self._column_args]
|
|
|
|
def __clause_element__(self) -> ScalarValues:
|
|
return self
|
|
|
|
|
|
class SelectBase(
|
|
roles.SelectStatementRole,
|
|
roles.DMLSelectRole,
|
|
roles.CompoundElementRole,
|
|
roles.InElementRole,
|
|
HasCTE,
|
|
SupportsCloneAnnotations,
|
|
Selectable,
|
|
):
|
|
"""Base class for SELECT statements.
|
|
|
|
|
|
This includes :class:`_expression.Select`,
|
|
:class:`_expression.CompoundSelect` and
|
|
:class:`_expression.TextualSelect`.
|
|
|
|
|
|
"""
|
|
|
|
_is_select_base = True
|
|
is_select = True
|
|
|
|
_label_style: SelectLabelStyle = LABEL_STYLE_NONE
|
|
|
|
def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
|
|
self._reset_memoizations()
|
|
|
|
@util.ro_non_memoized_property
|
|
def selected_columns(
|
|
self,
|
|
) -> ColumnCollection[str, ColumnElement[Any]]:
|
|
"""A :class:`_expression.ColumnCollection`
|
|
representing the columns that
|
|
this SELECT statement or similar construct returns in its result set.
|
|
|
|
This collection differs from the :attr:`_expression.FromClause.columns`
|
|
collection of a :class:`_expression.FromClause` in that the columns
|
|
within this collection cannot be directly nested inside another SELECT
|
|
statement; a subquery must be applied first which provides for the
|
|
necessary parenthesization required by SQL.
|
|
|
|
.. note::
|
|
|
|
The :attr:`_sql.SelectBase.selected_columns` collection does not
|
|
include expressions established in the columns clause using the
|
|
:func:`_sql.text` construct; these are silently omitted from the
|
|
collection. To use plain textual column expressions inside of a
|
|
:class:`_sql.Select` construct, use the :func:`_sql.literal_column`
|
|
construct.
|
|
|
|
.. seealso::
|
|
|
|
:attr:`_sql.Select.selected_columns`
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def _generate_fromclause_column_proxies(
|
|
self,
|
|
subquery: FromClause,
|
|
*,
|
|
proxy_compound_columns: Optional[
|
|
Iterable[Sequence[ColumnElement[Any]]]
|
|
] = None,
|
|
) -> None:
|
|
raise NotImplementedError()
|
|
|
|
@util.ro_non_memoized_property
|
|
def _all_selected_columns(self) -> _SelectIterable:
|
|
"""A sequence of expressions that correspond to what is rendered
|
|
in the columns clause, including :class:`_sql.TextClause`
|
|
constructs.
|
|
|
|
.. versionadded:: 1.4.12
|
|
|
|
.. seealso::
|
|
|
|
:attr:`_sql.SelectBase.exported_columns`
|
|
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
@property
|
|
def exported_columns(
|
|
self,
|
|
) -> ReadOnlyColumnCollection[str, ColumnElement[Any]]:
|
|
"""A :class:`_expression.ColumnCollection`
|
|
that represents the "exported"
|
|
columns of this :class:`_expression.Selectable`, not including
|
|
:class:`_sql.TextClause` constructs.
|
|
|
|
The "exported" columns for a :class:`_expression.SelectBase`
|
|
object are synonymous
|
|
with the :attr:`_expression.SelectBase.selected_columns` collection.
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
.. seealso::
|
|
|
|
:attr:`_expression.Select.exported_columns`
|
|
|
|
:attr:`_expression.Selectable.exported_columns`
|
|
|
|
:attr:`_expression.FromClause.exported_columns`
|
|
|
|
|
|
"""
|
|
return self.selected_columns.as_readonly()
|
|
|
|
@property
|
|
@util.deprecated(
|
|
"1.4",
|
|
"The :attr:`_expression.SelectBase.c` and "
|
|
":attr:`_expression.SelectBase.columns` attributes "
|
|
"are deprecated and will be removed in a future release; these "
|
|
"attributes implicitly create a subquery that should be explicit. "
|
|
"Please call :meth:`_expression.SelectBase.subquery` "
|
|
"first in order to create "
|
|
"a subquery, which then contains this attribute. To access the "
|
|
"columns that this SELECT object SELECTs "
|
|
"from, use the :attr:`_expression.SelectBase.selected_columns` "
|
|
"attribute.",
|
|
)
|
|
def c(self) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
|
|
return self._implicit_subquery.columns
|
|
|
|
@property
|
|
def columns(
|
|
self,
|
|
) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
|
|
return self.c
|
|
|
|
def get_label_style(self) -> SelectLabelStyle:
|
|
"""
|
|
Retrieve the current label style.
|
|
|
|
Implemented by subclasses.
|
|
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
def set_label_style(self, style: SelectLabelStyle) -> Self:
|
|
"""Return a new selectable with the specified label style.
|
|
|
|
Implemented by subclasses.
|
|
|
|
"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
@util.deprecated(
|
|
"1.4",
|
|
"The :meth:`_expression.SelectBase.select` method is deprecated "
|
|
"and will be removed in a future release; this method implicitly "
|
|
"creates a subquery that should be explicit. "
|
|
"Please call :meth:`_expression.SelectBase.subquery` "
|
|
"first in order to create "
|
|
"a subquery, which then can be selected.",
|
|
)
|
|
def select(self, *arg: Any, **kw: Any) -> Select[Any]:
|
|
return self._implicit_subquery.select(*arg, **kw)
|
|
|
|
@HasMemoized.memoized_attribute
|
|
def _implicit_subquery(self) -> Subquery:
|
|
return self.subquery()
|
|
|
|
def _scalar_type(self) -> TypeEngine[Any]:
|
|
raise NotImplementedError()
|
|
|
|
@util.deprecated(
|
|
"1.4",
|
|
"The :meth:`_expression.SelectBase.as_scalar` "
|
|
"method is deprecated and will be "
|
|
"removed in a future release. Please refer to "
|
|
":meth:`_expression.SelectBase.scalar_subquery`.",
|
|
)
|
|
def as_scalar(self) -> ScalarSelect[Any]:
|
|
return self.scalar_subquery()
|
|
|
|
def exists(self) -> Exists:
|
|
"""Return an :class:`_sql.Exists` representation of this selectable,
|
|
which can be used as a column expression.
|
|
|
|
The returned object is an instance of :class:`_sql.Exists`.
|
|
|
|
.. seealso::
|
|
|
|
:func:`_sql.exists`
|
|
|
|
:ref:`tutorial_exists` - in the :term:`2.0 style` tutorial.
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
"""
|
|
return Exists(self)
|
|
|
|
def scalar_subquery(self) -> ScalarSelect[Any]:
|
|
"""Return a 'scalar' representation of this selectable, which can be
|
|
used as a column expression.
|
|
|
|
The returned object is an instance of :class:`_sql.ScalarSelect`.
|
|
|
|
Typically, a select statement which has only one column in its columns
|
|
clause is eligible to be used as a scalar expression. The scalar
|
|
subquery can then be used in the WHERE clause or columns clause of
|
|
an enclosing SELECT.
|
|
|
|
Note that the scalar subquery differentiates from the FROM-level
|
|
subquery that can be produced using the
|
|
:meth:`_expression.SelectBase.subquery`
|
|
method.
|
|
|
|
.. versionchanged: 1.4 - the ``.as_scalar()`` method was renamed to
|
|
:meth:`_expression.SelectBase.scalar_subquery`.
|
|
|
|
.. seealso::
|
|
|
|
:ref:`tutorial_scalar_subquery` - in the 2.0 tutorial
|
|
|
|
"""
|
|
if self._label_style is not LABEL_STYLE_NONE:
|
|
self = self.set_label_style(LABEL_STYLE_NONE)
|
|
|
|
return ScalarSelect(self)
|
|
|
|
def label(self, name: Optional[str]) -> Label[Any]:
|
|
"""Return a 'scalar' representation of this selectable, embedded as a
|
|
subquery with a label.
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_expression.SelectBase.scalar_subquery`.
|
|
|
|
"""
|
|
return self.scalar_subquery().label(name)
|
|
|
|
def lateral(self, name: Optional[str] = None) -> LateralFromClause:
|
|
"""Return a LATERAL alias of this :class:`_expression.Selectable`.
|
|
|
|
The return value is the :class:`_expression.Lateral` construct also
|
|
provided by the top-level :func:`_expression.lateral` function.
|
|
|
|
.. seealso::
|
|
|
|
:ref:`tutorial_lateral_correlation` - overview of usage.
|
|
|
|
"""
|
|
return Lateral._factory(self, name)
|
|
|
|
def subquery(self, name: Optional[str] = None) -> Subquery:
|
|
"""Return a subquery of this :class:`_expression.SelectBase`.
|
|
|
|
A subquery is from a SQL perspective a parenthesized, named
|
|
construct that can be placed in the FROM clause of another
|
|
SELECT statement.
|
|
|
|
Given a SELECT statement such as::
|
|
|
|
stmt = select(table.c.id, table.c.name)
|
|
|
|
The above statement might look like::
|
|
|
|
SELECT table.id, table.name FROM table
|
|
|
|
The subquery form by itself renders the same way, however when
|
|
embedded into the FROM clause of another SELECT statement, it becomes
|
|
a named sub-element::
|
|
|
|
subq = stmt.subquery()
|
|
new_stmt = select(subq)
|
|
|
|
The above renders as::
|
|
|
|
SELECT anon_1.id, anon_1.name
|
|
FROM (SELECT table.id, table.name FROM table) AS anon_1
|
|
|
|
Historically, :meth:`_expression.SelectBase.subquery`
|
|
is equivalent to calling
|
|
the :meth:`_expression.FromClause.alias`
|
|
method on a FROM object; however,
|
|
as a :class:`_expression.SelectBase`
|
|
object is not directly FROM object,
|
|
the :meth:`_expression.SelectBase.subquery`
|
|
method provides clearer semantics.
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
"""
|
|
|
|
return Subquery._construct(
|
|
self._ensure_disambiguated_names(), name=name
|
|
)
|
|
|
|
def _ensure_disambiguated_names(self) -> Self:
|
|
"""Ensure that the names generated by this selectbase will be
|
|
disambiguated in some way, if possible.
|
|
|
|
"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
def alias(
|
|
self, name: Optional[str] = None, flat: bool = False
|
|
) -> Subquery:
|
|
"""Return a named subquery against this
|
|
:class:`_expression.SelectBase`.
|
|
|
|
For a :class:`_expression.SelectBase` (as opposed to a
|
|
:class:`_expression.FromClause`),
|
|
this returns a :class:`.Subquery` object which behaves mostly the
|
|
same as the :class:`_expression.Alias` object that is used with a
|
|
:class:`_expression.FromClause`.
|
|
|
|
.. versionchanged:: 1.4 The :meth:`_expression.SelectBase.alias`
|
|
method is now
|
|
a synonym for the :meth:`_expression.SelectBase.subquery` method.
|
|
|
|
"""
|
|
return self.subquery(name=name)
|
|
|
|
|
|
_SB = TypeVar("_SB", bound=SelectBase)
|
|
|
|
|
|
class SelectStatementGrouping(GroupedElement, SelectBase, Generic[_SB]):
|
|
"""Represent a grouping of a :class:`_expression.SelectBase`.
|
|
|
|
This differs from :class:`.Subquery` in that we are still
|
|
an "inner" SELECT statement, this is strictly for grouping inside of
|
|
compound selects.
|
|
|
|
"""
|
|
|
|
__visit_name__ = "select_statement_grouping"
|
|
_traverse_internals: _TraverseInternalsType = [
|
|
("element", InternalTraversal.dp_clauseelement)
|
|
]
|
|
|
|
_is_select_container = True
|
|
|
|
element: _SB
|
|
|
|
def __init__(self, element: _SB) -> None:
|
|
self.element = cast(
|
|
_SB, coercions.expect(roles.SelectStatementRole, element)
|
|
)
|
|
|
|
def _ensure_disambiguated_names(self) -> SelectStatementGrouping[_SB]:
|
|
new_element = self.element._ensure_disambiguated_names()
|
|
if new_element is not self.element:
|
|
return SelectStatementGrouping(new_element)
|
|
else:
|
|
return self
|
|
|
|
def get_label_style(self) -> SelectLabelStyle:
|
|
return self.element.get_label_style()
|
|
|
|
def set_label_style(
|
|
self, label_style: SelectLabelStyle
|
|
) -> SelectStatementGrouping[_SB]:
|
|
return SelectStatementGrouping(
|
|
self.element.set_label_style(label_style)
|
|
)
|
|
|
|
@property
|
|
def select_statement(self) -> _SB:
|
|
return self.element
|
|
|
|
def self_group(self, against: Optional[OperatorType] = None) -> Self:
|
|
...
|
|
return self
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
def _ungroup(self) -> _SB: ...
|
|
|
|
# def _generate_columns_plus_names(
|
|
# self, anon_for_dupe_key: bool
|
|
# ) -> List[Tuple[str, str, str, ColumnElement[Any], bool]]:
|
|
# return self.element._generate_columns_plus_names(anon_for_dupe_key)
|
|
|
|
def _generate_fromclause_column_proxies(
|
|
self,
|
|
subquery: FromClause,
|
|
*,
|
|
proxy_compound_columns: Optional[
|
|
Iterable[Sequence[ColumnElement[Any]]]
|
|
] = None,
|
|
) -> None:
|
|
self.element._generate_fromclause_column_proxies(
|
|
subquery, proxy_compound_columns=proxy_compound_columns
|
|
)
|
|
|
|
@util.ro_non_memoized_property
|
|
def _all_selected_columns(self) -> _SelectIterable:
|
|
return self.element._all_selected_columns
|
|
|
|
@util.ro_non_memoized_property
|
|
def selected_columns(self) -> ColumnCollection[str, ColumnElement[Any]]:
|
|
"""A :class:`_expression.ColumnCollection`
|
|
representing the columns that
|
|
the embedded SELECT statement returns in its result set, not including
|
|
:class:`_sql.TextClause` constructs.
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
.. seealso::
|
|
|
|
:attr:`_sql.Select.selected_columns`
|
|
|
|
"""
|
|
return self.element.selected_columns
|
|
|
|
@util.ro_non_memoized_property
|
|
def _from_objects(self) -> List[FromClause]:
|
|
return self.element._from_objects
|
|
|
|
|
|
class GenerativeSelect(SelectBase, Generative):
|
|
"""Base class for SELECT statements where additional elements can be
|
|
added.
|
|
|
|
This serves as the base for :class:`_expression.Select` and
|
|
:class:`_expression.CompoundSelect`
|
|
where elements such as ORDER BY, GROUP BY can be added and column
|
|
rendering can be controlled. Compare to
|
|
:class:`_expression.TextualSelect`, which,
|
|
while it subclasses :class:`_expression.SelectBase`
|
|
and is also a SELECT construct,
|
|
represents a fixed textual string which cannot be altered at this level,
|
|
only wrapped as a subquery.
|
|
|
|
"""
|
|
|
|
_order_by_clauses: Tuple[ColumnElement[Any], ...] = ()
|
|
_group_by_clauses: Tuple[ColumnElement[Any], ...] = ()
|
|
_limit_clause: Optional[ColumnElement[Any]] = None
|
|
_offset_clause: Optional[ColumnElement[Any]] = None
|
|
_fetch_clause: Optional[ColumnElement[Any]] = None
|
|
_fetch_clause_options: Optional[Dict[str, bool]] = None
|
|
_for_update_arg: Optional[ForUpdateArg] = None
|
|
|
|
def __init__(self, _label_style: SelectLabelStyle = LABEL_STYLE_DEFAULT):
|
|
self._label_style = _label_style
|
|
|
|
@_generative
|
|
def with_for_update(
|
|
self,
|
|
*,
|
|
nowait: bool = False,
|
|
read: bool = False,
|
|
of: Optional[_ForUpdateOfArgument] = None,
|
|
skip_locked: bool = False,
|
|
key_share: bool = False,
|
|
) -> Self:
|
|
"""Specify a ``FOR UPDATE`` clause for this
|
|
:class:`_expression.GenerativeSelect`.
|
|
|
|
E.g.::
|
|
|
|
stmt = select(table).with_for_update(nowait=True)
|
|
|
|
On a database like PostgreSQL or Oracle, the above would render a
|
|
statement like::
|
|
|
|
SELECT table.a, table.b FROM table FOR UPDATE NOWAIT
|
|
|
|
on other backends, the ``nowait`` option is ignored and instead
|
|
would produce::
|
|
|
|
SELECT table.a, table.b FROM table FOR UPDATE
|
|
|
|
When called with no arguments, the statement will render with
|
|
the suffix ``FOR UPDATE``. Additional arguments can then be
|
|
provided which allow for common database-specific
|
|
variants.
|
|
|
|
:param nowait: boolean; will render ``FOR UPDATE NOWAIT`` on Oracle
|
|
and PostgreSQL dialects.
|
|
|
|
:param read: boolean; will render ``LOCK IN SHARE MODE`` on MySQL,
|
|
``FOR SHARE`` on PostgreSQL. On PostgreSQL, when combined with
|
|
``nowait``, will render ``FOR SHARE NOWAIT``.
|
|
|
|
:param of: SQL expression or list of SQL expression elements,
|
|
(typically :class:`_schema.Column` objects or a compatible expression,
|
|
for some backends may also be a table expression) which will render
|
|
into a ``FOR UPDATE OF`` clause; supported by PostgreSQL, Oracle, some
|
|
MySQL versions and possibly others. May render as a table or as a
|
|
column depending on backend.
|
|
|
|
:param skip_locked: boolean, will render ``FOR UPDATE SKIP LOCKED``
|
|
on Oracle and PostgreSQL dialects or ``FOR SHARE SKIP LOCKED`` if
|
|
``read=True`` is also specified.
|
|
|
|
: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.
|
|
|
|
"""
|
|
self._for_update_arg = ForUpdateArg(
|
|
nowait=nowait,
|
|
read=read,
|
|
of=of,
|
|
skip_locked=skip_locked,
|
|
key_share=key_share,
|
|
)
|
|
return self
|
|
|
|
def get_label_style(self) -> SelectLabelStyle:
|
|
"""
|
|
Retrieve the current label style.
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
"""
|
|
return self._label_style
|
|
|
|
def set_label_style(self, style: SelectLabelStyle) -> Self:
|
|
"""Return a new selectable with the specified label style.
|
|
|
|
There are three "label styles" available,
|
|
:attr:`_sql.SelectLabelStyle.LABEL_STYLE_DISAMBIGUATE_ONLY`,
|
|
:attr:`_sql.SelectLabelStyle.LABEL_STYLE_TABLENAME_PLUS_COL`, and
|
|
:attr:`_sql.SelectLabelStyle.LABEL_STYLE_NONE`. The default style is
|
|
:attr:`_sql.SelectLabelStyle.LABEL_STYLE_TABLENAME_PLUS_COL`.
|
|
|
|
In modern SQLAlchemy, there is not generally a need to change the
|
|
labeling style, as per-expression labels are more effectively used by
|
|
making use of the :meth:`_sql.ColumnElement.label` method. In past
|
|
versions, :data:`_sql.LABEL_STYLE_TABLENAME_PLUS_COL` was used to
|
|
disambiguate same-named columns from different tables, aliases, or
|
|
subqueries; the newer :data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY` now
|
|
applies labels only to names that conflict with an existing name so
|
|
that the impact of this labeling is minimal.
|
|
|
|
The rationale for disambiguation is mostly so that all column
|
|
expressions are available from a given :attr:`_sql.FromClause.c`
|
|
collection when a subquery is created.
|
|
|
|
.. versionadded:: 1.4 - the
|
|
:meth:`_sql.GenerativeSelect.set_label_style` method replaces the
|
|
previous combination of ``.apply_labels()``, ``.with_labels()`` and
|
|
``use_labels=True`` methods and/or parameters.
|
|
|
|
.. seealso::
|
|
|
|
:data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY`
|
|
|
|
:data:`_sql.LABEL_STYLE_TABLENAME_PLUS_COL`
|
|
|
|
:data:`_sql.LABEL_STYLE_NONE`
|
|
|
|
:data:`_sql.LABEL_STYLE_DEFAULT`
|
|
|
|
"""
|
|
if self._label_style is not style:
|
|
self = self._generate()
|
|
self._label_style = style
|
|
return self
|
|
|
|
@property
|
|
def _group_by_clause(self) -> ClauseList:
|
|
"""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:
|
|
"""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: _LimitOffsetType,
|
|
name: Optional[str] = None,
|
|
type_: Optional[_TypeEngineArgument[int]] = None,
|
|
) -> ColumnElement[Any]:
|
|
"""Convert the given value to an "offset or limit" clause.
|
|
|
|
This handles incoming integers and converts to an expression; if
|
|
an expression is already given, it is passed through.
|
|
|
|
"""
|
|
return coercions.expect(
|
|
roles.LimitOffsetRole, element, name=name, type_=type_
|
|
)
|
|
|
|
@overload
|
|
def _offset_or_limit_clause_asint(
|
|
self, clause: ColumnElement[Any], attrname: str
|
|
) -> NoReturn: ...
|
|
|
|
@overload
|
|
def _offset_or_limit_clause_asint(
|
|
self, clause: Optional[_OffsetLimitParam], attrname: str
|
|
) -> Optional[int]: ...
|
|
|
|
def _offset_or_limit_clause_asint(
|
|
self, clause: Optional[ColumnElement[Any]], attrname: str
|
|
) -> Union[NoReturn, Optional[int]]:
|
|
"""Convert the "offset or limit" clause of a select construct to an
|
|
integer.
|
|
|
|
This is only possible if the value is stored as a simple bound
|
|
parameter. Otherwise, a compilation error is raised.
|
|
|
|
"""
|
|
if clause is None:
|
|
return None
|
|
try:
|
|
value = clause._limit_offset_value
|
|
except AttributeError as err:
|
|
raise exc.CompileError(
|
|
"This SELECT structure does not use a simple "
|
|
"integer value for %s" % attrname
|
|
) from err
|
|
else:
|
|
return util.asint(value)
|
|
|
|
@property
|
|
def _limit(self) -> Optional[int]:
|
|
"""Get an integer value for the limit. This should only be used
|
|
by code that cannot support a limit as a BindParameter or
|
|
other custom clause as it will throw an exception if the limit
|
|
isn't currently set to an integer.
|
|
|
|
"""
|
|
return self._offset_or_limit_clause_asint(self._limit_clause, "limit")
|
|
|
|
def _simple_int_clause(self, clause: ClauseElement) -> bool:
|
|
"""True if the clause is a simple integer, False
|
|
if it is not present or is a SQL expression.
|
|
"""
|
|
return isinstance(clause, _OffsetLimitParam)
|
|
|
|
@property
|
|
def _offset(self) -> Optional[int]:
|
|
"""Get an integer value for the offset. This should only be used
|
|
by code that cannot support an offset as a BindParameter or
|
|
other custom clause as it will throw an exception if the
|
|
offset isn't currently set to an integer.
|
|
|
|
"""
|
|
return self._offset_or_limit_clause_asint(
|
|
self._offset_clause, "offset"
|
|
)
|
|
|
|
@property
|
|
def _has_row_limiting_clause(self) -> bool:
|
|
return (
|
|
self._limit_clause is not None
|
|
or self._offset_clause is not None
|
|
or self._fetch_clause is not None
|
|
)
|
|
|
|
@_generative
|
|
def limit(self, limit: _LimitOffsetType) -> Self:
|
|
"""Return a new selectable with the given LIMIT criterion
|
|
applied.
|
|
|
|
This is a numerical value which usually renders as a ``LIMIT``
|
|
expression in the resulting select. Backends that don't
|
|
support ``LIMIT`` will attempt to provide similar
|
|
functionality.
|
|
|
|
.. note::
|
|
|
|
The :meth:`_sql.GenerativeSelect.limit` method will replace
|
|
any clause applied with :meth:`_sql.GenerativeSelect.fetch`.
|
|
|
|
:param limit: an integer LIMIT parameter, or a SQL expression
|
|
that provides an integer result. Pass ``None`` to reset it.
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_sql.GenerativeSelect.fetch`
|
|
|
|
:meth:`_sql.GenerativeSelect.offset`
|
|
|
|
"""
|
|
|
|
self._fetch_clause = self._fetch_clause_options = None
|
|
self._limit_clause = self._offset_or_limit_clause(limit)
|
|
return self
|
|
|
|
@_generative
|
|
def fetch(
|
|
self,
|
|
count: _LimitOffsetType,
|
|
with_ties: bool = False,
|
|
percent: bool = False,
|
|
) -> Self:
|
|
"""Return a new selectable with the given FETCH FIRST criterion
|
|
applied.
|
|
|
|
This is a numeric value which usually renders as
|
|
``FETCH {FIRST | NEXT} [ count ] {ROW | ROWS} {ONLY | WITH TIES}``
|
|
expression in the resulting select. This functionality is
|
|
is currently implemented for Oracle, PostgreSQL, MSSQL.
|
|
|
|
Use :meth:`_sql.GenerativeSelect.offset` to specify the offset.
|
|
|
|
.. note::
|
|
|
|
The :meth:`_sql.GenerativeSelect.fetch` method will replace
|
|
any clause applied with :meth:`_sql.GenerativeSelect.limit`.
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
:param count: an integer COUNT parameter, or a SQL expression
|
|
that provides an integer result. When ``percent=True`` this will
|
|
represent the percentage of rows to return, not the absolute value.
|
|
Pass ``None`` to reset it.
|
|
|
|
:param with_ties: When ``True``, the WITH TIES option is used
|
|
to return any additional rows that tie for the last place in the
|
|
result set according to the ``ORDER BY`` clause. The
|
|
``ORDER BY`` may be mandatory in this case. Defaults to ``False``
|
|
|
|
:param percent: When ``True``, ``count`` represents the percentage
|
|
of the total number of selected rows to return. Defaults to ``False``
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_sql.GenerativeSelect.limit`
|
|
|
|
:meth:`_sql.GenerativeSelect.offset`
|
|
|
|
"""
|
|
|
|
self._limit_clause = None
|
|
if count is None:
|
|
self._fetch_clause = self._fetch_clause_options = None
|
|
else:
|
|
self._fetch_clause = self._offset_or_limit_clause(count)
|
|
self._fetch_clause_options = {
|
|
"with_ties": with_ties,
|
|
"percent": percent,
|
|
}
|
|
return self
|
|
|
|
@_generative
|
|
def offset(self, offset: _LimitOffsetType) -> Self:
|
|
"""Return a new selectable with the given OFFSET criterion
|
|
applied.
|
|
|
|
|
|
This is a numeric value which usually renders as an ``OFFSET``
|
|
expression in the resulting select. Backends that don't
|
|
support ``OFFSET`` will attempt to provide similar
|
|
functionality.
|
|
|
|
:param offset: an integer OFFSET parameter, or a SQL expression
|
|
that provides an integer result. Pass ``None`` to reset it.
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_sql.GenerativeSelect.limit`
|
|
|
|
:meth:`_sql.GenerativeSelect.fetch`
|
|
|
|
"""
|
|
|
|
self._offset_clause = self._offset_or_limit_clause(offset)
|
|
return self
|
|
|
|
@_generative
|
|
@util.preload_module("sqlalchemy.sql.util")
|
|
def slice(
|
|
self,
|
|
start: int,
|
|
stop: int,
|
|
) -> Self:
|
|
"""Apply LIMIT / OFFSET to this statement based on a slice.
|
|
|
|
The start and stop indices behave like the argument to Python's
|
|
built-in :func:`range` function. This method provides an
|
|
alternative to using ``LIMIT``/``OFFSET`` to get a slice of the
|
|
query.
|
|
|
|
For example, ::
|
|
|
|
stmt = select(User).order_by(User).id.slice(1, 3)
|
|
|
|
renders as
|
|
|
|
.. sourcecode:: sql
|
|
|
|
SELECT users.id AS users_id,
|
|
users.name AS users_name
|
|
FROM users ORDER BY users.id
|
|
LIMIT ? OFFSET ?
|
|
(2, 1)
|
|
|
|
.. note::
|
|
|
|
The :meth:`_sql.GenerativeSelect.slice` method will replace
|
|
any clause applied with :meth:`_sql.GenerativeSelect.fetch`.
|
|
|
|
.. versionadded:: 1.4 Added the :meth:`_sql.GenerativeSelect.slice`
|
|
method generalized from the ORM.
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_sql.GenerativeSelect.limit`
|
|
|
|
:meth:`_sql.GenerativeSelect.offset`
|
|
|
|
:meth:`_sql.GenerativeSelect.fetch`
|
|
|
|
"""
|
|
sql_util = util.preloaded.sql_util
|
|
self._fetch_clause = self._fetch_clause_options = None
|
|
self._limit_clause, self._offset_clause = sql_util._make_slice(
|
|
self._limit_clause, self._offset_clause, start, stop
|
|
)
|
|
return self
|
|
|
|
@_generative
|
|
def order_by(
|
|
self,
|
|
__first: Union[
|
|
Literal[None, _NoArg.NO_ARG],
|
|
_ColumnExpressionOrStrLabelArgument[Any],
|
|
] = _NoArg.NO_ARG,
|
|
*clauses: _ColumnExpressionOrStrLabelArgument[Any],
|
|
) -> Self:
|
|
r"""Return a new selectable with the given list of ORDER BY
|
|
criteria applied.
|
|
|
|
e.g.::
|
|
|
|
stmt = select(table).order_by(table.c.id, table.c.name)
|
|
|
|
Calling this method multiple times is equivalent to calling it once
|
|
with all the clauses concatenated. All existing ORDER BY criteria may
|
|
be cancelled by passing ``None`` by itself. New ORDER BY criteria may
|
|
then be added by invoking :meth:`_orm.Query.order_by` again, e.g.::
|
|
|
|
# will erase all ORDER BY and ORDER BY new_col alone
|
|
stmt = stmt.order_by(None).order_by(new_col)
|
|
|
|
:param \*clauses: a series of :class:`_expression.ColumnElement`
|
|
constructs
|
|
which will be used to generate an ORDER BY clause.
|
|
|
|
.. seealso::
|
|
|
|
:ref:`tutorial_order_by` - in the :ref:`unified_tutorial`
|
|
|
|
:ref:`tutorial_order_by_label` - in the :ref:`unified_tutorial`
|
|
|
|
"""
|
|
|
|
if not clauses and __first is None:
|
|
self._order_by_clauses = ()
|
|
elif __first is not _NoArg.NO_ARG:
|
|
self._order_by_clauses += tuple(
|
|
coercions.expect(
|
|
roles.OrderByRole, clause, apply_propagate_attrs=self
|
|
)
|
|
for clause in (__first,) + clauses
|
|
)
|
|
return self
|
|
|
|
@_generative
|
|
def group_by(
|
|
self,
|
|
__first: Union[
|
|
Literal[None, _NoArg.NO_ARG],
|
|
_ColumnExpressionOrStrLabelArgument[Any],
|
|
] = _NoArg.NO_ARG,
|
|
*clauses: _ColumnExpressionOrStrLabelArgument[Any],
|
|
) -> Self:
|
|
r"""Return a new selectable with the given list of GROUP BY
|
|
criterion applied.
|
|
|
|
All existing GROUP BY settings can be suppressed by passing ``None``.
|
|
|
|
e.g.::
|
|
|
|
stmt = select(table.c.name, func.max(table.c.stat)).\
|
|
group_by(table.c.name)
|
|
|
|
:param \*clauses: a series of :class:`_expression.ColumnElement`
|
|
constructs
|
|
which will be used to generate an GROUP BY clause.
|
|
|
|
.. seealso::
|
|
|
|
:ref:`tutorial_group_by_w_aggregates` - in the
|
|
:ref:`unified_tutorial`
|
|
|
|
:ref:`tutorial_order_by_label` - in the :ref:`unified_tutorial`
|
|
|
|
"""
|
|
|
|
if not clauses and __first is None:
|
|
self._group_by_clauses = ()
|
|
elif __first is not _NoArg.NO_ARG:
|
|
self._group_by_clauses += tuple(
|
|
coercions.expect(
|
|
roles.GroupByRole, clause, apply_propagate_attrs=self
|
|
)
|
|
for clause in (__first,) + clauses
|
|
)
|
|
return self
|
|
|
|
|
|
@CompileState.plugin_for("default", "compound_select")
|
|
class CompoundSelectState(CompileState):
|
|
@util.memoized_property
|
|
def _label_resolve_dict(
|
|
self,
|
|
) -> Tuple[
|
|
Dict[str, ColumnElement[Any]],
|
|
Dict[str, ColumnElement[Any]],
|
|
Dict[str, ColumnElement[Any]],
|
|
]:
|
|
# TODO: this is hacky and slow
|
|
hacky_subquery = self.statement.subquery()
|
|
hacky_subquery.named_with_column = False
|
|
d = {c.key: c for c in hacky_subquery.c}
|
|
return d, d, d
|
|
|
|
|
|
class _CompoundSelectKeyword(Enum):
|
|
UNION = "UNION"
|
|
UNION_ALL = "UNION ALL"
|
|
EXCEPT = "EXCEPT"
|
|
EXCEPT_ALL = "EXCEPT ALL"
|
|
INTERSECT = "INTERSECT"
|
|
INTERSECT_ALL = "INTERSECT ALL"
|
|
|
|
|
|
class CompoundSelect(HasCompileState, GenerativeSelect, ExecutableReturnsRows):
|
|
"""Forms the basis of ``UNION``, ``UNION ALL``, and other
|
|
SELECT-based set operations.
|
|
|
|
|
|
.. seealso::
|
|
|
|
:func:`_expression.union`
|
|
|
|
:func:`_expression.union_all`
|
|
|
|
:func:`_expression.intersect`
|
|
|
|
:func:`_expression.intersect_all`
|
|
|
|
:func:`_expression.except`
|
|
|
|
:func:`_expression.except_all`
|
|
|
|
"""
|
|
|
|
__visit_name__ = "compound_select"
|
|
|
|
_traverse_internals: _TraverseInternalsType = [
|
|
("selects", InternalTraversal.dp_clauseelement_list),
|
|
("_limit_clause", InternalTraversal.dp_clauseelement),
|
|
("_offset_clause", InternalTraversal.dp_clauseelement),
|
|
("_fetch_clause", InternalTraversal.dp_clauseelement),
|
|
("_fetch_clause_options", InternalTraversal.dp_plain_dict),
|
|
("_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
|
|
|
|
selects: List[SelectBase]
|
|
|
|
_is_from_container = True
|
|
_auto_correlate = False
|
|
|
|
def __init__(
|
|
self,
|
|
keyword: _CompoundSelectKeyword,
|
|
*selects: _SelectStatementForCompoundArgument,
|
|
):
|
|
self.keyword = keyword
|
|
self.selects = [
|
|
coercions.expect(
|
|
roles.CompoundElementRole, s, apply_propagate_attrs=self
|
|
).self_group(against=self)
|
|
for s in selects
|
|
]
|
|
|
|
GenerativeSelect.__init__(self)
|
|
|
|
@classmethod
|
|
def _create_union(
|
|
cls, *selects: _SelectStatementForCompoundArgument
|
|
) -> CompoundSelect:
|
|
return CompoundSelect(_CompoundSelectKeyword.UNION, *selects)
|
|
|
|
@classmethod
|
|
def _create_union_all(
|
|
cls, *selects: _SelectStatementForCompoundArgument
|
|
) -> CompoundSelect:
|
|
return CompoundSelect(_CompoundSelectKeyword.UNION_ALL, *selects)
|
|
|
|
@classmethod
|
|
def _create_except(
|
|
cls, *selects: _SelectStatementForCompoundArgument
|
|
) -> CompoundSelect:
|
|
return CompoundSelect(_CompoundSelectKeyword.EXCEPT, *selects)
|
|
|
|
@classmethod
|
|
def _create_except_all(
|
|
cls, *selects: _SelectStatementForCompoundArgument
|
|
) -> CompoundSelect:
|
|
return CompoundSelect(_CompoundSelectKeyword.EXCEPT_ALL, *selects)
|
|
|
|
@classmethod
|
|
def _create_intersect(
|
|
cls, *selects: _SelectStatementForCompoundArgument
|
|
) -> CompoundSelect:
|
|
return CompoundSelect(_CompoundSelectKeyword.INTERSECT, *selects)
|
|
|
|
@classmethod
|
|
def _create_intersect_all(
|
|
cls, *selects: _SelectStatementForCompoundArgument
|
|
) -> CompoundSelect:
|
|
return CompoundSelect(_CompoundSelectKeyword.INTERSECT_ALL, *selects)
|
|
|
|
def _scalar_type(self) -> TypeEngine[Any]:
|
|
return self.selects[0]._scalar_type()
|
|
|
|
def self_group(
|
|
self, against: Optional[OperatorType] = None
|
|
) -> GroupedElement:
|
|
return SelectStatementGrouping(self)
|
|
|
|
def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
|
|
for s in self.selects:
|
|
if s.is_derived_from(fromclause):
|
|
return True
|
|
return False
|
|
|
|
def set_label_style(self, style: SelectLabelStyle) -> CompoundSelect:
|
|
if self._label_style is not style:
|
|
self = self._generate()
|
|
select_0 = self.selects[0].set_label_style(style)
|
|
self.selects = [select_0] + self.selects[1:]
|
|
|
|
return self
|
|
|
|
def _ensure_disambiguated_names(self) -> CompoundSelect:
|
|
new_select = self.selects[0]._ensure_disambiguated_names()
|
|
if new_select is not self.selects[0]:
|
|
self = self._generate()
|
|
self.selects = [new_select] + self.selects[1:]
|
|
|
|
return self
|
|
|
|
def _generate_fromclause_column_proxies(
|
|
self,
|
|
subquery: FromClause,
|
|
*,
|
|
proxy_compound_columns: Optional[
|
|
Iterable[Sequence[ColumnElement[Any]]]
|
|
] = None,
|
|
) -> None:
|
|
# this is a slightly hacky thing - the union exports a
|
|
# column that resembles just that of the *first* selectable.
|
|
# to get at a "composite" column, particularly foreign keys,
|
|
# you have to dig through the proxies collection which we
|
|
# generate below.
|
|
select_0 = self.selects[0]
|
|
|
|
if self._label_style is not LABEL_STYLE_DEFAULT:
|
|
select_0 = select_0.set_label_style(self._label_style)
|
|
|
|
# hand-construct the "_proxies" collection to include all
|
|
# derived columns place a 'weight' annotation corresponding
|
|
# to how low in the list of select()s the column occurs, so
|
|
# that the corresponding_column() operation can resolve
|
|
# conflicts
|
|
extra_col_iterator = zip(
|
|
*[
|
|
[
|
|
c._annotate(dd)
|
|
for c in stmt._all_selected_columns
|
|
if is_column_element(c)
|
|
]
|
|
for dd, stmt in [
|
|
({"weight": i + 1}, stmt)
|
|
for i, stmt in enumerate(self.selects)
|
|
]
|
|
]
|
|
)
|
|
|
|
# the incoming proxy_compound_columns can be present also if this is
|
|
# a compound embedded in a compound. it's probably more appropriate
|
|
# that we generate new weights local to this nested compound, though
|
|
# i haven't tried to think what it means for compound nested in
|
|
# compound
|
|
select_0._generate_fromclause_column_proxies(
|
|
subquery, proxy_compound_columns=extra_col_iterator
|
|
)
|
|
|
|
def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
|
|
super()._refresh_for_new_column(column)
|
|
for select in self.selects:
|
|
select._refresh_for_new_column(column)
|
|
|
|
@util.ro_non_memoized_property
|
|
def _all_selected_columns(self) -> _SelectIterable:
|
|
return self.selects[0]._all_selected_columns
|
|
|
|
@util.ro_non_memoized_property
|
|
def selected_columns(
|
|
self,
|
|
) -> ColumnCollection[str, ColumnElement[Any]]:
|
|
"""A :class:`_expression.ColumnCollection`
|
|
representing the columns that
|
|
this SELECT statement or similar construct returns in its result set,
|
|
not including :class:`_sql.TextClause` constructs.
|
|
|
|
For a :class:`_expression.CompoundSelect`, the
|
|
:attr:`_expression.CompoundSelect.selected_columns`
|
|
attribute returns the selected
|
|
columns of the first SELECT statement contained within the series of
|
|
statements within the set operation.
|
|
|
|
.. seealso::
|
|
|
|
:attr:`_sql.Select.selected_columns`
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
"""
|
|
return self.selects[0].selected_columns
|
|
|
|
|
|
# backwards compat
|
|
for elem in _CompoundSelectKeyword:
|
|
setattr(CompoundSelect, elem.name, elem)
|
|
|
|
|
|
@CompileState.plugin_for("default", "select")
|
|
class SelectState(util.MemoizedSlots, CompileState):
|
|
__slots__ = (
|
|
"from_clauses",
|
|
"froms",
|
|
"columns_plus_names",
|
|
"_label_resolve_dict",
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
default_select_compile_options: CacheableOptions
|
|
else:
|
|
|
|
class default_select_compile_options(CacheableOptions):
|
|
_cache_key_traversal = []
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
@classmethod
|
|
def get_plugin_class(
|
|
cls, statement: Executable
|
|
) -> Type[SelectState]: ...
|
|
|
|
def __init__(
|
|
self,
|
|
statement: Select[Any],
|
|
compiler: Optional[SQLCompiler],
|
|
**kw: Any,
|
|
):
|
|
self.statement = statement
|
|
self.from_clauses = statement._from_obj
|
|
|
|
for memoized_entities in statement._memoized_select_entities:
|
|
self._setup_joins(
|
|
memoized_entities._setup_joins, memoized_entities._raw_columns
|
|
)
|
|
|
|
if statement._setup_joins:
|
|
self._setup_joins(statement._setup_joins, statement._raw_columns)
|
|
|
|
self.froms = self._get_froms(statement)
|
|
|
|
self.columns_plus_names = statement._generate_columns_plus_names(True)
|
|
|
|
@classmethod
|
|
def _plugin_not_implemented(cls) -> NoReturn:
|
|
raise NotImplementedError(
|
|
"The default SELECT construct without plugins does not "
|
|
"implement this method."
|
|
)
|
|
|
|
@classmethod
|
|
def get_column_descriptions(
|
|
cls, statement: Select[Any]
|
|
) -> List[Dict[str, Any]]:
|
|
return [
|
|
{
|
|
"name": name,
|
|
"type": element.type,
|
|
"expr": element,
|
|
}
|
|
for _, name, _, element, _ in (
|
|
statement._generate_columns_plus_names(False)
|
|
)
|
|
]
|
|
|
|
@classmethod
|
|
def from_statement(
|
|
cls, statement: Select[Any], from_statement: roles.ReturnsRowsRole
|
|
) -> ExecutableReturnsRows:
|
|
cls._plugin_not_implemented()
|
|
|
|
@classmethod
|
|
def get_columns_clause_froms(
|
|
cls, statement: Select[Any]
|
|
) -> List[FromClause]:
|
|
return cls._normalize_froms(
|
|
itertools.chain.from_iterable(
|
|
element._from_objects for element in statement._raw_columns
|
|
)
|
|
)
|
|
|
|
@classmethod
|
|
def _column_naming_convention(
|
|
cls, label_style: SelectLabelStyle
|
|
) -> _LabelConventionCallable:
|
|
table_qualified = label_style is LABEL_STYLE_TABLENAME_PLUS_COL
|
|
dedupe = label_style is not LABEL_STYLE_NONE
|
|
|
|
pa = prefix_anon_map()
|
|
names = set()
|
|
|
|
def go(
|
|
c: Union[ColumnElement[Any], TextClause],
|
|
col_name: Optional[str] = None,
|
|
) -> Optional[str]:
|
|
if is_text_clause(c):
|
|
return None
|
|
elif TYPE_CHECKING:
|
|
assert is_column_element(c)
|
|
|
|
if not dedupe:
|
|
name = c._proxy_key
|
|
if name is None:
|
|
name = "_no_label"
|
|
return name
|
|
|
|
name = c._tq_key_label if table_qualified else c._proxy_key
|
|
|
|
if name is None:
|
|
name = "_no_label"
|
|
if name in names:
|
|
return c._anon_label(name) % pa
|
|
else:
|
|
names.add(name)
|
|
return name
|
|
|
|
elif name in names:
|
|
return (
|
|
c._anon_tq_key_label % pa
|
|
if table_qualified
|
|
else c._anon_key_label % pa
|
|
)
|
|
else:
|
|
names.add(name)
|
|
return name
|
|
|
|
return go
|
|
|
|
def _get_froms(self, statement: Select[Any]) -> List[FromClause]:
|
|
ambiguous_table_name_map: _AmbiguousTableNameMap
|
|
self._ambiguous_table_name_map = ambiguous_table_name_map = {}
|
|
|
|
return self._normalize_froms(
|
|
itertools.chain(
|
|
self.from_clauses,
|
|
itertools.chain.from_iterable(
|
|
[
|
|
element._from_objects
|
|
for element in statement._raw_columns
|
|
]
|
|
),
|
|
itertools.chain.from_iterable(
|
|
[
|
|
element._from_objects
|
|
for element in statement._where_criteria
|
|
]
|
|
),
|
|
),
|
|
check_statement=statement,
|
|
ambiguous_table_name_map=ambiguous_table_name_map,
|
|
)
|
|
|
|
@classmethod
|
|
def _normalize_froms(
|
|
cls,
|
|
iterable_of_froms: Iterable[FromClause],
|
|
check_statement: Optional[Select[Any]] = None,
|
|
ambiguous_table_name_map: Optional[_AmbiguousTableNameMap] = None,
|
|
) -> List[FromClause]:
|
|
"""given an iterable of things to select FROM, reduce them to what
|
|
would actually render in the FROM clause of a SELECT.
|
|
|
|
This does the job of checking for JOINs, tables, etc. that are in fact
|
|
overlapping due to cloning, adaption, present in overlapping joins,
|
|
etc.
|
|
|
|
"""
|
|
seen: Set[FromClause] = set()
|
|
froms: List[FromClause] = []
|
|
|
|
for item in iterable_of_froms:
|
|
if is_subquery(item) and item.element is check_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)
|
|
|
|
if 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 ambiguous_table_name_map is not None:
|
|
ambiguous_table_name_map.update(
|
|
(
|
|
fr.name,
|
|
_anonymous_label.safe_construct(
|
|
hash(fr.name), fr.name
|
|
),
|
|
)
|
|
for item in froms
|
|
for fr in item._from_objects
|
|
if is_table(fr)
|
|
and fr.schema
|
|
and fr.name not in ambiguous_table_name_map
|
|
)
|
|
|
|
return froms
|
|
|
|
def _get_display_froms(
|
|
self,
|
|
explicit_correlate_froms: Optional[Sequence[FromClause]] = None,
|
|
implicit_correlate_froms: Optional[Sequence[FromClause]] = None,
|
|
) -> List[FromClause]:
|
|
"""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
|
|
|
|
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 '%r"
|
|
"' returned no FROM clauses "
|
|
"due to auto-correlation; "
|
|
"specify correlate(<tables>) "
|
|
"to control correlation "
|
|
"manually." % self.statement
|
|
)
|
|
|
|
return froms
|
|
|
|
def _memoized_attr__label_resolve_dict(
|
|
self,
|
|
) -> Tuple[
|
|
Dict[str, ColumnElement[Any]],
|
|
Dict[str, ColumnElement[Any]],
|
|
Dict[str, ColumnElement[Any]],
|
|
]:
|
|
with_cols: Dict[str, ColumnElement[Any]] = {
|
|
c._tq_label or c.key: c
|
|
for c in self.statement._all_selected_columns
|
|
if c._allow_label_resolve
|
|
}
|
|
only_froms: Dict[str, ColumnElement[Any]] = {
|
|
c.key: c # type: ignore
|
|
for c in _select_iterables(self.froms)
|
|
if c._allow_label_resolve
|
|
}
|
|
only_cols: Dict[str, ColumnElement[Any]] = with_cols.copy()
|
|
for key, value in only_froms.items():
|
|
with_cols.setdefault(key, value)
|
|
|
|
return with_cols, only_froms, only_cols
|
|
|
|
@classmethod
|
|
def determine_last_joined_entity(
|
|
cls, stmt: Select[Any]
|
|
) -> Optional[_JoinTargetElement]:
|
|
if stmt._setup_joins:
|
|
return stmt._setup_joins[-1][0]
|
|
else:
|
|
return None
|
|
|
|
@classmethod
|
|
def all_selected_columns(cls, statement: Select[Any]) -> _SelectIterable:
|
|
return [c for c in _select_iterables(statement._raw_columns)]
|
|
|
|
def _setup_joins(
|
|
self,
|
|
args: Tuple[_SetupJoinsElement, ...],
|
|
raw_columns: List[_ColumnsClauseElement],
|
|
) -> None:
|
|
for right, onclause, left, flags in args:
|
|
if TYPE_CHECKING:
|
|
if onclause is not None:
|
|
assert isinstance(onclause, ColumnElement)
|
|
|
|
isouter = flags["isouter"]
|
|
full = flags["full"]
|
|
|
|
if left is None:
|
|
(
|
|
left,
|
|
replace_from_obj_index,
|
|
) = self._join_determine_implicit_left_side(
|
|
raw_columns, left, right, onclause
|
|
)
|
|
else:
|
|
(replace_from_obj_index) = self._join_place_explicit_left_side(
|
|
left
|
|
)
|
|
|
|
# these assertions can be made here, as if the right/onclause
|
|
# contained ORM elements, the select() statement would have been
|
|
# upgraded to an ORM select, and this method would not be called;
|
|
# orm.context.ORMSelectCompileState._join() would be
|
|
# used instead.
|
|
if TYPE_CHECKING:
|
|
assert isinstance(right, FromClause)
|
|
if onclause is not None:
|
|
assert isinstance(onclause, ColumnElement)
|
|
|
|
if replace_from_obj_index is not None:
|
|
# splice into an existing element in the
|
|
# self._from_obj list
|
|
left_clause = self.from_clauses[replace_from_obj_index]
|
|
|
|
self.from_clauses = (
|
|
self.from_clauses[:replace_from_obj_index]
|
|
+ (
|
|
Join(
|
|
left_clause,
|
|
right,
|
|
onclause,
|
|
isouter=isouter,
|
|
full=full,
|
|
),
|
|
)
|
|
+ self.from_clauses[replace_from_obj_index + 1 :]
|
|
)
|
|
else:
|
|
assert left is not None
|
|
self.from_clauses = self.from_clauses + (
|
|
Join(left, right, onclause, isouter=isouter, full=full),
|
|
)
|
|
|
|
@util.preload_module("sqlalchemy.sql.util")
|
|
def _join_determine_implicit_left_side(
|
|
self,
|
|
raw_columns: List[_ColumnsClauseElement],
|
|
left: Optional[FromClause],
|
|
right: _JoinTargetElement,
|
|
onclause: Optional[ColumnElement[Any]],
|
|
) -> Tuple[Optional[FromClause], Optional[int]]:
|
|
"""When join conditions don't express the left side explicitly,
|
|
determine if an existing FROM or entity in this query
|
|
can serve as the left hand side.
|
|
|
|
"""
|
|
|
|
sql_util = util.preloaded.sql_util
|
|
|
|
replace_from_obj_index: Optional[int] = None
|
|
|
|
from_clauses = self.from_clauses
|
|
|
|
if from_clauses:
|
|
indexes: List[int] = sql_util.find_left_clause_to_join_from(
|
|
from_clauses, right, onclause
|
|
)
|
|
|
|
if len(indexes) == 1:
|
|
replace_from_obj_index = indexes[0]
|
|
left = from_clauses[replace_from_obj_index]
|
|
else:
|
|
potential = {}
|
|
statement = self.statement
|
|
|
|
for from_clause in itertools.chain(
|
|
itertools.chain.from_iterable(
|
|
[element._from_objects for element in raw_columns]
|
|
),
|
|
itertools.chain.from_iterable(
|
|
[
|
|
element._from_objects
|
|
for element in statement._where_criteria
|
|
]
|
|
),
|
|
):
|
|
potential[from_clause] = ()
|
|
|
|
all_clauses = list(potential.keys())
|
|
indexes = sql_util.find_left_clause_to_join_from(
|
|
all_clauses, right, onclause
|
|
)
|
|
|
|
if len(indexes) == 1:
|
|
left = all_clauses[indexes[0]]
|
|
|
|
if len(indexes) > 1:
|
|
raise exc.InvalidRequestError(
|
|
"Can't determine which FROM clause to join "
|
|
"from, there are multiple FROMS which can "
|
|
"join to this entity. Please use the .select_from() "
|
|
"method to establish an explicit left side, as well as "
|
|
"providing an explicit ON clause if not present already to "
|
|
"help resolve the ambiguity."
|
|
)
|
|
elif not indexes:
|
|
raise exc.InvalidRequestError(
|
|
"Don't know how to join to %r. "
|
|
"Please use the .select_from() "
|
|
"method to establish an explicit left side, as well as "
|
|
"providing an explicit ON clause if not present already to "
|
|
"help resolve the ambiguity." % (right,)
|
|
)
|
|
return left, replace_from_obj_index
|
|
|
|
@util.preload_module("sqlalchemy.sql.util")
|
|
def _join_place_explicit_left_side(
|
|
self, left: FromClause
|
|
) -> Optional[int]:
|
|
replace_from_obj_index: Optional[int] = None
|
|
|
|
sql_util = util.preloaded.sql_util
|
|
|
|
from_clauses = list(self.statement._iterate_from_elements())
|
|
|
|
if from_clauses:
|
|
indexes: List[int] = sql_util.find_left_clause_that_matches_given(
|
|
self.from_clauses, left
|
|
)
|
|
else:
|
|
indexes = []
|
|
|
|
if len(indexes) > 1:
|
|
raise exc.InvalidRequestError(
|
|
"Can't identify which entity in which to assign the "
|
|
"left side of this join. Please use a more specific "
|
|
"ON clause."
|
|
)
|
|
|
|
# have an index, means the left side is already present in
|
|
# an existing FROM in the self._from_obj tuple
|
|
if indexes:
|
|
replace_from_obj_index = indexes[0]
|
|
|
|
# no index, means we need to add a new element to the
|
|
# self._from_obj tuple
|
|
|
|
return replace_from_obj_index
|
|
|
|
|
|
class _SelectFromElements:
|
|
__slots__ = ()
|
|
|
|
_raw_columns: List[_ColumnsClauseElement]
|
|
_where_criteria: Tuple[ColumnElement[Any], ...]
|
|
_from_obj: Tuple[FromClause, ...]
|
|
|
|
def _iterate_from_elements(self) -> Iterator[FromClause]:
|
|
# note this does not include elements
|
|
# in _setup_joins
|
|
|
|
seen = set()
|
|
for element in self._raw_columns:
|
|
for fr in element._from_objects:
|
|
if fr in seen:
|
|
continue
|
|
seen.add(fr)
|
|
yield fr
|
|
for element in self._where_criteria:
|
|
for fr in element._from_objects:
|
|
if fr in seen:
|
|
continue
|
|
seen.add(fr)
|
|
yield fr
|
|
for element in self._from_obj:
|
|
if element in seen:
|
|
continue
|
|
seen.add(element)
|
|
yield element
|
|
|
|
|
|
class _MemoizedSelectEntities(
|
|
cache_key.HasCacheKey, traversals.HasCopyInternals, visitors.Traversible
|
|
):
|
|
"""represents partial state from a Select object, for the case
|
|
where Select.columns() has redefined the set of columns/entities the
|
|
statement will be SELECTing from. This object represents
|
|
the entities from the SELECT before that transformation was applied,
|
|
so that transformations that were made in terms of the SELECT at that
|
|
time, such as join() as well as options(), can access the correct context.
|
|
|
|
In previous SQLAlchemy versions, this wasn't needed because these
|
|
constructs calculated everything up front, like when you called join()
|
|
or options(), it did everything to figure out how that would translate
|
|
into specific SQL constructs that would be ready to send directly to the
|
|
SQL compiler when needed. But as of
|
|
1.4, all of that stuff is done in the compilation phase, during the
|
|
"compile state" portion of the process, so that the work can all be
|
|
cached. So it needs to be able to resolve joins/options2 based on what
|
|
the list of entities was when those methods were called.
|
|
|
|
|
|
"""
|
|
|
|
__visit_name__ = "memoized_select_entities"
|
|
|
|
_traverse_internals: _TraverseInternalsType = [
|
|
("_raw_columns", InternalTraversal.dp_clauseelement_list),
|
|
("_setup_joins", InternalTraversal.dp_setup_join_tuple),
|
|
("_with_options", InternalTraversal.dp_executable_options),
|
|
]
|
|
|
|
_is_clone_of: Optional[ClauseElement]
|
|
_raw_columns: List[_ColumnsClauseElement]
|
|
_setup_joins: Tuple[_SetupJoinsElement, ...]
|
|
_with_options: Tuple[ExecutableOption, ...]
|
|
|
|
_annotations = util.EMPTY_DICT
|
|
|
|
def _clone(self, **kw: Any) -> Self:
|
|
c = self.__class__.__new__(self.__class__)
|
|
c.__dict__ = {k: v for k, v in self.__dict__.items()}
|
|
|
|
c._is_clone_of = self.__dict__.get("_is_clone_of", self)
|
|
return c
|
|
|
|
@classmethod
|
|
def _generate_for_statement(cls, select_stmt: Select[Any]) -> None:
|
|
if select_stmt._setup_joins or select_stmt._with_options:
|
|
self = _MemoizedSelectEntities()
|
|
self._raw_columns = select_stmt._raw_columns
|
|
self._setup_joins = select_stmt._setup_joins
|
|
self._with_options = select_stmt._with_options
|
|
|
|
select_stmt._memoized_select_entities += (self,)
|
|
select_stmt._raw_columns = []
|
|
select_stmt._setup_joins = select_stmt._with_options = ()
|
|
|
|
|
|
class Select(
|
|
HasPrefixes,
|
|
HasSuffixes,
|
|
HasHints,
|
|
HasCompileState,
|
|
_SelectFromElements,
|
|
GenerativeSelect,
|
|
TypedReturnsRows[_TP],
|
|
):
|
|
"""Represents a ``SELECT`` statement.
|
|
|
|
The :class:`_sql.Select` object is normally constructed using the
|
|
:func:`_sql.select` function. See that function for details.
|
|
|
|
.. seealso::
|
|
|
|
:func:`_sql.select`
|
|
|
|
:ref:`tutorial_selecting_data` - in the 2.0 tutorial
|
|
|
|
"""
|
|
|
|
__visit_name__ = "select"
|
|
|
|
_setup_joins: Tuple[_SetupJoinsElement, ...] = ()
|
|
_memoized_select_entities: Tuple[TODO_Any, ...] = ()
|
|
|
|
_raw_columns: List[_ColumnsClauseElement]
|
|
|
|
_distinct: bool = False
|
|
_distinct_on: Tuple[ColumnElement[Any], ...] = ()
|
|
_correlate: Tuple[FromClause, ...] = ()
|
|
_correlate_except: Optional[Tuple[FromClause, ...]] = None
|
|
_where_criteria: Tuple[ColumnElement[Any], ...] = ()
|
|
_having_criteria: Tuple[ColumnElement[Any], ...] = ()
|
|
_from_obj: Tuple[FromClause, ...] = ()
|
|
_auto_correlate = True
|
|
_is_select_statement = True
|
|
_compile_options: CacheableOptions = (
|
|
SelectState.default_select_compile_options
|
|
)
|
|
|
|
_traverse_internals: _TraverseInternalsType = (
|
|
[
|
|
("_raw_columns", InternalTraversal.dp_clauseelement_list),
|
|
(
|
|
"_memoized_select_entities",
|
|
InternalTraversal.dp_memoized_select_entities,
|
|
),
|
|
("_from_obj", InternalTraversal.dp_clauseelement_list),
|
|
("_where_criteria", InternalTraversal.dp_clauseelement_tuple),
|
|
("_having_criteria", InternalTraversal.dp_clauseelement_tuple),
|
|
("_order_by_clauses", InternalTraversal.dp_clauseelement_tuple),
|
|
("_group_by_clauses", InternalTraversal.dp_clauseelement_tuple),
|
|
("_setup_joins", InternalTraversal.dp_setup_join_tuple),
|
|
("_correlate", InternalTraversal.dp_clauseelement_tuple),
|
|
("_correlate_except", InternalTraversal.dp_clauseelement_tuple),
|
|
("_limit_clause", InternalTraversal.dp_clauseelement),
|
|
("_offset_clause", InternalTraversal.dp_clauseelement),
|
|
("_fetch_clause", InternalTraversal.dp_clauseelement),
|
|
("_fetch_clause_options", InternalTraversal.dp_plain_dict),
|
|
("_for_update_arg", InternalTraversal.dp_clauseelement),
|
|
("_distinct", InternalTraversal.dp_boolean),
|
|
("_distinct_on", InternalTraversal.dp_clauseelement_tuple),
|
|
("_label_style", InternalTraversal.dp_plain_obj),
|
|
]
|
|
+ HasCTE._has_ctes_traverse_internals
|
|
+ HasPrefixes._has_prefixes_traverse_internals
|
|
+ HasSuffixes._has_suffixes_traverse_internals
|
|
+ HasHints._has_hints_traverse_internals
|
|
+ SupportsCloneAnnotations._clone_annotations_traverse_internals
|
|
+ Executable._executable_traverse_internals
|
|
)
|
|
|
|
_cache_key_traversal: _CacheKeyTraversalType = _traverse_internals + [
|
|
("_compile_options", InternalTraversal.dp_has_cache_key)
|
|
]
|
|
|
|
_compile_state_factory: Type[SelectState]
|
|
|
|
@classmethod
|
|
def _create_raw_select(cls, **kw: Any) -> Select[Any]:
|
|
"""Create a :class:`.Select` using raw ``__new__`` with no coercions.
|
|
|
|
Used internally to build up :class:`.Select` constructs with
|
|
pre-established state.
|
|
|
|
"""
|
|
|
|
stmt = Select.__new__(Select)
|
|
stmt.__dict__.update(kw)
|
|
return stmt
|
|
|
|
def __init__(self, *entities: _ColumnsClauseArgument[Any]):
|
|
r"""Construct a new :class:`_expression.Select`.
|
|
|
|
The public constructor for :class:`_expression.Select` is the
|
|
:func:`_sql.select` function.
|
|
|
|
"""
|
|
self._raw_columns = [
|
|
coercions.expect(
|
|
roles.ColumnsClauseRole, ent, apply_propagate_attrs=self
|
|
)
|
|
for ent in entities
|
|
]
|
|
|
|
GenerativeSelect.__init__(self)
|
|
|
|
def _scalar_type(self) -> TypeEngine[Any]:
|
|
if not self._raw_columns:
|
|
return NULLTYPE
|
|
elem = self._raw_columns[0]
|
|
cols = list(elem._select_iterable)
|
|
return cols[0].type
|
|
|
|
def filter(self, *criteria: _ColumnExpressionArgument[bool]) -> Self:
|
|
"""A synonym for the :meth:`_sql.Select.where` method."""
|
|
|
|
return self.where(*criteria)
|
|
|
|
def _filter_by_zero(
|
|
self,
|
|
) -> Union[
|
|
FromClause, _JoinTargetProtocol, ColumnElement[Any], TextClause
|
|
]:
|
|
if self._setup_joins:
|
|
meth = SelectState.get_plugin_class(
|
|
self
|
|
).determine_last_joined_entity
|
|
_last_joined_entity = meth(self)
|
|
if _last_joined_entity is not None:
|
|
return _last_joined_entity
|
|
|
|
if self._from_obj:
|
|
return self._from_obj[0]
|
|
|
|
return self._raw_columns[0]
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
@overload
|
|
def scalar_subquery(
|
|
self: Select[Tuple[_MAYBE_ENTITY]],
|
|
) -> ScalarSelect[Any]: ...
|
|
|
|
@overload
|
|
def scalar_subquery(
|
|
self: Select[Tuple[_NOT_ENTITY]],
|
|
) -> ScalarSelect[_NOT_ENTITY]: ...
|
|
|
|
@overload
|
|
def scalar_subquery(self) -> ScalarSelect[Any]: ...
|
|
|
|
def scalar_subquery(self) -> ScalarSelect[Any]: ...
|
|
|
|
def filter_by(self, **kwargs: Any) -> Self:
|
|
r"""apply the given filtering criterion as a WHERE clause
|
|
to this select.
|
|
|
|
"""
|
|
from_entity = self._filter_by_zero()
|
|
|
|
clauses = [
|
|
_entity_namespace_key(from_entity, key) == value
|
|
for key, value in kwargs.items()
|
|
]
|
|
return self.filter(*clauses)
|
|
|
|
@property
|
|
def column_descriptions(self) -> Any:
|
|
"""Return a :term:`plugin-enabled` 'column descriptions' structure
|
|
referring to the columns which are SELECTed by this statement.
|
|
|
|
This attribute is generally useful when using the ORM, as an
|
|
extended structure which includes information about mapped
|
|
entities is returned. The section :ref:`queryguide_inspection`
|
|
contains more background.
|
|
|
|
For a Core-only statement, the structure returned by this accessor
|
|
is derived from the same objects that are returned by the
|
|
:attr:`.Select.selected_columns` accessor, formatted as a list of
|
|
dictionaries which contain the keys ``name``, ``type`` and ``expr``,
|
|
which indicate the column expressions to be selected::
|
|
|
|
>>> stmt = select(user_table)
|
|
>>> stmt.column_descriptions
|
|
[
|
|
{
|
|
'name': 'id',
|
|
'type': Integer(),
|
|
'expr': Column('id', Integer(), ...)},
|
|
{
|
|
'name': 'name',
|
|
'type': String(length=30),
|
|
'expr': Column('name', String(length=30), ...)}
|
|
]
|
|
|
|
.. versionchanged:: 1.4.33 The :attr:`.Select.column_descriptions`
|
|
attribute returns a structure for a Core-only set of entities,
|
|
not just ORM-only entities.
|
|
|
|
.. seealso::
|
|
|
|
:attr:`.UpdateBase.entity_description` - entity information for
|
|
an :func:`.insert`, :func:`.update`, or :func:`.delete`
|
|
|
|
:ref:`queryguide_inspection` - ORM background
|
|
|
|
"""
|
|
meth = SelectState.get_plugin_class(self).get_column_descriptions
|
|
return meth(self)
|
|
|
|
def from_statement(
|
|
self, statement: roles.ReturnsRowsRole
|
|
) -> ExecutableReturnsRows:
|
|
"""Apply the columns which this :class:`.Select` would select
|
|
onto another statement.
|
|
|
|
This operation is :term:`plugin-specific` and will raise a not
|
|
supported exception if this :class:`_sql.Select` does not select from
|
|
plugin-enabled entities.
|
|
|
|
|
|
The statement is typically either a :func:`_expression.text` or
|
|
:func:`_expression.select` construct, and should return the set of
|
|
columns appropriate to the entities represented by this
|
|
:class:`.Select`.
|
|
|
|
.. seealso::
|
|
|
|
:ref:`orm_queryguide_selecting_text` - usage examples in the
|
|
ORM Querying Guide
|
|
|
|
"""
|
|
meth = SelectState.get_plugin_class(self).from_statement
|
|
return meth(self, statement)
|
|
|
|
@_generative
|
|
def join(
|
|
self,
|
|
target: _JoinTargetArgument,
|
|
onclause: Optional[_OnClauseArgument] = None,
|
|
*,
|
|
isouter: bool = False,
|
|
full: bool = False,
|
|
) -> Self:
|
|
r"""Create a SQL JOIN against this :class:`_expression.Select`
|
|
object's criterion
|
|
and apply generatively, returning the newly resulting
|
|
:class:`_expression.Select`.
|
|
|
|
E.g.::
|
|
|
|
stmt = select(user_table).join(address_table, user_table.c.id == address_table.c.user_id)
|
|
|
|
The above statement generates SQL similar to::
|
|
|
|
SELECT user.id, user.name FROM user JOIN address ON user.id = address.user_id
|
|
|
|
.. versionchanged:: 1.4 :meth:`_expression.Select.join` now creates
|
|
a :class:`_sql.Join` object between a :class:`_sql.FromClause`
|
|
source that is within the FROM clause of the existing SELECT,
|
|
and a given target :class:`_sql.FromClause`, and then adds
|
|
this :class:`_sql.Join` to the FROM clause of the newly generated
|
|
SELECT statement. This is completely reworked from the behavior
|
|
in 1.3, which would instead create a subquery of the entire
|
|
:class:`_expression.Select` and then join that subquery to the
|
|
target.
|
|
|
|
This is a **backwards incompatible change** as the previous behavior
|
|
was mostly useless, producing an unnamed subquery rejected by
|
|
most databases in any case. The new behavior is modeled after
|
|
that of the very successful :meth:`_orm.Query.join` method in the
|
|
ORM, in order to support the functionality of :class:`_orm.Query`
|
|
being available by using a :class:`_sql.Select` object with an
|
|
:class:`_orm.Session`.
|
|
|
|
See the notes for this change at :ref:`change_select_join`.
|
|
|
|
|
|
:param target: target table to join towards
|
|
|
|
:param onclause: ON clause of the join. If omitted, an ON clause
|
|
is generated automatically based on the :class:`_schema.ForeignKey`
|
|
linkages between the two tables, if one can be unambiguously
|
|
determined, otherwise an error is raised.
|
|
|
|
:param isouter: if True, generate LEFT OUTER join. Same as
|
|
:meth:`_expression.Select.outerjoin`.
|
|
|
|
:param full: if True, generate FULL OUTER join.
|
|
|
|
.. seealso::
|
|
|
|
:ref:`tutorial_select_join` - in the :doc:`/tutorial/index`
|
|
|
|
:ref:`orm_queryguide_joins` - in the :ref:`queryguide_toplevel`
|
|
|
|
:meth:`_expression.Select.join_from`
|
|
|
|
:meth:`_expression.Select.outerjoin`
|
|
|
|
""" # noqa: E501
|
|
join_target = coercions.expect(
|
|
roles.JoinTargetRole, target, apply_propagate_attrs=self
|
|
)
|
|
if onclause is not None:
|
|
onclause_element = coercions.expect(roles.OnClauseRole, onclause)
|
|
else:
|
|
onclause_element = None
|
|
|
|
self._setup_joins += (
|
|
(
|
|
join_target,
|
|
onclause_element,
|
|
None,
|
|
{"isouter": isouter, "full": full},
|
|
),
|
|
)
|
|
return self
|
|
|
|
def outerjoin_from(
|
|
self,
|
|
from_: _FromClauseArgument,
|
|
target: _JoinTargetArgument,
|
|
onclause: Optional[_OnClauseArgument] = None,
|
|
*,
|
|
full: bool = False,
|
|
) -> Self:
|
|
r"""Create a SQL LEFT OUTER JOIN against this
|
|
:class:`_expression.Select` object's criterion and apply generatively,
|
|
returning the newly resulting :class:`_expression.Select`.
|
|
|
|
Usage is the same as that of :meth:`_selectable.Select.join_from`.
|
|
|
|
"""
|
|
return self.join_from(
|
|
from_, target, onclause=onclause, isouter=True, full=full
|
|
)
|
|
|
|
@_generative
|
|
def join_from(
|
|
self,
|
|
from_: _FromClauseArgument,
|
|
target: _JoinTargetArgument,
|
|
onclause: Optional[_OnClauseArgument] = None,
|
|
*,
|
|
isouter: bool = False,
|
|
full: bool = False,
|
|
) -> Self:
|
|
r"""Create a SQL JOIN against this :class:`_expression.Select`
|
|
object's criterion
|
|
and apply generatively, returning the newly resulting
|
|
:class:`_expression.Select`.
|
|
|
|
E.g.::
|
|
|
|
stmt = select(user_table, address_table).join_from(
|
|
user_table, address_table, user_table.c.id == address_table.c.user_id
|
|
)
|
|
|
|
The above statement generates SQL similar to::
|
|
|
|
SELECT user.id, user.name, address.id, address.email, address.user_id
|
|
FROM user JOIN address ON user.id = address.user_id
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
:param from\_: the left side of the join, will be rendered in the
|
|
FROM clause and is roughly equivalent to using the
|
|
:meth:`.Select.select_from` method.
|
|
|
|
:param target: target table to join towards
|
|
|
|
:param onclause: ON clause of the join.
|
|
|
|
:param isouter: if True, generate LEFT OUTER join. Same as
|
|
:meth:`_expression.Select.outerjoin`.
|
|
|
|
:param full: if True, generate FULL OUTER join.
|
|
|
|
.. seealso::
|
|
|
|
:ref:`tutorial_select_join` - in the :doc:`/tutorial/index`
|
|
|
|
:ref:`orm_queryguide_joins` - in the :ref:`queryguide_toplevel`
|
|
|
|
:meth:`_expression.Select.join`
|
|
|
|
""" # noqa: E501
|
|
|
|
# note the order of parsing from vs. target is important here, as we
|
|
# are also deriving the source of the plugin (i.e. the subject mapper
|
|
# in an ORM query) which should favor the "from_" over the "target"
|
|
|
|
from_ = coercions.expect(
|
|
roles.FromClauseRole, from_, apply_propagate_attrs=self
|
|
)
|
|
join_target = coercions.expect(
|
|
roles.JoinTargetRole, target, apply_propagate_attrs=self
|
|
)
|
|
if onclause is not None:
|
|
onclause_element = coercions.expect(roles.OnClauseRole, onclause)
|
|
else:
|
|
onclause_element = None
|
|
|
|
self._setup_joins += (
|
|
(
|
|
join_target,
|
|
onclause_element,
|
|
from_,
|
|
{"isouter": isouter, "full": full},
|
|
),
|
|
)
|
|
return self
|
|
|
|
def outerjoin(
|
|
self,
|
|
target: _JoinTargetArgument,
|
|
onclause: Optional[_OnClauseArgument] = None,
|
|
*,
|
|
full: bool = False,
|
|
) -> Self:
|
|
"""Create a left outer join.
|
|
|
|
Parameters are the same as that of :meth:`_expression.Select.join`.
|
|
|
|
.. versionchanged:: 1.4 :meth:`_expression.Select.outerjoin` now
|
|
creates a :class:`_sql.Join` object between a
|
|
:class:`_sql.FromClause` source that is within the FROM clause of
|
|
the existing SELECT, and a given target :class:`_sql.FromClause`,
|
|
and then adds this :class:`_sql.Join` to the FROM clause of the
|
|
newly generated SELECT statement. This is completely reworked
|
|
from the behavior in 1.3, which would instead create a subquery of
|
|
the entire
|
|
:class:`_expression.Select` and then join that subquery to the
|
|
target.
|
|
|
|
This is a **backwards incompatible change** as the previous behavior
|
|
was mostly useless, producing an unnamed subquery rejected by
|
|
most databases in any case. The new behavior is modeled after
|
|
that of the very successful :meth:`_orm.Query.join` method in the
|
|
ORM, in order to support the functionality of :class:`_orm.Query`
|
|
being available by using a :class:`_sql.Select` object with an
|
|
:class:`_orm.Session`.
|
|
|
|
See the notes for this change at :ref:`change_select_join`.
|
|
|
|
.. seealso::
|
|
|
|
:ref:`tutorial_select_join` - in the :doc:`/tutorial/index`
|
|
|
|
:ref:`orm_queryguide_joins` - in the :ref:`queryguide_toplevel`
|
|
|
|
:meth:`_expression.Select.join`
|
|
|
|
"""
|
|
return self.join(target, onclause=onclause, isouter=True, full=full)
|
|
|
|
def get_final_froms(self) -> Sequence[FromClause]:
|
|
"""Compute the final displayed list of :class:`_expression.FromClause`
|
|
elements.
|
|
|
|
This method will run through the full computation required to
|
|
determine what FROM elements will be displayed in the resulting
|
|
SELECT statement, including shadowing individual tables with
|
|
JOIN objects, as well as full computation for ORM use cases including
|
|
eager loading clauses.
|
|
|
|
For ORM use, this accessor returns the **post compilation**
|
|
list of FROM objects; this collection will include elements such as
|
|
eagerly loaded tables and joins. The objects will **not** be
|
|
ORM enabled and not work as a replacement for the
|
|
:meth:`_sql.Select.select_froms` collection; additionally, the
|
|
method is not well performing for an ORM enabled statement as it
|
|
will incur the full ORM construction process.
|
|
|
|
To retrieve the FROM list that's implied by the "columns" collection
|
|
passed to the :class:`_sql.Select` originally, use the
|
|
:attr:`_sql.Select.columns_clause_froms` accessor.
|
|
|
|
To select from an alternative set of columns while maintaining the
|
|
FROM list, use the :meth:`_sql.Select.with_only_columns` method and
|
|
pass the
|
|
:paramref:`_sql.Select.with_only_columns.maintain_column_froms`
|
|
parameter.
|
|
|
|
.. versionadded:: 1.4.23 - the :meth:`_sql.Select.get_final_froms`
|
|
method replaces the previous :attr:`_sql.Select.froms` accessor,
|
|
which is deprecated.
|
|
|
|
.. seealso::
|
|
|
|
:attr:`_sql.Select.columns_clause_froms`
|
|
|
|
"""
|
|
|
|
return self._compile_state_factory(self, None)._get_display_froms()
|
|
|
|
@property
|
|
@util.deprecated(
|
|
"1.4.23",
|
|
"The :attr:`_expression.Select.froms` attribute is moved to "
|
|
"the :meth:`_expression.Select.get_final_froms` method.",
|
|
)
|
|
def froms(self) -> Sequence[FromClause]:
|
|
"""Return the displayed list of :class:`_expression.FromClause`
|
|
elements.
|
|
|
|
|
|
"""
|
|
return self.get_final_froms()
|
|
|
|
@property
|
|
def columns_clause_froms(self) -> List[FromClause]:
|
|
"""Return the set of :class:`_expression.FromClause` objects implied
|
|
by the columns clause of this SELECT statement.
|
|
|
|
.. versionadded:: 1.4.23
|
|
|
|
.. seealso::
|
|
|
|
:attr:`_sql.Select.froms` - "final" FROM list taking the full
|
|
statement into account
|
|
|
|
:meth:`_sql.Select.with_only_columns` - makes use of this
|
|
collection to set up a new FROM list
|
|
|
|
"""
|
|
|
|
return SelectState.get_plugin_class(self).get_columns_clause_froms(
|
|
self
|
|
)
|
|
|
|
@property
|
|
def inner_columns(self) -> _SelectIterable:
|
|
"""An iterator of all :class:`_expression.ColumnElement`
|
|
expressions which would
|
|
be rendered into the columns clause of the resulting SELECT statement.
|
|
|
|
This method is legacy as of 1.4 and is superseded by the
|
|
:attr:`_expression.Select.exported_columns` collection.
|
|
|
|
"""
|
|
|
|
return iter(self._all_selected_columns)
|
|
|
|
def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
|
|
if fromclause is not None and self in fromclause._cloned_set:
|
|
return True
|
|
|
|
for f in self._iterate_from_elements():
|
|
if f.is_derived_from(fromclause):
|
|
return True
|
|
return False
|
|
|
|
def _copy_internals(
|
|
self, clone: _CloneCallableType = _clone, **kw: Any
|
|
) -> None:
|
|
# Select() object has been cloned and probably adapted by the
|
|
# given clone function. Apply the cloning function to internal
|
|
# objects
|
|
|
|
# 1. keep a dictionary of the froms we've cloned, and what
|
|
# 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 = set(
|
|
itertools.chain(
|
|
_from_objects(*self._raw_columns),
|
|
_from_objects(*self._where_criteria),
|
|
_from_objects(*[elem[0] for elem in self._setup_joins]),
|
|
)
|
|
)
|
|
|
|
# do a clone for the froms we've gathered. what is important here
|
|
# is if any of the things we are selecting from, like tables,
|
|
# were converted into Join objects. if so, these need to be
|
|
# added to _from_obj explicitly, because otherwise they won't be
|
|
# part of the new state, as they don't associate themselves with
|
|
# their columns.
|
|
new_froms = {f: clone(f, **kw) for f in all_the_froms}
|
|
|
|
# 2. copy FROM collections, adding in joins that we've created.
|
|
existing_from_obj = [clone(f, **kw) for f in self._from_obj]
|
|
add_froms = (
|
|
{f for f in new_froms.values() if isinstance(f, Join)}
|
|
.difference(all_the_froms)
|
|
.difference(existing_from_obj)
|
|
)
|
|
|
|
self._from_obj = tuple(existing_from_obj) + tuple(add_froms)
|
|
|
|
# 3. clone everything else, making sure we use columns
|
|
# corresponding to the froms we just made.
|
|
def replace(
|
|
obj: Union[BinaryExpression[Any], ColumnClause[Any]],
|
|
**kw: Any,
|
|
) -> Optional[KeyedColumnElement[ColumnElement[Any]]]:
|
|
if isinstance(obj, ColumnClause) and obj.table in new_froms:
|
|
newelem = new_froms[obj.table].corresponding_column(obj)
|
|
return newelem
|
|
return None
|
|
|
|
kw["replace"] = replace
|
|
|
|
# copy everything else. for table-ish things like correlate,
|
|
# correlate_except, setup_joins, these clone normally. For
|
|
# column-expression oriented things like raw_columns, where_criteria,
|
|
# order by, we get this from the new froms.
|
|
super()._copy_internals(clone=clone, omit_attrs=("_from_obj",), **kw)
|
|
|
|
self._reset_memoizations()
|
|
|
|
def get_children(self, **kw: Any) -> Iterable[ClauseElement]:
|
|
return itertools.chain(
|
|
super().get_children(
|
|
omit_attrs=("_from_obj", "_correlate", "_correlate_except"),
|
|
**kw,
|
|
),
|
|
self._iterate_from_elements(),
|
|
)
|
|
|
|
@_generative
|
|
def add_columns(
|
|
self, *entities: _ColumnsClauseArgument[Any]
|
|
) -> Select[Any]:
|
|
r"""Return a new :func:`_expression.select` construct with
|
|
the given entities appended to its columns clause.
|
|
|
|
E.g.::
|
|
|
|
my_select = my_select.add_columns(table.c.new_column)
|
|
|
|
The original expressions in the columns clause remain in place.
|
|
To replace the original expressions with new ones, see the method
|
|
:meth:`_expression.Select.with_only_columns`.
|
|
|
|
:param \*entities: column, table, or other entity expressions to be
|
|
added to the columns clause
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_expression.Select.with_only_columns` - replaces existing
|
|
expressions rather than appending.
|
|
|
|
:ref:`orm_queryguide_select_multiple_entities` - ORM-centric
|
|
example
|
|
|
|
"""
|
|
self._reset_memoizations()
|
|
|
|
self._raw_columns = self._raw_columns + [
|
|
coercions.expect(
|
|
roles.ColumnsClauseRole, column, apply_propagate_attrs=self
|
|
)
|
|
for column in entities
|
|
]
|
|
return self
|
|
|
|
def _set_entities(
|
|
self, entities: Iterable[_ColumnsClauseArgument[Any]]
|
|
) -> None:
|
|
self._raw_columns = [
|
|
coercions.expect(
|
|
roles.ColumnsClauseRole, ent, apply_propagate_attrs=self
|
|
)
|
|
for ent in util.to_list(entities)
|
|
]
|
|
|
|
@util.deprecated(
|
|
"1.4",
|
|
"The :meth:`_expression.Select.column` method is deprecated and will "
|
|
"be removed in a future release. Please use "
|
|
":meth:`_expression.Select.add_columns`",
|
|
)
|
|
def column(self, column: _ColumnsClauseArgument[Any]) -> Select[Any]:
|
|
"""Return a new :func:`_expression.select` construct with
|
|
the given column expression added to its columns clause.
|
|
|
|
E.g.::
|
|
|
|
my_select = my_select.column(table.c.new_column)
|
|
|
|
See the documentation for
|
|
:meth:`_expression.Select.with_only_columns`
|
|
for guidelines on adding /replacing the columns of a
|
|
:class:`_expression.Select` object.
|
|
|
|
"""
|
|
return self.add_columns(column)
|
|
|
|
@util.preload_module("sqlalchemy.sql.util")
|
|
def reduce_columns(self, only_synonyms: bool = True) -> Select[Any]:
|
|
"""Return a new :func:`_expression.select` construct with redundantly
|
|
named, equivalently-valued columns removed from the columns clause.
|
|
|
|
"Redundant" here means two columns where one refers to the
|
|
other either based on foreign key, or via a simple equality
|
|
comparison in the WHERE clause of the statement. The primary purpose
|
|
of this method is to automatically construct a select statement
|
|
with all uniquely-named columns, without the need to use
|
|
table-qualified labels as
|
|
:meth:`_expression.Select.set_label_style`
|
|
does.
|
|
|
|
When columns are omitted based on foreign key, the referred-to
|
|
column is the one that's kept. When columns are omitted based on
|
|
WHERE equivalence, the first column in the columns clause is the
|
|
one that's kept.
|
|
|
|
:param only_synonyms: when True, limit the removal of columns
|
|
to those which have the same name as the equivalent. Otherwise,
|
|
all columns that are equivalent to another are removed.
|
|
|
|
"""
|
|
woc: Select[Any]
|
|
woc = self.with_only_columns(
|
|
*util.preloaded.sql_util.reduce_columns(
|
|
self._all_selected_columns,
|
|
only_synonyms=only_synonyms,
|
|
*(self._where_criteria + self._from_obj),
|
|
)
|
|
)
|
|
return woc
|
|
|
|
# START OVERLOADED FUNCTIONS self.with_only_columns Select 8
|
|
|
|
# code within this block is **programmatically,
|
|
# statically generated** by tools/generate_sel_v1_overloads.py
|
|
|
|
@overload
|
|
def with_only_columns(self, __ent0: _TCCA[_T0]) -> Select[Tuple[_T0]]: ...
|
|
|
|
@overload
|
|
def with_only_columns(
|
|
self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1]
|
|
) -> Select[Tuple[_T0, _T1]]: ...
|
|
|
|
@overload
|
|
def with_only_columns(
|
|
self, __ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2]
|
|
) -> Select[Tuple[_T0, _T1, _T2]]: ...
|
|
|
|
@overload
|
|
def with_only_columns(
|
|
self,
|
|
__ent0: _TCCA[_T0],
|
|
__ent1: _TCCA[_T1],
|
|
__ent2: _TCCA[_T2],
|
|
__ent3: _TCCA[_T3],
|
|
) -> Select[Tuple[_T0, _T1, _T2, _T3]]: ...
|
|
|
|
@overload
|
|
def with_only_columns(
|
|
self,
|
|
__ent0: _TCCA[_T0],
|
|
__ent1: _TCCA[_T1],
|
|
__ent2: _TCCA[_T2],
|
|
__ent3: _TCCA[_T3],
|
|
__ent4: _TCCA[_T4],
|
|
) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4]]: ...
|
|
|
|
@overload
|
|
def with_only_columns(
|
|
self,
|
|
__ent0: _TCCA[_T0],
|
|
__ent1: _TCCA[_T1],
|
|
__ent2: _TCCA[_T2],
|
|
__ent3: _TCCA[_T3],
|
|
__ent4: _TCCA[_T4],
|
|
__ent5: _TCCA[_T5],
|
|
) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]: ...
|
|
|
|
@overload
|
|
def with_only_columns(
|
|
self,
|
|
__ent0: _TCCA[_T0],
|
|
__ent1: _TCCA[_T1],
|
|
__ent2: _TCCA[_T2],
|
|
__ent3: _TCCA[_T3],
|
|
__ent4: _TCCA[_T4],
|
|
__ent5: _TCCA[_T5],
|
|
__ent6: _TCCA[_T6],
|
|
) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]: ...
|
|
|
|
@overload
|
|
def with_only_columns(
|
|
self,
|
|
__ent0: _TCCA[_T0],
|
|
__ent1: _TCCA[_T1],
|
|
__ent2: _TCCA[_T2],
|
|
__ent3: _TCCA[_T3],
|
|
__ent4: _TCCA[_T4],
|
|
__ent5: _TCCA[_T5],
|
|
__ent6: _TCCA[_T6],
|
|
__ent7: _TCCA[_T7],
|
|
) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]]: ...
|
|
|
|
# END OVERLOADED FUNCTIONS self.with_only_columns
|
|
|
|
@overload
|
|
def with_only_columns(
|
|
self,
|
|
*entities: _ColumnsClauseArgument[Any],
|
|
maintain_column_froms: bool = False,
|
|
**__kw: Any,
|
|
) -> Select[Any]: ...
|
|
|
|
@_generative
|
|
def with_only_columns(
|
|
self,
|
|
*entities: _ColumnsClauseArgument[Any],
|
|
maintain_column_froms: bool = False,
|
|
**__kw: Any,
|
|
) -> Select[Any]:
|
|
r"""Return a new :func:`_expression.select` construct with its columns
|
|
clause replaced with the given entities.
|
|
|
|
By default, this method is exactly equivalent to as if the original
|
|
:func:`_expression.select` had been called with the given entities.
|
|
E.g. a statement::
|
|
|
|
s = select(table1.c.a, table1.c.b)
|
|
s = s.with_only_columns(table1.c.b)
|
|
|
|
should be exactly equivalent to::
|
|
|
|
s = select(table1.c.b)
|
|
|
|
In this mode of operation, :meth:`_sql.Select.with_only_columns`
|
|
will also dynamically alter the FROM clause of the
|
|
statement if it is not explicitly stated.
|
|
To maintain the existing set of FROMs including those implied by the
|
|
current columns clause, add the
|
|
:paramref:`_sql.Select.with_only_columns.maintain_column_froms`
|
|
parameter::
|
|
|
|
s = select(table1.c.a, table2.c.b)
|
|
s = s.with_only_columns(table1.c.a, maintain_column_froms=True)
|
|
|
|
The above parameter performs a transfer of the effective FROMs
|
|
in the columns collection to the :meth:`_sql.Select.select_from`
|
|
method, as though the following were invoked::
|
|
|
|
s = select(table1.c.a, table2.c.b)
|
|
s = s.select_from(table1, table2).with_only_columns(table1.c.a)
|
|
|
|
The :paramref:`_sql.Select.with_only_columns.maintain_column_froms`
|
|
parameter makes use of the :attr:`_sql.Select.columns_clause_froms`
|
|
collection and performs an operation equivalent to the following::
|
|
|
|
s = select(table1.c.a, table2.c.b)
|
|
s = s.select_from(*s.columns_clause_froms).with_only_columns(table1.c.a)
|
|
|
|
:param \*entities: column expressions to be used.
|
|
|
|
:param maintain_column_froms: boolean parameter that will ensure the
|
|
FROM list implied from the current columns clause will be transferred
|
|
to the :meth:`_sql.Select.select_from` method first.
|
|
|
|
.. versionadded:: 1.4.23
|
|
|
|
""" # noqa: E501
|
|
|
|
if __kw:
|
|
raise _no_kw()
|
|
|
|
# memoizations should be cleared here as of
|
|
# I95c560ffcbfa30b26644999412fb6a385125f663 , asserting this
|
|
# is the case for now.
|
|
self._assert_no_memoizations()
|
|
|
|
if maintain_column_froms:
|
|
self.select_from.non_generative( # type: ignore
|
|
self, *self.columns_clause_froms
|
|
)
|
|
|
|
# then memoize the FROMs etc.
|
|
_MemoizedSelectEntities._generate_for_statement(self)
|
|
|
|
self._raw_columns = [
|
|
coercions.expect(roles.ColumnsClauseRole, c)
|
|
for c in coercions._expression_collection_was_a_list(
|
|
"entities", "Select.with_only_columns", entities
|
|
)
|
|
]
|
|
return self
|
|
|
|
@property
|
|
def whereclause(self) -> Optional[ColumnElement[Any]]:
|
|
"""Return the completed WHERE clause for this
|
|
:class:`_expression.Select` statement.
|
|
|
|
This assembles the current collection of WHERE criteria
|
|
into a single :class:`_expression.BooleanClauseList` construct.
|
|
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
"""
|
|
|
|
return BooleanClauseList._construct_for_whereclause(
|
|
self._where_criteria
|
|
)
|
|
|
|
_whereclause = whereclause
|
|
|
|
@_generative
|
|
def where(self, *whereclause: _ColumnExpressionArgument[bool]) -> Self:
|
|
"""Return a new :func:`_expression.select` construct with
|
|
the given expression added to
|
|
its WHERE clause, joined to the existing clause via AND, if any.
|
|
|
|
"""
|
|
|
|
assert isinstance(self._where_criteria, tuple)
|
|
|
|
for criterion in whereclause:
|
|
where_criteria: ColumnElement[Any] = coercions.expect(
|
|
roles.WhereHavingRole, criterion, apply_propagate_attrs=self
|
|
)
|
|
self._where_criteria += (where_criteria,)
|
|
return self
|
|
|
|
@_generative
|
|
def having(self, *having: _ColumnExpressionArgument[bool]) -> Self:
|
|
"""Return a new :func:`_expression.select` construct with
|
|
the given expression added to
|
|
its HAVING clause, joined to the existing clause via AND, if any.
|
|
|
|
"""
|
|
|
|
for criterion in having:
|
|
having_criteria = coercions.expect(
|
|
roles.WhereHavingRole, criterion, apply_propagate_attrs=self
|
|
)
|
|
self._having_criteria += (having_criteria,)
|
|
return self
|
|
|
|
@_generative
|
|
def distinct(self, *expr: _ColumnExpressionArgument[Any]) -> Self:
|
|
r"""Return a new :func:`_expression.select` construct which
|
|
will apply DISTINCT to its columns clause.
|
|
|
|
:param \*expr: optional column expressions. When present,
|
|
the PostgreSQL dialect will render a ``DISTINCT ON (<expressions>>)``
|
|
construct.
|
|
|
|
.. deprecated:: 1.4 Using \*expr in other dialects is deprecated
|
|
and will raise :class:`_exc.CompileError` in a future version.
|
|
|
|
"""
|
|
if expr:
|
|
self._distinct = True
|
|
self._distinct_on = self._distinct_on + tuple(
|
|
coercions.expect(roles.ByOfRole, e, apply_propagate_attrs=self)
|
|
for e in expr
|
|
)
|
|
else:
|
|
self._distinct = True
|
|
return self
|
|
|
|
@_generative
|
|
def select_from(self, *froms: _FromClauseArgument) -> Self:
|
|
r"""Return a new :func:`_expression.select` construct with the
|
|
given FROM expression(s)
|
|
merged into its list of FROM objects.
|
|
|
|
E.g.::
|
|
|
|
table1 = table('t1', column('a'))
|
|
table2 = table('t2', column('b'))
|
|
s = select(table1.c.a).\
|
|
select_from(
|
|
table1.join(table2, table1.c.a==table2.c.b)
|
|
)
|
|
|
|
The "from" list is a unique set on the identity of each element,
|
|
so adding an already present :class:`_schema.Table`
|
|
or other selectable
|
|
will have no effect. Passing a :class:`_expression.Join` that refers
|
|
to an already present :class:`_schema.Table`
|
|
or other selectable will have
|
|
the effect of concealing the presence of that selectable as
|
|
an individual element in the rendered FROM list, instead
|
|
rendering it into a JOIN clause.
|
|
|
|
While the typical purpose of :meth:`_expression.Select.select_from`
|
|
is to
|
|
replace the default, derived FROM clause with a join, it can
|
|
also be called with individual table elements, multiple times
|
|
if desired, in the case that the FROM clause cannot be fully
|
|
derived from the columns clause::
|
|
|
|
select(func.count('*')).select_from(table1)
|
|
|
|
"""
|
|
|
|
self._from_obj += tuple(
|
|
coercions.expect(
|
|
roles.FromClauseRole, fromclause, apply_propagate_attrs=self
|
|
)
|
|
for fromclause in froms
|
|
)
|
|
return self
|
|
|
|
@_generative
|
|
def correlate(
|
|
self,
|
|
*fromclauses: Union[Literal[None, False], _FromClauseArgument],
|
|
) -> Self:
|
|
r"""Return a new :class:`_expression.Select`
|
|
which will correlate the given FROM
|
|
clauses to that of an enclosing :class:`_expression.Select`.
|
|
|
|
Calling this method turns off the :class:`_expression.Select` object's
|
|
default behavior of "auto-correlation". Normally, FROM elements
|
|
which appear in a :class:`_expression.Select`
|
|
that encloses this one via
|
|
its :term:`WHERE clause`, ORDER BY, HAVING or
|
|
:term:`columns clause` will be omitted from this
|
|
:class:`_expression.Select`
|
|
object's :term:`FROM clause`.
|
|
Setting an explicit correlation collection using the
|
|
:meth:`_expression.Select.correlate`
|
|
method provides a fixed list of FROM objects
|
|
that can potentially take place in this process.
|
|
|
|
When :meth:`_expression.Select.correlate`
|
|
is used to apply specific FROM clauses
|
|
for correlation, the FROM elements become candidates for
|
|
correlation regardless of how deeply nested this
|
|
:class:`_expression.Select`
|
|
object is, relative to an enclosing :class:`_expression.Select`
|
|
which refers to
|
|
the same FROM object. This is in contrast to the behavior of
|
|
"auto-correlation" which only correlates to an immediate enclosing
|
|
:class:`_expression.Select`.
|
|
Multi-level correlation ensures that the link
|
|
between enclosed and enclosing :class:`_expression.Select`
|
|
is always via
|
|
at least one WHERE/ORDER BY/HAVING/columns clause in order for
|
|
correlation to take place.
|
|
|
|
If ``None`` is passed, the :class:`_expression.Select`
|
|
object will correlate
|
|
none of its FROM entries, and all will render unconditionally
|
|
in the local FROM clause.
|
|
|
|
:param \*fromclauses: one or more :class:`.FromClause` or other
|
|
FROM-compatible construct such as an ORM mapped entity to become part
|
|
of the correlate collection; alternatively pass a single value
|
|
``None`` to remove all existing correlations.
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_expression.Select.correlate_except`
|
|
|
|
:ref:`tutorial_scalar_subquery`
|
|
|
|
"""
|
|
|
|
# tests failing when we try to change how these
|
|
# arguments are passed
|
|
|
|
self._auto_correlate = False
|
|
if not fromclauses or fromclauses[0] in {None, False}:
|
|
if len(fromclauses) > 1:
|
|
raise exc.ArgumentError(
|
|
"additional FROM objects not accepted when "
|
|
"passing None/False to correlate()"
|
|
)
|
|
self._correlate = ()
|
|
else:
|
|
self._correlate = self._correlate + tuple(
|
|
coercions.expect(roles.FromClauseRole, f) for f in fromclauses
|
|
)
|
|
return self
|
|
|
|
@_generative
|
|
def correlate_except(
|
|
self,
|
|
*fromclauses: Union[Literal[None, False], _FromClauseArgument],
|
|
) -> Self:
|
|
r"""Return a new :class:`_expression.Select`
|
|
which will omit the given FROM
|
|
clauses from the auto-correlation process.
|
|
|
|
Calling :meth:`_expression.Select.correlate_except` turns off the
|
|
:class:`_expression.Select` object's default behavior of
|
|
"auto-correlation" for the given FROM elements. An element
|
|
specified here will unconditionally appear in the FROM list, while
|
|
all other FROM elements remain subject to normal auto-correlation
|
|
behaviors.
|
|
|
|
If ``None`` is passed, or no arguments are passed,
|
|
the :class:`_expression.Select` object will correlate all of its
|
|
FROM entries.
|
|
|
|
:param \*fromclauses: a list of one or more
|
|
:class:`_expression.FromClause`
|
|
constructs, or other compatible constructs (i.e. ORM-mapped
|
|
classes) to become part of the correlate-exception collection.
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_expression.Select.correlate`
|
|
|
|
:ref:`tutorial_scalar_subquery`
|
|
|
|
"""
|
|
|
|
self._auto_correlate = False
|
|
if not fromclauses or fromclauses[0] in {None, False}:
|
|
if len(fromclauses) > 1:
|
|
raise exc.ArgumentError(
|
|
"additional FROM objects not accepted when "
|
|
"passing None/False to correlate_except()"
|
|
)
|
|
self._correlate_except = ()
|
|
else:
|
|
self._correlate_except = (self._correlate_except or ()) + tuple(
|
|
coercions.expect(roles.FromClauseRole, f) for f in fromclauses
|
|
)
|
|
|
|
return self
|
|
|
|
@HasMemoized_ro_memoized_attribute
|
|
def selected_columns(
|
|
self,
|
|
) -> ColumnCollection[str, ColumnElement[Any]]:
|
|
"""A :class:`_expression.ColumnCollection`
|
|
representing the columns that
|
|
this SELECT statement or similar construct returns in its result set,
|
|
not including :class:`_sql.TextClause` constructs.
|
|
|
|
This collection differs from the :attr:`_expression.FromClause.columns`
|
|
collection of a :class:`_expression.FromClause` in that the columns
|
|
within this collection cannot be directly nested inside another SELECT
|
|
statement; a subquery must be applied first which provides for the
|
|
necessary parenthesization required by SQL.
|
|
|
|
For a :func:`_expression.select` construct, the collection here is
|
|
exactly what would be rendered inside the "SELECT" statement, and the
|
|
:class:`_expression.ColumnElement` objects are directly present as they
|
|
were given, e.g.::
|
|
|
|
col1 = column('q', Integer)
|
|
col2 = column('p', Integer)
|
|
stmt = select(col1, col2)
|
|
|
|
Above, ``stmt.selected_columns`` would be a collection that contains
|
|
the ``col1`` and ``col2`` objects directly. For a statement that is
|
|
against a :class:`_schema.Table` or other
|
|
:class:`_expression.FromClause`, the collection will use the
|
|
:class:`_expression.ColumnElement` objects that are in the
|
|
:attr:`_expression.FromClause.c` collection of the from element.
|
|
|
|
A use case for the :attr:`_sql.Select.selected_columns` collection is
|
|
to allow the existing columns to be referenced when adding additional
|
|
criteria, e.g.::
|
|
|
|
def filter_on_id(my_select, id):
|
|
return my_select.where(my_select.selected_columns['id'] == id)
|
|
|
|
stmt = select(MyModel)
|
|
|
|
# adds "WHERE id=:param" to the statement
|
|
stmt = filter_on_id(stmt, 42)
|
|
|
|
.. note::
|
|
|
|
The :attr:`_sql.Select.selected_columns` collection does not
|
|
include expressions established in the columns clause using the
|
|
:func:`_sql.text` construct; these are silently omitted from the
|
|
collection. To use plain textual column expressions inside of a
|
|
:class:`_sql.Select` construct, use the :func:`_sql.literal_column`
|
|
construct.
|
|
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
"""
|
|
|
|
# compare to SelectState._generate_columns_plus_names, which
|
|
# generates the actual names used in the SELECT string. that
|
|
# method is more complex because it also renders columns that are
|
|
# fully ambiguous, e.g. same column more than once.
|
|
conv = cast(
|
|
"Callable[[Any], str]",
|
|
SelectState._column_naming_convention(self._label_style),
|
|
)
|
|
|
|
cc: ColumnCollection[str, ColumnElement[Any]] = ColumnCollection(
|
|
[
|
|
(conv(c), c)
|
|
for c in self._all_selected_columns
|
|
if is_column_element(c)
|
|
]
|
|
)
|
|
return cc.as_readonly()
|
|
|
|
@HasMemoized_ro_memoized_attribute
|
|
def _all_selected_columns(self) -> _SelectIterable:
|
|
meth = SelectState.get_plugin_class(self).all_selected_columns
|
|
return list(meth(self))
|
|
|
|
def _ensure_disambiguated_names(self) -> Select[Any]:
|
|
if self._label_style is LABEL_STYLE_NONE:
|
|
self = self.set_label_style(LABEL_STYLE_DISAMBIGUATE_ONLY)
|
|
return self
|
|
|
|
def _generate_fromclause_column_proxies(
|
|
self,
|
|
subquery: FromClause,
|
|
*,
|
|
proxy_compound_columns: Optional[
|
|
Iterable[Sequence[ColumnElement[Any]]]
|
|
] = None,
|
|
) -> None:
|
|
"""Generate column proxies to place in the exported ``.c``
|
|
collection of a subquery."""
|
|
|
|
if proxy_compound_columns:
|
|
extra_col_iterator = proxy_compound_columns
|
|
prox = [
|
|
c._make_proxy(
|
|
subquery,
|
|
key=proxy_key,
|
|
name=required_label_name,
|
|
name_is_truncatable=True,
|
|
compound_select_cols=extra_cols,
|
|
)
|
|
for (
|
|
(
|
|
required_label_name,
|
|
proxy_key,
|
|
fallback_label_name,
|
|
c,
|
|
repeated,
|
|
),
|
|
extra_cols,
|
|
) in (
|
|
zip(
|
|
self._generate_columns_plus_names(False),
|
|
extra_col_iterator,
|
|
)
|
|
)
|
|
if is_column_element(c)
|
|
]
|
|
else:
|
|
prox = [
|
|
c._make_proxy(
|
|
subquery,
|
|
key=proxy_key,
|
|
name=required_label_name,
|
|
name_is_truncatable=True,
|
|
)
|
|
for (
|
|
required_label_name,
|
|
proxy_key,
|
|
fallback_label_name,
|
|
c,
|
|
repeated,
|
|
) in (self._generate_columns_plus_names(False))
|
|
if is_column_element(c)
|
|
]
|
|
|
|
subquery._columns._populate_separate_keys(prox)
|
|
|
|
def _needs_parens_for_grouping(self) -> bool:
|
|
return self._has_row_limiting_clause or bool(
|
|
self._order_by_clause.clauses
|
|
)
|
|
|
|
def self_group(
|
|
self, against: Optional[OperatorType] = None
|
|
) -> Union[SelectStatementGrouping[Self], Self]:
|
|
...
|
|
"""Return a 'grouping' construct as per the
|
|
:class:`_expression.ClauseElement` specification.
|
|
|
|
This produces an element that can be embedded in an expression. Note
|
|
that this method is called automatically as needed when constructing
|
|
expressions and should not require explicit use.
|
|
|
|
"""
|
|
if (
|
|
isinstance(against, CompoundSelect)
|
|
and not self._needs_parens_for_grouping()
|
|
):
|
|
return self
|
|
else:
|
|
return SelectStatementGrouping(self)
|
|
|
|
def union(
|
|
self, *other: _SelectStatementForCompoundArgument
|
|
) -> CompoundSelect:
|
|
r"""Return a SQL ``UNION`` of this select() construct against
|
|
the given selectables provided as positional arguments.
|
|
|
|
:param \*other: one or more elements with which to create a
|
|
UNION.
|
|
|
|
.. versionchanged:: 1.4.28
|
|
|
|
multiple elements are now accepted.
|
|
|
|
:param \**kwargs: keyword arguments are forwarded to the constructor
|
|
for the newly created :class:`_sql.CompoundSelect` object.
|
|
|
|
"""
|
|
return CompoundSelect._create_union(self, *other)
|
|
|
|
def union_all(
|
|
self, *other: _SelectStatementForCompoundArgument
|
|
) -> CompoundSelect:
|
|
r"""Return a SQL ``UNION ALL`` of this select() construct against
|
|
the given selectables provided as positional arguments.
|
|
|
|
:param \*other: one or more elements with which to create a
|
|
UNION.
|
|
|
|
.. versionchanged:: 1.4.28
|
|
|
|
multiple elements are now accepted.
|
|
|
|
:param \**kwargs: keyword arguments are forwarded to the constructor
|
|
for the newly created :class:`_sql.CompoundSelect` object.
|
|
|
|
"""
|
|
return CompoundSelect._create_union_all(self, *other)
|
|
|
|
def except_(
|
|
self, *other: _SelectStatementForCompoundArgument
|
|
) -> CompoundSelect:
|
|
r"""Return a SQL ``EXCEPT`` of this select() construct against
|
|
the given selectable provided as positional arguments.
|
|
|
|
:param \*other: one or more elements with which to create a
|
|
UNION.
|
|
|
|
.. versionchanged:: 1.4.28
|
|
|
|
multiple elements are now accepted.
|
|
|
|
"""
|
|
return CompoundSelect._create_except(self, *other)
|
|
|
|
def except_all(
|
|
self, *other: _SelectStatementForCompoundArgument
|
|
) -> CompoundSelect:
|
|
r"""Return a SQL ``EXCEPT ALL`` of this select() construct against
|
|
the given selectables provided as positional arguments.
|
|
|
|
:param \*other: one or more elements with which to create a
|
|
UNION.
|
|
|
|
.. versionchanged:: 1.4.28
|
|
|
|
multiple elements are now accepted.
|
|
|
|
"""
|
|
return CompoundSelect._create_except_all(self, *other)
|
|
|
|
def intersect(
|
|
self, *other: _SelectStatementForCompoundArgument
|
|
) -> CompoundSelect:
|
|
r"""Return a SQL ``INTERSECT`` of this select() construct against
|
|
the given selectables provided as positional arguments.
|
|
|
|
:param \*other: one or more elements with which to create a
|
|
UNION.
|
|
|
|
.. versionchanged:: 1.4.28
|
|
|
|
multiple elements are now accepted.
|
|
|
|
:param \**kwargs: keyword arguments are forwarded to the constructor
|
|
for the newly created :class:`_sql.CompoundSelect` object.
|
|
|
|
"""
|
|
return CompoundSelect._create_intersect(self, *other)
|
|
|
|
def intersect_all(
|
|
self, *other: _SelectStatementForCompoundArgument
|
|
) -> CompoundSelect:
|
|
r"""Return a SQL ``INTERSECT ALL`` of this select() construct
|
|
against the given selectables provided as positional arguments.
|
|
|
|
:param \*other: one or more elements with which to create a
|
|
UNION.
|
|
|
|
.. versionchanged:: 1.4.28
|
|
|
|
multiple elements are now accepted.
|
|
|
|
:param \**kwargs: keyword arguments are forwarded to the constructor
|
|
for the newly created :class:`_sql.CompoundSelect` object.
|
|
|
|
"""
|
|
return CompoundSelect._create_intersect_all(self, *other)
|
|
|
|
|
|
class ScalarSelect(
|
|
roles.InElementRole, Generative, GroupedElement, ColumnElement[_T]
|
|
):
|
|
"""Represent a scalar subquery.
|
|
|
|
|
|
A :class:`_sql.ScalarSelect` is created by invoking the
|
|
:meth:`_sql.SelectBase.scalar_subquery` method. The object
|
|
then participates in other SQL expressions as a SQL column expression
|
|
within the :class:`_sql.ColumnElement` hierarchy.
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_sql.SelectBase.scalar_subquery`
|
|
|
|
:ref:`tutorial_scalar_subquery` - in the 2.0 tutorial
|
|
|
|
"""
|
|
|
|
_traverse_internals: _TraverseInternalsType = [
|
|
("element", InternalTraversal.dp_clauseelement),
|
|
("type", InternalTraversal.dp_type),
|
|
]
|
|
|
|
_from_objects: List[FromClause] = []
|
|
_is_from_container = True
|
|
if not TYPE_CHECKING:
|
|
_is_implicitly_boolean = False
|
|
inherit_cache = True
|
|
|
|
element: SelectBase
|
|
|
|
def __init__(self, element: SelectBase) -> None:
|
|
self.element = element
|
|
self.type = element._scalar_type()
|
|
self._propagate_attrs = element._propagate_attrs
|
|
|
|
def __getattr__(self, attr: str) -> Any:
|
|
return getattr(self.element, attr)
|
|
|
|
def __getstate__(self) -> Dict[str, Any]:
|
|
return {"element": self.element, "type": self.type}
|
|
|
|
def __setstate__(self, state: Dict[str, Any]) -> None:
|
|
self.element = state["element"]
|
|
self.type = state["type"]
|
|
|
|
@property
|
|
def columns(self) -> NoReturn:
|
|
raise exc.InvalidRequestError(
|
|
"Scalar Select expression has no "
|
|
"columns; use this object directly "
|
|
"within a column-level expression."
|
|
)
|
|
|
|
c = columns
|
|
|
|
@_generative
|
|
def where(self, crit: _ColumnExpressionArgument[bool]) -> Self:
|
|
"""Apply a WHERE clause to the SELECT statement referred to
|
|
by this :class:`_expression.ScalarSelect`.
|
|
|
|
"""
|
|
self.element = cast("Select[Any]", self.element).where(crit)
|
|
return self
|
|
|
|
@overload
|
|
def self_group(
|
|
self: ScalarSelect[Any], against: Optional[OperatorType] = None
|
|
) -> ScalarSelect[Any]: ...
|
|
|
|
@overload
|
|
def self_group(
|
|
self: ColumnElement[Any], against: Optional[OperatorType] = None
|
|
) -> ColumnElement[Any]: ...
|
|
|
|
def self_group(
|
|
self, against: Optional[OperatorType] = None
|
|
) -> ColumnElement[Any]:
|
|
return self
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
def _ungroup(self) -> Select[Any]: ...
|
|
|
|
@_generative
|
|
def correlate(
|
|
self,
|
|
*fromclauses: Union[Literal[None, False], _FromClauseArgument],
|
|
) -> Self:
|
|
r"""Return a new :class:`_expression.ScalarSelect`
|
|
which will correlate the given FROM
|
|
clauses to that of an enclosing :class:`_expression.Select`.
|
|
|
|
This method is mirrored from the :meth:`_sql.Select.correlate` method
|
|
of the underlying :class:`_sql.Select`. The method applies the
|
|
:meth:_sql.Select.correlate` method, then returns a new
|
|
:class:`_sql.ScalarSelect` against that statement.
|
|
|
|
.. versionadded:: 1.4 Previously, the
|
|
:meth:`_sql.ScalarSelect.correlate`
|
|
method was only available from :class:`_sql.Select`.
|
|
|
|
:param \*fromclauses: a list of one or more
|
|
:class:`_expression.FromClause`
|
|
constructs, or other compatible constructs (i.e. ORM-mapped
|
|
classes) to become part of the correlate collection.
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_expression.ScalarSelect.correlate_except`
|
|
|
|
:ref:`tutorial_scalar_subquery` - in the 2.0 tutorial
|
|
|
|
|
|
"""
|
|
self.element = cast("Select[Any]", self.element).correlate(
|
|
*fromclauses
|
|
)
|
|
return self
|
|
|
|
@_generative
|
|
def correlate_except(
|
|
self,
|
|
*fromclauses: Union[Literal[None, False], _FromClauseArgument],
|
|
) -> Self:
|
|
r"""Return a new :class:`_expression.ScalarSelect`
|
|
which will omit the given FROM
|
|
clauses from the auto-correlation process.
|
|
|
|
This method is mirrored from the
|
|
:meth:`_sql.Select.correlate_except` method of the underlying
|
|
:class:`_sql.Select`. The method applies the
|
|
:meth:_sql.Select.correlate_except` method, then returns a new
|
|
:class:`_sql.ScalarSelect` against that statement.
|
|
|
|
.. versionadded:: 1.4 Previously, the
|
|
:meth:`_sql.ScalarSelect.correlate_except`
|
|
method was only available from :class:`_sql.Select`.
|
|
|
|
:param \*fromclauses: a list of one or more
|
|
:class:`_expression.FromClause`
|
|
constructs, or other compatible constructs (i.e. ORM-mapped
|
|
classes) to become part of the correlate-exception collection.
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_expression.ScalarSelect.correlate`
|
|
|
|
:ref:`tutorial_scalar_subquery` - in the 2.0 tutorial
|
|
|
|
|
|
"""
|
|
|
|
self.element = cast("Select[Any]", self.element).correlate_except(
|
|
*fromclauses
|
|
)
|
|
return self
|
|
|
|
|
|
class Exists(UnaryExpression[bool]):
|
|
"""Represent an ``EXISTS`` clause.
|
|
|
|
See :func:`_sql.exists` for a description of usage.
|
|
|
|
An ``EXISTS`` clause can also be constructed from a :func:`_sql.select`
|
|
instance by calling :meth:`_sql.SelectBase.exists`.
|
|
|
|
"""
|
|
|
|
inherit_cache = True
|
|
element: Union[SelectStatementGrouping[Select[Any]], ScalarSelect[Any]]
|
|
|
|
def __init__(
|
|
self,
|
|
__argument: Optional[
|
|
Union[_ColumnsClauseArgument[Any], SelectBase, ScalarSelect[Any]]
|
|
] = None,
|
|
):
|
|
s: ScalarSelect[Any]
|
|
|
|
# TODO: this seems like we should be using coercions for this
|
|
if __argument is None:
|
|
s = Select(literal_column("*")).scalar_subquery()
|
|
elif isinstance(__argument, SelectBase):
|
|
s = __argument.scalar_subquery()
|
|
s._propagate_attrs = __argument._propagate_attrs
|
|
elif isinstance(__argument, ScalarSelect):
|
|
s = __argument
|
|
else:
|
|
s = Select(__argument).scalar_subquery()
|
|
|
|
UnaryExpression.__init__(
|
|
self,
|
|
s,
|
|
operator=operators.exists,
|
|
type_=type_api.BOOLEANTYPE,
|
|
wraps_column_expression=True,
|
|
)
|
|
|
|
@util.ro_non_memoized_property
|
|
def _from_objects(self) -> List[FromClause]:
|
|
return []
|
|
|
|
def _regroup(
|
|
self, fn: Callable[[Select[Any]], Select[Any]]
|
|
) -> SelectStatementGrouping[Select[Any]]:
|
|
element = self.element._ungroup()
|
|
new_element = fn(element)
|
|
|
|
return_value = new_element.self_group(against=operators.exists)
|
|
assert isinstance(return_value, SelectStatementGrouping)
|
|
return return_value
|
|
|
|
def select(self) -> Select[Any]:
|
|
r"""Return a SELECT of this :class:`_expression.Exists`.
|
|
|
|
e.g.::
|
|
|
|
stmt = exists(some_table.c.id).where(some_table.c.id == 5).select()
|
|
|
|
This will produce a statement resembling::
|
|
|
|
SELECT EXISTS (SELECT id FROM some_table WHERE some_table = :param) AS anon_1
|
|
|
|
.. seealso::
|
|
|
|
:func:`_expression.select` - general purpose
|
|
method which allows for arbitrary column lists.
|
|
|
|
""" # noqa
|
|
|
|
return Select(self)
|
|
|
|
def correlate(
|
|
self,
|
|
*fromclauses: Union[Literal[None, False], _FromClauseArgument],
|
|
) -> Self:
|
|
"""Apply correlation to the subquery noted by this
|
|
:class:`_sql.Exists`.
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_sql.ScalarSelect.correlate`
|
|
|
|
"""
|
|
e = self._clone()
|
|
e.element = self._regroup(
|
|
lambda element: element.correlate(*fromclauses)
|
|
)
|
|
return e
|
|
|
|
def correlate_except(
|
|
self,
|
|
*fromclauses: Union[Literal[None, False], _FromClauseArgument],
|
|
) -> Self:
|
|
"""Apply correlation to the subquery noted by this
|
|
:class:`_sql.Exists`.
|
|
|
|
.. seealso::
|
|
|
|
:meth:`_sql.ScalarSelect.correlate_except`
|
|
|
|
"""
|
|
|
|
e = self._clone()
|
|
e.element = self._regroup(
|
|
lambda element: element.correlate_except(*fromclauses)
|
|
)
|
|
return e
|
|
|
|
def select_from(self, *froms: _FromClauseArgument) -> Self:
|
|
"""Return a new :class:`_expression.Exists` construct,
|
|
applying the given
|
|
expression to the :meth:`_expression.Select.select_from`
|
|
method of the select
|
|
statement contained.
|
|
|
|
.. note:: it is typically preferable to build a :class:`_sql.Select`
|
|
statement first, including the desired WHERE clause, then use the
|
|
:meth:`_sql.SelectBase.exists` method to produce an
|
|
:class:`_sql.Exists` object at once.
|
|
|
|
"""
|
|
e = self._clone()
|
|
e.element = self._regroup(lambda element: element.select_from(*froms))
|
|
return e
|
|
|
|
def where(self, *clause: _ColumnExpressionArgument[bool]) -> Self:
|
|
"""Return a new :func:`_expression.exists` construct with the
|
|
given expression added to
|
|
its WHERE clause, joined to the existing clause via AND, if any.
|
|
|
|
|
|
.. note:: it is typically preferable to build a :class:`_sql.Select`
|
|
statement first, including the desired WHERE clause, then use the
|
|
:meth:`_sql.SelectBase.exists` method to produce an
|
|
:class:`_sql.Exists` object at once.
|
|
|
|
"""
|
|
e = self._clone()
|
|
e.element = self._regroup(lambda element: element.where(*clause))
|
|
return e
|
|
|
|
|
|
class TextualSelect(SelectBase, ExecutableReturnsRows, Generative):
|
|
"""Wrap a :class:`_expression.TextClause` construct within a
|
|
:class:`_expression.SelectBase`
|
|
interface.
|
|
|
|
This allows the :class:`_expression.TextClause` object to gain a
|
|
``.c`` collection
|
|
and other FROM-like capabilities such as
|
|
:meth:`_expression.FromClause.alias`,
|
|
:meth:`_expression.SelectBase.cte`, etc.
|
|
|
|
The :class:`_expression.TextualSelect` construct is produced via the
|
|
:meth:`_expression.TextClause.columns`
|
|
method - see that method for details.
|
|
|
|
.. versionchanged:: 1.4 the :class:`_expression.TextualSelect`
|
|
class was renamed
|
|
from ``TextAsFrom``, to more correctly suit its role as a
|
|
SELECT-oriented object and not a FROM clause.
|
|
|
|
.. seealso::
|
|
|
|
:func:`_expression.text`
|
|
|
|
:meth:`_expression.TextClause.columns` - primary creation interface.
|
|
|
|
"""
|
|
|
|
__visit_name__ = "textual_select"
|
|
|
|
_label_style = LABEL_STYLE_NONE
|
|
|
|
_traverse_internals: _TraverseInternalsType = [
|
|
("element", InternalTraversal.dp_clauseelement),
|
|
("column_args", InternalTraversal.dp_clauseelement_list),
|
|
] + SupportsCloneAnnotations._clone_annotations_traverse_internals
|
|
|
|
_is_textual = True
|
|
|
|
is_text = True
|
|
is_select = True
|
|
|
|
def __init__(
|
|
self,
|
|
text: TextClause,
|
|
columns: List[_ColumnExpressionArgument[Any]],
|
|
positional: bool = False,
|
|
) -> None:
|
|
self._init(
|
|
text,
|
|
# convert for ORM attributes->columns, etc
|
|
[
|
|
coercions.expect(roles.LabeledColumnExprRole, c)
|
|
for c in columns
|
|
],
|
|
positional,
|
|
)
|
|
|
|
def _init(
|
|
self,
|
|
text: TextClause,
|
|
columns: List[NamedColumn[Any]],
|
|
positional: bool = False,
|
|
) -> None:
|
|
self.element = text
|
|
self.column_args = columns
|
|
self.positional = positional
|
|
|
|
@HasMemoized_ro_memoized_attribute
|
|
def selected_columns(
|
|
self,
|
|
) -> ColumnCollection[str, KeyedColumnElement[Any]]:
|
|
"""A :class:`_expression.ColumnCollection`
|
|
representing the columns that
|
|
this SELECT statement or similar construct returns in its result set,
|
|
not including :class:`_sql.TextClause` constructs.
|
|
|
|
This collection differs from the :attr:`_expression.FromClause.columns`
|
|
collection of a :class:`_expression.FromClause` in that the columns
|
|
within this collection cannot be directly nested inside another SELECT
|
|
statement; a subquery must be applied first which provides for the
|
|
necessary parenthesization required by SQL.
|
|
|
|
For a :class:`_expression.TextualSelect` construct, the collection
|
|
contains the :class:`_expression.ColumnElement` objects that were
|
|
passed to the constructor, typically via the
|
|
:meth:`_expression.TextClause.columns` method.
|
|
|
|
|
|
.. versionadded:: 1.4
|
|
|
|
"""
|
|
return ColumnCollection(
|
|
(c.key, c) for c in self.column_args
|
|
).as_readonly()
|
|
|
|
@util.ro_non_memoized_property
|
|
def _all_selected_columns(self) -> _SelectIterable:
|
|
return self.column_args
|
|
|
|
def set_label_style(self, style: SelectLabelStyle) -> TextualSelect:
|
|
return self
|
|
|
|
def _ensure_disambiguated_names(self) -> TextualSelect:
|
|
return self
|
|
|
|
@_generative
|
|
def bindparams(
|
|
self,
|
|
*binds: BindParameter[Any],
|
|
**bind_as_values: Any,
|
|
) -> Self:
|
|
self.element = self.element.bindparams(*binds, **bind_as_values)
|
|
return self
|
|
|
|
def _generate_fromclause_column_proxies(
|
|
self,
|
|
fromclause: FromClause,
|
|
*,
|
|
proxy_compound_columns: Optional[
|
|
Iterable[Sequence[ColumnElement[Any]]]
|
|
] = None,
|
|
) -> None:
|
|
if TYPE_CHECKING:
|
|
assert isinstance(fromclause, Subquery)
|
|
|
|
if proxy_compound_columns:
|
|
fromclause._columns._populate_separate_keys(
|
|
c._make_proxy(fromclause, compound_select_cols=extra_cols)
|
|
for c, extra_cols in zip(
|
|
self.column_args, proxy_compound_columns
|
|
)
|
|
)
|
|
else:
|
|
fromclause._columns._populate_separate_keys(
|
|
c._make_proxy(fromclause) for c in self.column_args
|
|
)
|
|
|
|
def _scalar_type(self) -> Union[TypeEngine[Any], Any]:
|
|
return self.column_args[0].type
|
|
|
|
|
|
TextAsFrom = TextualSelect
|
|
"""Backwards compatibility with the previous name"""
|
|
|
|
|
|
class AnnotatedFromClause(Annotated):
|
|
def _copy_internals(self, **kw: Any) -> None:
|
|
super()._copy_internals(**kw)
|
|
if kw.get("ind_cols_on_fromclause", False):
|
|
ee = self._Annotated__element # type: ignore
|
|
|
|
self.c = ee.__class__.c.fget(self) # type: ignore
|
|
|
|
@util.ro_memoized_property
|
|
def c(self) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
|
|
"""proxy the .c collection of the underlying FromClause.
|
|
|
|
Originally implemented in 2008 as a simple load of the .c collection
|
|
when the annotated construct was created (see d3621ae961a), in modern
|
|
SQLAlchemy versions this can be expensive for statements constructed
|
|
with ORM aliases. So for #8796 SQLAlchemy 2.0 we instead proxy
|
|
it, which works just as well.
|
|
|
|
Two different use cases seem to require the collection either copied
|
|
from the underlying one, or unique to this AnnotatedFromClause.
|
|
|
|
See test_selectable->test_annotated_corresponding_column
|
|
|
|
"""
|
|
ee = self._Annotated__element # type: ignore
|
|
return ee.c # type: ignore
|