diff --git a/venv/Lib/site-packages/sqlalchemy/sql/selectable.py b/venv/Lib/site-packages/sqlalchemy/sql/selectable.py new file mode 100644 index 0000000..14a2674 --- /dev/null +++ b/venv/Lib/site-packages/sqlalchemy/sql/selectable.py @@ -0,0 +1,7250 @@ +# sql/selectable.py +# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors +# +# +# 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 _T +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 DialectKWArgs +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_ + + +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 _OnlyColumnArgument + 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 column, Table, ORM entity + Sequence[ + Union[ + "_ColumnExpressionArgument[Any]", + "_FromClauseArgument", + ] + ], +] + + +_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, + columns: ColumnCollection[str, KeyedColumnElement[Any]], + primary_key: ColumnSet, + foreign_keys: Set[KeyedColumnElement[Any]], + ) -> 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 a typed 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), + ] + + @_generative + def with_statement_hint(self, text: str, dialect_name: str = "*") -> Self: + """Add a statement hint to this :class:`_expression.Select` or + other selectable object. + + .. tip:: + + :meth:`_expression.Select.with_statement_hint` generally adds hints + **at the trailing end** of a SELECT statement. To place + dialect-specific hints such as optimizer hints at the **front** of + the SELECT statement after the SELECT keyword, use the + :meth:`_expression.Select.prefix_with` method for an open-ended + space, or for table-specific hints the + :meth:`_expression.Select.with_hint` may be used, which places + hints in a dialect-specific location. + + 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 or Oracle Database 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. + + .. tip:: + + The :meth:`_expression.Select.with_hint` method adds hints that are + **specific to a single table** to a statement, in a location that + is **dialect-specific**. To add generic optimizer hints to the + **beginning** of a statement ahead of the SELECT keyword such as + for MySQL or Oracle Database, use the + :meth:`_expression.Select.prefix_with` method. To add optimizer + hints to the **end** of a statement such as for PostgreSQL, use the + :meth:`_expression.Select.with_statement_hint` method. + + 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 Database, the + following:: + + select(mytable).with_hint(mytable, "index(%(name)s ix_mytable)") + + Would render SQL as: + + .. sourcecode:: sql + + 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 + Database and MSSql 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` + + :meth:`_expression.Select.prefix_with` - generic SELECT prefixing + which also can suit some database-specific HINT syntaxes such as + MySQL or Oracle Database optimizer hints + + """ + + 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: + + .. sourcecode:: sql + + 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` + + """ # noqa: E501 + + 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, + columns: ColumnCollection[str, KeyedColumnElement[Any]], + primary_key: ColumnSet, + foreign_keys: Set[KeyedColumnElement[Any]], + ) -> None: + columns._populate_separate_keys( + col._make_proxy( + fromclause, primary_key=primary_key, foreign_keys=foreign_keys + ) + 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._setup_collections() + return self._columns.as_readonly() + + def _setup_collections(self) -> None: + with util.mini_gil: + # detect another thread that raced ahead + if "_columns" in self.__dict__: + assert "primary_key" in self.__dict__ + assert "foreign_keys" in self.__dict__ + return + + _columns: ColumnCollection[Any, Any] = ColumnCollection() + primary_key = ColumnSet() + foreign_keys: Set[KeyedColumnElement[Any]] = set() + + self._populate_column_collection( + columns=_columns, + primary_key=primary_key, + foreign_keys=foreign_keys, + ) + + # assigning these three collections separately is not itself + # atomic, but greatly reduces the surface for problems + self._columns = _columns + self.primary_key = primary_key # type: ignore + self.foreign_keys = foreign_keys # type: ignore + + @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._setup_collections() + 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._setup_collections() + 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)) + + @property + def _cols_populated(self) -> bool: + return "_columns" in self.__dict__ + + def _populate_column_collection( + self, + columns: ColumnCollection[str, KeyedColumnElement[Any]], + primary_key: ColumnSet, + foreign_keys: Set[KeyedColumnElement[Any]], + ) -> 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 Database 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 + ``_`` 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, + columns: ColumnCollection[str, KeyedColumnElement[Any]], + primary_key: ColumnSet, + foreign_keys: Set[KeyedColumnElement[Any]], + ) -> None: + sqlutil = util.preloaded.sql_util + _columns: List[KeyedColumnElement[Any]] = [c for c in self.left.c] + [ + c for c in self.right.c + ] + + primary_key.extend( + sqlutil.reduce_columns( + (c for c in _columns if c.primary_key), self.onclause + ) + ) + columns._populate_separate_keys( + (col._tq_key_label, col) for col in _columns # type: ignore + ) + foreign_keys.update( + itertools.chain(*[col.foreign_keys for col in _columns]) # type: ignore # noqa: E501 + ) + + 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[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: + + .. sourcecode:: sql + + 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 isinstance(self.left, (FromGrouping, Join)): + left_name = name # will recurse + else: + if name and isinstance(self.left, NamedFromClause): + left_name = f"{name}_{self.left.name}" + else: + left_name = name + if isinstance(self.right, (FromGrouping, Join)): + right_name = name # will recurse + else: + if name and isinstance(self.right, NamedFromClause): + right_name = f"{name}_{self.right.name}" + else: + right_name = name + left_a, right_a = ( + self.left._anonymous_fromclause(name=left_name, flat=flat), + self.right._anonymous_fromclause(name=right_name, flat=flat), + ) + 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, + columns: ColumnCollection[str, KeyedColumnElement[Any]], + primary_key: ColumnSet, + foreign_keys: Set[KeyedColumnElement[Any]], + ) -> None: + self.element._generate_fromclause_column_proxies( + self, columns, primary_key=primary_key, foreign_keys=foreign_keys + ) + + @util.ro_non_memoized_property + def description(self) -> str: + name = self.name + if isinstance(name, _anonymous_label): + return "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 + + @util.ro_non_memoized_property + def description(self) -> str: + name = self.name + if isinstance(name, _anonymous_label): + return f"Anonymous alias of {self.element.description}" + + return name + + +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 Database). + + 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 reuse 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, + columns: ColumnCollection[str, KeyedColumnElement[Any]], + primary_key: ColumnSet, + foreign_keys: Set[KeyedColumnElement[Any]], + ) -> None: + if self._cte_alias is not None: + self._cte_alias._generate_fromclause_column_proxies( + self, + columns, + primary_key=primary_key, + foreign_keys=foreign_keys, + ) + else: + self.element._generate_fromclause_column_proxies( + self, + columns, + primary_key=primary_key, + foreign_keys=foreign_keys, + ) + + 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[Any]) -> 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[Any] + ) -> 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 " + """ + + 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 " 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, as well as tokens used to populate the .c. collection + on a :class:`.FromClause`. + + This is distinct from the _column_naming_convention generator that's + intended for population of the Select.selected_columns collection, + 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, ...] = () + + name_cte_columns: bool = False + """indicates if this HasCTE as contained within a CTE should compel the CTE + to render the column names of this object in the WITH clause. + + .. versionadded:: 2.0.42 + + """ + + @_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: + + .. sourcecode:: sql + + 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: + + .. sourcecode:: sql + + 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` + + + """ # noqa: E501 + 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: + + .. sourcecode:: sql + + 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: + + .. sourcecode:: sql + + 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`. + + """ # noqa: E501 + 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 " 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) + + @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"] + + if TYPE_CHECKING: + + def self_group( + self, against: Optional[OperatorType] = None + ) -> Self: ... + + +class NamedFromGrouping(FromGrouping, NamedFromClause): + """represent a grouping of a named FROM clause + + .. versionadded:: 2.0 + + """ + + inherit_cache = True + + if TYPE_CHECKING: + + def self_group( + self, against: Optional[OperatorType] = None + ) -> Self: ... + + +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 + + @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), + ("key_share", 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, HasCTE, 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, ...]], ...] = () + _column_args: Tuple[NamedColumn[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), + ] + HasCTE._has_ctes_traverse_internals + + name_cte_columns = True + + def __init__( + self, + *columns: _OnlyColumnArgument[Any], + name: Optional[str] = None, + literal_binds: bool = False, + ): + super().__init__() + self._column_args = tuple( + coercions.expect(roles.LabeledColumnExprRole, col) + for col in 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] + + @util.ro_non_memoized_property + def _all_selected_columns(self) -> _SelectIterable: + return 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) -> Self: + """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, + columns: ColumnCollection[str, KeyedColumnElement[Any]], + primary_key: ColumnSet, + foreign_keys: Set[KeyedColumnElement[Any]], + ) -> None: + for c in self._column_args: + if c.table is not None and c.table is not self: + _, c = c._make_proxy( + self, primary_key=primary_key, foreign_keys=foreign_keys + ) + 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() + 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[NamedColumn[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 + + if TYPE_CHECKING: + + def self_group( + self, against: Optional[OperatorType] = None + ) -> Self: ... + + def _ungroup(self) -> ColumnElement[Any]: ... + + +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, + columns: ColumnCollection[str, KeyedColumnElement[Any]], + primary_key: ColumnSet, + foreign_keys: Set[KeyedColumnElement[Any]], + *, + 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: + + .. sourcecode:: sql + + 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: + + .. sourcecode:: sql + + 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) + ] + SupportsCloneAnnotations._clone_annotations_traverse_internals + + _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, + columns: ColumnCollection[str, KeyedColumnElement[Any]], + primary_key: ColumnSet, + foreign_keys: Set[KeyedColumnElement[Any]], + *, + proxy_compound_columns: Optional[ + Iterable[Sequence[ColumnElement[Any]]] + ] = None, + ) -> None: + self.element._generate_fromclause_column_proxies( + subquery, + columns, + proxy_compound_columns=proxy_compound_columns, + primary_key=primary_key, + foreign_keys=foreign_keys, + ) + + @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 + + def _scalar_type(self) -> TypeEngine[Any]: + return self.element._scalar_type() + + def add_cte(self, *ctes: CTE, nest_here: bool = False) -> Self: + # SelectStatementGrouping not generative: has no attribute '_generate' + raise NotImplementedError + + +class GenerativeSelect(DialectKWArgs, 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 Database, the above would + render a statement like: + + .. sourcecode:: sql + + SELECT table.a, table.b FROM table FOR UPDATE NOWAIT + + on other backends, the ``nowait`` option is ignored and instead + would produce: + + .. sourcecode:: sql + + 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 + Database 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 + Database, 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 Database 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_DISAMBIGUATE_ONLY`. + + 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, + **dialect_kw: Any, + ) -> Self: + r"""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 + Database, 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`` + + :param \**dialect_kw: Additional dialect-specific keyword arguments + may be accepted by dialects. + + .. versionadded:: 2.0.41 + + .. seealso:: + + :meth:`_sql.GenerativeSelect.limit` + + :meth:`_sql.GenerativeSelect.offset` + + """ + self._validate_dialect_kwargs(dialect_kw) + 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. + + Alternatively, an individual entry may also be the string name of a + label located elsewhere in the columns clause of the statement which + will be matched and rendered in a backend-specific way based on + context; see :ref:`tutorial_order_by_label` for background on string + label matching in ORDER BY and GROUP BY expressions. + + .. 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. + + Alternatively, an individual entry may also be the string name of a + label located elsewhere in the columns clause of the statement which + will be matched and rendered in a backend-specific way based on + context; see :ref:`tutorial_order_by_label` for background on string + label matching in ORDER BY and GROUP BY expressions. + + .. seealso:: + + :ref:`tutorial_group_by_w_aggregates` - in the + :ref:`unified_tutorial` + + :ref:`tutorial_order_by_label` - in the :ref:`unified_tutorial` + + """ # noqa: E501 + + 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, TypedReturnsRows[_TP]): + """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 + + HasCTE._has_ctes_traverse_internals + + DialectKWArgs._dialect_kwargs_traverse_internals + + Executable._executable_traverse_internals + ) + + selects: List[SelectBase] + + _is_from_container = True + _auto_correlate = False + + def __init__( + self, + keyword: _CompoundSelectKeyword, + *selects: _SelectStatementForCompoundArgument[_TP], + ): + 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[_TP] + ) -> CompoundSelect[_TP]: + return CompoundSelect(_CompoundSelectKeyword.UNION, *selects) + + @classmethod + def _create_union_all( + cls, *selects: _SelectStatementForCompoundArgument[_TP] + ) -> CompoundSelect[_TP]: + return CompoundSelect(_CompoundSelectKeyword.UNION_ALL, *selects) + + @classmethod + def _create_except( + cls, *selects: _SelectStatementForCompoundArgument[_TP] + ) -> CompoundSelect[_TP]: + return CompoundSelect(_CompoundSelectKeyword.EXCEPT, *selects) + + @classmethod + def _create_except_all( + cls, *selects: _SelectStatementForCompoundArgument[_TP] + ) -> CompoundSelect[_TP]: + return CompoundSelect(_CompoundSelectKeyword.EXCEPT_ALL, *selects) + + @classmethod + def _create_intersect( + cls, *selects: _SelectStatementForCompoundArgument[_TP] + ) -> CompoundSelect[_TP]: + return CompoundSelect(_CompoundSelectKeyword.INTERSECT, *selects) + + @classmethod + def _create_intersect_all( + cls, *selects: _SelectStatementForCompoundArgument[_TP] + ) -> CompoundSelect[_TP]: + 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) -> Self: + 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) -> Self: + 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, + columns: ColumnCollection[str, KeyedColumnElement[Any]], + primary_key: ColumnSet, + foreign_keys: Set[KeyedColumnElement[Any]], + *, + 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, + columns, + proxy_compound_columns=extra_col_iterator, + primary_key=primary_key, + foreign_keys=foreign_keys, + ) + + 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: 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: + to_remove = _cloned_intersection( + _cloned_intersection( + froms, explicit_correlate_froms or () + ), + to_correlate, + ) + froms = [f for f in froms if f not in to_remove] + + if self.statement._correlate_except is not None: + to_remove = _cloned_difference( + _cloned_intersection(froms, explicit_correlate_froms or ()), + self.statement._correlate_except, + ) + froms = [f for f in froms if f not in to_remove] + + if ( + self.statement._auto_correlate + and implicit_correlate_froms + and len(froms) > 1 + ): + to_remove = _cloned_intersection(froms, implicit_correlate_froms) + froms = [f for f in froms if f not in to_remove] + + if not len(froms): + raise exc.InvalidRequestError( + "Select statement '%r" + "' returned no FROM clauses " + "due to auto-correlation; " + "specify correlate() " + "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 + + DialectKWArgs._dialect_kwargs_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], **dialect_kw: 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: + + .. sourcecode:: sql + + 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: + + .. sourcecode:: sql + + 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` + + """ + compiler = self._default_compiler() + + return self._compile_state_factory(self, compiler)._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[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 1-8 ", *, maintain_column_froms: bool =..." # noqa: E501 + + # code within this block is **programmatically, + # statically generated** by tools/generate_tuple_map_overloads.py + + @overload + def with_only_columns( + self, __ent0: _TCCA[_T0], *, maintain_column_froms: bool = ... + ) -> Select[Tuple[_T0]]: ... + + @overload + def with_only_columns( + self, + __ent0: _TCCA[_T0], + __ent1: _TCCA[_T1], + *, + maintain_column_froms: bool = ..., + ) -> Select[Tuple[_T0, _T1]]: ... + + @overload + def with_only_columns( + self, + __ent0: _TCCA[_T0], + __ent1: _TCCA[_T1], + __ent2: _TCCA[_T2], + *, + maintain_column_froms: bool = ..., + ) -> Select[Tuple[_T0, _T1, _T2]]: ... + + @overload + def with_only_columns( + self, + __ent0: _TCCA[_T0], + __ent1: _TCCA[_T1], + __ent2: _TCCA[_T2], + __ent3: _TCCA[_T3], + *, + maintain_column_froms: bool = ..., + ) -> 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], + *, + maintain_column_froms: bool = ..., + ) -> 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], + *, + maintain_column_froms: bool = ..., + ) -> 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], + *, + maintain_column_froms: bool = ..., + ) -> 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], + *, + maintain_column_froms: bool = ..., + ) -> 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 the SELECT statement overall. + + E.g.:: + + from sqlalchemy import select + + stmt = select(users_table.c.id, users_table.c.name).distinct() + + The above would produce an statement resembling: + + .. sourcecode:: sql + + SELECT DISTINCT user.id, user.name FROM user + + The method also accepts an ``*expr`` parameter which produces the + PostgreSQL dialect-specific ``DISTINCT ON`` expression. Using this + parameter on other backends which don't support this syntax will + raise an error. + + :param \*expr: optional column expressions. When present, + the PostgreSQL dialect will render a ``DISTINCT ON ()`` + construct. A deprecation warning and/or :class:`_exc.CompileError` + will be raised on other backends. + + .. 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, + columns: ColumnCollection[str, KeyedColumnElement[Any]], + primary_key: ColumnSet, + foreign_keys: Set[KeyedColumnElement[Any]], + *, + 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, + primary_key=primary_key, + foreign_keys=foreign_keys, + ) + 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, + primary_key=primary_key, + foreign_keys=foreign_keys, + ) + for ( + required_label_name, + proxy_key, + fallback_label_name, + c, + repeated, + ) in (self._generate_columns_plus_names(False)) + if is_column_element(c) + ] + + 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[_TP] + ) -> CompoundSelect[_TP]: + 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[_TP] + ) -> CompoundSelect[_TP]: + 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[_TP] + ) -> CompoundSelect[_TP]: + 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[_TP] + ) -> CompoundSelect[_TP]: + 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[_TP] + ) -> CompoundSelect[_TP]: + 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[_TP] + ) -> CompoundSelect[_TP]: + 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 + + def self_group(self, against: Optional[OperatorType] = None) -> Self: + return self + + def _ungroup(self) -> Self: + return self + + @_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 + + 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, + ) + + @util.ro_non_memoized_property + def _from_objects(self) -> List[FromClause]: + return [] + + def _regroup( + self, + fn: Callable[[Select[Any]], Select[Any]], + ) -> ScalarSelect[Any]: + + assert isinstance(self.element, ScalarSelect) + element = self.element.element + if not isinstance(element, Select): + raise exc.InvalidRequestError( + "Can only apply this operation to a plain SELECT construct" + ) + new_element = fn(element) + + return_value = new_element.scalar_subquery() + return return_value + + def select(self) -> Select[Tuple[bool]]: + 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: + + .. sourcecode:: sql + + 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 + + HasCTE._has_ctes_traverse_internals + + Executable._executable_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, + columns: ColumnCollection[str, KeyedColumnElement[Any]], + primary_key: ColumnSet, + foreign_keys: Set[KeyedColumnElement[Any]], + *, + proxy_compound_columns: Optional[ + Iterable[Sequence[ColumnElement[Any]]] + ] = None, + ) -> None: + if TYPE_CHECKING: + assert isinstance(fromclause, Subquery) + + if proxy_compound_columns: + columns._populate_separate_keys( + c._make_proxy( + fromclause, + compound_select_cols=extra_cols, + primary_key=primary_key, + foreign_keys=foreign_keys, + ) + for c, extra_cols in zip( + self.column_args, proxy_compound_columns + ) + ) + else: + columns._populate_separate_keys( + c._make_proxy( + fromclause, + primary_key=primary_key, + foreign_keys=foreign_keys, + ) + 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, + _annotations_traversal: bool = False, + ind_cols_on_fromclause: bool = False, + **kw: Any, + ) -> None: + super()._copy_internals(**kw) + + # passed from annotations._shallow_annotate(), _deep_annotate(), etc. + # the traversals used by annotations for these cases are not currently + # designed around expecting that inner elements inside of + # AnnotatedFromClause's element are also deep copied, so skip for these + # cases. in other cases such as plain visitors.cloned_traverse(), we + # expect this to happen. see issue #12915 + if not _annotations_traversal: + ee = self._Annotated__element # type: ignore + ee._copy_internals(**kw) + + if ind_cols_on_fromclause: + # passed from annotations._deep_annotate(). See that function + # for notes + 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 diff --git a/venv/Lib/site-packages/sqlalchemy/sql/sqltypes.py b/venv/Lib/site-packages/sqlalchemy/sql/sqltypes.py new file mode 100644 index 0000000..5490f94 --- /dev/null +++ b/venv/Lib/site-packages/sqlalchemy/sql/sqltypes.py @@ -0,0 +1,3930 @@ +# sql/sqltypes.py +# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: https://www.opensource.org/licenses/mit-license.php +# mypy: allow-untyped-defs, allow-untyped-calls + +"""SQL specific types.""" + +from __future__ import annotations + +import collections.abc as collections_abc +import datetime as dt +import decimal +import enum +import json +import pickle +from typing import Any +from typing import Callable +from typing import cast +from typing import Dict +from typing import Iterable +from typing import List +from typing import Mapping +from typing import Optional +from typing import overload +from typing import Sequence +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union +from uuid import UUID as _python_UUID + +from . import coercions +from . import elements +from . import operators +from . import roles +from . import type_api +from .base import _NONE_NAME +from .base import NO_ARG +from .base import SchemaEventTarget +from .cache_key import HasCacheKey +from .elements import quoted_name +from .elements import Slice +from .elements import TypeCoerce as type_coerce # noqa +from .type_api import Emulated +from .type_api import NativeForEmulated # noqa +from .type_api import to_instance as to_instance +from .type_api import TypeDecorator as TypeDecorator +from .type_api import TypeEngine as TypeEngine +from .type_api import TypeEngineMixin +from .type_api import Variant # noqa +from .visitors import InternalTraversal +from .. import event +from .. import exc +from .. import inspection +from .. import util +from ..engine import processors +from ..util import langhelpers +from ..util import OrderedDict +from ..util import warn_deprecated +from ..util.typing import get_args +from ..util.typing import is_literal +from ..util.typing import is_pep695 +from ..util.typing import Literal + +if TYPE_CHECKING: + from ._typing import _ColumnExpressionArgument + from ._typing import _CreateDropBind + from ._typing import _TypeEngineArgument + from .elements import ColumnElement + from .operators import OperatorType + from .schema import MetaData + from .type_api import _BindProcessorType + from .type_api import _ComparatorFactory + from .type_api import _LiteralProcessorType + from .type_api import _MatchedOnType + from .type_api import _ResultProcessorType + from ..engine.interfaces import Dialect + +_T = TypeVar("_T", bound="Any") +_CT = TypeVar("_CT", bound=Any) +_TE = TypeVar("_TE", bound="TypeEngine[Any]") +_P = TypeVar("_P") + + +class HasExpressionLookup(TypeEngineMixin): + """Mixin expression adaptations based on lookup tables. + + These rules are currently used by the numeric, integer and date types + which have detailed cross-expression coercion rules. + + """ + + @property + def _expression_adaptations(self): + raise NotImplementedError() + + class Comparator(TypeEngine.Comparator[_CT]): + __slots__ = () + + _blank_dict = util.EMPTY_DICT + + def _adapt_expression( + self, + op: OperatorType, + other_comparator: TypeEngine.Comparator[Any], + ) -> Tuple[OperatorType, TypeEngine[Any]]: + othertype = other_comparator.type._type_affinity + if TYPE_CHECKING: + assert isinstance(self.type, HasExpressionLookup) + lookup = self.type._expression_adaptations.get( + op, self._blank_dict + ).get(othertype, self.type) + if lookup is othertype: + return (op, other_comparator.type) + elif lookup is self.type._type_affinity: + return (op, self.type) + else: + return (op, to_instance(lookup)) + + comparator_factory: _ComparatorFactory[Any] = Comparator + + +class Concatenable(TypeEngineMixin): + """A mixin that marks a type as supporting 'concatenation', + typically strings.""" + + class Comparator(TypeEngine.Comparator[_T]): + __slots__ = () + + def _adapt_expression( + self, + op: OperatorType, + other_comparator: TypeEngine.Comparator[Any], + ) -> Tuple[OperatorType, TypeEngine[Any]]: + if op is operators.add and isinstance( + other_comparator, + (Concatenable.Comparator, NullType.Comparator), + ): + return operators.concat_op, self.expr.type + else: + return super()._adapt_expression(op, other_comparator) + + comparator_factory: _ComparatorFactory[Any] = Comparator + + +class Indexable(TypeEngineMixin): + """A mixin that marks a type as supporting indexing operations, + such as array or JSON structures. + + """ + + class Comparator(TypeEngine.Comparator[_T]): + __slots__ = () + + def _setup_getitem(self, index): + raise NotImplementedError() + + def __getitem__(self, index): + ( + adjusted_op, + adjusted_right_expr, + result_type, + ) = self._setup_getitem(index) + return self.operate( + adjusted_op, adjusted_right_expr, result_type=result_type + ) + + comparator_factory: _ComparatorFactory[Any] = Comparator + + +class String(Concatenable, TypeEngine[str]): + """The base for all string and character types. + + In SQL, corresponds to VARCHAR. + + The `length` field is usually required when the `String` type is + used within a CREATE TABLE statement, as VARCHAR requires a length + on most databases. + + """ + + __visit_name__ = "string" + + def __init__( + self, + length: Optional[int] = None, + collation: Optional[str] = None, + ): + """ + Create a string-holding type. + + :param length: optional, a length for the column for use in + DDL and CAST expressions. May be safely omitted if no ``CREATE + TABLE`` will be issued. Certain databases may require a + ``length`` for use in DDL, and will raise an exception when + the ``CREATE TABLE`` DDL is issued if a ``VARCHAR`` + with no length is included. Whether the value is + interpreted as bytes or characters is database specific. + + :param collation: Optional, a column-level collation for + use in DDL and CAST expressions. Renders using the + COLLATE keyword supported by SQLite, MySQL, and PostgreSQL. + E.g.: + + .. sourcecode:: pycon+sql + + >>> from sqlalchemy import cast, select, String + >>> print(select(cast("some string", String(collation="utf8")))) + {printsql}SELECT CAST(:param_1 AS VARCHAR COLLATE utf8) AS anon_1 + + .. note:: + + In most cases, the :class:`.Unicode` or :class:`.UnicodeText` + datatypes should be used for a :class:`_schema.Column` that expects + to store non-ascii data. These datatypes will ensure that the + correct types are used on the database. + + """ + + self.length = length + self.collation = collation + + def _with_collation(self, collation): + new_type = self.copy() + new_type.collation = collation + return new_type + + def _resolve_for_literal(self, value): + # I was SO PROUD of my regex trick, but we dont need it. + # re.search(r"[^\u0000-\u007F]", value) + + if value.isascii(): + return _STRING + else: + return _UNICODE + + def literal_processor(self, dialect): + def process(value): + value = value.replace("'", "''") + + if dialect.identifier_preparer._double_percents: + value = value.replace("%", "%%") + + return "'%s'" % value + + return process + + def bind_processor( + self, dialect: Dialect + ) -> Optional[_BindProcessorType[str]]: + return None + + def result_processor( + self, dialect: Dialect, coltype: object + ) -> Optional[_ResultProcessorType[str]]: + return None + + @property + def python_type(self): + return str + + def get_dbapi_type(self, dbapi): + return dbapi.STRING + + +class Text(String): + """A variably sized string type. + + In SQL, usually corresponds to CLOB or TEXT. In general, TEXT objects + do not have a length; while some databases will accept a length + argument here, it will be rejected by others. + + """ + + __visit_name__ = "text" + + +class Unicode(String): + """A variable length Unicode string type. + + The :class:`.Unicode` type is a :class:`.String` subclass that assumes + input and output strings that may contain non-ASCII characters, and for + some backends implies an underlying column type that is explicitly + supporting of non-ASCII data, such as ``NVARCHAR`` on Oracle Database and + SQL Server. This will impact the output of ``CREATE TABLE`` statements and + ``CAST`` functions at the dialect level. + + The character encoding used by the :class:`.Unicode` type that is used to + transmit and receive data to the database is usually determined by the + DBAPI itself. All modern DBAPIs accommodate non-ASCII strings but may have + different methods of managing database encodings; if necessary, this + encoding should be configured as detailed in the notes for the target DBAPI + in the :ref:`dialect_toplevel` section. + + In modern SQLAlchemy, use of the :class:`.Unicode` datatype does not + imply any encoding/decoding behavior within SQLAlchemy itself. In Python + 3, all string objects are inherently Unicode capable, and SQLAlchemy + does not produce bytestring objects nor does it accommodate a DBAPI that + does not return Python Unicode objects in result sets for string values. + + .. warning:: Some database backends, particularly SQL Server with pyodbc, + are known to have undesirable behaviors regarding data that is noted + as being of ``NVARCHAR`` type as opposed to ``VARCHAR``, including + datatype mismatch errors and non-use of indexes. See the section + on :meth:`.DialectEvents.do_setinputsizes` for background on working + around unicode character issues for backends like SQL Server with + pyodbc as well as cx_Oracle. + + .. seealso:: + + :class:`.UnicodeText` - unlengthed textual counterpart + to :class:`.Unicode`. + + :meth:`.DialectEvents.do_setinputsizes` + + """ + + __visit_name__ = "unicode" + + +class UnicodeText(Text): + """An unbounded-length Unicode string type. + + See :class:`.Unicode` for details on the unicode + behavior of this object. + + Like :class:`.Unicode`, usage the :class:`.UnicodeText` type implies a + unicode-capable type being used on the backend, such as + ``NCLOB``, ``NTEXT``. + + """ + + __visit_name__ = "unicode_text" + + +class Integer(HasExpressionLookup, TypeEngine[int]): + """A type for ``int`` integers.""" + + __visit_name__ = "integer" + + if TYPE_CHECKING: + + @util.ro_memoized_property + def _type_affinity(self) -> Type[Integer]: ... + + def get_dbapi_type(self, dbapi): + return dbapi.NUMBER + + @property + def python_type(self): + return int + + def _resolve_for_literal(self, value): + if value.bit_length() >= 32: + return _BIGINTEGER + else: + return self + + def literal_processor(self, dialect): + def process(value): + return str(int(value)) + + return process + + @util.memoized_property + def _expression_adaptations(self): + return { + operators.add: { + Date: Date, + Integer: self.__class__, + Numeric: Numeric, + }, + operators.mul: { + Interval: Interval, + Integer: self.__class__, + Numeric: Numeric, + }, + operators.truediv: {Integer: Numeric, Numeric: Numeric}, + operators.floordiv: {Integer: self.__class__, Numeric: Numeric}, + operators.sub: {Integer: self.__class__, Numeric: Numeric}, + } + + +class SmallInteger(Integer): + """A type for smaller ``int`` integers. + + Typically generates a ``SMALLINT`` in DDL, and otherwise acts like + a normal :class:`.Integer` on the Python side. + + """ + + __visit_name__ = "small_integer" + + +class BigInteger(Integer): + """A type for bigger ``int`` integers. + + Typically generates a ``BIGINT`` in DDL, and otherwise acts like + a normal :class:`.Integer` on the Python side. + + """ + + __visit_name__ = "big_integer" + + +_N = TypeVar("_N", bound=Union[decimal.Decimal, float]) + + +class Numeric(HasExpressionLookup, TypeEngine[_N]): + """Base for non-integer numeric types, such as + ``NUMERIC``, ``FLOAT``, ``DECIMAL``, and other variants. + + The :class:`.Numeric` datatype when used directly will render DDL + corresponding to precision numerics if available, such as + ``NUMERIC(precision, scale)``. The :class:`.Float` subclass will + attempt to render a floating-point datatype such as ``FLOAT(precision)``. + + :class:`.Numeric` returns Python ``decimal.Decimal`` objects by default, + based on the default value of ``True`` for the + :paramref:`.Numeric.asdecimal` parameter. If this parameter is set to + False, returned values are coerced to Python ``float`` objects. + + The :class:`.Float` subtype, being more specific to floating point, + defaults the :paramref:`.Float.asdecimal` flag to False so that the + default Python datatype is ``float``. + + .. note:: + + When using a :class:`.Numeric` datatype against a database type that + returns Python floating point values to the driver, the accuracy of the + decimal conversion indicated by :paramref:`.Numeric.asdecimal` may be + limited. The behavior of specific numeric/floating point datatypes + is a product of the SQL datatype in use, the Python :term:`DBAPI` + in use, as well as strategies that may be present within + the SQLAlchemy dialect in use. Users requiring specific precision/ + scale are encouraged to experiment with the available datatypes + in order to determine the best results. + + """ + + __visit_name__ = "numeric" + + if TYPE_CHECKING: + + @util.ro_memoized_property + def _type_affinity(self) -> Type[Numeric[_N]]: ... + + _default_decimal_return_scale = 10 + + @overload + def __init__( + self: Numeric[decimal.Decimal], + precision: Optional[int] = ..., + scale: Optional[int] = ..., + decimal_return_scale: Optional[int] = ..., + asdecimal: Literal[True] = ..., + ): ... + + @overload + def __init__( + self: Numeric[float], + precision: Optional[int] = ..., + scale: Optional[int] = ..., + decimal_return_scale: Optional[int] = ..., + asdecimal: Literal[False] = ..., + ): ... + + def __init__( + self, + precision: Optional[int] = None, + scale: Optional[int] = None, + decimal_return_scale: Optional[int] = None, + asdecimal: bool = True, + ): + """ + Construct a Numeric. + + :param precision: the numeric precision for use in DDL ``CREATE + TABLE``. + + :param scale: the numeric scale for use in DDL ``CREATE TABLE``. + + :param asdecimal: default True. Return whether or not + values should be sent as Python Decimal objects, or + as floats. Different DBAPIs send one or the other based on + datatypes - the Numeric type will ensure that return values + are one or the other across DBAPIs consistently. + + :param decimal_return_scale: Default scale to use when converting + from floats to Python decimals. Floating point values will typically + be much longer due to decimal inaccuracy, and most floating point + database types don't have a notion of "scale", so by default the + float type looks for the first ten decimal places when converting. + Specifying this value will override that length. Types which + do include an explicit ".scale" value, such as the base + :class:`.Numeric` as well as the MySQL float types, will use the + value of ".scale" as the default for decimal_return_scale, if not + otherwise specified. + + When using the ``Numeric`` type, care should be taken to ensure + that the asdecimal setting is appropriate for the DBAPI in use - + when Numeric applies a conversion from Decimal->float or float-> + Decimal, this conversion incurs an additional performance overhead + for all result columns received. + + DBAPIs that return Decimal natively (e.g. psycopg2) will have + better accuracy and higher performance with a setting of ``True``, + as the native translation to Decimal reduces the amount of floating- + point issues at play, and the Numeric type itself doesn't need + to apply any further conversions. However, another DBAPI which + returns floats natively *will* incur an additional conversion + overhead, and is still subject to floating point data loss - in + which case ``asdecimal=False`` will at least remove the extra + conversion overhead. + + """ + self.precision = precision + self.scale = scale + self.decimal_return_scale = decimal_return_scale + self.asdecimal = asdecimal + + @property + def _effective_decimal_return_scale(self): + if self.decimal_return_scale is not None: + return self.decimal_return_scale + elif getattr(self, "scale", None) is not None: + return self.scale + else: + return self._default_decimal_return_scale + + def get_dbapi_type(self, dbapi): + return dbapi.NUMBER + + def literal_processor(self, dialect): + def process(value): + return str(value) + + return process + + @property + def python_type(self): + if self.asdecimal: + return decimal.Decimal + else: + return float + + def bind_processor(self, dialect): + if dialect.supports_native_decimal: + return None + else: + return processors.to_float + + def result_processor(self, dialect, coltype): + if self.asdecimal: + if dialect.supports_native_decimal: + # we're a "numeric", DBAPI will give us Decimal directly + return None + else: + # we're a "numeric", DBAPI returns floats, convert. + return processors.to_decimal_processor_factory( + decimal.Decimal, + ( + self.scale + if self.scale is not None + else self._default_decimal_return_scale + ), + ) + else: + if dialect.supports_native_decimal: + return processors.to_float + else: + return None + + @util.memoized_property + def _expression_adaptations(self): + return { + operators.mul: { + Interval: Interval, + Numeric: self.__class__, + Integer: self.__class__, + }, + operators.truediv: { + Numeric: self.__class__, + Integer: self.__class__, + }, + operators.add: {Numeric: self.__class__, Integer: self.__class__}, + operators.sub: {Numeric: self.__class__, Integer: self.__class__}, + } + + +class Float(Numeric[_N]): + """Type representing floating point types, such as ``FLOAT`` or ``REAL``. + + This type returns Python ``float`` objects by default, unless the + :paramref:`.Float.asdecimal` flag is set to ``True``, in which case they + are coerced to ``decimal.Decimal`` objects. + + When a :paramref:`.Float.precision` is not provided in a + :class:`_types.Float` type some backend may compile this type as + an 8 bytes / 64 bit float datatype. To use a 4 bytes / 32 bit float + datatype a precision <= 24 can usually be provided or the + :class:`_types.REAL` type can be used. + This is known to be the case in the PostgreSQL and MSSQL dialects + that render the type as ``FLOAT`` that's in both an alias of + ``DOUBLE PRECISION``. Other third party dialects may have similar + behavior. + """ + + __visit_name__ = "float" + + if not TYPE_CHECKING: + # this is not in 2.1 branch, not clear if needed for 2.0 + # implementation + scale = None + + @overload + def __init__( + self: Float[float], + precision: Optional[int] = ..., + asdecimal: Literal[False] = ..., + decimal_return_scale: Optional[int] = ..., + ): ... + + @overload + def __init__( + self: Float[decimal.Decimal], + precision: Optional[int] = ..., + asdecimal: Literal[True] = ..., + decimal_return_scale: Optional[int] = ..., + ): ... + + def __init__( + self: Float[_N], + precision: Optional[int] = None, + asdecimal: bool = False, + decimal_return_scale: Optional[int] = None, + ): + r""" + Construct a Float. + + :param precision: the numeric precision for use in DDL ``CREATE + TABLE``. Backends **should** attempt to ensure this precision + indicates a number of digits for the generic + :class:`_sqltypes.Float` datatype. + + .. note:: For the Oracle Database backend, the + :paramref:`_sqltypes.Float.precision` parameter is not accepted + when rendering DDL, as Oracle Database does not support float precision + specified as a number of decimal places. Instead, use the + Oracle Database-specific :class:`_oracle.FLOAT` datatype and specify the + :paramref:`_oracle.FLOAT.binary_precision` parameter. This is new + in version 2.0 of SQLAlchemy. + + To create a database agnostic :class:`_types.Float` that + separately specifies binary precision for Oracle Database, use + :meth:`_types.TypeEngine.with_variant` as follows:: + + from sqlalchemy import Column + from sqlalchemy import Float + from sqlalchemy.dialects import oracle + + Column( + "float_data", + Float(5).with_variant(oracle.FLOAT(binary_precision=16), "oracle"), + ) + + :param asdecimal: the same flag as that of :class:`.Numeric`, but + defaults to ``False``. Note that setting this flag to ``True`` + results in floating point conversion. + + :param decimal_return_scale: Default scale to use when converting + from floats to Python decimals. Floating point values will typically + be much longer due to decimal inaccuracy, and most floating point + database types don't have a notion of "scale", so by default the + float type looks for the first ten decimal places when converting. + Specifying this value will override that length. Note that the + MySQL float types, which do include "scale", will use "scale" + as the default for decimal_return_scale, if not otherwise specified. + + """ # noqa: E501 + self.precision = precision + self.asdecimal = asdecimal + self.decimal_return_scale = decimal_return_scale + + def result_processor(self, dialect, coltype): + if self.asdecimal: + return processors.to_decimal_processor_factory( + decimal.Decimal, self._effective_decimal_return_scale + ) + elif dialect.supports_native_decimal: + return processors.to_float + else: + return None + + +class Double(Float[_N]): + """A type for double ``FLOAT`` floating point types. + + Typically generates a ``DOUBLE`` or ``DOUBLE_PRECISION`` in DDL, + and otherwise acts like a normal :class:`.Float` on the Python + side. + + .. versionadded:: 2.0 + + """ + + __visit_name__ = "double" + + +class _RenderISO8601NoT: + def _literal_processor_datetime(self, dialect): + return self._literal_processor_portion(dialect, None) + + def _literal_processor_date(self, dialect): + return self._literal_processor_portion(dialect, 0) + + def _literal_processor_time(self, dialect): + return self._literal_processor_portion(dialect, -1) + + def _literal_processor_portion(self, dialect, _portion=None): + assert _portion in (None, 0, -1) + if _portion is not None: + + def process(value): + return f"""'{value.isoformat().split("T")[_portion]}'""" + + else: + + def process(value): + return f"""'{value.isoformat().replace("T", " ")}'""" + + return process + + +class DateTime( + _RenderISO8601NoT, HasExpressionLookup, TypeEngine[dt.datetime] +): + """A type for ``datetime.datetime()`` objects. + + Date and time types return objects from the Python ``datetime`` + module. Most DBAPIs have built in support for the datetime + module, with the noted exception of SQLite. In the case of + SQLite, date and time types are stored as strings which are then + converted back to datetime objects when rows are returned. + + For the time representation within the datetime type, some + backends include additional options, such as timezone support and + fractional seconds support. For fractional seconds, use the + dialect-specific datatype, such as :class:`.mysql.TIME`. For + timezone support, use at least the :class:`_types.TIMESTAMP` datatype, + if not the dialect-specific datatype object. + + """ + + __visit_name__ = "datetime" + + def __init__(self, timezone: bool = False): + """Construct a new :class:`.DateTime`. + + :param timezone: boolean. Indicates that the datetime type should + enable timezone support, if available on the + **base date/time-holding type only**. It is recommended + to make use of the :class:`_types.TIMESTAMP` datatype directly when + using this flag, as some databases include separate generic + date/time-holding types distinct from the timezone-capable + TIMESTAMP datatype, such as Oracle Database. + + + """ + self.timezone = timezone + + def get_dbapi_type(self, dbapi): + return dbapi.DATETIME + + def _resolve_for_literal(self, value): + with_timezone = value.tzinfo is not None + if with_timezone and not self.timezone: + return DATETIME_TIMEZONE + else: + return self + + def literal_processor(self, dialect): + return self._literal_processor_datetime(dialect) + + @property + def python_type(self): + return dt.datetime + + @util.memoized_property + def _expression_adaptations(self): + # Based on + # https://www.postgresql.org/docs/current/static/functions-datetime.html. + + return { + operators.add: {Interval: self.__class__}, + operators.sub: {Interval: self.__class__, DateTime: Interval}, + } + + +class Date(_RenderISO8601NoT, HasExpressionLookup, TypeEngine[dt.date]): + """A type for ``datetime.date()`` objects.""" + + __visit_name__ = "date" + + def get_dbapi_type(self, dbapi): + return dbapi.DATETIME + + @property + def python_type(self): + return dt.date + + def literal_processor(self, dialect): + return self._literal_processor_date(dialect) + + @util.memoized_property + def _expression_adaptations(self): + # Based on + # https://www.postgresql.org/docs/current/static/functions-datetime.html. + + return { + operators.add: { + Integer: self.__class__, + Interval: DateTime, + Time: DateTime, + }, + operators.sub: { + # date - integer = date + Integer: self.__class__, + # date - date = integer. + Date: Integer, + Interval: DateTime, + # date - datetime = interval, + # this one is not in the PG docs + # but works + DateTime: Interval, + }, + } + + +class Time(_RenderISO8601NoT, HasExpressionLookup, TypeEngine[dt.time]): + """A type for ``datetime.time()`` objects.""" + + __visit_name__ = "time" + + def __init__(self, timezone: bool = False): + self.timezone = timezone + + def get_dbapi_type(self, dbapi): + return dbapi.DATETIME + + @property + def python_type(self): + return dt.time + + def _resolve_for_literal(self, value): + with_timezone = value.tzinfo is not None + if with_timezone and not self.timezone: + return TIME_TIMEZONE + else: + return self + + @util.memoized_property + def _expression_adaptations(self): + # Based on + # https://www.postgresql.org/docs/current/static/functions-datetime.html. + + return { + operators.add: {Date: DateTime, Interval: self.__class__}, + operators.sub: {Time: Interval, Interval: self.__class__}, + } + + def literal_processor(self, dialect): + return self._literal_processor_time(dialect) + + +class _Binary(TypeEngine[bytes]): + """Define base behavior for binary types.""" + + length: Optional[int] + + def __init__(self, length: Optional[int] = None): + self.length = length + + @util.ro_memoized_property + def _generic_type_affinity( + self, + ) -> Type[TypeEngine[bytes]]: + return LargeBinary + + def literal_processor(self, dialect): + def process(value): + # TODO: this is useless for real world scenarios; implement + # real binary literals + value = value.decode( + dialect._legacy_binary_type_literal_encoding + ).replace("'", "''") + return "'%s'" % value + + return process + + @property + def python_type(self): + return bytes + + # Python 3 - sqlite3 doesn't need the `Binary` conversion + # here, though pg8000 does to indicate "bytea" + def bind_processor(self, dialect): + if dialect.dbapi is None: + return None + + DBAPIBinary = dialect.dbapi.Binary + + def process(value): + if value is not None: + return DBAPIBinary(value) + else: + return None + + return process + + # Python 3 has native bytes() type + # both sqlite3 and pg8000 seem to return it, + # psycopg2 as of 2.5 returns 'memoryview' + def result_processor(self, dialect, coltype): + if dialect.returns_native_bytes: + return None + + def process(value): + if value is not None: + value = bytes(value) + return value + + return process + + def coerce_compared_value(self, op, value): + """See :meth:`.TypeEngine.coerce_compared_value` for a description.""" + + if isinstance(value, str): + return self + else: + return super().coerce_compared_value(op, value) + + def get_dbapi_type(self, dbapi): + return dbapi.BINARY + + +class LargeBinary(_Binary): + """A type for large binary byte data. + + The :class:`.LargeBinary` type corresponds to a large and/or unlengthed + binary type for the target platform, such as BLOB on MySQL and BYTEA for + PostgreSQL. It also handles the necessary conversions for the DBAPI. + + """ + + __visit_name__ = "large_binary" + + def __init__(self, length: Optional[int] = None): + """ + Construct a LargeBinary type. + + :param length: optional, a length for the column for use in + DDL statements, for those binary types that accept a length, + such as the MySQL BLOB type. + + """ + _Binary.__init__(self, length=length) + + +class SchemaType(SchemaEventTarget, TypeEngineMixin): + """Add capabilities to a type which allow for schema-level DDL to be + associated with a type. + + Supports types that must be explicitly created/dropped (i.e. PG ENUM type) + as well as types that are complimented by table or schema level + constraints, triggers, and other rules. + + :class:`.SchemaType` classes can also be targets for the + :meth:`.DDLEvents.before_parent_attach` and + :meth:`.DDLEvents.after_parent_attach` events, where the events fire off + surrounding the association of the type object with a parent + :class:`_schema.Column`. + + .. seealso:: + + :class:`.Enum` + + :class:`.Boolean` + + + """ + + _use_schema_map = True + + name: Optional[str] + + def __init__( + self, + name: Optional[str] = None, + schema: Optional[str] = None, + metadata: Optional[MetaData] = None, + inherit_schema: bool = False, + quote: Optional[bool] = None, + _create_events: bool = True, + _adapted_from: Optional[SchemaType] = None, + ): + if name is not None: + self.name = quoted_name(name, quote) + else: + self.name = None + self.schema = schema + self.metadata = metadata + self.inherit_schema = inherit_schema + self._create_events = _create_events + + if _create_events and self.metadata: + event.listen( + self.metadata, + "before_create", + util.portable_instancemethod(self._on_metadata_create), + ) + event.listen( + self.metadata, + "after_drop", + util.portable_instancemethod(self._on_metadata_drop), + ) + + if _adapted_from: + self.dispatch = self.dispatch._join(_adapted_from.dispatch) + + def _set_parent(self, parent, **kw): + # set parent hook is when this type is associated with a column. + # Column calls it for all SchemaEventTarget instances, either the + # base type and/or variants in _variant_mapping. + + # we want to register a second hook to trigger when that column is + # associated with a table. in that event, we and all of our variants + # may want to set up some state on the table such as a CheckConstraint + # that will conditionally render at DDL render time. + + # the base SchemaType also sets up events for + # on_table/metadata_create/drop in this method, which is used by + # "native" types with a separate CREATE/DROP e.g. Postgresql.ENUM + + parent._on_table_attach(util.portable_instancemethod(self._set_table)) + + def _variant_mapping_for_set_table(self, column): + if column.type._variant_mapping: + variant_mapping = dict(column.type._variant_mapping) + variant_mapping["_default"] = column.type + else: + variant_mapping = None + return variant_mapping + + def _set_table(self, column, table): + if self.inherit_schema: + self.schema = table.schema + elif self.metadata and self.schema is None and self.metadata.schema: + self.schema = self.metadata.schema + + if not self._create_events: + return + + variant_mapping = self._variant_mapping_for_set_table(column) + + event.listen( + table, + "before_create", + util.portable_instancemethod( + self._on_table_create, {"variant_mapping": variant_mapping} + ), + ) + event.listen( + table, + "after_drop", + util.portable_instancemethod( + self._on_table_drop, {"variant_mapping": variant_mapping} + ), + ) + if self.metadata is None: + # if SchemaType were created w/ a metadata argument, these + # events would already have been associated with that metadata + # and would preclude an association with table.metadata + event.listen( + table.metadata, + "before_create", + util.portable_instancemethod( + self._on_metadata_create, + {"variant_mapping": variant_mapping}, + ), + ) + event.listen( + table.metadata, + "after_drop", + util.portable_instancemethod( + self._on_metadata_drop, + {"variant_mapping": variant_mapping}, + ), + ) + + def copy(self, **kw): + return self.adapt( + cast("Type[TypeEngine[Any]]", self.__class__), + _create_events=True, + metadata=( + kw.get("_to_metadata", self.metadata) + if self.metadata is not None + else None + ), + ) + + @overload + def adapt(self, cls: Type[_TE], **kw: Any) -> _TE: ... + + @overload + def adapt( + self, cls: Type[TypeEngineMixin], **kw: Any + ) -> TypeEngine[Any]: ... + + def adapt( + self, cls: Type[Union[TypeEngine[Any], TypeEngineMixin]], **kw: Any + ) -> TypeEngine[Any]: + kw.setdefault("_create_events", False) + kw.setdefault("_adapted_from", self) + return super().adapt(cls, **kw) + + def create(self, bind: _CreateDropBind, checkfirst: bool = False) -> None: + """Issue CREATE DDL for this type, if applicable.""" + + t = self.dialect_impl(bind.dialect) + if isinstance(t, SchemaType) and t.__class__ is not self.__class__: + t.create(bind, checkfirst=checkfirst) + + def drop(self, bind: _CreateDropBind, checkfirst: bool = False) -> None: + """Issue DROP DDL for this type, if applicable.""" + + t = self.dialect_impl(bind.dialect) + if isinstance(t, SchemaType) and t.__class__ is not self.__class__: + t.drop(bind, checkfirst=checkfirst) + + def _on_table_create( + self, target: Any, bind: _CreateDropBind, **kw: Any + ) -> None: + if not self._is_impl_for_variant(bind.dialect, kw): + return + + t = self.dialect_impl(bind.dialect) + if isinstance(t, SchemaType) and t.__class__ is not self.__class__: + t._on_table_create(target, bind, **kw) + + def _on_table_drop( + self, target: Any, bind: _CreateDropBind, **kw: Any + ) -> None: + if not self._is_impl_for_variant(bind.dialect, kw): + return + + t = self.dialect_impl(bind.dialect) + if isinstance(t, SchemaType) and t.__class__ is not self.__class__: + t._on_table_drop(target, bind, **kw) + + def _on_metadata_create( + self, target: Any, bind: _CreateDropBind, **kw: Any + ) -> None: + if not self._is_impl_for_variant(bind.dialect, kw): + return + + t = self.dialect_impl(bind.dialect) + if isinstance(t, SchemaType) and t.__class__ is not self.__class__: + t._on_metadata_create(target, bind, **kw) + + def _on_metadata_drop( + self, target: Any, bind: _CreateDropBind, **kw: Any + ) -> None: + if not self._is_impl_for_variant(bind.dialect, kw): + return + + t = self.dialect_impl(bind.dialect) + if isinstance(t, SchemaType) and t.__class__ is not self.__class__: + t._on_metadata_drop(target, bind, **kw) + + def _is_impl_for_variant( + self, dialect: Dialect, kw: Dict[str, Any] + ) -> Optional[bool]: + variant_mapping = kw.pop("variant_mapping", None) + + if not variant_mapping: + return True + + # for types that have _variant_mapping, all the impls in the map + # that are SchemaEventTarget subclasses get set up as event holders. + # this is so that constructs that need + # to be associated with the Table at dialect-agnostic time etc. like + # CheckConstraints can be set up with that table. they then add + # to these constraints a DDL check_rule that among other things + # will check this _is_impl_for_variant() method to determine when + # the dialect is known that we are part of the table's DDL sequence. + + # since PostgreSQL is the only DB that has ARRAY this can only + # be integration tested by PG-specific tests + def _we_are_the_impl(typ: SchemaType) -> bool: + return ( + typ is self + or isinstance(typ, ARRAY) + and typ.item_type is self # type: ignore[comparison-overlap] + ) + + if dialect.name in variant_mapping and _we_are_the_impl( + variant_mapping[dialect.name] + ): + return True + elif dialect.name not in variant_mapping: + return _we_are_the_impl(variant_mapping["_default"]) + else: + return None + + +_EnumTupleArg = Union[Sequence[enum.Enum], Sequence[str]] + + +class Enum(String, SchemaType, Emulated, TypeEngine[Union[str, enum.Enum]]): + """Generic Enum Type. + + The :class:`.Enum` type provides a set of possible string values + which the column is constrained towards. + + The :class:`.Enum` type will make use of the backend's native "ENUM" + type if one is available; otherwise, it uses a VARCHAR datatype. + An option also exists to automatically produce a CHECK constraint + when the VARCHAR (so called "non-native") variant is produced; + see the :paramref:`.Enum.create_constraint` flag. + + The :class:`.Enum` type also provides in-Python validation of string + values during both read and write operations. When reading a value + from the database in a result set, the string value is always checked + against the list of possible values and a ``LookupError`` is raised + if no match is found. When passing a value to the database as a + plain string within a SQL statement, if the + :paramref:`.Enum.validate_strings` parameter is + set to True, a ``LookupError`` is raised for any string value that's + not located in the given list of possible values; note that this + impacts usage of LIKE expressions with enumerated values (an unusual + use case). + + The source of enumerated values may be a list of string values, or + alternatively a PEP-435-compliant enumerated class. For the purposes + of the :class:`.Enum` datatype, this class need only provide a + ``__members__`` method. + + When using an enumerated class, the enumerated objects are used + both for input and output, rather than strings as is the case with + a plain-string enumerated type:: + + import enum + from sqlalchemy import Enum + + + class MyEnum(enum.Enum): + one = 1 + two = 2 + three = 3 + + + t = Table("data", MetaData(), Column("value", Enum(MyEnum))) + + connection.execute(t.insert(), {"value": MyEnum.two}) + assert connection.scalar(t.select()) is MyEnum.two + + Above, the string names of each element, e.g. "one", "two", "three", + are persisted to the database; the values of the Python Enum, here + indicated as integers, are **not** used; the value of each enum can + therefore be any kind of Python object whether or not it is persistable. + + In order to persist the values and not the names, the + :paramref:`.Enum.values_callable` parameter may be used. The value of + this parameter is a user-supplied callable, which is intended to be used + with a PEP-435-compliant enumerated class and returns a list of string + values to be persisted. For a simple enumeration that uses string values, + a callable such as ``lambda x: [e.value for e in x]`` is sufficient. + + .. seealso:: + + :ref:`orm_declarative_mapped_column_enums` - background on using + the :class:`_sqltypes.Enum` datatype with the ORM's + :ref:`ORM Annotated Declarative ` + feature. + + :class:`_postgresql.ENUM` - PostgreSQL-specific type, + which has additional functionality. + + :class:`.mysql.ENUM` - MySQL-specific type + + """ + + __visit_name__ = "enum" + + values_callable: Optional[Callable[[Type[enum.Enum]], Sequence[str]]] + enum_class: Optional[Type[enum.Enum]] + _valid_lookup: Dict[Union[enum.Enum, str, None], Optional[str]] + _object_lookup: Dict[Optional[str], Union[enum.Enum, str, None]] + + @overload + def __init__(self, enums: Type[enum.Enum], **kw: Any) -> None: ... + + @overload + def __init__(self, *enums: str, **kw: Any) -> None: ... + + def __init__(self, *enums: Union[str, Type[enum.Enum]], **kw: Any) -> None: + r"""Construct an enum. + + Keyword arguments which don't apply to a specific backend are ignored + by that backend. + + :param \*enums: either exactly one PEP-435 compliant enumerated type + or one or more string labels. + + :param create_constraint: defaults to False. When creating a + non-native enumerated type, also build a CHECK constraint on the + database against the valid values. + + .. note:: it is strongly recommended that the CHECK constraint + have an explicit name in order to support schema-management + concerns. This can be established either by setting the + :paramref:`.Enum.name` parameter or by setting up an + appropriate naming convention; see + :ref:`constraint_naming_conventions` for background. + + .. versionchanged:: 1.4 - this flag now defaults to False, meaning + no CHECK constraint is generated for a non-native enumerated + type. + + :param metadata: Associate this type directly with a ``MetaData`` + object. For types that exist on the target database as an + independent schema construct (PostgreSQL), this type will be + created and dropped within ``create_all()`` and ``drop_all()`` + operations. If the type is not associated with any ``MetaData`` + object, it will associate itself with each ``Table`` in which it is + used, and will be created when any of those individual tables are + created, after a check is performed for its existence. The type is + only dropped when ``drop_all()`` is called for that ``Table`` + object's metadata, however. + + The value of the :paramref:`_schema.MetaData.schema` parameter of + the :class:`_schema.MetaData` object, if set, will be used as the + default value of the :paramref:`_types.Enum.schema` on this object + if an explicit value is not otherwise supplied. + + .. versionchanged:: 1.4.12 :class:`_types.Enum` inherits the + :paramref:`_schema.MetaData.schema` parameter of the + :class:`_schema.MetaData` object if present, when passed using + the :paramref:`_types.Enum.metadata` parameter. + + :param name: The name of this type. This is required for PostgreSQL + and any future supported database which requires an explicitly + named type, or an explicitly named constraint in order to generate + the type and/or a table that uses it. If a PEP-435 enumerated + class was used, its name (converted to lower case) is used by + default. + + :param native_enum: Use the database's native ENUM type when + available. Defaults to True. When False, uses VARCHAR + check + constraint for all backends. When False, the VARCHAR length can be + controlled with :paramref:`.Enum.length`; currently "length" is + ignored if native_enum=True. + + :param length: Allows specifying a custom length for the VARCHAR + when a non-native enumeration datatype is used. By default it uses + the length of the longest value. + + .. versionchanged:: 2.0.0 The :paramref:`.Enum.length` parameter + is used unconditionally for ``VARCHAR`` rendering regardless of + the :paramref:`.Enum.native_enum` parameter, for those backends + where ``VARCHAR`` is used for enumerated datatypes. + + + :param schema: Schema name of this type. For types that exist on the + target database as an independent schema construct (PostgreSQL), + this parameter specifies the named schema in which the type is + present. + + If not present, the schema name will be taken from the + :class:`_schema.MetaData` collection if passed as + :paramref:`_types.Enum.metadata`, for a :class:`_schema.MetaData` + that includes the :paramref:`_schema.MetaData.schema` parameter. + + .. versionchanged:: 1.4.12 :class:`_types.Enum` inherits the + :paramref:`_schema.MetaData.schema` parameter of the + :class:`_schema.MetaData` object if present, when passed using + the :paramref:`_types.Enum.metadata` parameter. + + Otherwise, if the :paramref:`_types.Enum.inherit_schema` flag is set + to ``True``, the schema will be inherited from the associated + :class:`_schema.Table` object if any; when + :paramref:`_types.Enum.inherit_schema` is at its default of + ``False``, the owning table's schema is **not** used. + + + :param quote: Set explicit quoting preferences for the type's name. + + :param inherit_schema: When ``True``, the "schema" from the owning + :class:`_schema.Table` + will be copied to the "schema" attribute of this + :class:`.Enum`, replacing whatever value was passed for the + ``schema`` attribute. This also takes effect when using the + :meth:`_schema.Table.to_metadata` operation. + + :param validate_strings: when True, string values that are being + passed to the database in a SQL statement will be checked + for validity against the list of enumerated values. Unrecognized + values will result in a ``LookupError`` being raised. + + :param values_callable: A callable which will be passed the PEP-435 + compliant enumerated type, which should then return a list of string + values to be persisted. This allows for alternate usages such as + using the string value of an enum to be persisted to the database + instead of its name. The callable must return the values to be + persisted in the same order as iterating through the Enum's + ``__member__`` attribute. For example + ``lambda x: [i.value for i in x]``. + + .. versionadded:: 1.2.3 + + :param sort_key_function: a Python callable which may be used as the + "key" argument in the Python ``sorted()`` built-in. The SQLAlchemy + ORM requires that primary key columns which are mapped must + be sortable in some way. When using an unsortable enumeration + object such as a Python 3 ``Enum`` object, this parameter may be + used to set a default sort key function for the objects. By + default, the database value of the enumeration is used as the + sorting function. + + .. versionadded:: 1.3.8 + + :param omit_aliases: A boolean that when true will remove aliases from + pep 435 enums. defaults to ``True``. + + .. versionchanged:: 2.0 This parameter now defaults to True. + + """ + self._enum_init(enums, kw) # type: ignore[arg-type] + + @property + def _enums_argument(self): + if self.enum_class is not None: + return [self.enum_class] + else: + return self.enums + + def _enum_init(self, enums: _EnumTupleArg, kw: Dict[str, Any]) -> None: + """internal init for :class:`.Enum` and subclasses. + + friendly init helper used by subclasses to remove + all the Enum-specific keyword arguments from kw. Allows all + other arguments in kw to pass through. + + """ + self.native_enum = kw.pop("native_enum", True) + self.create_constraint = kw.pop("create_constraint", False) + self.values_callable = kw.pop("values_callable", None) + self._sort_key_function = kw.pop("sort_key_function", NO_ARG) + length_arg = kw.pop("length", NO_ARG) + self._omit_aliases = kw.pop("omit_aliases", True) + _disable_warnings = kw.pop("_disable_warnings", False) + values, objects = self._parse_into_values(enums, kw) + self._setup_for_values(values, objects, kw) + + self.validate_strings = kw.pop("validate_strings", False) + + if self.enums: + self._default_length = length = max(len(x) for x in self.enums) + else: + self._default_length = length = 0 + + if length_arg is not NO_ARG: + if ( + not _disable_warnings + and length_arg is not None + and length_arg < length + ): + raise ValueError( + "When provided, length must be larger or equal" + " than the length of the longest enum value. %s < %s" + % (length_arg, length) + ) + length = length_arg + + self._valid_lookup[None] = self._object_lookup[None] = None + + super().__init__(length=length) + + # assign name to the given enum class if no other name, and this + # enum is not an "empty" enum. if the enum is "empty" we assume + # this is a template enum that will be used to generate + # new Enum classes. + if self.enum_class and values: + kw.setdefault("name", self.enum_class.__name__.lower()) + SchemaType.__init__( + self, + name=kw.pop("name", None), + schema=kw.pop("schema", None), + metadata=kw.pop("metadata", None), + inherit_schema=kw.pop("inherit_schema", False), + quote=kw.pop("quote", None), + _create_events=kw.pop("_create_events", True), + _adapted_from=kw.pop("_adapted_from", None), + ) + + def _parse_into_values( + self, enums: _EnumTupleArg, kw: Any + ) -> Tuple[Sequence[str], _EnumTupleArg]: + if not enums and "_enums" in kw: + enums = kw.pop("_enums") + + if len(enums) == 1 and hasattr(enums[0], "__members__"): + self.enum_class = enums[0] # type: ignore[assignment] + assert self.enum_class is not None + + _members = self.enum_class.__members__ + + members: Mapping[str, enum.Enum] + if self._omit_aliases is True: + # remove aliases + members = OrderedDict( + (n, v) for n, v in _members.items() if v.name == n + ) + else: + members = _members + if self.values_callable: + values = self.values_callable(self.enum_class) + else: + values = list(members) + objects = [members[k] for k in members] + return values, objects + else: + self.enum_class = None + return enums, enums # type: ignore[return-value] + + def _compare_type_affinity(self, other: TypeEngine[Any]) -> bool: + return ( + super()._compare_type_affinity(other) + or other._type_affinity is String + ) + + def _resolve_for_literal(self, value: Any) -> Enum: + tv = type(value) + typ = self._resolve_for_python_type(tv, tv, tv) + assert typ is not None + return typ + + def _resolve_for_python_type( + self, + python_type: Type[Any], + matched_on: _MatchedOnType, + matched_on_flattened: Type[Any], + ) -> Optional[Enum]: + # "generic form" indicates we were placed in a type map + # as ``sqlalchemy.Enum(enum.Enum)`` which indicates we need to + # get enumerated values from the datatype + we_are_generic_form = self._enums_argument == [enum.Enum] + + native_enum = None + + def process_literal(pt): + # for a literal, where we need to get its contents, parse it out. + enum_args = get_args(pt) + bad_args = [arg for arg in enum_args if not isinstance(arg, str)] + if bad_args: + raise exc.ArgumentError( + f"Can't create string-based Enum datatype from non-string " + f"values: {', '.join(repr(x) for x in bad_args)}. Please " + f"provide an explicit Enum datatype for this Python type" + ) + native_enum = False + return enum_args, native_enum + + if not we_are_generic_form and python_type is matched_on: + # if we have enumerated values, and the incoming python + # type is exactly the one that matched in the type map, + # then we use these enumerated values and dont try to parse + # what's incoming + enum_args = self._enums_argument + + elif is_literal(python_type): + enum_args, native_enum = process_literal(python_type) + elif is_pep695(python_type): + value = python_type.__value__ + if is_pep695(value): + new_value = value + while is_pep695(new_value): + new_value = new_value.__value__ + if is_literal(new_value): + value = new_value + warn_deprecated( + f"Mapping recursive TypeAliasType '{python_type}' " + "that resolve to literal to generate an Enum is " + "deprecated. SQLAlchemy 2.1 will not support this " + "use case. Please avoid using recursing " + "TypeAliasType.", + "2.0", + ) + if not is_literal(value): + raise exc.ArgumentError( + f"Can't associate TypeAliasType '{python_type}' to an " + "Enum since it's not a direct alias of a Literal. Only " + "aliases in this form `type my_alias = Literal['a', " + "'b']` are supported when generating Enums." + ) + enum_args, native_enum = process_literal(value) + + elif isinstance(python_type, type) and issubclass( + python_type, enum.Enum + ): + # same for an enum.Enum + enum_args = [python_type] + + else: + enum_args = self._enums_argument + + # make a new Enum that looks like this one. + # arguments or other rules + kw = self._make_enum_kw({}) + + if native_enum is False: + kw["native_enum"] = False + + kw["length"] = NO_ARG if self.length == 0 else self.length + return cast( + Enum, + self._generic_type_affinity(_enums=enum_args, **kw), # type: ignore # noqa: E501 + ) + + def _setup_for_values( + self, + values: Sequence[str], + objects: _EnumTupleArg, + kw: Any, + ) -> None: + self.enums = list(values) + + self._valid_lookup = dict(zip(reversed(objects), reversed(values))) + + self._object_lookup = dict(zip(values, objects)) + + self._valid_lookup.update( + [ + (value, self._valid_lookup[self._object_lookup[value]]) + for value in values + ] + ) + + @property + def sort_key_function(self): # type: ignore[override] + if self._sort_key_function is NO_ARG: + return self._db_value_for_elem + else: + return self._sort_key_function + + @property + def native(self): # type: ignore[override] + return self.native_enum + + def _db_value_for_elem(self, elem): + try: + return self._valid_lookup[elem] + except KeyError as err: + # for unknown string values, we return as is. While we can + # validate these if we wanted, that does not allow for lesser-used + # end-user use cases, such as using a LIKE comparison with an enum, + # or for an application that wishes to apply string tests to an + # ENUM (see [ticket:3725]). While we can decide to differentiate + # here between an INSERT statement and a criteria used in a SELECT, + # for now we're staying conservative w/ behavioral changes (perhaps + # someone has a trigger that handles strings on INSERT) + if not self.validate_strings and isinstance(elem, str): + return elem + else: + raise LookupError( + "'%s' is not among the defined enum values. " + "Enum name: %s. Possible values: %s" + % ( + elem, + self.name, + langhelpers.repr_tuple_names(self.enums), + ) + ) from err + + class Comparator(String.Comparator[str]): + __slots__ = () + + type: String + + def _adapt_expression( + self, + op: OperatorType, + other_comparator: TypeEngine.Comparator[Any], + ) -> Tuple[OperatorType, TypeEngine[Any]]: + op, typ = super()._adapt_expression(op, other_comparator) + if op is operators.concat_op: + typ = String(self.type.length) + return op, typ + + comparator_factory = Comparator + + def _object_value_for_elem(self, elem: str) -> Union[str, enum.Enum]: + try: + # Value will not be None because key is not None + return self._object_lookup[elem] # type: ignore[return-value] + except KeyError as err: + raise LookupError( + "'%s' is not among the defined enum values. " + "Enum name: %s. Possible values: %s" + % ( + elem, + self.name, + langhelpers.repr_tuple_names(self.enums), + ) + ) from err + + def __repr__(self): + return util.generic_repr( + self, + additional_kw=[ + ("native_enum", True), + ("create_constraint", False), + ("length", self._default_length), + ], + to_inspect=[Enum, SchemaType], + ) + + def as_generic(self, allow_nulltype=False): + try: + args = self.enums + except AttributeError: + raise NotImplementedError( + "TypeEngine.as_generic() heuristic " + "is undefined for types that inherit Enum but do not have " + "an `enums` attribute." + ) from None + + return util.constructor_copy( + self, self._generic_type_affinity, *args, _disable_warnings=True + ) + + def _make_enum_kw(self, kw): + kw.setdefault("validate_strings", self.validate_strings) + if self.name: + kw.setdefault("name", self.name) + kw.setdefault("schema", self.schema) + kw.setdefault("inherit_schema", self.inherit_schema) + kw.setdefault("metadata", self.metadata) + kw.setdefault("native_enum", self.native_enum) + kw.setdefault("values_callable", self.values_callable) + kw.setdefault("create_constraint", self.create_constraint) + kw.setdefault("length", self.length) + kw.setdefault("omit_aliases", self._omit_aliases) + return kw + + def adapt_to_emulated(self, impltype, **kw): + self._make_enum_kw(kw) + kw["_disable_warnings"] = True + kw.setdefault("_create_events", False) + assert "_enums" in kw + return impltype(**kw) + + def adapt(self, cls, **kw): + kw["_enums"] = self._enums_argument + kw["_disable_warnings"] = True + return super().adapt(cls, **kw) + + def _should_create_constraint(self, compiler, **kw): + if not self._is_impl_for_variant(compiler.dialect, kw): + return False + return ( + not self.native_enum or not compiler.dialect.supports_native_enum + ) + + @util.preload_module("sqlalchemy.sql.schema") + def _set_table(self, column, table): + schema = util.preloaded.sql_schema + SchemaType._set_table(self, column, table) + + if not self.create_constraint: + return + + variant_mapping = self._variant_mapping_for_set_table(column) + + e = schema.CheckConstraint( + type_coerce(column, String()).in_(self.enums), + name=_NONE_NAME if self.name is None else self.name, + _create_rule=util.portable_instancemethod( + self._should_create_constraint, + {"variant_mapping": variant_mapping}, + ), + _type_bound=True, + ) + assert e.table is table + + def literal_processor(self, dialect): + parent_processor = super().literal_processor(dialect) + + def process(value): + value = self._db_value_for_elem(value) + if parent_processor: + value = parent_processor(value) + return value + + return process + + def bind_processor(self, dialect): + parent_processor = super().bind_processor(dialect) + + def process(value): + value = self._db_value_for_elem(value) + if parent_processor: + value = parent_processor(value) + return value + + return process + + def result_processor(self, dialect, coltype): + parent_processor = super().result_processor(dialect, coltype) + + def process(value): + if parent_processor: + value = parent_processor(value) + + value = self._object_value_for_elem(value) + return value + + return process + + def copy(self, **kw): + return SchemaType.copy(self, **kw) + + @property + def python_type(self): + if self.enum_class: + return self.enum_class + else: + return super().python_type + + +class PickleType(TypeDecorator[object]): + """Holds Python objects, which are serialized using pickle. + + PickleType builds upon the Binary type to apply Python's + ``pickle.dumps()`` to incoming objects, and ``pickle.loads()`` on + the way out, allowing any pickleable Python object to be stored as + a serialized binary field. + + To allow ORM change events to propagate for elements associated + with :class:`.PickleType`, see :ref:`mutable_toplevel`. + + """ + + impl = LargeBinary + cache_ok = True + + def __init__( + self, + protocol: int = pickle.HIGHEST_PROTOCOL, + pickler: Any = None, + comparator: Optional[Callable[[Any, Any], bool]] = None, + impl: Optional[_TypeEngineArgument[Any]] = None, + ): + """ + Construct a PickleType. + + :param protocol: defaults to ``pickle.HIGHEST_PROTOCOL``. + + :param pickler: defaults to pickle. May be any object with + pickle-compatible ``dumps`` and ``loads`` methods. + + :param comparator: a 2-arg callable predicate used + to compare values of this type. If left as ``None``, + the Python "equals" operator is used to compare values. + + :param impl: A binary-storing :class:`_types.TypeEngine` class or + instance to use in place of the default :class:`_types.LargeBinary`. + For example the :class: `_mysql.LONGBLOB` class may be more effective + when using MySQL. + + .. versionadded:: 1.4.20 + + """ + self.protocol = protocol + self.pickler = pickler or pickle + self.comparator = comparator + super().__init__() + + if impl: + # custom impl is not necessarily a LargeBinary subclass. + # make an exception to typing for this + self.impl = to_instance(impl) # type: ignore + + def __reduce__(self): + return PickleType, (self.protocol, None, self.comparator) + + def bind_processor(self, dialect): + impl_processor = self.impl_instance.bind_processor(dialect) + dumps = self.pickler.dumps + protocol = self.protocol + if impl_processor: + fixed_impl_processor = impl_processor + + def process(value): + if value is not None: + value = dumps(value, protocol) + return fixed_impl_processor(value) + + else: + + def process(value): + if value is not None: + value = dumps(value, protocol) + return value + + return process + + def result_processor(self, dialect, coltype): + impl_processor = self.impl_instance.result_processor(dialect, coltype) + loads = self.pickler.loads + if impl_processor: + fixed_impl_processor = impl_processor + + def process(value): + value = fixed_impl_processor(value) + if value is None: + return None + return loads(value) + + else: + + def process(value): + if value is None: + return None + return loads(value) + + return process + + def compare_values(self, x, y): + if self.comparator: + return self.comparator(x, y) + else: + return x == y + + +class Boolean(SchemaType, Emulated, TypeEngine[bool]): + """A bool datatype. + + :class:`.Boolean` typically uses BOOLEAN or SMALLINT on the DDL side, + and on the Python side deals in ``True`` or ``False``. + + The :class:`.Boolean` datatype currently has two levels of assertion + that the values persisted are simple true/false values. For all + backends, only the Python values ``None``, ``True``, ``False``, ``1`` + or ``0`` are accepted as parameter values. For those backends that + don't support a "native boolean" datatype, an option exists to + also create a CHECK constraint on the target column + + .. versionchanged:: 1.2 the :class:`.Boolean` datatype now asserts that + incoming Python values are already in pure boolean form. + + + """ + + __visit_name__ = "boolean" + native = True + + def __init__( + self, + create_constraint: bool = False, + name: Optional[str] = None, + _create_events: bool = True, + _adapted_from: Optional[SchemaType] = None, + ): + """Construct a Boolean. + + :param create_constraint: defaults to False. If the boolean + is generated as an int/smallint, also create a CHECK constraint + on the table that ensures 1 or 0 as a value. + + .. note:: it is strongly recommended that the CHECK constraint + have an explicit name in order to support schema-management + concerns. This can be established either by setting the + :paramref:`.Boolean.name` parameter or by setting up an + appropriate naming convention; see + :ref:`constraint_naming_conventions` for background. + + .. versionchanged:: 1.4 - this flag now defaults to False, meaning + no CHECK constraint is generated for a non-native enumerated + type. + + :param name: if a CHECK constraint is generated, specify + the name of the constraint. + + """ + self.create_constraint = create_constraint + self.name = name + self._create_events = _create_events + if _adapted_from: + self.dispatch = self.dispatch._join(_adapted_from.dispatch) + + def copy(self, **kw): + # override SchemaType.copy() to not include to_metadata logic + return self.adapt( + cast("Type[TypeEngine[Any]]", self.__class__), + _create_events=True, + ) + + def _should_create_constraint(self, compiler, **kw): + if not self._is_impl_for_variant(compiler.dialect, kw): + return False + return ( + not compiler.dialect.supports_native_boolean + and compiler.dialect.non_native_boolean_check_constraint + ) + + @util.preload_module("sqlalchemy.sql.schema") + def _set_table(self, column, table): + schema = util.preloaded.sql_schema + if not self.create_constraint: + return + + variant_mapping = self._variant_mapping_for_set_table(column) + + e = schema.CheckConstraint( + type_coerce(column, self).in_([0, 1]), + name=_NONE_NAME if self.name is None else self.name, + _create_rule=util.portable_instancemethod( + self._should_create_constraint, + {"variant_mapping": variant_mapping}, + ), + _type_bound=True, + ) + assert e.table is table + + @property + def python_type(self): + return bool + + _strict_bools = frozenset([None, True, False]) + + def _strict_as_bool(self, value): + if value not in self._strict_bools: + if not isinstance(value, int): + raise TypeError("Not a boolean value: %r" % (value,)) + else: + raise ValueError( + "Value %r is not None, True, or False" % (value,) + ) + return value + + def literal_processor(self, dialect): + compiler = dialect.statement_compiler(dialect, None) + true = compiler.visit_true(None) + false = compiler.visit_false(None) + + def process(value): + return true if self._strict_as_bool(value) else false + + return process + + def bind_processor(self, dialect): + _strict_as_bool = self._strict_as_bool + + _coerce: Union[Type[bool], Type[int]] + + if dialect.supports_native_boolean: + _coerce = bool + else: + _coerce = int + + def process(value): + value = _strict_as_bool(value) + if value is not None: + value = _coerce(value) + return value + + return process + + def result_processor(self, dialect, coltype): + if dialect.supports_native_boolean: + return None + else: + return processors.int_to_boolean + + +class _AbstractInterval(HasExpressionLookup, TypeEngine[dt.timedelta]): + @util.memoized_property + def _expression_adaptations(self): + # Based on + # https://www.postgresql.org/docs/current/static/functions-datetime.html. + + return { + operators.add: { + Date: DateTime, + Interval: self.__class__, + DateTime: DateTime, + Time: Time, + }, + operators.sub: {Interval: self.__class__}, + operators.mul: {Numeric: self.__class__}, + operators.truediv: {Numeric: self.__class__}, + } + + @util.ro_non_memoized_property + def _type_affinity(self) -> Type[Interval]: + return Interval + + +class Interval(Emulated, _AbstractInterval, TypeDecorator[dt.timedelta]): + """A type for ``datetime.timedelta()`` objects. + + The Interval type deals with ``datetime.timedelta`` objects. In PostgreSQL + and Oracle Database, the native ``INTERVAL`` type is used; for others, the + value is stored as a date which is relative to the "epoch" (Jan. 1, 1970). + + Note that the ``Interval`` type does not currently provide date arithmetic + operations on platforms which do not support interval types natively. Such + operations usually require transformation of both sides of the expression + (such as, conversion of both sides into integer epoch values first) which + currently is a manual procedure (such as via + :attr:`~sqlalchemy.sql.expression.func`). + + """ + + impl = DateTime + epoch = dt.datetime.fromtimestamp(0, dt.timezone.utc).replace(tzinfo=None) + cache_ok = True + + def __init__( + self, + native: bool = True, + second_precision: Optional[int] = None, + day_precision: Optional[int] = None, + ): + """Construct an Interval object. + + :param native: when True, use the actual + INTERVAL type provided by the database, if + supported (currently PostgreSQL, Oracle Database). + Otherwise, represent the interval data as + an epoch value regardless. + + :param second_precision: For native interval types + which support a "fractional seconds precision" parameter, + i.e. Oracle Database and PostgreSQL + + :param day_precision: for native interval types which + support a "day precision" parameter, i.e. Oracle Database. + + """ + super().__init__() + self.native = native + self.second_precision = second_precision + self.day_precision = day_precision + + class Comparator( + TypeDecorator.Comparator[_CT], + _AbstractInterval.Comparator[_CT], + ): + __slots__ = () + + comparator_factory = Comparator + + @property + def python_type(self): + return dt.timedelta + + def adapt_to_emulated(self, impltype, **kw): + return _AbstractInterval.adapt(self, impltype, **kw) + + def coerce_compared_value(self, op, value): + return self.impl_instance.coerce_compared_value(op, value) + + def bind_processor( + self, dialect: Dialect + ) -> _BindProcessorType[dt.timedelta]: + if TYPE_CHECKING: + assert isinstance(self.impl_instance, DateTime) + impl_processor = self.impl_instance.bind_processor(dialect) + epoch = self.epoch + if impl_processor: + fixed_impl_processor = impl_processor + + def process( + value: Optional[dt.timedelta], + ) -> Any: + if value is not None: + dt_value = epoch + value + else: + dt_value = None + return fixed_impl_processor(dt_value) + + else: + + def process( + value: Optional[dt.timedelta], + ) -> Any: + if value is not None: + dt_value = epoch + value + else: + dt_value = None + return dt_value + + return process + + def result_processor( + self, dialect: Dialect, coltype: Any + ) -> _ResultProcessorType[dt.timedelta]: + if TYPE_CHECKING: + assert isinstance(self.impl_instance, DateTime) + impl_processor = self.impl_instance.result_processor(dialect, coltype) + epoch = self.epoch + if impl_processor: + fixed_impl_processor = impl_processor + + def process(value: Any) -> Optional[dt.timedelta]: + dt_value = fixed_impl_processor(value) + if dt_value is None: + return None + return dt_value - epoch + + else: + + def process(value: Any) -> Optional[dt.timedelta]: + if value is None: + return None + return value - epoch # type: ignore + + return process + + +class JSON(Indexable, TypeEngine[Any]): + """Represent a SQL JSON type. + + .. note:: :class:`_types.JSON` + is provided as a facade for vendor-specific + JSON types. Since it supports JSON SQL operations, it only + works on backends that have an actual JSON type, currently: + + * PostgreSQL - see :class:`sqlalchemy.dialects.postgresql.JSON` and + :class:`sqlalchemy.dialects.postgresql.JSONB` for backend-specific + notes + + * MySQL - see + :class:`sqlalchemy.dialects.mysql.JSON` for backend-specific notes + + * SQLite as of version 3.9 - see + :class:`sqlalchemy.dialects.sqlite.JSON` for backend-specific notes + + * Microsoft SQL Server 2016 and later - see + :class:`sqlalchemy.dialects.mssql.JSON` for backend-specific notes + + :class:`_types.JSON` is part of the Core in support of the growing + popularity of native JSON datatypes. + + The :class:`_types.JSON` type stores arbitrary JSON format data, e.g.:: + + data_table = Table( + "data_table", + metadata, + Column("id", Integer, primary_key=True), + Column("data", JSON), + ) + + with engine.connect() as conn: + conn.execute( + data_table.insert(), {"data": {"key1": "value1", "key2": "value2"}} + ) + + **JSON-Specific Expression Operators** + + The :class:`_types.JSON` + datatype provides these additional SQL operations: + + * Keyed index operations:: + + data_table.c.data["some key"] + + * Integer index operations:: + + data_table.c.data[3] + + * Path index operations:: + + data_table.c.data[("key_1", "key_2", 5, ..., "key_n")] + + * Data casters for specific JSON element types, subsequent to an index + or path operation being invoked:: + + data_table.c.data["some key"].as_integer() + + .. versionadded:: 1.3.11 + + Additional operations may be available from the dialect-specific versions + of :class:`_types.JSON`, such as + :class:`sqlalchemy.dialects.postgresql.JSON` and + :class:`sqlalchemy.dialects.postgresql.JSONB` which both offer additional + PostgreSQL-specific operations. + + **Casting JSON Elements to Other Types** + + Index operations, i.e. those invoked by calling upon the expression using + the Python bracket operator as in ``some_column['some key']``, return an + expression object whose type defaults to :class:`_types.JSON` by default, + so that + further JSON-oriented instructions may be called upon the result type. + However, it is likely more common that an index operation is expected + to return a specific scalar element, such as a string or integer. In + order to provide access to these elements in a backend-agnostic way, + a series of data casters are provided: + + * :meth:`.JSON.Comparator.as_string` - return the element as a string + + * :meth:`.JSON.Comparator.as_boolean` - return the element as a boolean + + * :meth:`.JSON.Comparator.as_float` - return the element as a float + + * :meth:`.JSON.Comparator.as_integer` - return the element as an integer + + These data casters are implemented by supporting dialects in order to + assure that comparisons to the above types will work as expected, such as:: + + # integer comparison + data_table.c.data["some_integer_key"].as_integer() == 5 + + # boolean comparison + data_table.c.data["some_boolean"].as_boolean() == True + + .. versionadded:: 1.3.11 Added type-specific casters for the basic JSON + data element types. + + .. note:: + + The data caster functions are new in version 1.3.11, and supersede + the previous documented approaches of using CAST; for reference, + this looked like:: + + from sqlalchemy import cast, type_coerce + from sqlalchemy import String, JSON + + cast(data_table.c.data["some_key"], String) == type_coerce(55, JSON) + + The above case now works directly as:: + + data_table.c.data["some_key"].as_integer() == 5 + + For details on the previous comparison approach within the 1.3.x + series, see the documentation for SQLAlchemy 1.2 or the included HTML + files in the doc/ directory of the version's distribution. + + **Detecting Changes in JSON columns when using the ORM** + + The :class:`_types.JSON` type, when used with the SQLAlchemy ORM, does not + detect in-place mutations to the structure. In order to detect these, the + :mod:`sqlalchemy.ext.mutable` extension must be used, most typically + using the :class:`.MutableDict` class. This extension will + allow "in-place" changes to the datastructure to produce events which + will be detected by the unit of work. See the example at :class:`.HSTORE` + for a simple example involving a dictionary. + + Alternatively, assigning a JSON structure to an ORM element that + replaces the old one will always trigger a change event. + + **Support for JSON null vs. SQL NULL** + + When working with NULL values, the :class:`_types.JSON` type recommends the + use of two specific constants in order to differentiate between a column + that evaluates to SQL NULL, e.g. no value, vs. the JSON-encoded string of + ``"null"``. To insert or select against a value that is SQL NULL, use the + constant :func:`.null`. This symbol may be passed as a parameter value + specifically when using the :class:`_types.JSON` datatype, which contains + special logic that interprets this symbol to mean that the column value + should be SQL NULL as opposed to JSON ``"null"``:: + + from sqlalchemy import null + + conn.execute(table.insert(), {"json_value": null()}) + + To insert or select against a value that is JSON ``"null"``, use the + constant :attr:`_types.JSON.NULL`:: + + conn.execute(table.insert(), {"json_value": JSON.NULL}) + + The :class:`_types.JSON` type supports a flag + :paramref:`_types.JSON.none_as_null` which when set to True will result + in the Python constant ``None`` evaluating to the value of SQL + NULL, and when set to False results in the Python constant + ``None`` evaluating to the value of JSON ``"null"``. The Python + value ``None`` may be used in conjunction with either + :attr:`_types.JSON.NULL` and :func:`.null` in order to indicate NULL + values, but care must be taken as to the value of the + :paramref:`_types.JSON.none_as_null` in these cases. + + **Customizing the JSON Serializer** + + The JSON serializer and deserializer used by :class:`_types.JSON` + defaults to + Python's ``json.dumps`` and ``json.loads`` functions; in the case of the + psycopg2 dialect, psycopg2 may be using its own custom loader function. + + In order to affect the serializer / deserializer, they are currently + configurable at the :func:`_sa.create_engine` level via the + :paramref:`_sa.create_engine.json_serializer` and + :paramref:`_sa.create_engine.json_deserializer` parameters. For example, + to turn off ``ensure_ascii``:: + + engine = create_engine( + "sqlite://", + json_serializer=lambda obj: json.dumps(obj, ensure_ascii=False), + ) + + .. versionchanged:: 1.3.7 + + SQLite dialect's ``json_serializer`` and ``json_deserializer`` + parameters renamed from ``_json_serializer`` and + ``_json_deserializer``. + + .. seealso:: + + :class:`sqlalchemy.dialects.postgresql.JSON` + + :class:`sqlalchemy.dialects.postgresql.JSONB` + + :class:`sqlalchemy.dialects.mysql.JSON` + + :class:`sqlalchemy.dialects.sqlite.JSON` + + """ # noqa: E501 + + __visit_name__ = "JSON" + + hashable = False + NULL = util.symbol("JSON_NULL") + """Describe the json value of NULL. + + This value is used to force the JSON value of ``"null"`` to be + used as the value. A value of Python ``None`` will be recognized + either as SQL NULL or JSON ``"null"``, based on the setting + of the :paramref:`_types.JSON.none_as_null` flag; the + :attr:`_types.JSON.NULL` + constant can be used to always resolve to JSON ``"null"`` regardless + of this setting. This is in contrast to the :func:`_expression.null` + construct, + which always resolves to SQL NULL. E.g.:: + + from sqlalchemy import null + from sqlalchemy.dialects.postgresql import JSON + + # will *always* insert SQL NULL + obj1 = MyObject(json_value=null()) + + # will *always* insert JSON string "null" + obj2 = MyObject(json_value=JSON.NULL) + + session.add_all([obj1, obj2]) + session.commit() + + In order to set JSON NULL as a default value for a column, the most + transparent method is to use :func:`_expression.text`:: + + Table( + "my_table", metadata, Column("json_data", JSON, default=text("'null'")) + ) + + While it is possible to use :attr:`_types.JSON.NULL` in this context, the + :attr:`_types.JSON.NULL` value will be returned as the value of the + column, + which in the context of the ORM or other repurposing of the default + value, may not be desirable. Using a SQL expression means the value + will be re-fetched from the database within the context of retrieving + generated defaults. + + + """ # noqa: E501 + + def __init__(self, none_as_null: bool = False): + """Construct a :class:`_types.JSON` type. + + :param none_as_null=False: if True, persist the value ``None`` as a + SQL NULL value, not the JSON encoding of ``null``. Note that when this + flag is False, the :func:`.null` construct can still be used to + persist a NULL value, which may be passed directly as a parameter + value that is specially interpreted by the :class:`_types.JSON` type + as SQL NULL:: + + from sqlalchemy import null + + conn.execute(table.insert(), {"data": null()}) + + .. note:: + + :paramref:`_types.JSON.none_as_null` does **not** apply to the + values passed to :paramref:`_schema.Column.default` and + :paramref:`_schema.Column.server_default`; a value of ``None`` + passed for these parameters means "no default present". + + Additionally, when used in SQL comparison expressions, the + Python value ``None`` continues to refer to SQL null, and not + JSON NULL. The :paramref:`_types.JSON.none_as_null` flag refers + explicitly to the **persistence** of the value within an + INSERT or UPDATE statement. The :attr:`_types.JSON.NULL` + value should be used for SQL expressions that wish to compare to + JSON null. + + .. seealso:: + + :attr:`.types.JSON.NULL` + + """ + self.none_as_null = none_as_null + + class JSONElementType(TypeEngine[Any]): + """Common function for index / path elements in a JSON expression.""" + + _integer = Integer() + _string = String() + + def string_bind_processor( + self, dialect: Dialect + ) -> Optional[_BindProcessorType[str]]: + return self._string._cached_bind_processor(dialect) + + def string_literal_processor( + self, dialect: Dialect + ) -> Optional[_LiteralProcessorType[str]]: + return self._string._cached_literal_processor(dialect) + + def bind_processor(self, dialect: Dialect) -> _BindProcessorType[Any]: + int_processor = self._integer._cached_bind_processor(dialect) + string_processor = self.string_bind_processor(dialect) + + def process(value: Optional[Any]) -> Any: + if int_processor and isinstance(value, int): + value = int_processor(value) + elif string_processor and isinstance(value, str): + value = string_processor(value) + return value + + return process + + def literal_processor( + self, dialect: Dialect + ) -> _LiteralProcessorType[Any]: + int_processor = self._integer._cached_literal_processor(dialect) + string_processor = self.string_literal_processor(dialect) + + def process(value: Optional[Any]) -> Any: + if int_processor and isinstance(value, int): + value = int_processor(value) + elif string_processor and isinstance(value, str): + value = string_processor(value) + else: + raise NotImplementedError() + + return value + + return process + + class JSONIndexType(JSONElementType): + """Placeholder for the datatype of a JSON index value. + + This allows execution-time processing of JSON index values + for special syntaxes. + + """ + + class JSONIntIndexType(JSONIndexType): + """Placeholder for the datatype of a JSON index value. + + This allows execution-time processing of JSON index values + for special syntaxes. + + """ + + class JSONStrIndexType(JSONIndexType): + """Placeholder for the datatype of a JSON index value. + + This allows execution-time processing of JSON index values + for special syntaxes. + + """ + + class JSONPathType(JSONElementType): + """Placeholder type for JSON path operations. + + This allows execution-time processing of a path-based + index value into a specific SQL syntax. + + """ + + __visit_name__ = "json_path" + + class Comparator(Indexable.Comparator[_T], Concatenable.Comparator[_T]): + """Define comparison operations for :class:`_types.JSON`.""" + + __slots__ = () + + type: JSON + + def _setup_getitem(self, index): + if not isinstance(index, str) and isinstance( + index, collections_abc.Sequence + ): + index = coercions.expect( + roles.BinaryElementRole, + index, + expr=self.expr, + operator=operators.json_path_getitem_op, + bindparam_type=JSON.JSONPathType, + ) + + operator = operators.json_path_getitem_op + else: + index = coercions.expect( + roles.BinaryElementRole, + index, + expr=self.expr, + operator=operators.json_getitem_op, + bindparam_type=( + JSON.JSONIntIndexType + if isinstance(index, int) + else JSON.JSONStrIndexType + ), + ) + operator = operators.json_getitem_op + + return operator, index, self.type + + def as_boolean(self): + """Consider an indexed value as boolean. + + This is similar to using :class:`_sql.type_coerce`, and will + usually not apply a ``CAST()``. + + e.g.:: + + stmt = select(mytable.c.json_column["some_data"].as_boolean()).where( + mytable.c.json_column["some_data"].as_boolean() == True + ) + + .. versionadded:: 1.3.11 + + """ # noqa: E501 + return self._binary_w_type(Boolean(), "as_boolean") + + def as_string(self): + """Consider an indexed value as string. + + This is similar to using :class:`_sql.type_coerce`, and will + usually not apply a ``CAST()``. + + e.g.:: + + stmt = select(mytable.c.json_column["some_data"].as_string()).where( + mytable.c.json_column["some_data"].as_string() == "some string" + ) + + .. versionadded:: 1.3.11 + + """ # noqa: E501 + return self._binary_w_type(Unicode(), "as_string") + + def as_integer(self): + """Consider an indexed value as integer. + + This is similar to using :class:`_sql.type_coerce`, and will + usually not apply a ``CAST()``. + + e.g.:: + + stmt = select(mytable.c.json_column["some_data"].as_integer()).where( + mytable.c.json_column["some_data"].as_integer() == 5 + ) + + .. versionadded:: 1.3.11 + + """ # noqa: E501 + return self._binary_w_type(Integer(), "as_integer") + + def as_float(self): + """Consider an indexed value as float. + + This is similar to using :class:`_sql.type_coerce`, and will + usually not apply a ``CAST()``. + + e.g.:: + + stmt = select(mytable.c.json_column["some_data"].as_float()).where( + mytable.c.json_column["some_data"].as_float() == 29.75 + ) + + .. versionadded:: 1.3.11 + + """ # noqa: E501 + return self._binary_w_type(Float(), "as_float") + + def as_numeric(self, precision, scale, asdecimal=True): + """Consider an indexed value as numeric/decimal. + + This is similar to using :class:`_sql.type_coerce`, and will + usually not apply a ``CAST()``. + + e.g.:: + + stmt = select(mytable.c.json_column["some_data"].as_numeric(10, 6)).where( + mytable.c.json_column["some_data"].as_numeric(10, 6) == 29.75 + ) + + .. versionadded:: 1.4.0b2 + + """ # noqa: E501 + return self._binary_w_type( + Numeric(precision, scale, asdecimal=asdecimal), "as_numeric" + ) + + def as_json(self): + """Consider an indexed value as JSON. + + This is similar to using :class:`_sql.type_coerce`, and will + usually not apply a ``CAST()``. + + e.g.:: + + stmt = select(mytable.c.json_column["some_data"].as_json()) + + This is typically the default behavior of indexed elements in any + case. + + Note that comparison of full JSON structures may not be + supported by all backends. + + .. versionadded:: 1.3.11 + + """ + return self.expr + + def _binary_w_type(self, typ, method_name): + if not isinstance( + self.expr, elements.BinaryExpression + ) or self.expr.operator not in ( + operators.json_getitem_op, + operators.json_path_getitem_op, + ): + raise exc.InvalidRequestError( + "The JSON cast operator JSON.%s() only works with a JSON " + "index expression e.g. col['q'].%s()" + % (method_name, method_name) + ) + expr = self.expr._clone() + expr.type = typ + return expr + + comparator_factory = Comparator + + @property + def python_type(self): + return dict + + @property + def should_evaluate_none(self): + """Alias of :attr:`_types.JSON.none_as_null`""" + return not self.none_as_null + + @should_evaluate_none.setter + def should_evaluate_none(self, value): + self.none_as_null = not value + + @util.memoized_property + def _str_impl(self): + return String() + + def _make_bind_processor(self, string_process, json_serializer): + if string_process: + + def process(value): + if value is self.NULL: + value = None + elif isinstance(value, elements.Null) or ( + value is None and self.none_as_null + ): + return None + + serialized = json_serializer(value) + return string_process(serialized) + + else: + + def process(value): + if value is self.NULL: + value = None + elif isinstance(value, elements.Null) or ( + value is None and self.none_as_null + ): + return None + + return json_serializer(value) + + return process + + def bind_processor(self, dialect): + string_process = self._str_impl.bind_processor(dialect) + json_serializer = dialect._json_serializer or json.dumps + + return self._make_bind_processor(string_process, json_serializer) + + def result_processor(self, dialect, coltype): + string_process = self._str_impl.result_processor(dialect, coltype) + json_deserializer = dialect._json_deserializer or json.loads + + def process(value): + if value is None: + return None + if string_process: + value = string_process(value) + return json_deserializer(value) + + return process + + +class ARRAY( + SchemaEventTarget, Indexable, Concatenable, TypeEngine[Sequence[_T]] +): + """Represent a SQL Array type. + + .. note:: This type serves as the basis for all ARRAY operations. + However, currently **only the PostgreSQL backend has support for SQL + arrays in SQLAlchemy**. It is recommended to use the PostgreSQL-specific + :class:`sqlalchemy.dialects.postgresql.ARRAY` type directly when using + ARRAY types with PostgreSQL, as it provides additional operators + specific to that backend. + + :class:`_types.ARRAY` is part of the Core in support of various SQL + standard functions such as :class:`_functions.array_agg` + which explicitly involve + arrays; however, with the exception of the PostgreSQL backend and possibly + some third-party dialects, no other SQLAlchemy built-in dialect has support + for this type. + + An :class:`_types.ARRAY` type is constructed given the "type" + of element:: + + mytable = Table("mytable", metadata, Column("data", ARRAY(Integer))) + + The above type represents an N-dimensional array, + meaning a supporting backend such as PostgreSQL will interpret values + with any number of dimensions automatically. To produce an INSERT + construct that passes in a 1-dimensional array of integers:: + + connection.execute(mytable.insert(), {"data": [1, 2, 3]}) + + The :class:`_types.ARRAY` type can be constructed given a fixed number + of dimensions:: + + mytable = Table( + "mytable", metadata, Column("data", ARRAY(Integer, dimensions=2)) + ) + + Sending a number of dimensions is optional, but recommended if the + datatype is to represent arrays of more than one dimension. This number + is used: + + * When emitting the type declaration itself to the database, e.g. + ``INTEGER[][]`` + + * When translating Python values to database values, and vice versa, e.g. + an ARRAY of :class:`.Unicode` objects uses this number to efficiently + access the string values inside of array structures without resorting + to per-row type inspection + + * When used with the Python ``getitem`` accessor, the number of dimensions + serves to define the kind of type that the ``[]`` operator should + return, e.g. for an ARRAY of INTEGER with two dimensions:: + + >>> expr = table.c.column[5] # returns ARRAY(Integer, dimensions=1) + >>> expr = expr[6] # returns Integer + + For 1-dimensional arrays, an :class:`_types.ARRAY` instance with no + dimension parameter will generally assume single-dimensional behaviors. + + SQL expressions of type :class:`_types.ARRAY` have support for "index" and + "slice" behavior. The ``[]`` operator produces expression + constructs which will produce the appropriate SQL, both for + SELECT statements:: + + select(mytable.c.data[5], mytable.c.data[2:7]) + + as well as UPDATE statements when the :meth:`_expression.Update.values` + method is used:: + + mytable.update().values( + {mytable.c.data[5]: 7, mytable.c.data[2:7]: [1, 2, 3]} + ) + + Indexed access is one-based by default; + for zero-based index conversion, set :paramref:`_types.ARRAY.zero_indexes`. + + The :class:`_types.ARRAY` type also provides for the operators + :meth:`.types.ARRAY.Comparator.any` and + :meth:`.types.ARRAY.Comparator.all`. The PostgreSQL-specific version of + :class:`_types.ARRAY` also provides additional operators. + + .. container:: topic + + **Detecting Changes in ARRAY columns when using the ORM** + + The :class:`_sqltypes.ARRAY` type, when used with the SQLAlchemy ORM, + does not detect in-place mutations to the array. In order to detect + these, the :mod:`sqlalchemy.ext.mutable` extension must be used, using + the :class:`.MutableList` class:: + + from sqlalchemy import ARRAY + from sqlalchemy.ext.mutable import MutableList + + + class SomeOrmClass(Base): + # ... + + data = Column(MutableList.as_mutable(ARRAY(Integer))) + + This extension will allow "in-place" changes such to the array + such as ``.append()`` to produce events which will be detected by the + unit of work. Note that changes to elements **inside** the array, + including subarrays that are mutated in place, are **not** detected. + + Alternatively, assigning a new array value to an ORM element that + replaces the old one will always trigger a change event. + + .. seealso:: + + :class:`sqlalchemy.dialects.postgresql.ARRAY` + + """ + + __visit_name__ = "ARRAY" + + _is_array = True + + zero_indexes = False + """If True, Python zero-based indexes should be interpreted as one-based + on the SQL expression side.""" + + def __init__( + self, + item_type: _TypeEngineArgument[_T], + as_tuple: bool = False, + dimensions: Optional[int] = None, + zero_indexes: bool = False, + ): + """Construct an :class:`_types.ARRAY`. + + E.g.:: + + Column("myarray", ARRAY(Integer)) + + Arguments are: + + :param item_type: The data type of items of this array. Note that + dimensionality is irrelevant here, so multi-dimensional arrays like + ``INTEGER[][]``, are constructed as ``ARRAY(Integer)``, not as + ``ARRAY(ARRAY(Integer))`` or such. + + :param as_tuple=False: Specify whether return results + should be converted to tuples from lists. This parameter is + not generally needed as a Python list corresponds well + to a SQL array. + + :param dimensions: if non-None, the ARRAY will assume a fixed + number of dimensions. This impacts how the array is declared + on the database, how it goes about interpreting Python and + result values, as well as how expression behavior in conjunction + with the "getitem" operator works. See the description at + :class:`_types.ARRAY` for additional detail. + + :param zero_indexes=False: when True, index values will be converted + between Python zero-based and SQL one-based indexes, e.g. + a value of one will be added to all index values before passing + to the database. + + """ + if isinstance(item_type, ARRAY): + raise ValueError( + "Do not nest ARRAY types; ARRAY(basetype) " + "handles multi-dimensional arrays of basetype" + ) + if isinstance(item_type, type): + item_type = item_type() + self.item_type = item_type + self.as_tuple = as_tuple + self.dimensions = dimensions + self.zero_indexes = zero_indexes + + class Comparator( + Indexable.Comparator[Sequence[_CT]], + Concatenable.Comparator[Sequence[_CT]], + ): + """Define comparison operations for :class:`_types.ARRAY`. + + More operators are available on the dialect-specific form + of this type. See :class:`.postgresql.ARRAY.Comparator`. + + """ + + __slots__ = () + + type: ARRAY[_CT] + + @overload + def _setup_getitem( + self, index: int + ) -> Tuple[OperatorType, int, TypeEngine[Any]]: ... + + @overload + def _setup_getitem( + self, index: slice + ) -> Tuple[OperatorType, Slice, TypeEngine[Any]]: ... + + def _setup_getitem(self, index: Union[int, slice]) -> Union[ + Tuple[OperatorType, int, TypeEngine[Any]], + Tuple[OperatorType, Slice, TypeEngine[Any]], + ]: + arr_type = self.type + + return_type: TypeEngine[Any] + + if isinstance(index, slice): + return_type = arr_type + if arr_type.zero_indexes: + index = slice(index.start + 1, index.stop + 1, index.step) + slice_ = Slice( + index.start, index.stop, index.step, _name=self.expr.key + ) + return operators.getitem, slice_, return_type + else: + if arr_type.zero_indexes: + index += 1 + if arr_type.dimensions is None or arr_type.dimensions == 1: + return_type = arr_type.item_type + else: + adapt_kw = {"dimensions": arr_type.dimensions - 1} + return_type = arr_type.adapt( + arr_type.__class__, **adapt_kw + ) + + return operators.getitem, index, return_type + + def contains(self, *arg: Any, **kw: Any) -> ColumnElement[bool]: + """``ARRAY.contains()`` not implemented for the base ARRAY type. + Use the dialect-specific ARRAY type. + + .. seealso:: + + :class:`_postgresql.ARRAY` - PostgreSQL specific version. + """ + raise NotImplementedError( + "ARRAY.contains() not implemented for the base " + "ARRAY type; please use the dialect-specific ARRAY type" + ) + + @util.preload_module("sqlalchemy.sql.elements") + def any( + self, other: Any, operator: Optional[OperatorType] = None + ) -> ColumnElement[bool]: + """Return ``other operator ANY (array)`` clause. + + .. legacy:: This method is an :class:`_types.ARRAY` - specific + construct that is now superseded by the :func:`_sql.any_` + function, which features a different calling style. The + :func:`_sql.any_` function is also mirrored at the method level + via the :meth:`_sql.ColumnOperators.any_` method. + + Usage of array-specific :meth:`_types.ARRAY.Comparator.any` + is as follows:: + + from sqlalchemy.sql import operators + + conn.execute( + select(table.c.data).where(table.c.data.any(7, operator=operators.lt)) + ) + + :param other: expression to be compared + :param operator: an operator object from the + :mod:`sqlalchemy.sql.operators` + package, defaults to :func:`.operators.eq`. + + .. seealso:: + + :func:`_expression.any_` + + :meth:`.types.ARRAY.Comparator.all` + + """ # noqa: E501 + elements = util.preloaded.sql_elements + operator = operator if operator else operators.eq + + arr_type = self.type + + return elements.CollectionAggregate._create_any(self.expr).operate( + operators.mirror(operator), + coercions.expect( + roles.BinaryElementRole, + element=other, + operator=operator, + expr=self.expr, + bindparam_type=arr_type.item_type, + ), + ) + + @util.preload_module("sqlalchemy.sql.elements") + def all( + self, other: Any, operator: Optional[OperatorType] = None + ) -> ColumnElement[bool]: + """Return ``other operator ALL (array)`` clause. + + .. legacy:: This method is an :class:`_types.ARRAY` - specific + construct that is now superseded by the :func:`_sql.all_` + function, which features a different calling style. The + :func:`_sql.all_` function is also mirrored at the method level + via the :meth:`_sql.ColumnOperators.all_` method. + + Usage of array-specific :meth:`_types.ARRAY.Comparator.all` + is as follows:: + + from sqlalchemy.sql import operators + + conn.execute( + select(table.c.data).where(table.c.data.all(7, operator=operators.lt)) + ) + + :param other: expression to be compared + :param operator: an operator object from the + :mod:`sqlalchemy.sql.operators` + package, defaults to :func:`.operators.eq`. + + .. seealso:: + + :func:`_expression.all_` + + :meth:`.types.ARRAY.Comparator.any` + + """ # noqa: E501 + elements = util.preloaded.sql_elements + operator = operator if operator else operators.eq + + arr_type = self.type + + return elements.CollectionAggregate._create_all(self.expr).operate( + operators.mirror(operator), + coercions.expect( + roles.BinaryElementRole, + element=other, + operator=operator, + expr=self.expr, + bindparam_type=arr_type.item_type, + ), + ) + + comparator_factory = Comparator + + @property + def hashable(self) -> bool: # type: ignore[override] + return self.as_tuple + + @property + def python_type(self) -> Type[Any]: + return list + + def compare_values(self, x: Any, y: Any) -> bool: + return x == y # type: ignore[no-any-return] + + def _set_parent( + self, parent: SchemaEventTarget, outer: bool = False, **kw: Any + ) -> None: + """Support SchemaEventTarget""" + + if not outer and isinstance(self.item_type, SchemaEventTarget): + self.item_type._set_parent(parent, **kw) + + def _set_parent_with_dispatch( + self, parent: SchemaEventTarget, **kw: Any + ) -> None: + """Support SchemaEventTarget""" + + super()._set_parent_with_dispatch(parent, outer=True) + + if isinstance(self.item_type, SchemaEventTarget): + self.item_type._set_parent_with_dispatch(parent) + + def literal_processor( + self, dialect: Dialect + ) -> Optional[_LiteralProcessorType[_T]]: + item_proc = self.item_type.dialect_impl(dialect).literal_processor( + dialect + ) + if item_proc is None: + return None + + def to_str(elements: Iterable[Any]) -> str: + return f"[{', '.join(elements)}]" + + def process(value: Sequence[Any]) -> str: + inner = self._apply_item_processor( + value, item_proc, self.dimensions, to_str + ) + return inner + + return process + + def _apply_item_processor( + self, + arr: Sequence[Any], + itemproc: Optional[Callable[[Any], Any]], + dim: Optional[int], + collection_callable: Callable[[Iterable[Any]], _P], + ) -> _P: + """Helper method that can be used by bind_processor(), + literal_processor(), etc. to apply an item processor to elements of + an array value, taking into account the 'dimensions' for this + array type. + + See the Postgresql ARRAY datatype for usage examples. + + .. versionadded:: 2.0 + + """ + + if dim is None: + arr = list(arr) + if ( + dim == 1 + or dim is None + and ( + # this has to be (list, tuple), or at least + # not hasattr('__iter__'), since Py3K strings + # etc. have __iter__ + not arr + or not isinstance(arr[0], (list, tuple)) + ) + ): + if itemproc: + return collection_callable(itemproc(x) for x in arr) + else: + return collection_callable(arr) + else: + return collection_callable( + ( + self._apply_item_processor( + x, + itemproc, + dim - 1 if dim is not None else None, + collection_callable, + ) + if x is not None + else None + ) + for x in arr + ) + + +class TupleType(TypeEngine[Tuple[Any, ...]]): + """represent the composite type of a Tuple.""" + + _is_tuple_type = True + + types: List[TypeEngine[Any]] + + def __init__(self, *types: _TypeEngineArgument[Any]): + self._fully_typed = NULLTYPE not in types + self.types = [ + item_type() if isinstance(item_type, type) else item_type + for item_type in types + ] + + def coerce_compared_value( + self, op: Optional[OperatorType], value: Any + ) -> TypeEngine[Any]: + if value is type_api._NO_VALUE_IN_LIST: + return super().coerce_compared_value(op, value) + else: + return TupleType( + *[ + typ.coerce_compared_value(op, elem) + for typ, elem in zip(self.types, value) + ] + ) + + def _resolve_values_to_types(self, value: Any) -> TupleType: + if self._fully_typed: + return self + else: + return TupleType( + *[ + _resolve_value_to_type(elem) if typ is NULLTYPE else typ + for typ, elem in zip(self.types, value) + ] + ) + + def result_processor(self, dialect, coltype): + raise NotImplementedError( + "The tuple type does not support being fetched " + "as a column in a result row." + ) + + +class REAL(Float[_N]): + """The SQL REAL type. + + .. seealso:: + + :class:`_types.Float` - documentation for the base type. + + """ + + __visit_name__ = "REAL" + + +class FLOAT(Float[_N]): + """The SQL FLOAT type. + + .. seealso:: + + :class:`_types.Float` - documentation for the base type. + + """ + + __visit_name__ = "FLOAT" + + +class DOUBLE(Double[_N]): + """The SQL DOUBLE type. + + .. versionadded:: 2.0 + + .. seealso:: + + :class:`_types.Double` - documentation for the base type. + + """ + + __visit_name__ = "DOUBLE" + + +class DOUBLE_PRECISION(Double[_N]): + """The SQL DOUBLE PRECISION type. + + .. versionadded:: 2.0 + + .. seealso:: + + :class:`_types.Double` - documentation for the base type. + + """ + + __visit_name__ = "DOUBLE_PRECISION" + + +class NUMERIC(Numeric[_N]): + """The SQL NUMERIC type. + + .. seealso:: + + :class:`_types.Numeric` - documentation for the base type. + + """ + + __visit_name__ = "NUMERIC" + + +class DECIMAL(Numeric[_N]): + """The SQL DECIMAL type. + + .. seealso:: + + :class:`_types.Numeric` - documentation for the base type. + + """ + + __visit_name__ = "DECIMAL" + + +class INTEGER(Integer): + """The SQL INT or INTEGER type. + + .. seealso:: + + :class:`_types.Integer` - documentation for the base type. + + """ + + __visit_name__ = "INTEGER" + + +INT = INTEGER + + +class SMALLINT(SmallInteger): + """The SQL SMALLINT type. + + .. seealso:: + + :class:`_types.SmallInteger` - documentation for the base type. + + """ + + __visit_name__ = "SMALLINT" + + +class BIGINT(BigInteger): + """The SQL BIGINT type. + + .. seealso:: + + :class:`_types.BigInteger` - documentation for the base type. + + """ + + __visit_name__ = "BIGINT" + + +class TIMESTAMP(DateTime): + """The SQL TIMESTAMP type. + + :class:`_types.TIMESTAMP` datatypes have support for timezone storage on + some backends, such as PostgreSQL and Oracle Database. Use the + :paramref:`~types.TIMESTAMP.timezone` argument in order to enable + "TIMESTAMP WITH TIMEZONE" for these backends. + + """ + + __visit_name__ = "TIMESTAMP" + + def __init__(self, timezone: bool = False): + """Construct a new :class:`_types.TIMESTAMP`. + + :param timezone: boolean. Indicates that the TIMESTAMP type should + enable timezone support, if available on the target database. + On a per-dialect basis is similar to "TIMESTAMP WITH TIMEZONE". + If the target database does not support timezones, this flag is + ignored. + + + """ + super().__init__(timezone=timezone) + + def get_dbapi_type(self, dbapi): + return dbapi.TIMESTAMP + + +class DATETIME(DateTime): + """The SQL DATETIME type.""" + + __visit_name__ = "DATETIME" + + +class DATE(Date): + """The SQL DATE type.""" + + __visit_name__ = "DATE" + + +class TIME(Time): + """The SQL TIME type.""" + + __visit_name__ = "TIME" + + +class TEXT(Text): + """The SQL TEXT type.""" + + __visit_name__ = "TEXT" + + +class CLOB(Text): + """The CLOB type. + + This type is found in Oracle Database and Informix. + """ + + __visit_name__ = "CLOB" + + +class VARCHAR(String): + """The SQL VARCHAR type.""" + + __visit_name__ = "VARCHAR" + + +class NVARCHAR(Unicode): + """The SQL NVARCHAR type.""" + + __visit_name__ = "NVARCHAR" + + +class CHAR(String): + """The SQL CHAR type.""" + + __visit_name__ = "CHAR" + + +class NCHAR(Unicode): + """The SQL NCHAR type.""" + + __visit_name__ = "NCHAR" + + +class BLOB(LargeBinary): + """The SQL BLOB type.""" + + __visit_name__ = "BLOB" + + +class BINARY(_Binary): + """The SQL BINARY type.""" + + __visit_name__ = "BINARY" + + +class VARBINARY(_Binary): + """The SQL VARBINARY type.""" + + __visit_name__ = "VARBINARY" + + +class BOOLEAN(Boolean): + """The SQL BOOLEAN type.""" + + __visit_name__ = "BOOLEAN" + + +class NullType(TypeEngine[None]): + """An unknown type. + + :class:`.NullType` is used as a default type for those cases where + a type cannot be determined, including: + + * During table reflection, when the type of a column is not recognized + by the :class:`.Dialect` + * When constructing SQL expressions using plain Python objects of + unknown types (e.g. ``somecolumn == my_special_object``) + * When a new :class:`_schema.Column` is created, + and the given type is passed + as ``None`` or is not passed at all. + + The :class:`.NullType` can be used within SQL expression invocation + without issue, it just has no behavior either at the expression + construction level or at the bind-parameter/result processing level. + :class:`.NullType` will result in a :exc:`.CompileError` if the compiler + is asked to render the type itself, such as if it is used in a + :func:`.cast` operation or within a schema creation operation such as that + invoked by :meth:`_schema.MetaData.create_all` or the + :class:`.CreateTable` + construct. + + """ + + __visit_name__ = "null" + + _isnull = True + + def literal_processor(self, dialect): + return None + + class Comparator(TypeEngine.Comparator[_T]): + __slots__ = () + + def _adapt_expression( + self, + op: OperatorType, + other_comparator: TypeEngine.Comparator[Any], + ) -> Tuple[OperatorType, TypeEngine[Any]]: + if isinstance( + other_comparator, NullType.Comparator + ) or not operators.is_commutative(op): + return op, self.expr.type + else: + return other_comparator._adapt_expression(op, self) + + comparator_factory = Comparator + + +class TableValueType(HasCacheKey, TypeEngine[Any]): + """Refers to a table value type.""" + + _is_table_value = True + + _traverse_internals = [ + ("_elements", InternalTraversal.dp_clauseelement_list), + ] + + def __init__(self, *elements: Union[str, _ColumnExpressionArgument[Any]]): + self._elements = [ + coercions.expect(roles.StrAsPlainColumnRole, elem) + for elem in elements + ] + + +class MatchType(Boolean): + """Refers to the return type of the MATCH operator. + + As the :meth:`.ColumnOperators.match` is probably the most open-ended + operator in generic SQLAlchemy Core, we can't assume the return type + at SQL evaluation time, as MySQL returns a floating point, not a boolean, + and other backends might do something different. So this type + acts as a placeholder, currently subclassing :class:`.Boolean`. + The type allows dialects to inject result-processing functionality + if needed, and on MySQL will return floating-point values. + + """ + + +_UUID_RETURN = TypeVar("_UUID_RETURN", str, _python_UUID) + + +class Uuid(Emulated, TypeEngine[_UUID_RETURN]): + """Represent a database agnostic UUID datatype. + + For backends that have no "native" UUID datatype, the value will + make use of ``CHAR(32)`` and store the UUID as a 32-character alphanumeric + hex string. + + For backends which are known to support ``UUID`` directly or a similar + uuid-storing datatype such as SQL Server's ``UNIQUEIDENTIFIER``, a + "native" mode enabled by default allows these types will be used on those + backends. + + In its default mode of use, the :class:`_sqltypes.Uuid` datatype expects + **Python uuid objects**, from the Python + `uuid `_ + module:: + + import uuid + + from sqlalchemy import Uuid + from sqlalchemy import Table, Column, MetaData, String + + metadata_obj = MetaData() + + t = Table( + "t", + metadata_obj, + Column("uuid_data", Uuid, primary_key=True), + Column("other_data", String), + ) + + with engine.begin() as conn: + conn.execute( + t.insert(), {"uuid_data": uuid.uuid4(), "other_data": "some data"} + ) + + To have the :class:`_sqltypes.Uuid` datatype work with string-based + Uuids (e.g. 32 character hexadecimal strings), pass the + :paramref:`_sqltypes.Uuid.as_uuid` parameter with the value ``False``. + + .. versionadded:: 2.0 + + .. seealso:: + + :class:`_sqltypes.UUID` - represents exactly the ``UUID`` datatype + without any backend-agnostic behaviors. + + """ # noqa: E501 + + __visit_name__ = "uuid" + + length: Optional[int] = None + collation: Optional[str] = None + + @overload + def __init__( + self: Uuid[_python_UUID], + as_uuid: Literal[True] = ..., + native_uuid: bool = ..., + ): ... + + @overload + def __init__( + self: Uuid[str], + as_uuid: Literal[False] = ..., + native_uuid: bool = ..., + ): ... + + def __init__(self, as_uuid: bool = True, native_uuid: bool = True): + """Construct a :class:`_sqltypes.Uuid` type. + + :param as_uuid=True: if True, values will be interpreted + as Python uuid objects, converting to/from string via the + DBAPI. + + .. versionchanged: 2.0 ``as_uuid`` now defaults to ``True``. + + :param native_uuid=True: if True, backends that support either the + ``UUID`` datatype directly, or a UUID-storing value + (such as SQL Server's ``UNIQUEIDENTIFIER`` will be used by those + backends. If False, a ``CHAR(32)`` datatype will be used for + all backends regardless of native support. + + """ + self.as_uuid = as_uuid + self.native_uuid = native_uuid + + @property + def python_type(self): + return _python_UUID if self.as_uuid else str + + @property + def native(self): # type: ignore[override] + return self.native_uuid + + def coerce_compared_value(self, op, value): + """See :meth:`.TypeEngine.coerce_compared_value` for a description.""" + + if isinstance(value, str): + return self + else: + return super().coerce_compared_value(op, value) + + def bind_processor( + self, dialect: Dialect + ) -> Optional[_BindProcessorType[_UUID_RETURN]]: + character_based_uuid = ( + not dialect.supports_native_uuid or not self.native_uuid + ) + + if character_based_uuid: + if self.as_uuid: + + def process(value): + if value is not None: + value = value.hex + return value + + return process + else: + + def process(value): + if value is not None: + value = value.replace("-", "") + return value + + return process + else: + return None + + def result_processor(self, dialect, coltype): + character_based_uuid = ( + not dialect.supports_native_uuid or not self.native_uuid + ) + + if character_based_uuid: + if self.as_uuid: + + def process(value): + if value is not None: + value = _python_UUID(value) + return value + + return process + else: + + def process(value): + if value is not None: + value = str(_python_UUID(value)) + return value + + return process + else: + if not self.as_uuid: + + def process(value): + if value is not None: + value = str(value) + return value + + return process + else: + return None + + def literal_processor(self, dialect): + character_based_uuid = ( + not dialect.supports_native_uuid or not self.native_uuid + ) + + if not self.as_uuid: + + def process(value): + return f"""'{value.replace("-", "").replace("'", "''")}'""" + + return process + else: + if character_based_uuid: + + def process(value): + return f"""'{value.hex}'""" + + return process + else: + + def process(value): + return f"""'{str(value).replace("'", "''")}'""" + + return process + + +class UUID(Uuid[_UUID_RETURN], type_api.NativeForEmulated): + """Represent the SQL UUID type. + + This is the SQL-native form of the :class:`_types.Uuid` database agnostic + datatype, and is backwards compatible with the previous PostgreSQL-only + version of ``UUID``. + + The :class:`_sqltypes.UUID` datatype only works on databases that have a + SQL datatype named ``UUID``. It will not function for backends which don't + have this exact-named type, including SQL Server. For backend-agnostic UUID + values with native support, including for SQL Server's ``UNIQUEIDENTIFIER`` + datatype, use the :class:`_sqltypes.Uuid` datatype. + + .. versionadded:: 2.0 + + .. seealso:: + + :class:`_sqltypes.Uuid` + + """ + + __visit_name__ = "UUID" + + @overload + def __init__(self: UUID[_python_UUID], as_uuid: Literal[True] = ...): ... + + @overload + def __init__(self: UUID[str], as_uuid: Literal[False] = ...): ... + + def __init__(self, as_uuid: bool = True): + """Construct a :class:`_sqltypes.UUID` type. + + + :param as_uuid=True: if True, values will be interpreted + as Python uuid objects, converting to/from string via the + DBAPI. + + .. versionchanged: 2.0 ``as_uuid`` now defaults to ``True``. + + """ + self.as_uuid = as_uuid + self.native_uuid = True + + @classmethod + def adapt_emulated_to_native(cls, impl, **kw): + kw.setdefault("as_uuid", impl.as_uuid) + return cls(**kw) + + +NULLTYPE = NullType() +BOOLEANTYPE = Boolean() +STRINGTYPE = String() +INTEGERTYPE = Integer() +NUMERICTYPE: Numeric[decimal.Decimal] = Numeric() +MATCHTYPE = MatchType() +TABLEVALUE = TableValueType() +DATETIME_TIMEZONE = DateTime(timezone=True) +TIME_TIMEZONE = Time(timezone=True) +_BIGINTEGER = BigInteger() +_DATETIME = DateTime() +_TIME = Time() +_STRING = String() +_UNICODE = Unicode() + +_type_map: Dict[Type[Any], TypeEngine[Any]] = { + int: Integer(), + float: Float(), + bool: BOOLEANTYPE, + _python_UUID: Uuid(), + decimal.Decimal: Numeric(), + dt.date: Date(), + dt.datetime: _DATETIME, + dt.time: _TIME, + dt.timedelta: Interval(), + type(None): NULLTYPE, + bytes: LargeBinary(), + str: _STRING, + enum.Enum: Enum(enum.Enum), + Literal: Enum(enum.Enum), # type: ignore[dict-item] +} + + +_type_map_get = _type_map.get + + +def _resolve_value_to_type(value: Any) -> TypeEngine[Any]: + _result_type = _type_map_get(type(value), False) + + if _result_type is False: + _result_type = getattr(value, "__sa_type_engine__", False) + + if _result_type is False: + # use inspect() to detect SQLAlchemy built-in + # objects. + insp = inspection.inspect(value, False) + if ( + insp is not None + and + # foil mock.Mock() and other impostors by ensuring + # the inspection target itself self-inspects + insp.__class__ in inspection._registrars + ): + raise exc.ArgumentError( + "Object %r is not legal as a SQL literal value" % (value,) + ) + return NULLTYPE + else: + return _result_type._resolve_for_literal( # type: ignore [union-attr] + value + ) + + +# back-assign to type_api +type_api.BOOLEANTYPE = BOOLEANTYPE +type_api.STRINGTYPE = STRINGTYPE +type_api.INTEGERTYPE = INTEGERTYPE +type_api.NULLTYPE = NULLTYPE +type_api.NUMERICTYPE = NUMERICTYPE +type_api.MATCHTYPE = MATCHTYPE +type_api.INDEXABLE = INDEXABLE = Indexable +type_api.TABLEVALUE = TABLEVALUE +type_api._resolve_value_to_type = _resolve_value_to_type diff --git a/venv/Lib/site-packages/sqlalchemy/sql/traversals.py b/venv/Lib/site-packages/sqlalchemy/sql/traversals.py new file mode 100644 index 0000000..d1ddb8a --- /dev/null +++ b/venv/Lib/site-packages/sqlalchemy/sql/traversals.py @@ -0,0 +1,1023 @@ +# sql/traversals.py +# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: https://www.opensource.org/licenses/mit-license.php +# mypy: allow-untyped-defs, allow-untyped-calls + +from __future__ import annotations + +from collections import deque +import collections.abc as collections_abc +import itertools +from itertools import zip_longest +import operator +import typing +from typing import Any +from typing import Callable +from typing import Deque +from typing import Dict +from typing import Iterable +from typing import Optional +from typing import Set +from typing import Tuple +from typing import Type + +from . import operators +from .cache_key import HasCacheKey +from .visitors import _TraverseInternalsType +from .visitors import anon_map +from .visitors import ExternallyTraversible +from .visitors import HasTraversalDispatch +from .visitors import HasTraverseInternals +from .. import util +from ..util import langhelpers +from ..util.typing import Self + +SKIP_TRAVERSE = util.symbol("skip_traverse") +COMPARE_FAILED = False +COMPARE_SUCCEEDED = True + + +def compare(obj1: Any, obj2: Any, **kw: Any) -> bool: + strategy: TraversalComparatorStrategy + if kw.get("use_proxies", False): + strategy = ColIdentityComparatorStrategy() + else: + strategy = TraversalComparatorStrategy() + + return strategy.compare(obj1, obj2, **kw) + + +def _preconfigure_traversals(target_hierarchy: Type[Any]) -> None: + for cls in util.walk_subclasses(target_hierarchy): + if hasattr(cls, "_generate_cache_attrs") and hasattr( + cls, "_traverse_internals" + ): + cls._generate_cache_attrs() + _copy_internals.generate_dispatch( + cls, + cls._traverse_internals, + "_generated_copy_internals_traversal", + ) + _get_children.generate_dispatch( + cls, + cls._traverse_internals, + "_generated_get_children_traversal", + ) + + +class HasShallowCopy(HasTraverseInternals): + """attribute-wide operations that are useful for classes that use + __slots__ and therefore can't operate on their attributes in a dictionary. + + + """ + + __slots__ = () + + if typing.TYPE_CHECKING: + + def _generated_shallow_copy_traversal(self, other: Self) -> None: ... + + def _generated_shallow_from_dict_traversal( + self, d: Dict[str, Any] + ) -> None: ... + + def _generated_shallow_to_dict_traversal(self) -> Dict[str, Any]: ... + + @classmethod + def _generate_shallow_copy( + cls, + internal_dispatch: _TraverseInternalsType, + method_name: str, + ) -> Callable[[Self, Self], None]: + code = "\n".join( + f" other.{attrname} = self.{attrname}" + for attrname, _ in internal_dispatch + ) + meth_text = f"def {method_name}(self, other):\n{code}\n" + return langhelpers._exec_code_in_env(meth_text, {}, method_name) + + @classmethod + def _generate_shallow_to_dict( + cls, + internal_dispatch: _TraverseInternalsType, + method_name: str, + ) -> Callable[[Self], Dict[str, Any]]: + code = ",\n".join( + f" '{attrname}': self.{attrname}" + for attrname, _ in internal_dispatch + ) + meth_text = f"def {method_name}(self):\n return {{{code}}}\n" + return langhelpers._exec_code_in_env(meth_text, {}, method_name) + + @classmethod + def _generate_shallow_from_dict( + cls, + internal_dispatch: _TraverseInternalsType, + method_name: str, + ) -> Callable[[Self, Dict[str, Any]], None]: + code = "\n".join( + f" self.{attrname} = d['{attrname}']" + for attrname, _ in internal_dispatch + ) + meth_text = f"def {method_name}(self, d):\n{code}\n" + return langhelpers._exec_code_in_env(meth_text, {}, method_name) + + def _shallow_from_dict(self, d: Dict[str, Any]) -> None: + cls = self.__class__ + + shallow_from_dict: Callable[[HasShallowCopy, Dict[str, Any]], None] + try: + shallow_from_dict = cls.__dict__[ + "_generated_shallow_from_dict_traversal" + ] + except KeyError: + shallow_from_dict = self._generate_shallow_from_dict( + cls._traverse_internals, + "_generated_shallow_from_dict_traversal", + ) + + cls._generated_shallow_from_dict_traversal = shallow_from_dict # type: ignore # noqa: E501 + + shallow_from_dict(self, d) + + def _shallow_to_dict(self) -> Dict[str, Any]: + cls = self.__class__ + + shallow_to_dict: Callable[[HasShallowCopy], Dict[str, Any]] + + try: + shallow_to_dict = cls.__dict__[ + "_generated_shallow_to_dict_traversal" + ] + except KeyError: + shallow_to_dict = self._generate_shallow_to_dict( + cls._traverse_internals, "_generated_shallow_to_dict_traversal" + ) + + cls._generated_shallow_to_dict_traversal = shallow_to_dict # type: ignore # noqa: E501 + return shallow_to_dict(self) + + def _shallow_copy_to(self, other: Self) -> None: + cls = self.__class__ + + shallow_copy: Callable[[Self, Self], None] + try: + shallow_copy = cls.__dict__["_generated_shallow_copy_traversal"] + except KeyError: + shallow_copy = self._generate_shallow_copy( + cls._traverse_internals, "_generated_shallow_copy_traversal" + ) + + cls._generated_shallow_copy_traversal = shallow_copy # type: ignore # noqa: E501 + shallow_copy(self, other) + + def _clone(self, **kw: Any) -> Self: + """Create a shallow copy""" + c = self.__class__.__new__(self.__class__) + self._shallow_copy_to(c) + return c + + +class GenerativeOnTraversal(HasShallowCopy): + """Supplies Generative behavior but making use of traversals to shallow + copy. + + .. seealso:: + + :class:`sqlalchemy.sql.base.Generative` + + + """ + + __slots__ = () + + def _generate(self) -> Self: + cls = self.__class__ + s = cls.__new__(cls) + self._shallow_copy_to(s) + return s + + +def _clone(element, **kw): + return element._clone() + + +class HasCopyInternals(HasTraverseInternals): + __slots__ = () + + def _clone(self, **kw): + raise NotImplementedError() + + def _copy_internals( + self, *, omit_attrs: Iterable[str] = (), **kw: Any + ) -> None: + """Reassign internal elements to be clones of themselves. + + Called during a copy-and-traverse operation on newly + shallow-copied elements to create a deep copy. + + The given clone function should be used, which may be applying + additional transformations to the element (i.e. replacement + traversal, cloned traversal, annotations). + + """ + + try: + traverse_internals = self._traverse_internals + except AttributeError: + # user-defined classes may not have a _traverse_internals + return + + for attrname, obj, meth in _copy_internals.run_generated_dispatch( + self, traverse_internals, "_generated_copy_internals_traversal" + ): + if attrname in omit_attrs: + continue + + if obj is not None: + result = meth(attrname, self, obj, **kw) + if result is not None: + setattr(self, attrname, result) + + +class _CopyInternalsTraversal(HasTraversalDispatch): + """Generate a _copy_internals internal traversal dispatch for classes + with a _traverse_internals collection.""" + + def visit_clauseelement( + self, attrname, parent, element, clone=_clone, **kw + ): + return clone(element, **kw) + + def visit_clauseelement_list( + self, attrname, parent, element, clone=_clone, **kw + ): + return [clone(clause, **kw) for clause in element] + + def visit_clauseelement_tuple( + self, attrname, parent, element, clone=_clone, **kw + ): + return tuple([clone(clause, **kw) for clause in element]) + + def visit_executable_options( + self, attrname, parent, element, clone=_clone, **kw + ): + return tuple([clone(clause, **kw) for clause in element]) + + def visit_clauseelement_unordered_set( + self, attrname, parent, element, clone=_clone, **kw + ): + return {clone(clause, **kw) for clause in element} + + def visit_clauseelement_tuples( + self, attrname, parent, element, clone=_clone, **kw + ): + return [ + tuple(clone(tup_elem, **kw) for tup_elem in elem) + for elem in element + ] + + def visit_string_clauseelement_dict( + self, attrname, parent, element, clone=_clone, **kw + ): + return {key: clone(value, **kw) for key, value in element.items()} + + def visit_setup_join_tuple( + self, attrname, parent, element, clone=_clone, **kw + ): + return tuple( + ( + clone(target, **kw) if target is not None else None, + clone(onclause, **kw) if onclause is not None else None, + clone(from_, **kw) if from_ is not None else None, + flags, + ) + for (target, onclause, from_, flags) in element + ) + + def visit_memoized_select_entities(self, attrname, parent, element, **kw): + return self.visit_clauseelement_tuple(attrname, parent, element, **kw) + + def visit_dml_ordered_values( + self, attrname, parent, element, clone=_clone, **kw + ): + # sequence of 2-tuples + return [ + ( + ( + clone(key, **kw) + if hasattr(key, "__clause_element__") + else key + ), + clone(value, **kw), + ) + for key, value in element + ] + + def visit_dml_values(self, attrname, parent, element, clone=_clone, **kw): + return { + ( + clone(key, **kw) if hasattr(key, "__clause_element__") else key + ): clone(value, **kw) + for key, value in element.items() + } + + def visit_dml_multi_values( + self, attrname, parent, element, clone=_clone, **kw + ): + # sequence of sequences, each sequence contains a list/dict/tuple + + def copy(elem): + if isinstance(elem, (list, tuple)): + return [ + ( + clone(value, **kw) + if hasattr(value, "__clause_element__") + else value + ) + for value in elem + ] + elif isinstance(elem, dict): + return { + ( + clone(key, **kw) + if hasattr(key, "__clause_element__") + else key + ): ( + clone(value, **kw) + if hasattr(value, "__clause_element__") + else value + ) + for key, value in elem.items() + } + else: + # TODO: use abc classes + assert False + + return [ + [copy(sub_element) for sub_element in sequence] + for sequence in element + ] + + def visit_propagate_attrs( + self, attrname, parent, element, clone=_clone, **kw + ): + return element + + +_copy_internals = _CopyInternalsTraversal() + + +def _flatten_clauseelement(element): + while hasattr(element, "__clause_element__") and not getattr( + element, "is_clause_element", False + ): + element = element.__clause_element__() + + return element + + +class _GetChildrenTraversal(HasTraversalDispatch): + """Generate a _children_traversal internal traversal dispatch for classes + with a _traverse_internals collection.""" + + def visit_has_cache_key(self, element, **kw): + # the GetChildren traversal refers explicitly to ClauseElement + # structures. Within these, a plain HasCacheKey is not a + # ClauseElement, so don't include these. + return () + + def visit_clauseelement(self, element, **kw): + return (element,) + + def visit_clauseelement_list(self, element, **kw): + return element + + def visit_clauseelement_tuple(self, element, **kw): + return element + + def visit_clauseelement_tuples(self, element, **kw): + return itertools.chain.from_iterable(element) + + def visit_fromclause_canonical_column_collection(self, element, **kw): + return () + + def visit_string_clauseelement_dict(self, element, **kw): + return element.values() + + def visit_fromclause_ordered_set(self, element, **kw): + return element + + def visit_clauseelement_unordered_set(self, element, **kw): + return element + + def visit_setup_join_tuple(self, element, **kw): + for target, onclause, from_, flags in element: + if from_ is not None: + yield from_ + + if not isinstance(target, str): + yield _flatten_clauseelement(target) + + if onclause is not None and not isinstance(onclause, str): + yield _flatten_clauseelement(onclause) + + def visit_memoized_select_entities(self, element, **kw): + return self.visit_clauseelement_tuple(element, **kw) + + def visit_dml_ordered_values(self, element, **kw): + for k, v in element: + if hasattr(k, "__clause_element__"): + yield k + yield v + + def visit_dml_values(self, element, **kw): + expr_values = {k for k in element if hasattr(k, "__clause_element__")} + str_values = expr_values.symmetric_difference(element) + + for k in sorted(str_values): + yield element[k] + for k in expr_values: + yield k + yield element[k] + + def visit_dml_multi_values(self, element, **kw): + return () + + def visit_propagate_attrs(self, element, **kw): + return () + + +_get_children = _GetChildrenTraversal() + + +@util.preload_module("sqlalchemy.sql.elements") +def _resolve_name_for_compare(element, name, anon_map, **kw): + if isinstance(name, util.preloaded.sql_elements._anonymous_label): + name = name.apply_map(anon_map) + + return name + + +class TraversalComparatorStrategy(HasTraversalDispatch, util.MemoizedSlots): + __slots__ = "stack", "cache", "anon_map" + + def __init__(self): + self.stack: Deque[ + Tuple[ + Optional[ExternallyTraversible], + Optional[ExternallyTraversible], + ] + ] = deque() + self.cache = set() + + def _memoized_attr_anon_map(self): + return (anon_map(), anon_map()) + + def compare( + self, + obj1: ExternallyTraversible, + obj2: ExternallyTraversible, + **kw: Any, + ) -> bool: + stack = self.stack + cache = self.cache + + compare_annotations = kw.get("compare_annotations", False) + + stack.append((obj1, obj2)) + + while stack: + left, right = stack.popleft() + + if left is right: + continue + elif left is None or right is None: + # we know they are different so no match + return False + elif (left, right) in cache: + continue + cache.add((left, right)) + + visit_name = left.__visit_name__ + if visit_name != right.__visit_name__: + return False + + meth = getattr(self, "compare_%s" % visit_name, None) + + if meth: + attributes_compared = meth(left, right, **kw) + if attributes_compared is COMPARE_FAILED: + return False + elif attributes_compared is SKIP_TRAVERSE: + continue + + # attributes_compared is returned as a list of attribute + # names that were "handled" by the comparison method above. + # remaining attribute names in the _traverse_internals + # will be compared. + else: + attributes_compared = () + + for ( + (left_attrname, left_visit_sym), + (right_attrname, right_visit_sym), + ) in zip_longest( + left._traverse_internals, + right._traverse_internals, + fillvalue=(None, None), + ): + if not compare_annotations and ( + (left_attrname == "_annotations") + or (right_attrname == "_annotations") + ): + continue + + if ( + left_attrname != right_attrname + or left_visit_sym is not right_visit_sym + ): + return False + elif left_attrname in attributes_compared: + continue + + assert left_visit_sym is not None + assert left_attrname is not None + assert right_attrname is not None + + dispatch = self.dispatch(left_visit_sym) + assert dispatch is not None, ( + f"{self.__class__} has no dispatch for " + f"'{self._dispatch_lookup[left_visit_sym]}'" + ) + left_child = operator.attrgetter(left_attrname)(left) + right_child = operator.attrgetter(right_attrname)(right) + if left_child is None: + if right_child is not None: + return False + else: + continue + elif right_child is None: + return False + + comparison = dispatch( + left_attrname, left, left_child, right, right_child, **kw + ) + if comparison is COMPARE_FAILED: + return False + + return True + + def compare_inner(self, obj1, obj2, **kw): + comparator = self.__class__() + return comparator.compare(obj1, obj2, **kw) + + def visit_has_cache_key( + self, attrname, left_parent, left, right_parent, right, **kw + ): + if left._gen_cache_key(self.anon_map[0], []) != right._gen_cache_key( + self.anon_map[1], [] + ): + return COMPARE_FAILED + + def visit_propagate_attrs( + self, attrname, left_parent, left, right_parent, right, **kw + ): + return self.compare_inner( + left.get("plugin_subject", None), right.get("plugin_subject", None) + ) + + def visit_has_cache_key_list( + self, attrname, left_parent, left, right_parent, right, **kw + ): + for l, r in zip_longest(left, right, fillvalue=None): + if l is None: + if r is not None: + return COMPARE_FAILED + else: + continue + elif r is None: + return COMPARE_FAILED + + if l._gen_cache_key(self.anon_map[0], []) != r._gen_cache_key( + self.anon_map[1], [] + ): + return COMPARE_FAILED + + def visit_executable_options( + self, attrname, left_parent, left, right_parent, right, **kw + ): + for l, r in zip_longest(left, right, fillvalue=None): + if l is None: + if r is not None: + return COMPARE_FAILED + else: + continue + elif r is None: + return COMPARE_FAILED + + if ( + l._gen_cache_key(self.anon_map[0], []) + if l._is_has_cache_key + else l + ) != ( + r._gen_cache_key(self.anon_map[1], []) + if r._is_has_cache_key + else r + ): + return COMPARE_FAILED + + def visit_clauseelement( + self, attrname, left_parent, left, right_parent, right, **kw + ): + self.stack.append((left, right)) + + def visit_fromclause_canonical_column_collection( + self, attrname, left_parent, left, right_parent, right, **kw + ): + for lcol, rcol in zip_longest(left, right, fillvalue=None): + self.stack.append((lcol, rcol)) + + def visit_fromclause_derived_column_collection( + self, attrname, left_parent, left, right_parent, right, **kw + ): + pass + + def visit_string_clauseelement_dict( + self, attrname, left_parent, left, right_parent, right, **kw + ): + for lstr, rstr in zip_longest( + sorted(left), sorted(right), fillvalue=None + ): + if lstr != rstr: + return COMPARE_FAILED + self.stack.append((left[lstr], right[rstr])) + + def visit_clauseelement_tuples( + self, attrname, left_parent, left, right_parent, right, **kw + ): + for ltup, rtup in zip_longest(left, right, fillvalue=None): + if ltup is None or rtup is None: + return COMPARE_FAILED + + for l, r in zip_longest(ltup, rtup, fillvalue=None): + self.stack.append((l, r)) + + def visit_clauseelement_list( + self, attrname, left_parent, left, right_parent, right, **kw + ): + for l, r in zip_longest(left, right, fillvalue=None): + self.stack.append((l, r)) + + def visit_clauseelement_tuple( + self, attrname, left_parent, left, right_parent, right, **kw + ): + for l, r in zip_longest(left, right, fillvalue=None): + self.stack.append((l, r)) + + def _compare_unordered_sequences(self, seq1, seq2, **kw): + if seq1 is None: + return seq2 is None + + completed: Set[object] = set() + for clause in seq1: + for other_clause in set(seq2).difference(completed): + if self.compare_inner(clause, other_clause, **kw): + completed.add(other_clause) + break + return len(completed) == len(seq1) == len(seq2) + + def visit_clauseelement_unordered_set( + self, attrname, left_parent, left, right_parent, right, **kw + ): + return self._compare_unordered_sequences(left, right, **kw) + + def visit_fromclause_ordered_set( + self, attrname, left_parent, left, right_parent, right, **kw + ): + for l, r in zip_longest(left, right, fillvalue=None): + self.stack.append((l, r)) + + def visit_string( + self, attrname, left_parent, left, right_parent, right, **kw + ): + return left == right + + def visit_string_list( + self, attrname, left_parent, left, right_parent, right, **kw + ): + return left == right + + def visit_string_multi_dict( + self, attrname, left_parent, left, right_parent, right, **kw + ): + for lk, rk in zip_longest( + sorted(left.keys()), sorted(right.keys()), fillvalue=(None, None) + ): + if lk != rk: + return COMPARE_FAILED + + lv, rv = left[lk], right[rk] + + lhc = isinstance(left, HasCacheKey) + rhc = isinstance(right, HasCacheKey) + if lhc and rhc: + if lv._gen_cache_key( + self.anon_map[0], [] + ) != rv._gen_cache_key(self.anon_map[1], []): + return COMPARE_FAILED + elif lhc != rhc: + return COMPARE_FAILED + elif lv != rv: + return COMPARE_FAILED + + def visit_multi( + self, attrname, left_parent, left, right_parent, right, **kw + ): + lhc = isinstance(left, HasCacheKey) + rhc = isinstance(right, HasCacheKey) + if lhc and rhc: + if left._gen_cache_key( + self.anon_map[0], [] + ) != right._gen_cache_key(self.anon_map[1], []): + return COMPARE_FAILED + elif lhc != rhc: + return COMPARE_FAILED + else: + return left == right + + def visit_anon_name( + self, attrname, left_parent, left, right_parent, right, **kw + ): + return _resolve_name_for_compare( + left_parent, left, self.anon_map[0], **kw + ) == _resolve_name_for_compare( + right_parent, right, self.anon_map[1], **kw + ) + + def visit_boolean( + self, attrname, left_parent, left, right_parent, right, **kw + ): + return left == right + + def visit_operator( + self, attrname, left_parent, left, right_parent, right, **kw + ): + return left == right + + def visit_type( + self, attrname, left_parent, left, right_parent, right, **kw + ): + return left._compare_type_affinity(right) + + def visit_plain_dict( + self, attrname, left_parent, left, right_parent, right, **kw + ): + return left == right + + def visit_dialect_options( + self, attrname, left_parent, left, right_parent, right, **kw + ): + return left == right + + def visit_annotations_key( + self, attrname, left_parent, left, right_parent, right, **kw + ): + if left and right: + return ( + left_parent._annotations_cache_key + == right_parent._annotations_cache_key + ) + else: + return left == right + + def visit_with_context_options( + self, attrname, left_parent, left, right_parent, right, **kw + ): + return tuple((fn.__code__, c_key) for fn, c_key in left) == tuple( + (fn.__code__, c_key) for fn, c_key in right + ) + + def visit_plain_obj( + self, attrname, left_parent, left, right_parent, right, **kw + ): + return left == right + + def visit_named_ddl_element( + self, attrname, left_parent, left, right_parent, right, **kw + ): + if left is None: + if right is not None: + return COMPARE_FAILED + + return left.name == right.name + + def visit_prefix_sequence( + self, attrname, left_parent, left, right_parent, right, **kw + ): + for (l_clause, l_str), (r_clause, r_str) in zip_longest( + left, right, fillvalue=(None, None) + ): + if l_str != r_str: + return COMPARE_FAILED + else: + self.stack.append((l_clause, r_clause)) + + def visit_setup_join_tuple( + self, attrname, left_parent, left, right_parent, right, **kw + ): + # TODO: look at attrname for "legacy_join" and use different structure + for ( + (l_target, l_onclause, l_from, l_flags), + (r_target, r_onclause, r_from, r_flags), + ) in zip_longest(left, right, fillvalue=(None, None, None, None)): + if l_flags != r_flags: + return COMPARE_FAILED + self.stack.append((l_target, r_target)) + self.stack.append((l_onclause, r_onclause)) + self.stack.append((l_from, r_from)) + + def visit_memoized_select_entities( + self, attrname, left_parent, left, right_parent, right, **kw + ): + return self.visit_clauseelement_tuple( + attrname, left_parent, left, right_parent, right, **kw + ) + + def visit_table_hint_list( + self, attrname, left_parent, left, right_parent, right, **kw + ): + left_keys = sorted(left, key=lambda elem: (elem[0].fullname, elem[1])) + right_keys = sorted( + right, key=lambda elem: (elem[0].fullname, elem[1]) + ) + for (ltable, ldialect), (rtable, rdialect) in zip_longest( + left_keys, right_keys, fillvalue=(None, None) + ): + if ldialect != rdialect: + return COMPARE_FAILED + elif left[(ltable, ldialect)] != right[(rtable, rdialect)]: + return COMPARE_FAILED + else: + self.stack.append((ltable, rtable)) + + def visit_statement_hint_list( + self, attrname, left_parent, left, right_parent, right, **kw + ): + return left == right + + def visit_unknown_structure( + self, attrname, left_parent, left, right_parent, right, **kw + ): + raise NotImplementedError() + + def visit_dml_ordered_values( + self, attrname, left_parent, left, right_parent, right, **kw + ): + # sequence of tuple pairs + + for (lk, lv), (rk, rv) in zip_longest( + left, right, fillvalue=(None, None) + ): + if not self._compare_dml_values_or_ce(lk, rk, **kw): + return COMPARE_FAILED + + def _compare_dml_values_or_ce(self, lv, rv, **kw): + lvce = hasattr(lv, "__clause_element__") + rvce = hasattr(rv, "__clause_element__") + if lvce != rvce: + return False + elif lvce and not self.compare_inner(lv, rv, **kw): + return False + elif not lvce and lv != rv: + return False + elif not self.compare_inner(lv, rv, **kw): + return False + + return True + + def visit_dml_values( + self, attrname, left_parent, left, right_parent, right, **kw + ): + if left is None or right is None or len(left) != len(right): + return COMPARE_FAILED + + if isinstance(left, collections_abc.Sequence): + for lv, rv in zip(left, right): + if not self._compare_dml_values_or_ce(lv, rv, **kw): + return COMPARE_FAILED + elif isinstance(right, collections_abc.Sequence): + return COMPARE_FAILED + else: + # dictionaries guaranteed to support insert ordering in + # py37 so that we can compare the keys in order. without + # this, we can't compare SQL expression keys because we don't + # know which key is which + for (lk, lv), (rk, rv) in zip(left.items(), right.items()): + if not self._compare_dml_values_or_ce(lk, rk, **kw): + return COMPARE_FAILED + if not self._compare_dml_values_or_ce(lv, rv, **kw): + return COMPARE_FAILED + + def visit_dml_multi_values( + self, attrname, left_parent, left, right_parent, right, **kw + ): + for lseq, rseq in zip_longest(left, right, fillvalue=None): + if lseq is None or rseq is None: + return COMPARE_FAILED + + for ld, rd in zip_longest(lseq, rseq, fillvalue=None): + if ( + self.visit_dml_values( + attrname, left_parent, ld, right_parent, rd, **kw + ) + is COMPARE_FAILED + ): + return COMPARE_FAILED + + def compare_expression_clauselist(self, left, right, **kw): + if left.operator is right.operator: + if operators.is_associative(left.operator): + if self._compare_unordered_sequences( + left.clauses, right.clauses, **kw + ): + return ["operator", "clauses"] + else: + return COMPARE_FAILED + else: + return ["operator"] + else: + return COMPARE_FAILED + + def compare_clauselist(self, left, right, **kw): + return self.compare_expression_clauselist(left, right, **kw) + + def compare_binary(self, left, right, **kw): + if left.operator == right.operator: + if operators.is_commutative(left.operator): + if ( + self.compare_inner(left.left, right.left, **kw) + and self.compare_inner(left.right, right.right, **kw) + ) or ( + self.compare_inner(left.left, right.right, **kw) + and self.compare_inner(left.right, right.left, **kw) + ): + return ["operator", "negate", "left", "right"] + else: + return COMPARE_FAILED + else: + return ["operator", "negate"] + else: + return COMPARE_FAILED + + def compare_bindparam(self, left, right, **kw): + compare_keys = kw.pop("compare_keys", True) + compare_values = kw.pop("compare_values", True) + + if compare_values: + omit = [] + else: + # this means, "skip these, we already compared" + omit = ["callable", "value"] + + if not compare_keys: + omit.append("key") + + return omit + + +class ColIdentityComparatorStrategy(TraversalComparatorStrategy): + def compare_column_element( + self, left, right, use_proxies=True, equivalents=(), **kw + ): + """Compare ColumnElements using proxies and equivalent collections. + + This is a comparison strategy specific to the ORM. + """ + + to_compare = (right,) + if equivalents and right in equivalents: + to_compare = equivalents[right].union(to_compare) + + for oth in to_compare: + if use_proxies and left.shares_lineage(oth): + return SKIP_TRAVERSE + elif hash(left) == hash(right): + return SKIP_TRAVERSE + else: + return COMPARE_FAILED + + def compare_column(self, left, right, **kw): + return self.compare_column_element(left, right, **kw) + + def compare_label(self, left, right, **kw): + return self.compare_column_element(left, right, **kw) + + def compare_table(self, left, right, **kw): + # tables compare on identity, since it's not really feasible to + # compare them column by column with the above rules + return SKIP_TRAVERSE if left is right else COMPARE_FAILED diff --git a/venv/Lib/site-packages/sqlalchemy/sql/type_api.py b/venv/Lib/site-packages/sqlalchemy/sql/type_api.py new file mode 100644 index 0000000..ab078bb --- /dev/null +++ b/venv/Lib/site-packages/sqlalchemy/sql/type_api.py @@ -0,0 +1,2368 @@ +# sql/type_api.py +# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: https://www.opensource.org/licenses/mit-license.php + +"""Base types API.""" + +from __future__ import annotations + +from enum import Enum +import typing +from typing import Any +from typing import Callable +from typing import cast +from typing import Dict +from typing import Generic +from typing import Mapping +from typing import NewType +from typing import Optional +from typing import overload +from typing import Sequence +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +from .base import SchemaEventTarget +from .cache_key import CacheConst +from .cache_key import NO_CACHE +from .operators import ColumnOperators +from .visitors import Visitable +from .. import exc +from .. import util +from ..util.typing import Protocol +from ..util.typing import Self +from ..util.typing import TypeAliasType +from ..util.typing import TypedDict +from ..util.typing import TypeGuard + +# these are back-assigned by sqltypes. +if typing.TYPE_CHECKING: + from ._typing import _TypeEngineArgument + from .elements import BindParameter + from .elements import ColumnElement + from .operators import OperatorType + from .sqltypes import _resolve_value_to_type as _resolve_value_to_type + from .sqltypes import BOOLEANTYPE as BOOLEANTYPE # noqa: F401 + from .sqltypes import INDEXABLE as INDEXABLE # noqa: F401 + from .sqltypes import INTEGERTYPE as INTEGERTYPE # noqa: F401 + from .sqltypes import MATCHTYPE as MATCHTYPE # noqa: F401 + from .sqltypes import NULLTYPE as NULLTYPE + from .sqltypes import NUMERICTYPE as NUMERICTYPE # noqa: F401 + from .sqltypes import STRINGTYPE as STRINGTYPE # noqa: F401 + from .sqltypes import TABLEVALUE as TABLEVALUE # noqa: F401 + from ..engine.interfaces import DBAPIModule + from ..engine.interfaces import Dialect + from ..util.typing import GenericProtocol + +_T = TypeVar("_T", bound=Any) +_T_co = TypeVar("_T_co", bound=Any, covariant=True) +_T_con = TypeVar("_T_con", bound=Any, contravariant=True) +_O = TypeVar("_O", bound=object) +_TE = TypeVar("_TE", bound="TypeEngine[Any]") +_CT = TypeVar("_CT", bound=Any) +_RT = TypeVar("_RT", bound=Any) + +_MatchedOnType = Union[ + "GenericProtocol[Any]", TypeAliasType, NewType, Type[Any] +] + + +class _NoValueInList(Enum): + NO_VALUE_IN_LIST = 0 + """indicates we are trying to determine the type of an expression + against an empty list.""" + + +_NO_VALUE_IN_LIST = _NoValueInList.NO_VALUE_IN_LIST + + +class _LiteralProcessorType(Protocol[_T_co]): + def __call__(self, value: Any) -> str: ... + + +class _BindProcessorType(Protocol[_T_con]): + def __call__(self, value: Optional[_T_con]) -> Any: ... + + +class _ResultProcessorType(Protocol[_T_co]): + def __call__(self, value: Any) -> Optional[_T_co]: ... + + +class _SentinelProcessorType(Protocol[_T_co]): + def __call__(self, value: Any) -> Optional[_T_co]: ... + + +class _BaseTypeMemoDict(TypedDict): + impl: TypeEngine[Any] + result: Dict[Any, Optional[_ResultProcessorType[Any]]] + + +class _TypeMemoDict(_BaseTypeMemoDict, total=False): + literal: Optional[_LiteralProcessorType[Any]] + bind: Optional[_BindProcessorType[Any]] + sentinel: Optional[_SentinelProcessorType[Any]] + custom: Dict[Any, object] + + +class _ComparatorFactory(Protocol[_T]): + def __call__( + self, expr: ColumnElement[_T] + ) -> TypeEngine.Comparator[_T]: ... + + +class TypeEngine(Visitable, Generic[_T]): + """The ultimate base class for all SQL datatypes. + + Common subclasses of :class:`.TypeEngine` include + :class:`.String`, :class:`.Integer`, and :class:`.Boolean`. + + For an overview of the SQLAlchemy typing system, see + :ref:`types_toplevel`. + + .. seealso:: + + :ref:`types_toplevel` + + """ + + _sqla_type = True + _isnull = False + _is_tuple_type = False + _is_table_value = False + _is_array = False + _is_type_decorator = False + + render_bind_cast = False + """Render bind casts for :attr:`.BindTyping.RENDER_CASTS` mode. + + If True, this type (usually a dialect level impl type) signals + to the compiler that a cast should be rendered around a bound parameter + for this type. + + .. versionadded:: 2.0 + + .. seealso:: + + :class:`.BindTyping` + + """ + + render_literal_cast = False + """render casts when rendering a value as an inline literal, + e.g. with :meth:`.TypeEngine.literal_processor`. + + .. versionadded:: 2.0 + + """ + + class Comparator( + ColumnOperators, + Generic[_CT], + ): + """Base class for custom comparison operations defined at the + type level. See :attr:`.TypeEngine.comparator_factory`. + + + """ + + __slots__ = "expr", "type" + + expr: ColumnElement[_CT] + type: TypeEngine[_CT] + + def __clause_element__(self) -> ColumnElement[_CT]: + return self.expr + + def __init__(self, expr: ColumnElement[_CT]): + self.expr = expr + self.type = expr.type + + def __reduce__(self) -> Any: + return self.__class__, (self.expr,) + + @overload + def operate( + self, + op: OperatorType, + *other: Any, + result_type: Type[TypeEngine[_RT]], + **kwargs: Any, + ) -> ColumnElement[_RT]: ... + + @overload + def operate( + self, op: OperatorType, *other: Any, **kwargs: Any + ) -> ColumnElement[_CT]: ... + + @util.preload_module("sqlalchemy.sql.default_comparator") + def operate( + self, op: OperatorType, *other: Any, **kwargs: Any + ) -> ColumnElement[Any]: + default_comparator = util.preloaded.sql_default_comparator + op_fn, addtl_kw = default_comparator.operator_lookup[op.__name__] + if kwargs: + addtl_kw = addtl_kw.union(kwargs) + return op_fn(self.expr, op, *other, **addtl_kw) + + @util.preload_module("sqlalchemy.sql.default_comparator") + def reverse_operate( + self, op: OperatorType, other: Any, **kwargs: Any + ) -> ColumnElement[_CT]: + default_comparator = util.preloaded.sql_default_comparator + op_fn, addtl_kw = default_comparator.operator_lookup[op.__name__] + if kwargs: + addtl_kw = addtl_kw.union(kwargs) + return op_fn(self.expr, op, other, reverse=True, **addtl_kw) + + def _adapt_expression( + self, + op: OperatorType, + other_comparator: TypeEngine.Comparator[Any], + ) -> Tuple[OperatorType, TypeEngine[Any]]: + """evaluate the return type of , + and apply any adaptations to the given operator. + + This method determines the type of a resulting binary expression + given two source types and an operator. For example, two + :class:`_schema.Column` objects, both of the type + :class:`.Integer`, will + produce a :class:`.BinaryExpression` that also has the type + :class:`.Integer` when compared via the addition (``+``) operator. + However, using the addition operator with an :class:`.Integer` + and a :class:`.Date` object will produce a :class:`.Date`, assuming + "days delta" behavior by the database (in reality, most databases + other than PostgreSQL don't accept this particular operation). + + The method returns a tuple of the form , . + The resulting operator and type will be those applied to the + resulting :class:`.BinaryExpression` as the final operator and the + right-hand side of the expression. + + Note that only a subset of operators make usage of + :meth:`._adapt_expression`, + including math operators and user-defined operators, but not + boolean comparison or special SQL keywords like MATCH or BETWEEN. + + """ + + return op, self.type + + hashable = True + """Flag, if False, means values from this type aren't hashable. + + Used by the ORM when uniquing result lists. + + """ + + comparator_factory: _ComparatorFactory[Any] = Comparator + """A :class:`.TypeEngine.Comparator` class which will apply + to operations performed by owning :class:`_expression.ColumnElement` + objects. + + The :attr:`.comparator_factory` attribute is a hook consulted by + the core expression system when column and SQL expression operations + are performed. When a :class:`.TypeEngine.Comparator` class is + associated with this attribute, it allows custom re-definition of + all existing operators, as well as definition of new operators. + Existing operators include those provided by Python operator overloading + such as :meth:`.operators.ColumnOperators.__add__` and + :meth:`.operators.ColumnOperators.__eq__`, + those provided as standard + attributes of :class:`.operators.ColumnOperators` such as + :meth:`.operators.ColumnOperators.like` + and :meth:`.operators.ColumnOperators.in_`. + + Rudimentary usage of this hook is allowed through simple subclassing + of existing types, or alternatively by using :class:`.TypeDecorator`. + See the documentation section :ref:`types_operators` for examples. + + """ + + sort_key_function: Optional[Callable[[Any], Any]] = None + """A sorting function that can be passed as the key to sorted. + + The default value of ``None`` indicates that the values stored by + this type are self-sorting. + + .. versionadded:: 1.3.8 + + """ + + should_evaluate_none: bool = False + """If True, the Python constant ``None`` is considered to be handled + explicitly by this type. + + The ORM uses this flag to indicate that a positive value of ``None`` + is passed to the column in an INSERT statement, rather than omitting + the column from the INSERT statement which has the effect of firing + off column-level defaults. It also allows types which have special + behavior for Python None, such as a JSON type, to indicate that + they'd like to handle the None value explicitly. + + To set this flag on an existing type, use the + :meth:`.TypeEngine.evaluates_none` method. + + .. seealso:: + + :meth:`.TypeEngine.evaluates_none` + + """ + + _variant_mapping: util.immutabledict[str, TypeEngine[Any]] = ( + util.EMPTY_DICT + ) + + def evaluates_none(self) -> Self: + """Return a copy of this type which has the + :attr:`.should_evaluate_none` flag set to True. + + E.g.:: + + Table( + "some_table", + metadata, + Column( + String(50).evaluates_none(), + nullable=True, + server_default="no value", + ), + ) + + The ORM uses this flag to indicate that a positive value of ``None`` + is passed to the column in an INSERT statement, rather than omitting + the column from the INSERT statement which has the effect of firing + off column-level defaults. It also allows for types which have + special behavior associated with the Python None value to indicate + that the value doesn't necessarily translate into SQL NULL; a + prime example of this is a JSON type which may wish to persist the + JSON value ``'null'``. + + In all cases, the actual NULL SQL value can be always be + persisted in any column by using + the :obj:`_expression.null` SQL construct in an INSERT statement + or associated with an ORM-mapped attribute. + + .. note:: + + The "evaluates none" flag does **not** apply to a value + of ``None`` passed to :paramref:`_schema.Column.default` or + :paramref:`_schema.Column.server_default`; in these cases, + ``None`` + still means "no default". + + .. seealso:: + + :ref:`session_forcing_null` - in the ORM documentation + + :paramref:`.postgresql.JSON.none_as_null` - PostgreSQL JSON + interaction with this flag. + + :attr:`.TypeEngine.should_evaluate_none` - class-level flag + + """ + typ = self.copy() + typ.should_evaluate_none = True + return typ + + def copy(self, **kw: Any) -> Self: + return self.adapt(self.__class__) + + def copy_value(self, value: Any) -> Any: + return value + + def literal_processor( + self, dialect: Dialect + ) -> Optional[_LiteralProcessorType[_T]]: + """Return a conversion function for processing literal values that are + to be rendered directly without using binds. + + This function is used when the compiler makes use of the + "literal_binds" flag, typically used in DDL generation as well + as in certain scenarios where backends don't accept bound parameters. + + Returns a callable which will receive a literal Python value + as the sole positional argument and will return a string representation + to be rendered in a SQL statement. + + .. tip:: + + This method is only called relative to a **dialect specific type + object**, which is often **private to a dialect in use** and is not + the same type object as the public facing one, which means it's not + feasible to subclass a :class:`.types.TypeEngine` class in order to + provide an alternate :meth:`_types.TypeEngine.literal_processor` + method, unless subclassing the :class:`_types.UserDefinedType` + class explicitly. + + To provide alternate behavior for + :meth:`_types.TypeEngine.literal_processor`, implement a + :class:`_types.TypeDecorator` class and provide an implementation + of :meth:`_types.TypeDecorator.process_literal_param`. + + .. seealso:: + + :ref:`types_typedecorator` + + + """ + return None + + def bind_processor( + self, dialect: Dialect + ) -> Optional[_BindProcessorType[_T]]: + """Return a conversion function for processing bind values. + + Returns a callable which will receive a bind parameter value + as the sole positional argument and will return a value to + send to the DB-API. + + If processing is not necessary, the method should return ``None``. + + .. tip:: + + This method is only called relative to a **dialect specific type + object**, which is often **private to a dialect in use** and is not + the same type object as the public facing one, which means it's not + feasible to subclass a :class:`.types.TypeEngine` class in order to + provide an alternate :meth:`_types.TypeEngine.bind_processor` + method, unless subclassing the :class:`_types.UserDefinedType` + class explicitly. + + To provide alternate behavior for + :meth:`_types.TypeEngine.bind_processor`, implement a + :class:`_types.TypeDecorator` class and provide an implementation + of :meth:`_types.TypeDecorator.process_bind_param`. + + .. seealso:: + + :ref:`types_typedecorator` + + + :param dialect: Dialect instance in use. + + """ + return None + + def result_processor( + self, dialect: Dialect, coltype: object + ) -> Optional[_ResultProcessorType[_T]]: + """Return a conversion function for processing result row values. + + Returns a callable which will receive a result row column + value as the sole positional argument and will return a value + to return to the user. + + If processing is not necessary, the method should return ``None``. + + .. tip:: + + This method is only called relative to a **dialect specific type + object**, which is often **private to a dialect in use** and is not + the same type object as the public facing one, which means it's not + feasible to subclass a :class:`.types.TypeEngine` class in order to + provide an alternate :meth:`_types.TypeEngine.result_processor` + method, unless subclassing the :class:`_types.UserDefinedType` + class explicitly. + + To provide alternate behavior for + :meth:`_types.TypeEngine.result_processor`, implement a + :class:`_types.TypeDecorator` class and provide an implementation + of :meth:`_types.TypeDecorator.process_result_value`. + + .. seealso:: + + :ref:`types_typedecorator` + + :param dialect: Dialect instance in use. + + :param coltype: DBAPI coltype argument received in cursor.description. + + """ + return None + + def column_expression( + self, colexpr: ColumnElement[_T] + ) -> Optional[ColumnElement[_T]]: + """Given a SELECT column expression, return a wrapping SQL expression. + + This is typically a SQL function that wraps a column expression + as rendered in the columns clause of a SELECT statement. + It is used for special data types that require + columns to be wrapped in some special database function in order + to coerce the value before being sent back to the application. + It is the SQL analogue of the :meth:`.TypeEngine.result_processor` + method. + + .. note:: The :func:`.TypeEngine.column_expression` method is applied + only to the **outermost columns clause** of a SELECT statement, that + is, the columns that are to be delivered directly into the returned + result rows. It does **not** apply to the columns clause inside + of subqueries. This necessarily avoids double conversions against + the column and only runs the conversion when ready to be returned + to the client. + + This method is called during the **SQL compilation** phase of a + statement, when rendering a SQL string. It is **not** called + against specific values. + + .. tip:: + + This method is only called relative to a **dialect specific type + object**, which is often **private to a dialect in use** and is not + the same type object as the public facing one, which means it's not + feasible to subclass a :class:`.types.TypeEngine` class in order to + provide an alternate :meth:`_types.TypeEngine.column_expression` + method, unless subclassing the :class:`_types.UserDefinedType` + class explicitly. + + To provide alternate behavior for + :meth:`_types.TypeEngine.column_expression`, implement a + :class:`_types.TypeDecorator` class and provide an implementation + of :meth:`_types.TypeDecorator.column_expression`. + + .. seealso:: + + :ref:`types_typedecorator` + + + .. seealso:: + + :ref:`types_sql_value_processing` + + """ + + return None + + @util.memoized_property + def _has_column_expression(self) -> bool: + """memoized boolean, check if column_expression is implemented. + + Allows the method to be skipped for the vast majority of expression + types that don't use this feature. + + """ + + return ( + self.__class__.column_expression.__code__ + is not TypeEngine.column_expression.__code__ + ) + + def bind_expression( + self, bindvalue: BindParameter[_T] + ) -> Optional[ColumnElement[_T]]: + """Given a bind value (i.e. a :class:`.BindParameter` instance), + return a SQL expression in its place. + + This is typically a SQL function that wraps the existing bound + parameter within the statement. It is used for special data types + that require literals being wrapped in some special database function + in order to coerce an application-level value into a database-specific + format. It is the SQL analogue of the + :meth:`.TypeEngine.bind_processor` method. + + This method is called during the **SQL compilation** phase of a + statement, when rendering a SQL string. It is **not** called + against specific values. + + Note that this method, when implemented, should always return + the exact same structure, without any conditional logic, as it + may be used in an executemany() call against an arbitrary number + of bound parameter sets. + + .. note:: + + This method is only called relative to a **dialect specific type + object**, which is often **private to a dialect in use** and is not + the same type object as the public facing one, which means it's not + feasible to subclass a :class:`.types.TypeEngine` class in order to + provide an alternate :meth:`_types.TypeEngine.bind_expression` + method, unless subclassing the :class:`_types.UserDefinedType` + class explicitly. + + To provide alternate behavior for + :meth:`_types.TypeEngine.bind_expression`, implement a + :class:`_types.TypeDecorator` class and provide an implementation + of :meth:`_types.TypeDecorator.bind_expression`. + + .. seealso:: + + :ref:`types_typedecorator` + + .. seealso:: + + :ref:`types_sql_value_processing` + + """ + return None + + @util.memoized_property + def _has_bind_expression(self) -> bool: + """memoized boolean, check if bind_expression is implemented. + + Allows the method to be skipped for the vast majority of expression + types that don't use this feature. + + """ + + return util.method_is_overridden(self, TypeEngine.bind_expression) + + @staticmethod + def _to_instance(cls_or_self: Union[Type[_TE], _TE]) -> _TE: + return to_instance(cls_or_self) + + def compare_values(self, x: Any, y: Any) -> bool: + """Compare two values for equality.""" + + return x == y # type: ignore[no-any-return] + + def get_dbapi_type(self, dbapi: DBAPIModule) -> Optional[Any]: + """Return the corresponding type object from the underlying DB-API, if + any. + + This can be useful for calling ``setinputsizes()``, for example. + + """ + return None + + @property + def python_type(self) -> Type[Any]: + """Return the Python type object expected to be returned + by instances of this type, if known. + + Basically, for those types which enforce a return type, + or are known across the board to do such for all common + DBAPIs (like ``int`` for example), will return that type. + + If a return type is not defined, raises + ``NotImplementedError``. + + Note that any type also accommodates NULL in SQL which + means you can also get back ``None`` from any type + in practice. + + """ + raise NotImplementedError() + + def with_variant( + self, + type_: _TypeEngineArgument[Any], + *dialect_names: str, + ) -> Self: + r"""Produce a copy of this type object that will utilize the given + type when applied to the dialect of the given name. + + e.g.:: + + from sqlalchemy.types import String + from sqlalchemy.dialects import mysql + + string_type = String() + + string_type = string_type.with_variant( + mysql.VARCHAR(collation="foo"), "mysql", "mariadb" + ) + + The variant mapping indicates that when this type is + interpreted by a specific dialect, it will instead be + transmuted into the given type, rather than using the + primary type. + + .. versionchanged:: 2.0 the :meth:`_types.TypeEngine.with_variant` + method now works with a :class:`_types.TypeEngine` object "in + place", returning a copy of the original type rather than returning + a wrapping object; the ``Variant`` class is no longer used. + + :param type\_: a :class:`.TypeEngine` that will be selected + as a variant from the originating type, when a dialect + of the given name is in use. + :param \*dialect_names: one or more base names of the dialect which + uses this type. (i.e. ``'postgresql'``, ``'mysql'``, etc.) + + .. versionchanged:: 2.0 multiple dialect names can be specified + for one variant. + + .. seealso:: + + :ref:`types_with_variant` - illustrates the use of + :meth:`_types.TypeEngine.with_variant`. + + """ + + if not dialect_names: + raise exc.ArgumentError("At least one dialect name is required") + for dialect_name in dialect_names: + if dialect_name in self._variant_mapping: + raise exc.ArgumentError( + f"Dialect {dialect_name!r} is already present in " + f"the mapping for this {self!r}" + ) + new_type = self.copy() + type_ = to_instance(type_) + if type_._variant_mapping: + raise exc.ArgumentError( + "can't pass a type that already has variants as a " + "dialect-level type to with_variant()" + ) + + new_type._variant_mapping = self._variant_mapping.union( + {dialect_name: type_ for dialect_name in dialect_names} + ) + return new_type + + def _resolve_for_literal(self, value: Any) -> Self: + """adjust this type given a literal Python value that will be + stored in a bound parameter. + + Used exclusively by _resolve_value_to_type(). + + .. versionadded:: 1.4.30 or 2.0 + + TODO: this should be part of public API + + .. seealso:: + + :meth:`.TypeEngine._resolve_for_python_type` + + """ + return self + + def _resolve_for_python_type( + self, + python_type: Type[Any], + matched_on: _MatchedOnType, + matched_on_flattened: Type[Any], + ) -> Optional[Self]: + """given a Python type (e.g. ``int``, ``str``, etc. ) return an + instance of this :class:`.TypeEngine` that's appropriate for this type. + + An additional argument ``matched_on`` is passed, which indicates an + entry from the ``__mro__`` of the given ``python_type`` that more + specifically matches how the caller located this :class:`.TypeEngine` + object. Such as, if a lookup of some kind links the ``int`` Python + type to the :class:`.Integer` SQL type, and the original object + was some custom subclass of ``int`` such as ``MyInt(int)``, the + arguments passed would be ``(MyInt, int)``. + + If the given Python type does not correspond to this + :class:`.TypeEngine`, or the Python type is otherwise ambiguous, the + method should return None. + + For simple cases, the method checks that the ``python_type`` + and ``matched_on`` types are the same (i.e. not a subclass), and + returns self; for all other cases, it returns ``None``. + + The initial use case here is for the ORM to link user-defined + Python standard library ``enum.Enum`` classes to the SQLAlchemy + :class:`.Enum` SQL type when constructing ORM Declarative mappings. + + :param python_type: the Python type we want to use + :param matched_on: the Python type that led us to choose this + particular :class:`.TypeEngine` class, which would be a supertype + of ``python_type``. By default, the request is rejected if + ``python_type`` doesn't match ``matched_on`` (None is returned). + + .. versionadded:: 2.0.0b4 + + TODO: this should be part of public API + + .. seealso:: + + :meth:`.TypeEngine._resolve_for_literal` + + """ + + if python_type is not matched_on_flattened: + return None + + return self + + def _with_collation(self, collation: str) -> Self: + """set up error handling for the collate expression""" + raise NotImplementedError("this datatype does not support collation") + + @util.ro_memoized_property + def _type_affinity(self) -> Optional[Type[TypeEngine[_T]]]: + """Return a rudimental 'affinity' value expressing the general class + of type.""" + + typ = None + for t in self.__class__.__mro__: + if t is TypeEngine or TypeEngineMixin in t.__bases__: + return typ + elif issubclass(t, TypeEngine): + typ = t + else: + return self.__class__ + + @util.ro_memoized_property + def _generic_type_affinity( + self, + ) -> Type[TypeEngine[_T]]: + best_camelcase = None + best_uppercase = None + + if not isinstance(self, TypeEngine): + return self.__class__ + + for t in self.__class__.__mro__: + if ( + t.__module__ + in ( + "sqlalchemy.sql.sqltypes", + "sqlalchemy.sql.type_api", + ) + and issubclass(t, TypeEngine) + and TypeEngineMixin not in t.__bases__ + and t not in (TypeEngine, TypeEngineMixin) + and t.__name__[0] != "_" + ): + if t.__name__.isupper() and not best_uppercase: + best_uppercase = t + elif not t.__name__.isupper() and not best_camelcase: + best_camelcase = t + + return ( + best_camelcase + or best_uppercase + or cast("Type[TypeEngine[_T]]", NULLTYPE.__class__) + ) + + def as_generic(self, allow_nulltype: bool = False) -> TypeEngine[_T]: + """ + Return an instance of the generic type corresponding to this type + using heuristic rule. The method may be overridden if this + heuristic rule is not sufficient. + + >>> from sqlalchemy.dialects.mysql import INTEGER + >>> INTEGER(display_width=4).as_generic() + Integer() + + >>> from sqlalchemy.dialects.mysql import NVARCHAR + >>> NVARCHAR(length=100).as_generic() + Unicode(length=100) + + .. versionadded:: 1.4.0b2 + + + .. seealso:: + + :ref:`metadata_reflection_dbagnostic_types` - describes the + use of :meth:`_types.TypeEngine.as_generic` in conjunction with + the :meth:`_sql.DDLEvents.column_reflect` event, which is its + intended use. + + """ + if ( + not allow_nulltype + and self._generic_type_affinity == NULLTYPE.__class__ + ): + raise NotImplementedError( + "Default TypeEngine.as_generic() " + "heuristic method was unsuccessful for {}. A custom " + "as_generic() method must be implemented for this " + "type class.".format( + self.__class__.__module__ + "." + self.__class__.__name__ + ) + ) + + return util.constructor_copy(self, self._generic_type_affinity) + + def dialect_impl(self, dialect: Dialect) -> TypeEngine[_T]: + """Return a dialect-specific implementation for this + :class:`.TypeEngine`. + + """ + try: + tm = dialect._type_memos[self] + except KeyError: + pass + else: + return tm["impl"] + return self._dialect_info(dialect)["impl"] + + def _unwrapped_dialect_impl(self, dialect: Dialect) -> TypeEngine[_T]: + """Return the 'unwrapped' dialect impl for this type. + + For a type that applies wrapping logic (e.g. TypeDecorator), give + us the real, actual dialect-level type that is used. + + This is used by TypeDecorator itself as well at least one case where + dialects need to check that a particular specific dialect-level + type is in use, within the :meth:`.DefaultDialect.set_input_sizes` + method. + + """ + return self.dialect_impl(dialect) + + def _cached_literal_processor( + self, dialect: Dialect + ) -> Optional[_LiteralProcessorType[_T]]: + """Return a dialect-specific literal processor for this type.""" + + try: + return dialect._type_memos[self]["literal"] + except KeyError: + pass + + # avoid KeyError context coming into literal_processor() function + # raises + d = self._dialect_info(dialect) + d["literal"] = lp = d["impl"].literal_processor(dialect) + return lp + + def _cached_bind_processor( + self, dialect: Dialect + ) -> Optional[_BindProcessorType[_T]]: + """Return a dialect-specific bind processor for this type.""" + + try: + return dialect._type_memos[self]["bind"] + except KeyError: + pass + + # avoid KeyError context coming into bind_processor() function + # raises + d = self._dialect_info(dialect) + d["bind"] = bp = d["impl"].bind_processor(dialect) + return bp + + def _cached_result_processor( + self, dialect: Dialect, coltype: Any + ) -> Optional[_ResultProcessorType[_T]]: + """Return a dialect-specific result processor for this type.""" + + try: + return dialect._type_memos[self]["result"][coltype] + except KeyError: + pass + + # avoid KeyError context coming into result_processor() function + # raises + d = self._dialect_info(dialect) + # key assumption: DBAPI type codes are + # constants. Else this dictionary would + # grow unbounded. + rp = d["impl"].result_processor(dialect, coltype) + d["result"][coltype] = rp + return rp + + def _cached_custom_processor( + self, dialect: Dialect, key: str, fn: Callable[[TypeEngine[_T]], _O] + ) -> _O: + """return a dialect-specific processing object for + custom purposes. + + The cx_Oracle dialect uses this at the moment. + + """ + try: + return cast(_O, dialect._type_memos[self]["custom"][key]) + except KeyError: + pass + # avoid KeyError context coming into fn() function + # raises + d = self._dialect_info(dialect) + impl = d["impl"] + custom_dict = d.setdefault("custom", {}) + custom_dict[key] = result = fn(impl) + return result + + def _dialect_info(self, dialect: Dialect) -> _TypeMemoDict: + """Return a dialect-specific registry which + caches a dialect-specific implementation, bind processing + function, and one or more result processing functions.""" + + if self in dialect._type_memos: + return dialect._type_memos[self] + else: + impl = self._gen_dialect_impl(dialect) + if impl is self: + impl = self.adapt(type(self)) + # this can't be self, else we create a cycle + assert impl is not self + d: _TypeMemoDict = {"impl": impl, "result": {}} + dialect._type_memos[self] = d + return d + + def _gen_dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]: + if dialect.name in self._variant_mapping: + return self._variant_mapping[dialect.name]._gen_dialect_impl( + dialect + ) + else: + return dialect.type_descriptor(self) + + @util.memoized_property + def _static_cache_key( + self, + ) -> Union[CacheConst, Tuple[Any, ...]]: + names = util.get_cls_kwargs(self.__class__) + return (self.__class__,) + tuple( + ( + k, + ( + self.__dict__[k]._static_cache_key + if isinstance(self.__dict__[k], TypeEngine) + else self.__dict__[k] + ), + ) + for k in names + if k in self.__dict__ + and not k.startswith("_") + and self.__dict__[k] is not None + ) + + @overload + def adapt(self, cls: Type[_TE], **kw: Any) -> _TE: ... + + @overload + def adapt( + self, cls: Type[TypeEngineMixin], **kw: Any + ) -> TypeEngine[Any]: ... + + def adapt( + self, cls: Type[Union[TypeEngine[Any], TypeEngineMixin]], **kw: Any + ) -> TypeEngine[Any]: + """Produce an "adapted" form of this type, given an "impl" class + to work with. + + This method is used internally to associate generic + types with "implementation" types that are specific to a particular + dialect. + """ + typ = util.constructor_copy( + self, cast(Type[TypeEngine[Any]], cls), **kw + ) + typ._variant_mapping = self._variant_mapping + return typ + + def coerce_compared_value( + self, op: Optional[OperatorType], value: Any + ) -> TypeEngine[Any]: + """Suggest a type for a 'coerced' Python value in an expression. + + Given an operator and value, gives the type a chance + to return a type which the value should be coerced into. + + The default behavior here is conservative; if the right-hand + side is already coerced into a SQL type based on its + Python type, it is usually left alone. + + End-user functionality extension here should generally be via + :class:`.TypeDecorator`, which provides more liberal behavior in that + it defaults to coercing the other side of the expression into this + type, thus applying special Python conversions above and beyond those + needed by the DBAPI to both ides. It also provides the public method + :meth:`.TypeDecorator.coerce_compared_value` which is intended for + end-user customization of this behavior. + + """ + _coerced_type = _resolve_value_to_type(value) + if ( + _coerced_type is NULLTYPE + or _coerced_type._type_affinity is self._type_affinity + ): + return self + else: + return _coerced_type + + def _compare_type_affinity(self, other: TypeEngine[Any]) -> bool: + return self._type_affinity is other._type_affinity + + def compile(self, dialect: Optional[Dialect] = None) -> str: + """Produce a string-compiled form of this :class:`.TypeEngine`. + + When called with no arguments, uses a "default" dialect + to produce a string result. + + :param dialect: a :class:`.Dialect` instance. + + """ + # arg, return value is inconsistent with + # ClauseElement.compile()....this is a mistake. + + if dialect is None: + dialect = self._default_dialect() + + return dialect.type_compiler_instance.process(self) + + @util.preload_module("sqlalchemy.engine.default") + def _default_dialect(self) -> Dialect: + default = util.preloaded.engine_default + + # dmypy / mypy seems to sporadically keep thinking this line is + # returning Any, which seems to be caused by the @deprecated_params + # decorator on the DefaultDialect constructor + return default.StrCompileDialect() # type: ignore + + def __str__(self) -> str: + return str(self.compile()) + + def __repr__(self) -> str: + return util.generic_repr(self) + + +class TypeEngineMixin: + """classes which subclass this can act as "mixin" classes for + TypeEngine.""" + + __slots__ = () + + if TYPE_CHECKING: + + @util.memoized_property + def _static_cache_key( + self, + ) -> Union[CacheConst, Tuple[Any, ...]]: ... + + @overload + def adapt(self, cls: Type[_TE], **kw: Any) -> _TE: ... + + @overload + def adapt( + self, cls: Type[TypeEngineMixin], **kw: Any + ) -> TypeEngine[Any]: ... + + def adapt( + self, cls: Type[Union[TypeEngine[Any], TypeEngineMixin]], **kw: Any + ) -> TypeEngine[Any]: ... + + def dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]: ... + + +class ExternalType(TypeEngineMixin): + """mixin that defines attributes and behaviors specific to third-party + datatypes. + + "Third party" refers to datatypes that are defined outside the scope + of SQLAlchemy within either end-user application code or within + external extensions to SQLAlchemy. + + Subclasses currently include :class:`.TypeDecorator` and + :class:`.UserDefinedType`. + + .. versionadded:: 1.4.28 + + """ + + cache_ok: Optional[bool] = None + '''Indicate if statements using this :class:`.ExternalType` are "safe to + cache". + + The default value ``None`` will emit a warning and then not allow caching + of a statement which includes this type. Set to ``False`` to disable + statements using this type from being cached at all without a warning. + When set to ``True``, the object's class and selected elements from its + state will be used as part of the cache key. For example, using a + :class:`.TypeDecorator`:: + + class MyType(TypeDecorator): + impl = String + + cache_ok = True + + def __init__(self, choices): + self.choices = tuple(choices) + self.internal_only = True + + The cache key for the above type would be equivalent to:: + + >>> MyType(["a", "b", "c"])._static_cache_key + (, ('choices', ('a', 'b', 'c'))) + + The caching scheme will extract attributes from the type that correspond + to the names of parameters in the ``__init__()`` method. Above, the + "choices" attribute becomes part of the cache key but "internal_only" + does not, because there is no parameter named "internal_only". + + The requirements for cacheable elements is that they are hashable + and also that they indicate the same SQL rendered for expressions using + this type every time for a given cache value. + + To accommodate for datatypes that refer to unhashable structures such + as dictionaries, sets and lists, these objects can be made "cacheable" + by assigning hashable structures to the attributes whose names + correspond with the names of the arguments. For example, a datatype + which accepts a dictionary of lookup values may publish this as a sorted + series of tuples. Given a previously un-cacheable type as:: + + class LookupType(UserDefinedType): + """a custom type that accepts a dictionary as a parameter. + + this is the non-cacheable version, as "self.lookup" is not + hashable. + + """ + + def __init__(self, lookup): + self.lookup = lookup + + def get_col_spec(self, **kw): + return "VARCHAR(255)" + + def bind_processor(self, dialect): ... # works with "self.lookup" ... + + Where "lookup" is a dictionary. The type will not be able to generate + a cache key:: + + >>> type_ = LookupType({"a": 10, "b": 20}) + >>> type_._static_cache_key + :1: SAWarning: UserDefinedType LookupType({'a': 10, 'b': 20}) will not + produce a cache key because the ``cache_ok`` flag is not set to True. + Set this flag to True if this type object's state is safe to use + in a cache key, or False to disable this warning. + symbol('no_cache') + + If we **did** set up such a cache key, it wouldn't be usable. We would + get a tuple structure that contains a dictionary inside of it, which + cannot itself be used as a key in a "cache dictionary" such as SQLAlchemy's + statement cache, since Python dictionaries aren't hashable:: + + >>> # set cache_ok = True + >>> type_.cache_ok = True + + >>> # this is the cache key it would generate + >>> key = type_._static_cache_key + >>> key + (, ('lookup', {'a': 10, 'b': 20})) + + >>> # however this key is not hashable, will fail when used with + >>> # SQLAlchemy statement cache + >>> some_cache = {key: "some sql value"} + Traceback (most recent call last): File "", line 1, + in TypeError: unhashable type: 'dict' + + The type may be made cacheable by assigning a sorted tuple of tuples + to the ".lookup" attribute:: + + class LookupType(UserDefinedType): + """a custom type that accepts a dictionary as a parameter. + + The dictionary is stored both as itself in a private variable, + and published in a public variable as a sorted tuple of tuples, + which is hashable and will also return the same value for any + two equivalent dictionaries. Note it assumes the keys and + values of the dictionary are themselves hashable. + + """ + + cache_ok = True + + def __init__(self, lookup): + self._lookup = lookup + + # assume keys/values of "lookup" are hashable; otherwise + # they would also need to be converted in some way here + self.lookup = tuple((key, lookup[key]) for key in sorted(lookup)) + + def get_col_spec(self, **kw): + return "VARCHAR(255)" + + def bind_processor(self, dialect): ... # works with "self._lookup" ... + + Where above, the cache key for ``LookupType({"a": 10, "b": 20})`` will be:: + + >>> LookupType({"a": 10, "b": 20})._static_cache_key + (, ('lookup', (('a', 10), ('b', 20)))) + + .. versionadded:: 1.4.14 - added the ``cache_ok`` flag to allow + some configurability of caching for :class:`.TypeDecorator` classes. + + .. versionadded:: 1.4.28 - added the :class:`.ExternalType` mixin which + generalizes the ``cache_ok`` flag to both the :class:`.TypeDecorator` + and :class:`.UserDefinedType` classes. + + .. seealso:: + + :ref:`sql_caching` + + ''' # noqa: E501 + + @util.non_memoized_property + def _static_cache_key( + self, + ) -> Union[CacheConst, Tuple[Any, ...]]: + cache_ok = self.__class__.__dict__.get("cache_ok", None) + + if cache_ok is None: + for subtype in self.__class__.__mro__: + if ExternalType in subtype.__bases__: + break + else: + subtype = self.__class__.__mro__[1] + + util.warn( + "%s %r will not produce a cache key because " + "the ``cache_ok`` attribute is not set to True. This can " + "have significant performance implications including some " + "performance degradations in comparison to prior SQLAlchemy " + "versions. Set this attribute to True if this type object's " + "state is safe to use in a cache key, or False to " + "disable this warning." % (subtype.__name__, self), + code="cprf", + ) + elif cache_ok is True: + return super()._static_cache_key + + return NO_CACHE + + +class UserDefinedType( + ExternalType, TypeEngineMixin, TypeEngine[_T], util.EnsureKWArg +): + """Base for user defined types. + + This should be the base of new types. Note that + for most cases, :class:`.TypeDecorator` is probably + more appropriate:: + + import sqlalchemy.types as types + + + class MyType(types.UserDefinedType): + cache_ok = True + + def __init__(self, precision=8): + self.precision = precision + + def get_col_spec(self, **kw): + return "MYTYPE(%s)" % self.precision + + def bind_processor(self, dialect): + def process(value): + return value + + return process + + def result_processor(self, dialect, coltype): + def process(value): + return value + + return process + + Once the type is made, it's immediately usable:: + + table = Table( + "foo", + metadata_obj, + Column("id", Integer, primary_key=True), + Column("data", MyType(16)), + ) + + The ``get_col_spec()`` method will in most cases receive a keyword + argument ``type_expression`` which refers to the owning expression + of the type as being compiled, such as a :class:`_schema.Column` or + :func:`.cast` construct. This keyword is only sent if the method + accepts keyword arguments (e.g. ``**kw``) in its argument signature; + introspection is used to check for this in order to support legacy + forms of this function. + + The :attr:`.UserDefinedType.cache_ok` class-level flag indicates if this + custom :class:`.UserDefinedType` is safe to be used as part of a cache key. + This flag defaults to ``None`` which will initially generate a warning + when the SQL compiler attempts to generate a cache key for a statement + that uses this type. If the :class:`.UserDefinedType` is not guaranteed + to produce the same bind/result behavior and SQL generation + every time, this flag should be set to ``False``; otherwise if the + class produces the same behavior each time, it may be set to ``True``. + See :attr:`.UserDefinedType.cache_ok` for further notes on how this works. + + .. versionadded:: 1.4.28 Generalized the :attr:`.ExternalType.cache_ok` + flag so that it is available for both :class:`.TypeDecorator` as well + as :class:`.UserDefinedType`. + + """ + + __visit_name__ = "user_defined" + + ensure_kwarg = "get_col_spec" + + def coerce_compared_value( + self, op: Optional[OperatorType], value: Any + ) -> TypeEngine[Any]: + """Suggest a type for a 'coerced' Python value in an expression. + + Default behavior for :class:`.UserDefinedType` is the + same as that of :class:`.TypeDecorator`; by default it returns + ``self``, assuming the compared value should be coerced into + the same type as this one. See + :meth:`.TypeDecorator.coerce_compared_value` for more detail. + + """ + + return self + + if TYPE_CHECKING: + + def get_col_spec(self, **kw: Any) -> str: ... + + +class Emulated(TypeEngineMixin): + """Mixin for base types that emulate the behavior of a DB-native type. + + An :class:`.Emulated` type will use an available database type + in conjunction with Python-side routines and/or database constraints + in order to approximate the behavior of a database type that is provided + natively by some backends. When a native-providing backend is in + use, the native version of the type is used. This native version + should include the :class:`.NativeForEmulated` mixin to allow it to be + distinguished from :class:`.Emulated`. + + Current examples of :class:`.Emulated` are: :class:`.Interval`, + :class:`.Enum`, :class:`.Boolean`. + + .. versionadded:: 1.2.0b3 + + """ + + native: bool + + def adapt_to_emulated( + self, + impltype: Type[Union[TypeEngine[Any], TypeEngineMixin]], + **kw: Any, + ) -> TypeEngine[Any]: + """Given an impl class, adapt this type to the impl assuming + "emulated". + + The impl should also be an "emulated" version of this type, + most likely the same class as this type itself. + + e.g.: sqltypes.Enum adapts to the Enum class. + + """ + return super().adapt(impltype, **kw) + + @overload + def adapt(self, cls: Type[_TE], **kw: Any) -> _TE: ... + + @overload + def adapt( + self, cls: Type[TypeEngineMixin], **kw: Any + ) -> TypeEngine[Any]: ... + + def adapt( + self, cls: Type[Union[TypeEngine[Any], TypeEngineMixin]], **kw: Any + ) -> TypeEngine[Any]: + if _is_native_for_emulated(cls): + if self.native: + # native support requested, dialect gave us a native + # implementor, pass control over to it + return cls.adapt_emulated_to_native(self, **kw) + else: + # non-native support, let the native implementor + # decide also, at the moment this is just to help debugging + # as only the default logic is implemented. + return cls.adapt_native_to_emulated(self, **kw) + else: + # this would be, both classes are Enum, or both classes + # are postgresql.ENUM + if issubclass(cls, self.__class__): + return self.adapt_to_emulated(cls, **kw) + else: + return super().adapt(cls, **kw) + + +def _is_native_for_emulated( + typ: Type[Union[TypeEngine[Any], TypeEngineMixin]], +) -> TypeGuard[Type[NativeForEmulated]]: + return hasattr(typ, "adapt_emulated_to_native") + + +class NativeForEmulated(TypeEngineMixin): + """Indicates DB-native types supported by an :class:`.Emulated` type. + + .. versionadded:: 1.2.0b3 + + """ + + @classmethod + def adapt_native_to_emulated( + cls, + impl: Union[TypeEngine[Any], TypeEngineMixin], + **kw: Any, + ) -> TypeEngine[Any]: + """Given an impl, adapt this type's class to the impl assuming + "emulated". + + + """ + impltype = impl.__class__ + return impl.adapt(impltype, **kw) + + @classmethod + def adapt_emulated_to_native( + cls, + impl: Union[TypeEngine[Any], TypeEngineMixin], + **kw: Any, + ) -> TypeEngine[Any]: + """Given an impl, adapt this type's class to the impl assuming + "native". + + The impl will be an :class:`.Emulated` class but not a + :class:`.NativeForEmulated`. + + e.g.: postgresql.ENUM produces a type given an Enum instance. + + """ + + # dmypy seems to crash on this + return cls(**kw) # type: ignore + + # dmypy seems to crash with this, on repeated runs with changes + # if TYPE_CHECKING: + # def __init__(self, **kw: Any): + # ... + + +class TypeDecorator(SchemaEventTarget, ExternalType, TypeEngine[_T]): + '''Allows the creation of types which add additional functionality + to an existing type. + + This method is preferred to direct subclassing of SQLAlchemy's + built-in types as it ensures that all required functionality of + the underlying type is kept in place. + + Typical usage:: + + import sqlalchemy.types as types + + + class MyType(types.TypeDecorator): + """Prefixes Unicode values with "PREFIX:" on the way in and + strips it off on the way out. + """ + + impl = types.Unicode + + cache_ok = True + + def process_bind_param(self, value, dialect): + return "PREFIX:" + value + + def process_result_value(self, value, dialect): + return value[7:] + + def copy(self, **kw): + return MyType(self.impl.length) + + The class-level ``impl`` attribute is required, and can reference any + :class:`.TypeEngine` class. Alternatively, the :meth:`load_dialect_impl` + method can be used to provide different type classes based on the dialect + given; in this case, the ``impl`` variable can reference + ``TypeEngine`` as a placeholder. + + The :attr:`.TypeDecorator.cache_ok` class-level flag indicates if this + custom :class:`.TypeDecorator` is safe to be used as part of a cache key. + This flag defaults to ``None`` which will initially generate a warning + when the SQL compiler attempts to generate a cache key for a statement + that uses this type. If the :class:`.TypeDecorator` is not guaranteed + to produce the same bind/result behavior and SQL generation + every time, this flag should be set to ``False``; otherwise if the + class produces the same behavior each time, it may be set to ``True``. + See :attr:`.TypeDecorator.cache_ok` for further notes on how this works. + + Types that receive a Python type that isn't similar to the ultimate type + used may want to define the :meth:`TypeDecorator.coerce_compared_value` + method. This is used to give the expression system a hint when coercing + Python objects into bind parameters within expressions. Consider this + expression:: + + mytable.c.somecol + datetime.date(2009, 5, 15) + + Above, if "somecol" is an ``Integer`` variant, it makes sense that + we're doing date arithmetic, where above is usually interpreted + by databases as adding a number of days to the given date. + The expression system does the right thing by not attempting to + coerce the "date()" value into an integer-oriented bind parameter. + + However, in the case of ``TypeDecorator``, we are usually changing an + incoming Python type to something new - ``TypeDecorator`` by default will + "coerce" the non-typed side to be the same type as itself. Such as below, + we define an "epoch" type that stores a date value as an integer:: + + class MyEpochType(types.TypeDecorator): + impl = types.Integer + + cache_ok = True + + epoch = datetime.date(1970, 1, 1) + + def process_bind_param(self, value, dialect): + return (value - self.epoch).days + + def process_result_value(self, value, dialect): + return self.epoch + timedelta(days=value) + + Our expression of ``somecol + date`` with the above type will coerce the + "date" on the right side to also be treated as ``MyEpochType``. + + This behavior can be overridden via the + :meth:`~TypeDecorator.coerce_compared_value` method, which returns a type + that should be used for the value of the expression. Below we set it such + that an integer value will be treated as an ``Integer``, and any other + value is assumed to be a date and will be treated as a ``MyEpochType``:: + + def coerce_compared_value(self, op, value): + if isinstance(value, int): + return Integer() + else: + return self + + .. warning:: + + Note that the **behavior of coerce_compared_value is not inherited + by default from that of the base type**. + If the :class:`.TypeDecorator` is augmenting a + type that requires special logic for certain types of operators, + this method **must** be overridden. A key example is when decorating + the :class:`_postgresql.JSON` and :class:`_postgresql.JSONB` types; + the default rules of :meth:`.TypeEngine.coerce_compared_value` should + be used in order to deal with operators like index operations:: + + from sqlalchemy import JSON + from sqlalchemy import TypeDecorator + + + class MyJsonType(TypeDecorator): + impl = JSON + + cache_ok = True + + def coerce_compared_value(self, op, value): + return self.impl.coerce_compared_value(op, value) + + Without the above step, index operations such as ``mycol['foo']`` + will cause the index value ``'foo'`` to be JSON encoded. + + Similarly, when working with the :class:`.ARRAY` datatype, the + type coercion for index operations (e.g. ``mycol[5]``) is also + handled by :meth:`.TypeDecorator.coerce_compared_value`, where + again a simple override is sufficient unless special rules are needed + for particular operators:: + + from sqlalchemy import ARRAY + from sqlalchemy import TypeDecorator + + + class MyArrayType(TypeDecorator): + impl = ARRAY + + cache_ok = True + + def coerce_compared_value(self, op, value): + return self.impl.coerce_compared_value(op, value) + + ''' + + __visit_name__ = "type_decorator" + + _is_type_decorator = True + + # this is that pattern I've used in a few places (Dialect.dbapi, + # Dialect.type_compiler) where the "cls.attr" is a class to make something, + # and "instance.attr" is an instance of that thing. It's such a nifty, + # great pattern, and there is zero chance Python typing tools will ever be + # OK with it. For TypeDecorator.impl, this is a highly public attribute so + # we really can't change its behavior without a major deprecation routine. + impl: Union[TypeEngine[Any], Type[TypeEngine[Any]]] + + # we are changing its behavior *slightly*, which is that we now consume + # the instance level version from this memoized property instead, so you + # can't reassign "impl" on an existing TypeDecorator that's already been + # used (something one shouldn't do anyway) without also updating + # impl_instance. + @util.memoized_property + def impl_instance(self) -> TypeEngine[Any]: + return self.impl # type: ignore + + def __init__(self, *args: Any, **kwargs: Any): + """Construct a :class:`.TypeDecorator`. + + Arguments sent here are passed to the constructor + of the class assigned to the ``impl`` class level attribute, + assuming the ``impl`` is a callable, and the resulting + object is assigned to the ``self.impl`` instance attribute + (thus overriding the class attribute of the same name). + + If the class level ``impl`` is not a callable (the unusual case), + it will be assigned to the same instance attribute 'as-is', + ignoring those arguments passed to the constructor. + + Subclasses can override this to customize the generation + of ``self.impl`` entirely. + + """ + + if not hasattr(self.__class__, "impl"): + raise AssertionError( + "TypeDecorator implementations " + "require a class-level variable " + "'impl' which refers to the class of " + "type being decorated" + ) + + self.impl = to_instance(self.__class__.impl, *args, **kwargs) + + coerce_to_is_types: Sequence[Type[Any]] = (type(None),) + """Specify those Python types which should be coerced at the expression + level to "IS " when compared using ``==`` (and same for + ``IS NOT`` in conjunction with ``!=``). + + For most SQLAlchemy types, this includes ``NoneType``, as well as + ``bool``. + + :class:`.TypeDecorator` modifies this list to only include ``NoneType``, + as typedecorator implementations that deal with boolean types are common. + + Custom :class:`.TypeDecorator` classes can override this attribute to + return an empty tuple, in which case no values will be coerced to + constants. + + """ + + class Comparator(TypeEngine.Comparator[_CT]): + """A :class:`.TypeEngine.Comparator` that is specific to + :class:`.TypeDecorator`. + + User-defined :class:`.TypeDecorator` classes should not typically + need to modify this. + + + """ + + __slots__ = () + + def operate( + self, op: OperatorType, *other: Any, **kwargs: Any + ) -> ColumnElement[_CT]: + if TYPE_CHECKING: + assert isinstance(self.expr.type, TypeDecorator) + kwargs["_python_is_types"] = self.expr.type.coerce_to_is_types + return super().operate(op, *other, **kwargs) + + def reverse_operate( + self, op: OperatorType, other: Any, **kwargs: Any + ) -> ColumnElement[_CT]: + if TYPE_CHECKING: + assert isinstance(self.expr.type, TypeDecorator) + kwargs["_python_is_types"] = self.expr.type.coerce_to_is_types + return super().reverse_operate(op, other, **kwargs) + + @staticmethod + def _reduce_td_comparator( + impl: TypeEngine[Any], expr: ColumnElement[_T] + ) -> Any: + return TypeDecorator._create_td_comparator_type(impl)(expr) + + @staticmethod + def _create_td_comparator_type( + impl: TypeEngine[Any], + ) -> _ComparatorFactory[Any]: + + def __reduce__(self: TypeDecorator.Comparator[Any]) -> Any: + return (TypeDecorator._reduce_td_comparator, (impl, self.expr)) + + return type( + "TDComparator", + (TypeDecorator.Comparator, impl.comparator_factory), # type: ignore # noqa: E501 + {"__reduce__": __reduce__}, + ) + + @property + def comparator_factory( # type: ignore # mypy properties bug + self, + ) -> _ComparatorFactory[Any]: + if TypeDecorator.Comparator in self.impl.comparator_factory.__mro__: # type: ignore # noqa: E501 + return self.impl_instance.comparator_factory + else: + # reconcile the Comparator class on the impl with that + # of TypeDecorator. + # the use of multiple staticmethods is to support repeated + # pickling of the Comparator itself + return TypeDecorator._create_td_comparator_type(self.impl_instance) + + def _copy_with_check(self) -> Self: + tt = self.copy() + if not isinstance(tt, self.__class__): + raise AssertionError( + "Type object %s does not properly " + "implement the copy() method, it must " + "return an object of type %s" % (self, self.__class__) + ) + return tt + + def _gen_dialect_impl(self, dialect: Dialect) -> TypeEngine[_T]: + if dialect.name in self._variant_mapping: + adapted = dialect.type_descriptor( + self._variant_mapping[dialect.name] + ) + else: + adapted = dialect.type_descriptor(self) + if adapted is not self: + return adapted + + # otherwise adapt the impl type, link + # to a copy of this TypeDecorator and return + # that. + typedesc = self.load_dialect_impl(dialect).dialect_impl(dialect) + tt = self._copy_with_check() + tt.impl = tt.impl_instance = typedesc + return tt + + def _with_collation(self, collation: str) -> Self: + tt = self._copy_with_check() + tt.impl = tt.impl_instance = self.impl_instance._with_collation( + collation + ) + return tt + + @util.ro_non_memoized_property + def _type_affinity(self) -> Optional[Type[TypeEngine[Any]]]: + return self.impl_instance._type_affinity + + def _set_parent( + self, parent: SchemaEventTarget, outer: bool = False, **kw: Any + ) -> None: + """Support SchemaEventTarget""" + + super()._set_parent(parent) + + if not outer and isinstance(self.impl_instance, SchemaEventTarget): + self.impl_instance._set_parent(parent, outer=False, **kw) + + def _set_parent_with_dispatch( + self, parent: SchemaEventTarget, **kw: Any + ) -> None: + """Support SchemaEventTarget""" + + super()._set_parent_with_dispatch(parent, outer=True, **kw) + + if isinstance(self.impl_instance, SchemaEventTarget): + self.impl_instance._set_parent_with_dispatch(parent) + + def type_engine(self, dialect: Dialect) -> TypeEngine[Any]: + """Return a dialect-specific :class:`.TypeEngine` instance + for this :class:`.TypeDecorator`. + + In most cases this returns a dialect-adapted form of + the :class:`.TypeEngine` type represented by ``self.impl``. + Makes usage of :meth:`dialect_impl`. + Behavior can be customized here by overriding + :meth:`load_dialect_impl`. + + """ + adapted = dialect.type_descriptor(self) + if not isinstance(adapted, type(self)): + return adapted + else: + return self.load_dialect_impl(dialect) + + def load_dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]: + """Return a :class:`.TypeEngine` object corresponding to a dialect. + + This is an end-user override hook that can be used to provide + differing types depending on the given dialect. It is used + by the :class:`.TypeDecorator` implementation of :meth:`type_engine` + to help determine what type should ultimately be returned + for a given :class:`.TypeDecorator`. + + By default returns ``self.impl``. + + """ + return self.impl_instance + + def _unwrapped_dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]: + """Return the 'unwrapped' dialect impl for this type. + + This is used by the :meth:`.DefaultDialect.set_input_sizes` + method. + + """ + # some dialects have a lookup for a TypeDecorator subclass directly. + # postgresql.INTERVAL being the main example + typ = self.dialect_impl(dialect) + + # if we are still a type decorator, load the per-dialect switch + # (such as what Variant uses), then get the dialect impl for that. + if isinstance(typ, self.__class__): + return typ.load_dialect_impl(dialect).dialect_impl(dialect) + else: + return typ + + def __getattr__(self, key: str) -> Any: + """Proxy all other undefined accessors to the underlying + implementation.""" + return getattr(self.impl_instance, key) + + def process_literal_param( + self, value: Optional[_T], dialect: Dialect + ) -> str: + """Receive a literal parameter value to be rendered inline within + a statement. + + .. note:: + + This method is called during the **SQL compilation** phase of a + statement, when rendering a SQL string. Unlike other SQL + compilation methods, it is passed a specific Python value to be + rendered as a string. However it should not be confused with the + :meth:`_types.TypeDecorator.process_bind_param` method, which is + the more typical method that processes the actual value passed to a + particular parameter at statement execution time. + + Custom subclasses of :class:`_types.TypeDecorator` should override + this method to provide custom behaviors for incoming data values + that are in the special case of being rendered as literals. + + The returned string will be rendered into the output string. + + """ + raise NotImplementedError() + + def process_bind_param(self, value: Optional[_T], dialect: Dialect) -> Any: + """Receive a bound parameter value to be converted. + + Custom subclasses of :class:`_types.TypeDecorator` should override + this method to provide custom behaviors for incoming data values. + This method is called at **statement execution time** and is passed + the literal Python data value which is to be associated with a bound + parameter in the statement. + + The operation could be anything desired to perform custom + behavior, such as transforming or serializing data. + This could also be used as a hook for validating logic. + + :param value: Data to operate upon, of any type expected by + this method in the subclass. Can be ``None``. + :param dialect: the :class:`.Dialect` in use. + + .. seealso:: + + :ref:`types_typedecorator` + + :meth:`_types.TypeDecorator.process_result_value` + + """ + + raise NotImplementedError() + + def process_result_value( + self, value: Optional[Any], dialect: Dialect + ) -> Optional[_T]: + """Receive a result-row column value to be converted. + + Custom subclasses of :class:`_types.TypeDecorator` should override + this method to provide custom behaviors for data values + being received in result rows coming from the database. + This method is called at **result fetching time** and is passed + the literal Python data value that's extracted from a database result + row. + + The operation could be anything desired to perform custom + behavior, such as transforming or deserializing data. + + :param value: Data to operate upon, of any type expected by + this method in the subclass. Can be ``None``. + :param dialect: the :class:`.Dialect` in use. + + .. seealso:: + + :ref:`types_typedecorator` + + :meth:`_types.TypeDecorator.process_bind_param` + + + """ + + raise NotImplementedError() + + @util.memoized_property + def _has_bind_processor(self) -> bool: + """memoized boolean, check if process_bind_param is implemented. + + Allows the base process_bind_param to raise + NotImplementedError without needing to test an expensive + exception throw. + + """ + + return util.method_is_overridden( + self, TypeDecorator.process_bind_param + ) + + @util.memoized_property + def _has_literal_processor(self) -> bool: + """memoized boolean, check if process_literal_param is implemented.""" + + return util.method_is_overridden( + self, TypeDecorator.process_literal_param + ) + + def literal_processor( + self, dialect: Dialect + ) -> Optional[_LiteralProcessorType[_T]]: + """Provide a literal processing function for the given + :class:`.Dialect`. + + This is the method that fulfills the :class:`.TypeEngine` + contract for literal value conversion which normally occurs via + the :meth:`_types.TypeEngine.literal_processor` method. + + .. note:: + + User-defined subclasses of :class:`_types.TypeDecorator` should + **not** implement this method, and should instead implement + :meth:`_types.TypeDecorator.process_literal_param` so that the + "inner" processing provided by the implementing type is maintained. + + """ + + if self._has_literal_processor: + process_literal_param = self.process_literal_param + process_bind_param = None + elif self._has_bind_processor: + # use the bind processor if dont have a literal processor, + # but we have an impl literal processor + process_literal_param = None + process_bind_param = self.process_bind_param + else: + process_literal_param = None + process_bind_param = None + + if process_literal_param is not None: + impl_processor = self.impl_instance.literal_processor(dialect) + if impl_processor: + fixed_impl_processor = impl_processor + fixed_process_literal_param = process_literal_param + + def process(value: Any) -> str: + return fixed_impl_processor( + fixed_process_literal_param(value, dialect) + ) + + else: + fixed_process_literal_param = process_literal_param + + def process(value: Any) -> str: + return fixed_process_literal_param(value, dialect) + + return process + + elif process_bind_param is not None: + impl_processor = self.impl_instance.literal_processor(dialect) + if not impl_processor: + return None + else: + fixed_impl_processor = impl_processor + fixed_process_bind_param = process_bind_param + + def process(value: Any) -> str: + return fixed_impl_processor( + fixed_process_bind_param(value, dialect) + ) + + return process + else: + return self.impl_instance.literal_processor(dialect) + + def bind_processor( + self, dialect: Dialect + ) -> Optional[_BindProcessorType[_T]]: + """Provide a bound value processing function for the + given :class:`.Dialect`. + + This is the method that fulfills the :class:`.TypeEngine` + contract for bound value conversion which normally occurs via + the :meth:`_types.TypeEngine.bind_processor` method. + + .. note:: + + User-defined subclasses of :class:`_types.TypeDecorator` should + **not** implement this method, and should instead implement + :meth:`_types.TypeDecorator.process_bind_param` so that the "inner" + processing provided by the implementing type is maintained. + + :param dialect: Dialect instance in use. + + """ + if self._has_bind_processor: + process_param = self.process_bind_param + impl_processor = self.impl_instance.bind_processor(dialect) + if impl_processor: + fixed_impl_processor = impl_processor + fixed_process_param = process_param + + def process(value: Optional[_T]) -> Any: + return fixed_impl_processor( + fixed_process_param(value, dialect) + ) + + else: + fixed_process_param = process_param + + def process(value: Optional[_T]) -> Any: + return fixed_process_param(value, dialect) + + return process + else: + return self.impl_instance.bind_processor(dialect) + + @util.memoized_property + def _has_result_processor(self) -> bool: + """memoized boolean, check if process_result_value is implemented. + + Allows the base process_result_value to raise + NotImplementedError without needing to test an expensive + exception throw. + + """ + + return util.method_is_overridden( + self, TypeDecorator.process_result_value + ) + + def result_processor( + self, dialect: Dialect, coltype: Any + ) -> Optional[_ResultProcessorType[_T]]: + """Provide a result value processing function for the given + :class:`.Dialect`. + + This is the method that fulfills the :class:`.TypeEngine` + contract for bound value conversion which normally occurs via + the :meth:`_types.TypeEngine.result_processor` method. + + .. note:: + + User-defined subclasses of :class:`_types.TypeDecorator` should + **not** implement this method, and should instead implement + :meth:`_types.TypeDecorator.process_result_value` so that the + "inner" processing provided by the implementing type is maintained. + + :param dialect: Dialect instance in use. + :param coltype: A SQLAlchemy data type + + """ + if self._has_result_processor: + process_value = self.process_result_value + impl_processor = self.impl_instance.result_processor( + dialect, coltype + ) + if impl_processor: + fixed_process_value = process_value + fixed_impl_processor = impl_processor + + def process(value: Any) -> Optional[_T]: + return fixed_process_value( + fixed_impl_processor(value), dialect + ) + + else: + fixed_process_value = process_value + + def process(value: Any) -> Optional[_T]: + return fixed_process_value(value, dialect) + + return process + else: + return self.impl_instance.result_processor(dialect, coltype) + + @util.memoized_property + def _has_bind_expression(self) -> bool: + return ( + util.method_is_overridden(self, TypeDecorator.bind_expression) + or self.impl_instance._has_bind_expression + ) + + def bind_expression( + self, bindparam: BindParameter[_T] + ) -> Optional[ColumnElement[_T]]: + """Given a bind value (i.e. a :class:`.BindParameter` instance), + return a SQL expression which will typically wrap the given parameter. + + .. note:: + + This method is called during the **SQL compilation** phase of a + statement, when rendering a SQL string. It is **not** necessarily + called against specific values, and should not be confused with the + :meth:`_types.TypeDecorator.process_bind_param` method, which is + the more typical method that processes the actual value passed to a + particular parameter at statement execution time. + + Subclasses of :class:`_types.TypeDecorator` can override this method + to provide custom bind expression behavior for the type. This + implementation will **replace** that of the underlying implementation + type. + + """ + return self.impl_instance.bind_expression(bindparam) + + @util.memoized_property + def _has_column_expression(self) -> bool: + """memoized boolean, check if column_expression is implemented. + + Allows the method to be skipped for the vast majority of expression + types that don't use this feature. + + """ + + return ( + util.method_is_overridden(self, TypeDecorator.column_expression) + or self.impl_instance._has_column_expression + ) + + def column_expression( + self, column: ColumnElement[_T] + ) -> Optional[ColumnElement[_T]]: + """Given a SELECT column expression, return a wrapping SQL expression. + + .. note:: + + This method is called during the **SQL compilation** phase of a + statement, when rendering a SQL string. It is **not** called + against specific values, and should not be confused with the + :meth:`_types.TypeDecorator.process_result_value` method, which is + the more typical method that processes the actual value returned + in a result row subsequent to statement execution time. + + Subclasses of :class:`_types.TypeDecorator` can override this method + to provide custom column expression behavior for the type. This + implementation will **replace** that of the underlying implementation + type. + + See the description of :meth:`_types.TypeEngine.column_expression` + for a complete description of the method's use. + + """ + + return self.impl_instance.column_expression(column) + + def coerce_compared_value( + self, op: Optional[OperatorType], value: Any + ) -> Any: + """Suggest a type for a 'coerced' Python value in an expression. + + By default, returns self. This method is called by + the expression system when an object using this type is + on the left or right side of an expression against a plain Python + object which does not yet have a SQLAlchemy type assigned:: + + expr = table.c.somecolumn + 35 + + Where above, if ``somecolumn`` uses this type, this method will + be called with the value ``operator.add`` + and ``35``. The return value is whatever SQLAlchemy type should + be used for ``35`` for this particular operation. + + """ + return self + + def copy(self, **kw: Any) -> Self: + """Produce a copy of this :class:`.TypeDecorator` instance. + + This is a shallow copy and is provided to fulfill part of + the :class:`.TypeEngine` contract. It usually does not + need to be overridden unless the user-defined :class:`.TypeDecorator` + has local state that should be deep-copied. + + """ + + instance = self.__class__.__new__(self.__class__) + instance.__dict__.update(self.__dict__) + return instance + + def get_dbapi_type(self, dbapi: DBAPIModule) -> Optional[Any]: + """Return the DBAPI type object represented by this + :class:`.TypeDecorator`. + + By default this calls upon :meth:`.TypeEngine.get_dbapi_type` of the + underlying "impl". + """ + return self.impl_instance.get_dbapi_type(dbapi) + + def compare_values(self, x: Any, y: Any) -> bool: + """Given two values, compare them for equality. + + By default this calls upon :meth:`.TypeEngine.compare_values` + of the underlying "impl", which in turn usually + uses the Python equals operator ``==``. + + This function is used by the ORM to compare + an original-loaded value with an intercepted + "changed" value, to determine if a net change + has occurred. + + """ + return self.impl_instance.compare_values(x, y) + + # mypy property bug + @property + def sort_key_function(self) -> Optional[Callable[[Any], Any]]: # type: ignore # noqa: E501 + return self.impl_instance.sort_key_function + + def __repr__(self) -> str: + return util.generic_repr(self, to_inspect=self.impl_instance) + + +class Variant(TypeDecorator[_T]): + """deprecated. symbol is present for backwards-compatibility with + workaround recipes, however this actual type should not be used. + + """ + + def __init__(self, *arg: Any, **kw: Any): + raise NotImplementedError( + "Variant is no longer used in SQLAlchemy; this is a " + "placeholder symbol for backwards compatibility." + ) + + +@overload +def to_instance( + typeobj: Union[Type[_TE], _TE], *arg: Any, **kw: Any +) -> _TE: ... + + +@overload +def to_instance(typeobj: None, *arg: Any, **kw: Any) -> TypeEngine[None]: ... + + +def to_instance( + typeobj: Union[Type[_TE], _TE, None], *arg: Any, **kw: Any +) -> Union[_TE, TypeEngine[None]]: + if typeobj is None: + return NULLTYPE + + if callable(typeobj): + return typeobj(*arg, **kw) + else: + return typeobj + + +def adapt_type( + typeobj: _TypeEngineArgument[Any], + colspecs: Mapping[Type[Any], Type[TypeEngine[Any]]], +) -> TypeEngine[Any]: + typeobj = to_instance(typeobj) + for t in typeobj.__class__.__mro__[0:-1]: + try: + impltype = colspecs[t] + break + except KeyError: + pass + else: + # couldn't adapt - so just return the type itself + # (it may be a user-defined type) + return typeobj + # if we adapted the given generic type to a database-specific type, + # but it turns out the originally given "generic" type + # is actually a subclass of our resulting type, then we were already + # given a more specific type than that required; so use that. + if issubclass(typeobj.__class__, impltype): + return typeobj + return typeobj.adapt(impltype)