This commit is contained in:
macbook_max
2026-10-15 20:36:59 +08:00
commit 6bf949949c
7923 changed files with 1369582 additions and 0 deletions
@@ -0,0 +1,156 @@
# sql/__init__.py
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
from typing import Any
from typing import TYPE_CHECKING
from ._typing import ColumnExpressionArgument as ColumnExpressionArgument
from ._typing import NotNullable as NotNullable
from ._typing import Nullable as Nullable
from .base import Executable as Executable
from .base import ExecutableStatement as ExecutableStatement
from .base import SyntaxExtension as SyntaxExtension
from .compiler import COLLECT_CARTESIAN_PRODUCTS as COLLECT_CARTESIAN_PRODUCTS
from .compiler import FROM_LINTING as FROM_LINTING
from .compiler import NO_LINTING as NO_LINTING
from .compiler import WARN_LINTING as WARN_LINTING
from .ddl import BaseDDLElement as BaseDDLElement
from .ddl import CheckFirst as CheckFirst
from .ddl import DDL as DDL
from .ddl import DDLElement as DDLElement
from .ddl import ExecutableDDLElement as ExecutableDDLElement
from .expression import aggregate_order_by as aggregate_order_by
from .expression import Alias as Alias
from .expression import alias as alias
from .expression import all_ as all_
from .expression import and_ as and_
from .expression import any_ as any_
from .expression import asc as asc
from .expression import between as between
from .expression import bindparam as bindparam
from .expression import case as case
from .expression import cast as cast
from .expression import ClauseElement as ClauseElement
from .expression import collate as collate
from .expression import column as column
from .expression import ColumnCollection as ColumnCollection
from .expression import ColumnElement as ColumnElement
from .expression import CompoundSelect as CompoundSelect
from .expression import cte as cte
from .expression import Delete as Delete
from .expression import delete as delete
from .expression import desc as desc
from .expression import distinct as distinct
from .expression import except_ as except_
from .expression import except_all as except_all
from .expression import exists as exists
from .expression import extract as extract
from .expression import false as false
from .expression import False_ as False_
from .expression import from_dml_column as from_dml_column
from .expression import FromClause as FromClause
from .expression import func as func
from .expression import funcfilter as funcfilter
from .expression import Insert as Insert
from .expression import insert as insert
from .expression import intersect as intersect
from .expression import intersect_all as intersect_all
from .expression import Join as Join
from .expression import join as join
from .expression import label as label
from .expression import LABEL_STYLE_DEFAULT as LABEL_STYLE_DEFAULT
from .expression import (
LABEL_STYLE_DISAMBIGUATE_ONLY as LABEL_STYLE_DISAMBIGUATE_ONLY,
)
from .expression import LABEL_STYLE_NONE as LABEL_STYLE_NONE
from .expression import (
LABEL_STYLE_TABLENAME_PLUS_COL as LABEL_STYLE_TABLENAME_PLUS_COL,
)
from .expression import lambda_stmt as lambda_stmt
from .expression import LambdaElement as LambdaElement
from .expression import lateral as lateral
from .expression import literal as literal
from .expression import literal_column as literal_column
from .expression import modifier as modifier
from .expression import not_ as not_
from .expression import null as null
from .expression import nulls_first as nulls_first
from .expression import nulls_last as nulls_last
from .expression import nullsfirst as nullsfirst
from .expression import nullslast as nullslast
from .expression import or_ as or_
from .expression import outerjoin as outerjoin
from .expression import outparam as outparam
from .expression import over as over
from .expression import quoted_name as quoted_name
from .expression import Select as Select
from .expression import select as select
from .expression import Selectable as Selectable
from .expression import SelectLabelStyle as SelectLabelStyle
from .expression import SQLColumnExpression as SQLColumnExpression
from .expression import StatementLambdaElement as StatementLambdaElement
from .expression import Subquery as Subquery
from .expression import table as table
from .expression import TableClause as TableClause
from .expression import TableSample as TableSample
from .expression import tablesample as tablesample
from .expression import TableValuedAlias as TableValuedAlias
from .expression import TableValuedColumn as TableValuedColumn
from .expression import text as text
from .expression import TextClause as TextClause
from .expression import true as true
from .expression import True_ as True_
from .expression import try_cast as try_cast
from .expression import TString as TString
from .expression import tstring as tstring
from .expression import tuple_ as tuple_
from .expression import type_coerce as type_coerce
from .expression import union as union
from .expression import union_all as union_all
from .expression import Update as Update
from .expression import update as update
from .expression import Values as Values
from .expression import values as values
from .expression import within_group as within_group
from .expression import WriteableColumnCollection as WriteableColumnCollection
from .visitors import ClauseVisitor as ClauseVisitor
def __go(lcls: Any) -> None:
from .. import util as _sa_util
from . import base
from . import coercions
from . import elements
from . import lambdas
from . import selectable
from . import schema
from . import traversals
from . import type_api
if not TYPE_CHECKING:
base.coercions = elements.coercions = coercions
base.elements = elements
base.type_api = type_api
coercions.elements = elements
coercions.lambdas = lambdas
coercions.schema = schema
coercions.selectable = selectable
from .annotation import _prepare_annotations
from .annotation import Annotated
from .elements import AnnotatedColumnElement
from .elements import ClauseList
from .selectable import AnnotatedFromClause
_prepare_annotations(ColumnElement, AnnotatedColumnElement)
_prepare_annotations(FromClause, AnnotatedFromClause)
_prepare_annotations(ClauseList, Annotated)
_sa_util.preloaded.import_prefix("sqlalchemy.sql")
__go(locals())
@@ -0,0 +1,402 @@
# sql/_annotated_cols.py
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
from __future__ import annotations
from typing import Any
from typing import Generic
from typing import Literal
from typing import NoReturn
from typing import overload
from typing import Protocol
from typing import TYPE_CHECKING
from . import sqltypes
from ._typing import _T
from ._typing import _Ts
from .base import _NoArg
from .base import ReadOnlyColumnCollection
from .. import util
from ..exc import ArgumentError
from ..exc import InvalidRequestError
from ..util import dunders_re
from ..util import typing as sa_typing
from ..util.typing import Never
from ..util.typing import Self
from ..util.typing import TypeVar
from ..util.typing import Unpack
if TYPE_CHECKING:
from .elements import ColumnClause # noqa (for zimports)
from .elements import KeyedColumnElement # noqa (for zimports)
from .schema import Column
from .type_api import TypeEngine
from ..util.typing import _AnnotationScanType
class Named(Generic[_T]):
"""A named descriptor that is interpreted by SQLAlchemy in various ways.
.. seealso::
:class:`_schema.TypedColumns` Define table columns using this
descriptor.
.. versionadded:: 2.1.0b2
"""
__slots__ = ()
key: str
if TYPE_CHECKING:
# NOTE: this overload prevents users from using the a TypedColumns
# class like if it were an orm mapped class
@overload
def __get__(self, instance: None, owner: Any) -> Never: ...
@overload
def __get__(
self, instance: TypedColumns, owner: Any
) -> Column[_T]: ...
@overload
def __get__(self, instance: Any, owner: Any) -> Self: ...
def __get__(self, instance: object | None, owner: Any) -> Any: ...
# NOTE: TypedColumns subclasses are ignored by the ORM mapping process
class TypedColumns(ReadOnlyColumnCollection[str, "Column[Any]"]):
"""Class that generally represent the typed columns of a :class:`.Table`,
but can be used with most :class:`_sql.FromClause` subclasses with the
:meth:`_sql.FromClause.with_cols` method.
This is a "typing only" class that is never instantiated at runtime: the
type checker will think that this class is exposed as the ``table.c``
attribute, but in reality a normal :class:`_schema.ColumnCollection` is
used at runtime.
Subclasses should just list the columns as class attributes, without
specifying method or other non column members.
To resolve the columns, a simplified version of the ORM logic is used,
in particular, columns can be declared by:
* directly instantiating :class:`_schema.Column`, to declare constraint,
custom SQL types and additional column options. The annotation is
usually inferred by type checkers from the column instance;
* using only a :class:`.Named` or :class:`_schema.Column` type annotation,
where nullability and SQL type will be inferred by the python type
provided.
Type inference is available for a common subset of python types.
* a mix of both, where the instance can be used to declare
constraints and other column options while the annotation will be used
to set the SQL type and nullability if not provided by the instance.
The information provided in the instance will take precedence over
the annotation when they are conflicting, for example if setting
nullable=True with an annotation tha does not include ``None``.
In all cases the name is inferred from the attribute name, unless
explicitly provided.
.. note::
The generated table will create a copy of any column instance assigned
as attributes of this class, so columns should be accessed only via
the ``table.c`` collection, not using this class directly.
Example of the inference behavior::
from sqlalchemy import Column, Integer, Named, String, TypedColumns
class tbl_cols(TypedColumns):
# the name will be set to ``id``, type is inferred as Column[int]
# from the Integer SQL type.
id = Column(Integer, primary_key=True)
# not null String column is generated
name: Named[str]
# nullable Double column is generated
weight: Named[float | None]
# nullable Integer column, with SQL name 'user_age'
age: Named[int | None] = Column("user_age")
# not null column with type String(42)
middle_name: Named[str] = Column(String(42))
Mixins and subclasses are also supported::
class with_id(TypedColumns):
id = Column(Integer, primary_key=True)
class named_cols(TypedColumns):
name: Named[str]
description: Named[str | None]
class product_cols(named_cols, with_id):
ean: Named[str] = Column(unique=True)
product = Table("product", metadata, product_cols)
class office_cols(named_cols, with_id):
address: Named[str]
office = Table("office", metadata, office_cols)
The positional types returned when selecting the table can
be optionally declared by specifying a :attr:`.HasRowPos.__row_pos__`
annotation::
from sqlalchemy import select
class some_cols(TypedColumns):
id = Column(Integer, primary_key=True)
name: Named[str]
weight: Named[float | None]
__row_pos__: tuple[int, str, float | None]
some_table = Table("st", metadata, some_cols)
# both will be typed as Select[int, str, float | None]
stmt1 = some_table.select()
stmt2 = select(some_table)
.. seealso::
:class:`.Table` for usage details on how to use this class to
create a table instance.
:meth:`_sql.FromClause.with_cols` to apply a :class:`.TypedColumns`
to a from clause.
.. versionadded:: 2.1.0b2
""" # noqa
__slots__ = ()
if not TYPE_CHECKING:
def __new__(cls, *args: Any, **kwargs: Any) -> NoReturn:
raise InvalidRequestError(
"Cannot instantiate a TypedColumns object."
)
def __init_subclass__(cls) -> None:
methods = {
name
for name, value in cls.__dict__.items()
if not dunders_re.match(name) and callable(value)
}
if methods:
raise InvalidRequestError(
"TypedColumns subclasses may not define methods. "
f"Found {sorted(methods)}"
)
_KeyColCC_co = TypeVar(
"_KeyColCC_co",
bound=ReadOnlyColumnCollection[str, "KeyedColumnElement[Any]"],
covariant=True,
default=ReadOnlyColumnCollection[str, "KeyedColumnElement[Any]"],
)
_ColClauseCC_co = TypeVar(
"_ColClauseCC_co",
bound=ReadOnlyColumnCollection[str, "ColumnClause[Any]"],
covariant=True,
default=ReadOnlyColumnCollection[str, "ColumnClause[Any]"],
)
_ColCC_co = TypeVar(
"_ColCC_co",
bound=ReadOnlyColumnCollection[str, "Column[Any]"],
covariant=True,
default=ReadOnlyColumnCollection[str, "Column[Any]"],
)
_TC = TypeVar("_TC", bound=TypedColumns)
_TC_co = TypeVar("_TC_co", bound=TypedColumns, covariant=True)
class HasRowPos(Protocol[Unpack[_Ts]]):
"""Protocol for a :class:`_schema.TypedColumns` used to indicate the
positional types will be returned when selecting the table.
.. versionadded:: 2.1.0b2
"""
__row_pos__: tuple[Unpack[_Ts]]
"""A tuple that represents the types that will be returned when
selecting from the table.
"""
@util.preload_module("sqlalchemy.sql.schema")
def _extract_columns_from_class(
table_columns_cls: type[TypedColumns],
) -> list[Column[Any]]:
columns: dict[str, Column[Any]] = {}
Column = util.preloaded.sql_schema.Column
NULL_UNSPECIFIED = util.preloaded.sql_schema.NULL_UNSPECIFIED
for base in table_columns_cls.__mro__[::-1]:
if base in TypedColumns.__mro__:
continue
# _ClassScanAbstractConfig._cls_attr_resolver
cls_annotations = util.get_annotations(base)
cls_vars = vars(base)
items = [
(n, cls_vars.get(n), cls_annotations.get(n))
for n in util.merge_lists_w_ordering(
list(cls_vars), list(cls_annotations)
)
if not dunders_re.match(n)
]
# --
for name, obj, annotation in items:
if obj is None:
assert annotation is not None
# no attribute, just annotation
extracted_type = _collect_annotation(
table_columns_cls, name, base.__module__, annotation
)
if extracted_type is _NoArg.NO_ARG:
raise ArgumentError(
"No type information could be extracted from "
f"annotation {annotation} for attribute "
f"'{base.__name__}.{name}'"
)
sqltype = _get_sqltype(extracted_type)
if sqltype is None:
raise ArgumentError(
f"Could not find a SQL type for type {extracted_type} "
f"obtained from annotation {annotation} in "
f"attribute '{base.__name__}.{name}'"
)
columns[name] = Column(
name,
sqltype,
nullable=sa_typing.includes_none(extracted_type),
)
elif isinstance(obj, Column):
# has attribute attribute
# _DeclarativeMapperConfig._produce_column_copies
# as with orm this case is not supported
for fk in obj.foreign_keys:
if (
fk._table_column is not None
and fk._table_column.table is None
):
raise InvalidRequestError(
f"Column '{base.__name__}.{name}' with foreign "
"key to non-table-bound columns is not supported "
"when using a TypedColumns. If possible use the "
"qualified string name the column"
)
col = obj._copy()
# MapptedColumn.declarative_scan
if col.key == col.name and col.key != name:
col.key = name
if col.name is None:
col.name = name
sqltype = col.type
anno_sqltype = None
nullable: Literal[_NoArg.NO_ARG] | bool = _NoArg.NO_ARG
if annotation is not None:
# there is an annotation, extract the type
extracted_type = _collect_annotation(
table_columns_cls, name, base.__module__, annotation
)
if extracted_type is not _NoArg.NO_ARG:
anno_sqltype = _get_sqltype(extracted_type)
nullable = sa_typing.includes_none(extracted_type)
if sqltype._isnull:
if anno_sqltype is None and not col.foreign_keys:
raise ArgumentError(
"Python typing annotation is required for "
f"attribute '{base.__name__}.{name}' when "
"primary argument(s) for Column construct are "
"None or not present"
)
elif anno_sqltype is not None:
col._set_type(anno_sqltype)
if (
nullable is not _NoArg.NO_ARG
and col._user_defined_nullable is NULL_UNSPECIFIED
and not col.primary_key
):
col.nullable = nullable
columns[name] = col
else:
raise ArgumentError(
f"Unexpected value for attribute '{base.__name__}.{name}'"
f". Expected a Column, not: {type(obj)}"
)
# Return columns as a list
return list(columns.values())
@util.preload_module("sqlalchemy.sql.schema")
def _collect_annotation(
cls: type[Any], name: str, module: str, raw_annotation: _AnnotationScanType
) -> _AnnotationScanType | Literal[_NoArg.NO_ARG]:
Column = util.preloaded.sql_schema.Column
_locals = {"Column": Column, "Named": Named}
# _ClassScanAbstractConfig._collect_annotation & _extract_mapped_subtype
try:
annotation = sa_typing.de_stringify_annotation(
cls, raw_annotation, module, _locals
)
except Exception as e:
raise ArgumentError(
f"Could not interpret annotation {raw_annotation} for "
f"attribute '{cls.__name__}.{name}'"
) from e
if (
not sa_typing.is_generic(annotation)
and isinstance(annotation, type)
and issubclass(annotation, (Column, Named))
):
# no generic information, ignore
return _NoArg.NO_ARG
elif not sa_typing.is_origin_of_cls(annotation, (Column, Named)):
raise ArgumentError(
f"Annotation {raw_annotation} for attribute "
f"'{cls.__name__}.{name}' is not of type Named/Column[...]"
)
else:
assert len(annotation.__args__) == 1 # Column[int, int] raises
return annotation.__args__[0] # type: ignore[no-any-return]
def _get_sqltype(annotation: _AnnotationScanType) -> TypeEngine[Any] | None:
our_type = sa_typing.de_optionalize_union_types(annotation)
# simplified version of registry._resolve_type given no customizable
# type map
sql_type = sqltypes._type_map_get(our_type) # type: ignore[arg-type]
if sql_type is not None and not sql_type._isnull:
return sqltypes.to_instance(sql_type)
else:
return None
@@ -0,0 +1,363 @@
# sql/_cache_key_cy.py
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
"""Cython implementation of the cache key traversal.
The traversal for a single class is represented as a :class:`.ClassAttrs`
holding one :class:`.AttrInfo` per attribute that participates in the cache
key. :class:`.AttrInfo` records the attribute name together with an integer
``kind`` identifying its handler, so that when compiled the per-attribute
dispatch in :meth:`.BaseHasCacheKey._gen_cache_key` is a comparison against a
C integer, rather than the chain of Python ``is`` comparisons against
singleton handler objects that the pure Python traversal loop has to run per
attribute, per cache key.
Traversal kinds that have no inline handler are represented by
:class:`.AttrInfoMethod`, which additionally carries the Python level visit
function to call; these are dispatched under the ``_PY_METHOD`` kind.
Recursion into child elements calls ``_gen_cache_key()`` on the child, which
is this compiled method for the classes that inherit it and the Python level
override otherwise.
"""
# mypy: disable-error-code="untyped-decorator"
from __future__ import annotations
from collections.abc import Sequence
from enum import Enum
from typing import Any
from typing import Callable
from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple
from typing import TYPE_CHECKING
from .visitors import InternalTraversal
from .. import util
if TYPE_CHECKING:
from .elements import BindParameter
from .visitors import _TraverseInternalsType
# START GENERATED CYTHON IMPORT
# This section is automatically generated by the script tools/cython_imports.py
try:
# NOTE: the cython compiler needs this "import cython" in the file, it
# can't be only "from sqlalchemy.util import cython" with the fallback
# in that module
import cython
except ModuleNotFoundError:
from sqlalchemy.util import cython
def _is_compiled() -> bool:
"""Utility function to indicate if this module is compiled or not."""
return cython.compiled # type: ignore[no-any-return,unused-ignore]
# END GENERATED CYTHON IMPORT
if cython.compiled:
from cython.cimports.sqlalchemy.sql._util_cy import anon_map
else:
from ._util_cy import anon_map
class BaseHasCacheKey:
__slots__ = ()
@cython.boundscheck(False)
@cython.wraparound(False)
def _gen_cache_key(
self, am: anon_map, bindparams: List[BindParameter[Any]]
) -> Optional[Tuple[Any, ...]]:
"""return an optional cache key.
The cache key is a tuple which can contain any series of
objects that are hashable and also identifies
this object uniquely within the presence of a larger SQL expression
or statement, for the purposes of caching the resulting query.
The cache key should be based on the SQL compiled structure that would
ultimately be produced. That is, two structures that are composed in
exactly the same way should produce the same cache key; any difference
in the structures that would affect the SQL string or the type handlers
should result in a different cache key.
If a structure cannot produce a useful cache key, the NO_CACHE
symbol should be added to the anon_map and the method should
return None.
"""
cls = self.__class__
id_: object
if cython.compiled:
# inline of anon_map.get_anon(), which otherwise costs a Python
# level call plus a throwaway (id, found) tuple per node
am_dict = cython.cast(dict, am)
idself: object = id(self)
if idself in am_dict:
return (am_dict[idself], cls)
id_ = am._add_missing(idself)
else:
id_, found = am.get_anon(self)
if found:
return (id_, cls)
try:
dispatcher = cls.__dict__["_generated_cache_key_traversal"]
except KeyError:
# traversals.py -> _preconfigure_traversals()
# may be used to run these ahead of time, but
# is not enabled right now.
# this block will generate any remaining dispatchers.
dispatcher = cls._generate_cache_attrs() # type: ignore[attr-defined] # noqa: E501
if dispatcher is _NO_CACHE:
am[_NO_CACHE] = True
return None
disp_cls: ClassAttrs = dispatcher
result: list[Any] = [id_, cls]
elements: tuple[Any, ...] = disp_cls.elements
element: AttrInfo
py_method: AttrInfoMethod
for element in elements:
obj = getattr(self, element.name)
kind: cython.int = element.kind
if obj is not None:
if kind == _STATIC_CACHE_KEY:
sck = obj._static_cache_key
if sck is _NO_CACHE:
am[_NO_CACHE] = True
return None
result.append(element.name)
result.append(sck)
elif kind == _ANON_NAME:
if isinstance(obj, disp_cls.anon_label):
obj = obj.apply_map(am) # type: ignore[attr-defined]
result.append(element.name)
result.append(obj)
elif kind == _CALL_GEN_CACHE_KEY:
result.append(element.name)
result.append(obj._gen_cache_key(am, bindparams))
# remaining cache functions are against
# Python tuples, dicts, lists, etc. so we can skip
# if they are empty
elif obj:
if kind == _CACHE_IN_PLACE:
result.append(element.name)
result.append(obj)
elif kind == _PROPAGATE_ATTRS:
result.append(element.name)
result.append(obj["compile_state_plugin"])
if ps := obj["plugin_subject"]:
result.append(ps._gen_cache_key(am, bindparams))
else:
result.append(None)
elif kind == _ANNOTATIONS_KEY:
# obj is here is the _annotations dict. Table uses
# a memoized version of it. however in other cases,
# we generate it given am as we may be from a
# Join, Aliased, etc.
# see #8790
if self._gen_static_annotations_cache_key: # type: ignore[attr-defined] # noqa: E501
result.extend(self._annotations_cache_key) # type: ignore[attr-defined] # noqa: E501
else:
result.extend(self._gen_annotations_cache_key(am)) # type: ignore[attr-defined] # noqa: E501
elif kind == _CLAUSEELEMENT_LIST:
result.append(element.name)
result.append(
tuple(
[
elem._gen_cache_key(am, bindparams)
for elem in obj
]
),
)
elif kind == _PY_METHOD:
py_method = element # type: ignore[assignment]
result.extend(
py_method.meth( # type: ignore[operator]
py_method.name, obj, self, am, bindparams
)
)
else:
raise NotImplementedError(f"Unknown kind {kind}")
return tuple(result)
class CacheConst(Enum):
NO_CACHE = 0
PARAMS = 1
# NOTE: declarations are invisible from python
_NO_CACHE = cython.declare(object, CacheConst.NO_CACHE)
_CACHE_IN_PLACE = cython.declare(cython.const[cython.int], 0)
_CALL_GEN_CACHE_KEY = cython.declare(cython.const[cython.int], 1)
_STATIC_CACHE_KEY = cython.declare(cython.const[cython.int], 2)
_PROPAGATE_ATTRS = cython.declare(cython.const[cython.int], 3)
_ANON_NAME = cython.declare(cython.const[cython.int], 4)
_ANNOTATIONS_KEY = cython.declare(cython.const[cython.int], 5)
_CLAUSEELEMENT_LIST = cython.declare(cython.const[cython.int], 6)
_PY_METHOD = cython.declare(cython.const[cython.int], 7)
class CacheTraverseTarget(Enum):
"""Handlers that the traversal implements inline.
The value of each member is the integer ``kind`` recorded on the
:class:`.AttrInfo` for an attribute using that handler, so that the
traversal can switch on a C integer.
"""
CACHE_IN_PLACE = _CACHE_IN_PLACE
CALL_GEN_CACHE_KEY = _CALL_GEN_CACHE_KEY
STATIC_CACHE_KEY = _STATIC_CACHE_KEY
PROPAGATE_ATTRS = _PROPAGATE_ATTRS
ANON_NAME = _ANON_NAME
# the traversal kind for each handler that _generate_class_attrs() may find.
# CacheTraverseTarget members carry their own kind as the enum value; the
# InternalTraversal symbols below have an inline handler but no dedicated
# CacheTraverseTarget member.
_attr_info_kind: Dict[Any, int] = {
**{member: member.value for member in CacheTraverseTarget},
InternalTraversal.dp_annotations_key: _ANNOTATIONS_KEY,
InternalTraversal.dp_clauseelement_list: _CLAUSEELEMENT_LIST,
InternalTraversal.dp_clauseelement_tuple: _CLAUSEELEMENT_LIST,
InternalTraversal.dp_memoized_select_entities: _CLAUSEELEMENT_LIST,
}
@cython.cclass
class AttrInfo:
"""An attribute of a specific kind"""
kind: cython.int
name: str
def __init__(self, kind: cython.int, name: str) -> None:
self.kind = kind
self.name = name
@cython.cclass
class AttrInfoMethod(AttrInfo):
"""An attribute that requires a python method."""
meth: object
def __init__(self, name: str, arg: object) -> None:
self.kind = _PY_METHOD
self.name = name
self.meth = arg
@cython.cclass
class ClassAttrs:
"""The whole cache key traversal for a single class."""
elements: tuple[AttrInfo, ...]
anon_label: type
def __init__(self, elements: Sequence[AttrInfo], anon_label: type) -> None:
self.elements = tuple(elements)
self.anon_label = anon_label
class _BaseCacheKeyTraversal:
"""Builds the per-class traversal structure that
:meth:`.BaseHasCacheKey._gen_cache_key` consumes.
Mixed in alongside :class:`.HasTraversalDispatch`, which supplies
:meth:`.dispatch`; the visit symbols themselves are declared by
:class:`._CacheKeyTraversal` in ``sql/cache_key.py``.
"""
__slots__ = ()
if TYPE_CHECKING:
def dispatch(
self, visit_symbol: InternalTraversal
) -> Callable[..., Any]: ...
def generate_class_attrs(
self,
# NOTE: not annotated as "type": cython enforces annotations on a
# compiled function and rejects subclasses of builtin types, which
# fails for any class using a metaclass, e.g. _MetaOptions
target_cls: Any,
internal_dispatch: _TraverseInternalsType,
attrname: str,
) -> ClassAttrs:
"""Build the :class:`.ClassAttrs` for ``target_cls`` and memoize it
on the class under ``attrname``.
"""
class_attrs = self._generate_class_attrs(internal_dispatch)
setattr(target_cls, attrname, class_attrs)
return class_attrs
@util.preload_module("sqlalchemy.sql.elements")
def _generate_class_attrs(
self, internal_dispatch: _TraverseInternalsType
) -> ClassAttrs:
"""Resolve the handler for every attribute that participates in the
cache key for a single class.
In contrast to :meth:`.HasTraversalDispatch._generate_dispatcher`,
which emits a function returning a list of
``(attrname, value, handler)`` triples that a runtime loop then has to
interpret, this returns a :class:`.ClassAttrs` holding one
:class:`.AttrInfo` per attribute, with the handler recorded as an
integer kind. Which handler applies to which attribute is a fact
that's constant for the class, so it's resolved once here rather than
being rediscovered by a chain of ``is`` comparisons on every cache key
generation.
Attributes whose handler has no inline form in the traversal are
recorded as :class:`.AttrInfoMethod`, which carries the visit method
itself.
"""
elems: List[AttrInfo] = []
meth: Any
for attrname, visit_sym in internal_dispatch:
meth = self.dispatch(visit_sym)
if meth is None:
continue
if meth in _attr_info_kind:
elems.append(AttrInfo(_attr_info_kind[meth], attrname))
elif isinstance(meth, (CacheTraverseTarget, InternalTraversal)):
raise NotImplementedError(
"No cache key traversal generation rule for symbol "
f"{meth!r}, used by attribute {attrname!r}"
)
else:
elems.append(AttrInfoMethod(attrname, meth))
return ClassAttrs(elems, util.preloaded.sql_elements._anonymous_label)
@@ -0,0 +1,132 @@
# sql/_dml_constructors.py
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
from __future__ import annotations
from typing import TYPE_CHECKING
from .dml import Delete
from .dml import Insert
from .dml import Update
if TYPE_CHECKING:
from ._typing import _DMLTableArgument
def insert(table: _DMLTableArgument) -> Insert:
"""Construct an :class:`_expression.Insert` object.
E.g.::
from sqlalchemy import insert
stmt = insert(user_table).values(name="username", fullname="Full Username")
Similar functionality is available via the
:meth:`_expression.TableClause.insert` method on
:class:`_schema.Table`.
.. seealso::
:ref:`tutorial_core_insert` - in the :ref:`unified_tutorial`
:param table: :class:`_expression.TableClause`
which is the subject of the
insert.
:param values: collection of values to be inserted; see
:meth:`_expression.Insert.values`
for a description of allowed formats here.
Can be omitted entirely; a :class:`_expression.Insert` construct
will also dynamically render the VALUES clause at execution time
based on the parameters passed to :meth:`_engine.Connection.execute`.
:param inline: if True, no attempt will be made to retrieve the
SQL-generated default values to be provided within the statement;
in particular,
this allows SQL expressions to be rendered 'inline' within the
statement without the need to pre-execute them beforehand; for
backends that support "returning", this turns off the "implicit
returning" feature for the statement.
If both :paramref:`_expression.insert.values` and compile-time bind
parameters are present, the compile-time bind parameters override the
information specified within :paramref:`_expression.insert.values` on a
per-key basis.
The keys within :paramref:`_expression.Insert.values` can be either
:class:`~sqlalchemy.schema.Column` objects or their string
identifiers. Each key may reference one of:
* a literal data value (i.e. string, number, etc.);
* a Column object;
* a SELECT statement.
If a ``SELECT`` statement is specified which references this
``INSERT`` statement's table, the statement will be correlated
against the ``INSERT`` statement.
.. seealso::
:ref:`tutorial_core_insert` - in the :ref:`unified_tutorial`
""" # noqa: E501
return Insert(table)
def update(table: _DMLTableArgument) -> Update:
r"""Construct an :class:`_expression.Update` object.
E.g.::
from sqlalchemy import update
stmt = (
update(user_table).where(user_table.c.id == 5).values(name="user #5")
)
Similar functionality is available via the
:meth:`_expression.TableClause.update` method on
:class:`_schema.Table`.
:param table: A :class:`_schema.Table`
object representing the database
table to be updated.
.. seealso::
:ref:`tutorial_core_update_delete` - in the :ref:`unified_tutorial`
""" # noqa: E501
return Update(table)
def delete(table: _DMLTableArgument) -> Delete:
r"""Construct :class:`_expression.Delete` object.
E.g.::
from sqlalchemy import delete
stmt = delete(user_table).where(user_table.c.id == 5)
Similar functionality is available via the
:meth:`_expression.TableClause.delete` method on
:class:`_schema.Table`.
:param table: The table to delete rows from.
.. seealso::
:ref:`tutorial_core_update_delete` - in the :ref:`unified_tutorial`
"""
return Delete(table)
@@ -0,0 +1,19 @@
# sql/_orm_types.py
# Copyright (C) 2022-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
"""ORM types that need to present specifically for **documentation only** of
the Executable.execution_options() method, which includes options that
are meaningful to the ORM.
"""
from __future__ import annotations
from typing import Literal
SynchronizeSessionArgument = Literal[False, "auto", "evaluate", "fetch"]
DMLStrategyArgument = Literal["bulk", "raw", "orm", "auto"]
@@ -0,0 +1,840 @@
# sql/_selectable_constructors.py
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
from __future__ import annotations
from typing import Any
from typing import Optional
from typing import overload
from typing import TYPE_CHECKING
from typing import Union
from . import coercions
from . import roles
from ._annotated_cols import _KeyColCC_co
from ._annotated_cols import HasRowPos
from ._typing import _ColumnsClauseArgument
from ._typing import _no_kw
from .elements import ColumnClause
from .selectable import Alias
from .selectable import CompoundSelect
from .selectable import Exists
from .selectable import FromClause
from .selectable import Join
from .selectable import Lateral
from .selectable import LateralFromClause
from .selectable import NamedFromClause
from .selectable import Select
from .selectable import TableClause
from .selectable import TableSample
from .selectable import Values
from ..util.typing import TupleAny
from ..util.typing import Unpack
if TYPE_CHECKING:
from ._typing import _FromClauseArgument
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 _T8
from ._typing import _T9
from ._typing import _Ts
from ._typing import _Ts2
from ._typing import _TypedColumnClauseArgument as _TCCA
from .functions import Function
from .selectable import CTE
from .selectable import HasCTE
from .selectable import ScalarSelect
from .selectable import SelectBase
def alias(
selectable: FromClause[_KeyColCC_co],
name: Optional[str] = None,
flat: bool = False,
) -> NamedFromClause[_KeyColCC_co]:
"""Return a named alias of the given :class:`.FromClause`.
For :class:`.Table` and :class:`.Join` objects, the return type is the
:class:`_expression.Alias` object. Other kinds of :class:`.NamedFromClause`
objects may be returned for other kinds of :class:`.FromClause` objects.
The named alias represents any :class:`_expression.FromClause` with an
alternate name assigned within SQL, typically using the ``AS`` clause when
generated, e.g. ``SELECT * FROM table AS aliasname``.
Equivalent functionality is available via the
:meth:`_expression.FromClause.alias`
method available on all :class:`_expression.FromClause` objects.
:param selectable: any :class:`_expression.FromClause` subclass,
such as a table, select statement, etc.
:param name: string name to be assigned as the alias.
If ``None``, a name will be deterministically generated at compile
time. Deterministic means the name is guaranteed to be unique against
other constructs used in the same statement, and will also be the same
name for each successive compilation of the same statement object.
:param flat: Will be passed through to if the given selectable
is an instance of :class:`_expression.Join` - see
:meth:`_expression.Join.alias` for details.
"""
return Alias._factory(selectable, name=name, flat=flat)
def cte(
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
)
# TODO: mypy requires the _TypedSelectable overloads in all compound select
# constructors since _SelectStatementForCompoundArgument includes
# untyped args that make it return CompoundSelect[Unpack[tuple[Never, ...]]]
# pyright does not have this issue
_TypedSelectable = Union["Select[Unpack[_Ts]]", "CompoundSelect[Unpack[_Ts]]"]
@overload
def except_(
*selects: _TypedSelectable[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]: ...
@overload
def except_(
*selects: _SelectStatementForCompoundArgument[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]: ...
def except_(
*selects: _SelectStatementForCompoundArgument[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]:
r"""Return an ``EXCEPT`` of multiple selectables.
The returned object is an instance of
:class:`_expression.CompoundSelect`.
:param \*selects:
a list of :class:`_expression.Select` instances.
"""
return CompoundSelect._create_except(*selects)
@overload
def except_all(
*selects: _TypedSelectable[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]: ...
@overload
def except_all(
*selects: _SelectStatementForCompoundArgument[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]: ...
def except_all(
*selects: _SelectStatementForCompoundArgument[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]:
r"""Return an ``EXCEPT ALL`` of multiple selectables.
The returned object is an instance of
:class:`_expression.CompoundSelect`.
:param \*selects:
a list of :class:`_expression.Select` instances.
"""
return CompoundSelect._create_except_all(*selects)
def exists(
__argument: Optional[
Union[_ColumnsClauseArgument[Any], SelectBase, ScalarSelect[Any]]
] = None,
/,
) -> Exists:
"""Construct a new :class:`_expression.Exists` construct.
The :func:`_sql.exists` can be invoked by itself to produce an
:class:`_sql.Exists` construct, which will accept simple WHERE
criteria::
exists_criteria = exists().where(table1.c.col1 == table2.c.col2)
However, for greater flexibility in constructing the SELECT, an
existing :class:`_sql.Select` construct may be converted to an
:class:`_sql.Exists`, most conveniently by making use of the
:meth:`_sql.SelectBase.exists` method::
exists_criteria = (
select(table2.c.col2).where(table1.c.col1 == table2.c.col2).exists()
)
The EXISTS criteria is then used inside of an enclosing SELECT::
stmt = select(table1.c.col1).where(exists_criteria)
The above statement will then be of the form:
.. sourcecode:: sql
SELECT col1 FROM table1 WHERE EXISTS
(SELECT table2.col2 FROM table2 WHERE table2.col2 = table1.col1)
.. seealso::
:ref:`tutorial_exists` - in the :term:`2.0 style` tutorial.
:meth:`_sql.SelectBase.exists` - method to transform a ``SELECT`` to an
``EXISTS`` clause.
""" # noqa: E501
return Exists(__argument)
@overload
def intersect(
*selects: _TypedSelectable[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]: ...
@overload
def intersect(
*selects: _SelectStatementForCompoundArgument[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]: ...
def intersect(
*selects: _SelectStatementForCompoundArgument[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]:
r"""Return an ``INTERSECT`` of multiple selectables.
The returned object is an instance of
:class:`_expression.CompoundSelect`.
:param \*selects:
a list of :class:`_expression.Select` instances.
"""
return CompoundSelect._create_intersect(*selects)
@overload
def intersect_all(
*selects: _TypedSelectable[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]: ...
@overload
def intersect_all(
*selects: _SelectStatementForCompoundArgument[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]: ...
def intersect_all(
*selects: _SelectStatementForCompoundArgument[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]:
r"""Return an ``INTERSECT ALL`` of multiple selectables.
The returned object is an instance of
:class:`_expression.CompoundSelect`.
:param \*selects:
a list of :class:`_expression.Select` instances.
"""
return CompoundSelect._create_intersect_all(*selects)
def join(
left: _FromClauseArgument,
right: _FromClauseArgument,
onclause: Optional[_OnClauseArgument] = None,
isouter: bool = False,
full: bool = False,
) -> Join:
"""Produce a :class:`_expression.Join` object, given two
:class:`_expression.FromClause`
expressions.
E.g.::
j = join(
user_table, 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
Similar functionality is available given any
:class:`_expression.FromClause` object (e.g. such as a
:class:`_schema.Table`) using
the :meth:`_expression.FromClause.join` method.
:param left: The left side of the join.
: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 JOIN.
.. seealso::
:meth:`_expression.FromClause.join` - method form,
based on a given left side.
:class:`_expression.Join` - the type of object produced.
""" # noqa: E501
return Join(left, right, onclause, isouter, full)
def lateral(
selectable: Union[SelectBase, _FromClauseArgument],
name: Optional[str] = None,
) -> LateralFromClause:
"""Return a :class:`_expression.Lateral` object.
:class:`_expression.Lateral` is an :class:`_expression.Alias`
subclass that represents
a subquery with the LATERAL keyword applied to it.
The special behavior of a LATERAL subquery is that it appears in the
FROM clause of an enclosing SELECT, but may correlate to other
FROM clauses of that SELECT. It is a special case of subquery
only supported by a small number of backends, currently more recent
PostgreSQL versions.
.. seealso::
:ref:`tutorial_lateral_correlation` - overview of usage.
"""
return Lateral._factory(selectable, name=name)
def outerjoin(
left: _FromClauseArgument,
right: _FromClauseArgument,
onclause: Optional[_OnClauseArgument] = None,
full: bool = False,
) -> Join:
"""Return an ``OUTER JOIN`` clause element.
The returned object is an instance of :class:`_expression.Join`.
Similar functionality is also available via the
:meth:`_expression.FromClause.outerjoin` method on any
:class:`_expression.FromClause`.
:param left: The left side of the join.
:param right: The right side of the join.
:param onclause: Optional criterion for the ``ON`` clause, is
derived from foreign key relationships established between
left and right otherwise.
To chain joins together, use the :meth:`_expression.FromClause.join`
or
:meth:`_expression.FromClause.outerjoin` methods on the resulting
:class:`_expression.Join` object.
"""
return Join(left, right, onclause, isouter=True, full=full)
# START OVERLOADED FUNCTIONS select Select 1-10
# code within this block is **programmatically,
# statically generated** by tools/generate_tuple_map_overloads.py
@overload
def select(__ent0: _TCCA[_T0], /) -> Select[_T0]: ...
@overload
def select(__ent0: _TCCA[_T0], __ent1: _TCCA[_T1], /) -> Select[_T0, _T1]: ...
@overload
def select(
__ent0: _TCCA[_T0], __ent1: _TCCA[_T1], __ent2: _TCCA[_T2], /
) -> Select[_T0, _T1, _T2]: ...
@overload
def select(
__ent0: _TCCA[_T0],
__ent1: _TCCA[_T1],
__ent2: _TCCA[_T2],
__ent3: _TCCA[_T3],
/,
) -> Select[_T0, _T1, _T2, _T3]: ...
@overload
def select(
__ent0: _TCCA[_T0],
__ent1: _TCCA[_T1],
__ent2: _TCCA[_T2],
__ent3: _TCCA[_T3],
__ent4: _TCCA[_T4],
/,
) -> Select[_T0, _T1, _T2, _T3, _T4]: ...
@overload
def select(
__ent0: _TCCA[_T0],
__ent1: _TCCA[_T1],
__ent2: _TCCA[_T2],
__ent3: _TCCA[_T3],
__ent4: _TCCA[_T4],
__ent5: _TCCA[_T5],
/,
) -> Select[_T0, _T1, _T2, _T3, _T4, _T5]: ...
@overload
def select(
__ent0: _TCCA[_T0],
__ent1: _TCCA[_T1],
__ent2: _TCCA[_T2],
__ent3: _TCCA[_T3],
__ent4: _TCCA[_T4],
__ent5: _TCCA[_T5],
__ent6: _TCCA[_T6],
/,
) -> Select[_T0, _T1, _T2, _T3, _T4, _T5, _T6]: ...
@overload
def select(
__ent0: _TCCA[_T0],
__ent1: _TCCA[_T1],
__ent2: _TCCA[_T2],
__ent3: _TCCA[_T3],
__ent4: _TCCA[_T4],
__ent5: _TCCA[_T5],
__ent6: _TCCA[_T6],
__ent7: _TCCA[_T7],
/,
) -> Select[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]: ...
@overload
def select(
__ent0: _TCCA[_T0],
__ent1: _TCCA[_T1],
__ent2: _TCCA[_T2],
__ent3: _TCCA[_T3],
__ent4: _TCCA[_T4],
__ent5: _TCCA[_T5],
__ent6: _TCCA[_T6],
__ent7: _TCCA[_T7],
__ent8: _TCCA[_T8],
/,
) -> Select[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8]: ...
@overload
def select(
__ent0: _TCCA[_T0],
__ent1: _TCCA[_T1],
__ent2: _TCCA[_T2],
__ent3: _TCCA[_T3],
__ent4: _TCCA[_T4],
__ent5: _TCCA[_T5],
__ent6: _TCCA[_T6],
__ent7: _TCCA[_T7],
__ent8: _TCCA[_T8],
__ent9: _TCCA[_T9],
/,
*entities: _ColumnsClauseArgument[Any],
) -> Select[
_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7, _T8, _T9, Unpack[TupleAny]
]: ...
# END OVERLOADED FUNCTIONS select
@overload
def select(
__table: FromClause[HasRowPos[Unpack[_Ts]]], # type: ignore[type-var]
) -> Select[Unpack[_Ts]]: ...
# NOTE: this seems to currently be interpreted by mypy as not allowed.
# https://peps.python.org/pep-0646/#multiple-type-variable-tuples-not-allowed
# https://github.com/python/mypy/issues/20188
@overload
def select(
__table: FromClause[HasRowPos[Unpack[_Ts]]], # type: ignore[type-var]
__table2: FromClause[HasRowPos[Unpack[_Ts2]]], # type: ignore[type-var]
) -> Select[Unpack[_Ts], Unpack[_Ts2]]: ... # type: ignore[misc]
@overload
def select(
__table: FromClause[HasRowPos[Unpack[_Ts]]], # type: ignore[type-var]
__ent0: _TCCA[_T0],
) -> Select[Unpack[_Ts], _T0]: ...
@overload
def select(
__table: FromClause[HasRowPos[Unpack[_Ts]]], # type: ignore[type-var]
__ent0: _TCCA[_T0],
__ent1: _TCCA[_T1],
) -> Select[Unpack[_Ts], _T0, _T1]: ...
@overload
def select(
__table: FromClause[HasRowPos[Unpack[_Ts]]], # type: ignore[type-var]
__ent0: _TCCA[_T0],
__ent1: _TCCA[_T1],
__ent2: _TCCA[_T2],
) -> Select[Unpack[_Ts], _T0, _T1, _T2]: ...
@overload
def select(
__ent0: _TCCA[_T0],
__table: FromClause[HasRowPos[Unpack[_Ts]]], # type: ignore[type-var]
) -> Select[_T0, Unpack[_Ts]]: ...
@overload
def select(
__ent0: _TCCA[_T0],
__ent1: _TCCA[_T1],
__table: FromClause[HasRowPos[Unpack[_Ts]]], # type: ignore[type-var]
) -> Select[_T0, _T1, Unpack[_Ts]]: ...
@overload
def select(
__ent0: _TCCA[_T0],
__ent1: _TCCA[_T1],
__ent2: _TCCA[_T2],
__table: FromClause[HasRowPos[Unpack[_Ts]]], # type: ignore[type-var]
) -> Select[_T0, _T1, _T2, Unpack[_Ts]]: ...
@overload
def select(
*entities: _ColumnsClauseArgument[Any], **__kw: Any
) -> Select[Unpack[TupleAny]]: ...
def select(
*entities: _ColumnsClauseArgument[Any], **__kw: Any
) -> Select[Unpack[TupleAny]]:
r"""Construct a new :class:`_expression.Select`.
.. versionadded:: 1.4 - The :func:`_sql.select` function now accepts
column arguments positionally. The top-level :func:`_sql.select`
function will automatically use the 1.x or 2.x style API based on
the incoming arguments; using :func:`_sql.select` from the
``sqlalchemy.future`` module will enforce that only the 2.x style
constructor is used.
Similar functionality is also available via the
:meth:`_expression.FromClause.select` method on any
:class:`_expression.FromClause`.
.. seealso::
:ref:`tutorial_selecting_data` - in the :ref:`unified_tutorial`
:param \*entities:
Entities to SELECT from. For Core usage, this is typically a series
of :class:`_expression.ColumnElement` and / or
:class:`_expression.FromClause`
objects which will form the columns clause of the resulting
statement. For those objects that are instances of
:class:`_expression.FromClause` (typically :class:`_schema.Table`
or :class:`_expression.Alias`
objects), the :attr:`_expression.FromClause.c`
collection is extracted
to form a collection of :class:`_expression.ColumnElement` objects.
This parameter will also accept :class:`_expression.TextClause`
constructs as
given, as well as ORM-mapped classes.
"""
# the keyword args are a necessary element in order for the typing
# to work out w/ the varargs vs. having named "keyword" arguments that
# aren't always present.
if __kw:
raise _no_kw()
return Select(*entities)
def table(name: str, *columns: ColumnClause[Any], **kw: Any) -> TableClause:
"""Produce a new :class:`_expression.TableClause`.
The object returned is an instance of
:class:`_expression.TableClause`, which
represents the "syntactical" portion of the schema-level
:class:`_schema.Table` object.
It may be used to construct lightweight table constructs.
:param name: Name of the table.
:param columns: A collection of :func:`_expression.column` constructs.
:param schema: The schema name for this table.
"""
return TableClause(name, *columns, **kw)
def tablesample(
selectable: _FromClauseArgument,
sampling: Union[float, Function[Any]],
name: Optional[str] = None,
seed: Optional[roles.ExpressionElementRole[Any]] = None,
) -> TableSample:
"""Return a :class:`_expression.TableSample` object.
:class:`_expression.TableSample` is an :class:`_expression.Alias`
subclass that represents
a table with the TABLESAMPLE clause applied to it.
:func:`_expression.tablesample`
is also available from the :class:`_expression.FromClause`
class via the
:meth:`_expression.FromClause.tablesample` method.
The TABLESAMPLE clause allows selecting a randomly selected approximate
percentage of rows from a table. It supports multiple sampling methods,
most commonly BERNOULLI and SYSTEM.
e.g.::
from sqlalchemy import func
selectable = people.tablesample(
func.bernoulli(1), name="alias", seed=func.random()
)
stmt = select(selectable.c.people_id)
Assuming ``people`` with a column ``people_id``, the above
statement would render as:
.. sourcecode:: sql
SELECT alias.people_id FROM
people AS alias TABLESAMPLE bernoulli(:bernoulli_1)
REPEATABLE (random())
:param sampling: a ``float`` percentage between 0 and 100 or
:class:`_functions.Function`.
:param name: optional alias name
:param seed: any real-valued SQL expression. When specified, the
REPEATABLE sub-clause is also rendered.
"""
return TableSample._factory(selectable, sampling, name=name, seed=seed)
@overload
def union(
*selects: _TypedSelectable[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]: ...
@overload
def union(
*selects: _SelectStatementForCompoundArgument[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]: ...
def union(
*selects: _SelectStatementForCompoundArgument[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]:
r"""Return a ``UNION`` of multiple selectables.
The returned object is an instance of
:class:`_expression.CompoundSelect`.
A similar :func:`union()` method is available on all
:class:`_expression.FromClause` subclasses.
:param \*selects:
a list of :class:`_expression.Select` instances.
:param \**kwargs:
available keyword arguments are the same as those of
:func:`select`.
"""
return CompoundSelect._create_union(*selects)
@overload
def union_all(
*selects: _TypedSelectable[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]: ...
@overload
def union_all(
*selects: _SelectStatementForCompoundArgument[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]: ...
def union_all(
*selects: _SelectStatementForCompoundArgument[Unpack[_Ts]],
) -> CompoundSelect[Unpack[_Ts]]:
r"""Return a ``UNION ALL`` of multiple selectables.
The returned object is an instance of
:class:`_expression.CompoundSelect`.
A similar :func:`union_all()` method is available on all
:class:`_expression.FromClause` subclasses.
:param \*selects:
a list of :class:`_expression.Select` instances.
"""
return CompoundSelect._create_union_all(*selects)
def values(
*columns: _OnlyColumnArgument[Any],
name: Optional[str] = None,
literal_binds: bool = False,
) -> Values:
r"""Construct a :class:`_expression.Values` construct representing the
SQL ``VALUES`` clause.
The column expressions and the actual data for :class:`_expression.Values`
are given in two separate steps. The constructor receives the column
expressions typically as :func:`_expression.column` constructs, and the
data is then passed via the :meth:`_expression.Values.data` method as a
list, which can be called multiple times to add more data, e.g.::
from sqlalchemy import column
from sqlalchemy import values
from sqlalchemy import Integer
from sqlalchemy import String
value_expr = (
values(
column("id", Integer),
column("name", String),
)
.data([(1, "name1"), (2, "name2")])
.data([(3, "name3")])
)
Would represent a SQL fragment like::
VALUES(1, "name1"), (2, "name2"), (3, "name3")
The :class:`_sql.values` construct has an optional
:paramref:`_sql.values.name` field; when using this field, the
PostgreSQL-specific "named VALUES" clause may be generated::
value_expr = values(
column("id", Integer), column("name", String), name="somename"
).data([(1, "name1"), (2, "name2"), (3, "name3")])
When selecting from the above construct, the name and column names will
be listed out using a PostgreSQL-specific syntax::
>>> print(value_expr.select())
SELECT somename.id, somename.name
FROM (VALUES (:param_1, :param_2), (:param_3, :param_4),
(:param_5, :param_6)) AS somename (id, name)
For a more database-agnostic means of SELECTing named columns from a
VALUES expression, the :meth:`.Values.cte` method may be used, which
produces a named CTE with explicit column names against the VALUES
construct within; this syntax works on PostgreSQL, SQLite, and MariaDB::
value_expr = (
values(
column("id", Integer),
column("name", String),
)
.data([(1, "name1"), (2, "name2"), (3, "name3")])
.cte()
)
Rendering as::
>>> print(value_expr.select())
WITH anon_1(id, name) AS
(VALUES (:param_1, :param_2), (:param_3, :param_4), (:param_5, :param_6))
SELECT anon_1.id, anon_1.name
FROM anon_1
.. versionadded:: 2.0.42 Added the :meth:`.Values.cte` method to
:class:`.Values`
:param \*columns: column expressions, typically composed using
:func:`_expression.column` objects.
:param name: the name for this VALUES construct. If omitted, the
VALUES construct will be unnamed in a SQL expression. Different
backends may have different requirements here.
:param literal_binds: Defaults to False. Whether or not to render
the data values inline in the SQL output, rather than using bound
parameters.
""" # noqa: E501
return Values(*columns, literal_binds=literal_binds, name=name)
@@ -0,0 +1,500 @@
# sql/_typing.py
# Copyright (C) 2022-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
from __future__ import annotations
import operator
from typing import Any
from typing import Callable
from typing import Dict
from typing import Generic
from typing import Iterable
from typing import Literal
from typing import Mapping
from typing import NoReturn
from typing import Optional
from typing import overload
from typing import Protocol
from typing import Set
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeAlias
from typing import TypeVar
from typing import Union
from . import roles
from .. import exc
from .. import util
from ..inspection import Inspectable
from ..util.typing import TupleAny
from ..util.typing import TypeVarTuple
from ..util.typing import Unpack
if TYPE_CHECKING:
from datetime import date
from datetime import datetime
from datetime import time
from datetime import timedelta
from decimal import Decimal
from typing import TypeGuard
from uuid import UUID
from .base import Executable
from .compiler import Compiled
from .compiler import DDLCompiler
from .compiler import SQLCompiler
from .dml import UpdateBase
from .dml import ValuesBase
from .elements import ClauseElement
from .elements import ColumnElement
from .elements import KeyedColumnElement
from .elements import quoted_name
from .elements import SQLCoreOperations
from .elements import TextClause
from .lambdas import LambdaElement
from .roles import FromClauseRole
from .schema import Column
from .selectable import Alias
from .selectable import CompoundSelect
from .selectable import CTE
from .selectable import FromClause
from .selectable import Join
from .selectable import NamedFromClause
from .selectable import ReturnsRows
from .selectable import Select
from .selectable import Selectable
from .selectable import SelectBase
from .selectable import Subquery
from .selectable import TableClause
from .sqltypes import TableValueType
from .sqltypes import TupleType
from .type_api import TypeEngine
from ..engine import Connection
from ..engine import Dialect
from ..engine import Engine
from ..engine.mock import MockConnection
_T = TypeVar("_T", bound=Any)
_T_co = TypeVar("_T_co", bound=Any, covariant=True)
_Ts = TypeVarTuple("_Ts")
_Ts2 = TypeVarTuple("_Ts2")
_CE = TypeVar("_CE", bound="ColumnElement[Any]")
_CLE = TypeVar("_CLE", bound="ClauseElement")
class _HasClauseElement(Protocol, Generic[_T_co]):
"""indicates a class that has a __clause_element__() method"""
def __clause_element__(self) -> roles.ExpressionElementRole[_T_co]: ...
class _CoreAdapterProto(Protocol):
"""protocol for the ClauseAdapter/ColumnAdapter.traverse() method."""
def __call__(self, obj: _CE) -> _CE: ...
class _HasDialect(Protocol):
"""protocol for Engine/Connection-like objects that have dialect
attribute.
"""
@property
def dialect(self) -> Dialect: ...
# match column types that are not ORM entities
_NOT_ENTITY = TypeVar(
"_NOT_ENTITY",
int,
str,
bool,
"datetime",
"date",
"time",
"timedelta",
"UUID",
float,
"Decimal",
)
_StarOrOne = Literal["*", 1]
_MAYBE_ENTITY = TypeVar(
"_MAYBE_ENTITY",
roles.ColumnsClauseRole,
_StarOrOne,
Type[Any],
Inspectable[_HasClauseElement[Any]],
_HasClauseElement[Any],
)
# convention:
# XYZArgument - something that the end user is passing to a public API method
# XYZElement - the internal representation that we use for the thing.
# the coercions system is responsible for converting from XYZArgument to
# XYZElement.
_TextCoercedExpressionArgument = Union[
str,
"TextClause",
"ColumnElement[_T]",
_HasClauseElement[_T],
roles.ExpressionElementRole[_T],
]
_ColumnsClauseArgument = Union[
roles.TypedColumnsClauseRole[_T],
roles.ColumnsClauseRole,
"SQLCoreOperations[_T]",
_StarOrOne,
Type[_T],
Inspectable[_HasClauseElement[_T]],
_HasClauseElement[_T],
]
"""open-ended SELECT columns clause argument.
Includes column expressions, tables, ORM mapped entities, a few literal values.
This type is used for lists of columns / entities to be returned in result
sets; select(...), insert().returning(...), etc.
"""
_TypedColumnClauseArgument = Union[
roles.TypedColumnsClauseRole[_T],
"SQLCoreOperations[_T]",
Type[_T],
]
_T0 = TypeVar("_T0", bound=Any)
_T1 = TypeVar("_T1", bound=Any)
_T2 = TypeVar("_T2", bound=Any)
_T3 = TypeVar("_T3", bound=Any)
_T4 = TypeVar("_T4", bound=Any)
_T5 = TypeVar("_T5", bound=Any)
_T6 = TypeVar("_T6", bound=Any)
_T7 = TypeVar("_T7", bound=Any)
_T8 = TypeVar("_T8", bound=Any)
_T9 = TypeVar("_T9", bound=Any)
_OnlyColumnArgument = Union[
"ColumnElement[_T]",
_HasClauseElement[_T],
roles.DMLColumnRole,
]
"""A narrow type that is looking for a ColumnClause (e.g. table column with a
name) or an ORM element that produces this.
This is used for constructs that need a named column to represent a
position in a selectable, like TextClause().columns() or values(...).
"""
_ColumnExpressionArgument = Union[
"ColumnElement[_T]",
_HasClauseElement[_T],
"SQLCoreOperations[_T]",
roles.ExpressionElementRole[_T],
roles.TypedColumnsClauseRole[_T],
Callable[[], "ColumnElement[_T]"],
"LambdaElement",
]
"See docs in public alias ColumnExpressionArgument."
ColumnExpressionArgument: TypeAlias = _ColumnExpressionArgument[_T]
"""Narrower "column expression" argument.
This type is used for all the other "column" kinds of expressions that
typically represent a single SQL column expression, not a set of columns the
way a table or ORM entity does.
This includes ColumnElement, or ORM-mapped attributes that will have a
``__clause_element__()`` method, it also has the ExpressionElementRole
overall which brings in the TextClause object also.
.. versionadded:: 2.0.13
"""
_ColumnExpressionOrLiteralArgument = Union[Any, _ColumnExpressionArgument[_T]]
_ColumnExpressionOrStrLabelArgument = Union[str, _ColumnExpressionArgument[_T]]
_ByArgument = Union[
Iterable[_ColumnExpressionOrStrLabelArgument[Any]],
_ColumnExpressionOrStrLabelArgument[Any],
]
"""Used for keyword-based ``order_by`` and ``partition_by`` parameters."""
_InfoType = Dict[Any, Any]
"""the .info dictionary accepted and used throughout Core /ORM"""
_FromClauseArgument = Union[
roles.FromClauseRole,
roles.TypedColumnsClauseRole[Any],
Type[Any],
Inspectable[_HasClauseElement[Any]],
_HasClauseElement[Any],
]
"""A FROM clause, like we would send to select().select_from().
Also accommodates ORM entities and related constructs.
"""
_JoinTargetArgument = Union[_FromClauseArgument, roles.JoinTargetRole]
"""target for join() builds on _FromClauseArgument to include additional
join target roles such as those which come from the ORM.
"""
_OnClauseArgument = Union[_ColumnExpressionArgument[Any], roles.OnClauseRole]
"""target for an ON clause, includes additional roles such as those which
come from the ORM.
"""
_SelectStatementForCompoundArgument = Union[
"Select[Unpack[_Ts]]",
"CompoundSelect[Unpack[_Ts]]",
roles.CompoundElementRole,
]
"""SELECT statement acceptable by ``union()`` and other SQL set operations"""
_DMLColumnArgument = Union[
str,
_HasClauseElement[Any],
roles.DMLColumnRole,
"SQLCoreOperations[Any]",
]
"""A DML column expression. This is a "key" inside of insert().values(),
update().values(), and related.
These are usually strings or SQL table columns.
There's also edge cases like JSON expression assignment, which we would want
the DMLColumnRole to be able to accommodate.
"""
_DMLKey = TypeVar("_DMLKey", bound=_DMLColumnArgument)
_DMLColumnKeyMapping = Mapping[_DMLKey, Any]
_DDLColumnArgument = Union[str, "Column[Any]", roles.DDLConstraintColumnRole]
"""DDL column.
used for :class:`.PrimaryKeyConstraint`, :class:`.UniqueConstraint`, etc.
"""
_DDLColumnReferenceArgument = Union[
_DDLColumnArgument,
Tuple[Optional[str], str, Optional[str]],
Tuple[str, Optional[str]],
]
"""DDL column reference, as used by :class:`.ForeignKey`.
In addition to the forms accepted by ``_DDLColumnArgument``, the target may
be given as a ``(schema, table_name, column_name)`` or ``(table_name,
column_name)`` tuple, which is the only form that can express a name that
itself contains a dot.
"""
_DMLTableArgument = Union[
"TableClause",
"Join",
"Alias",
"CTE",
Type[Any],
Inspectable[_HasClauseElement[Any]],
_HasClauseElement[Any],
]
_PropagateAttrsType = util.immutabledict[str, Any]
_TypeEngineArgument = Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"]
_EquivalentColumnMap = Dict["ColumnElement[Any]", Set["ColumnElement[Any]"]]
_LimitOffsetType = Union[int, _ColumnExpressionArgument[int], None]
_AutoIncrementType = Union[bool, Literal["auto", "ignore_fk"]]
_CreateDropBind = Union["Engine", "Connection", "MockConnection"]
if TYPE_CHECKING:
def is_sql_compiler(c: Compiled) -> TypeGuard[SQLCompiler]: ...
def is_ddl_compiler(c: Compiled) -> TypeGuard[DDLCompiler]: ...
def is_named_from_clause(
t: FromClauseRole,
) -> TypeGuard[NamedFromClause]: ...
def is_column_element(
c: ClauseElement,
) -> TypeGuard[ColumnElement[Any]]: ...
def is_keyed_column_element(
c: ClauseElement,
) -> TypeGuard[KeyedColumnElement[Any]]: ...
def is_text_clause(c: ClauseElement) -> TypeGuard[TextClause]: ...
def is_from_clause(c: ClauseElement) -> TypeGuard[FromClause]: ...
def is_tuple_type(t: TypeEngine[Any]) -> TypeGuard[TupleType]: ...
def is_table_value_type(
t: TypeEngine[Any],
) -> TypeGuard[TableValueType]: ...
def is_selectable(t: Any) -> TypeGuard[Selectable]: ...
def is_select_base(
t: Union[Executable, ReturnsRows],
) -> TypeGuard[SelectBase]: ...
def is_select_statement(
t: Union[Executable, ReturnsRows],
) -> TypeGuard[Select[Unpack[TupleAny]]]: ...
def is_table(t: FromClause) -> TypeGuard[TableClause]: ...
def is_subquery(t: FromClause) -> TypeGuard[Subquery]: ...
def is_dml(c: ClauseElement) -> TypeGuard[UpdateBase]: ...
else:
is_sql_compiler = operator.attrgetter("is_sql")
is_ddl_compiler = operator.attrgetter("is_ddl")
is_named_from_clause = operator.attrgetter("named_with_column")
is_column_element = operator.attrgetter("_is_column_element")
is_keyed_column_element = operator.attrgetter("_is_keyed_column_element")
is_text_clause = operator.attrgetter("_is_text_clause")
is_from_clause = operator.attrgetter("_is_from_clause")
is_tuple_type = operator.attrgetter("_is_tuple_type")
is_table_value_type = operator.attrgetter("_is_table_value")
is_selectable = operator.attrgetter("is_selectable")
is_select_base = operator.attrgetter("_is_select_base")
is_select_statement = operator.attrgetter("_is_select_statement")
is_table = operator.attrgetter("_is_table")
is_subquery = operator.attrgetter("_is_subquery")
is_dml = operator.attrgetter("is_dml")
def has_schema_attr(t: FromClauseRole) -> TypeGuard[TableClause]:
return hasattr(t, "schema")
def is_quoted_name(s: str) -> TypeGuard[quoted_name]:
return hasattr(s, "quote")
def is_has_clause_element(s: object) -> TypeGuard[_HasClauseElement[Any]]:
return hasattr(s, "__clause_element__")
def is_insert_update(c: ClauseElement) -> TypeGuard[ValuesBase]:
return c.is_dml and (c.is_insert or c.is_update) # type: ignore[attr-defined] # noqa: E501
def _no_kw() -> exc.ArgumentError:
return exc.ArgumentError(
"Additional keyword arguments are not accepted by this "
"function/method. The presence of **kw is for pep-484 typing purposes"
)
def _unexpected_kw(methname: str, kw: Dict[str, Any]) -> NoReturn:
k = list(kw)[0]
raise TypeError(f"{methname} got an unexpected keyword argument '{k}'")
@overload
def Nullable(
val: "SQLCoreOperations[_T]",
) -> "SQLCoreOperations[Optional[_T]]": ...
@overload
def Nullable(
val: roles.ExpressionElementRole[_T],
) -> roles.ExpressionElementRole[Optional[_T]]: ...
@overload
def Nullable(val: Type[_T]) -> Type[Optional[_T]]: ...
def Nullable(
val: _TypedColumnClauseArgument[_T],
) -> _TypedColumnClauseArgument[Optional[_T]]:
"""Types a column or ORM class as nullable.
This can be used in select and other contexts to express that the value of
a column can be null, for example due to an outer join::
stmt1 = select(A, Nullable(B)).outerjoin(A.bs)
stmt2 = select(A.data, Nullable(B.data)).outerjoin(A.bs)
At runtime this method returns the input unchanged.
.. versionadded:: 2.0.20
"""
return val
@overload
def NotNullable(
val: "SQLCoreOperations[Optional[_T]]",
) -> "SQLCoreOperations[_T]": ...
@overload
def NotNullable(
val: roles.ExpressionElementRole[Optional[_T]],
) -> roles.ExpressionElementRole[_T]: ...
@overload
def NotNullable(val: Type[Optional[_T]]) -> Type[_T]: ...
@overload
def NotNullable(val: Optional[Type[_T]]) -> Type[_T]: ...
def NotNullable(
val: Union[_TypedColumnClauseArgument[Optional[_T]], Optional[Type[_T]]],
) -> _TypedColumnClauseArgument[_T]:
"""Types a column or ORM class as not nullable.
This can be used in select and other contexts to express that the value of
a column cannot be null, for example due to a where condition on a
nullable column::
stmt = select(NotNullable(A.value)).where(A.value.is_not(None))
At runtime this method returns the input unchanged.
.. versionadded:: 2.0.20
"""
return val # type: ignore[return-value]
@@ -0,0 +1,11 @@
# sql/_util_cy.pxd
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
cdef class anon_map(dict):
cdef unsigned int _index
cdef inline object _add_missing(self, object key)
@@ -0,0 +1,127 @@
# sql/_util_cy.py
# Copyright (C) 2010-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
# mypy: disable-error-code="untyped-decorator"
from __future__ import annotations
from typing import Dict
from typing import Literal
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union
if TYPE_CHECKING:
from .cache_key import CacheConst
from ..engine.interfaces import _CoreSingleExecuteParams
# START GENERATED CYTHON IMPORT
# This section is automatically generated by the script tools/cython_imports.py
try:
# NOTE: the cython compiler needs this "import cython" in the file, it
# can't be only "from sqlalchemy.util import cython" with the fallback
# in that module
import cython
except ModuleNotFoundError:
from sqlalchemy.util import cython
def _is_compiled() -> bool:
"""Utility function to indicate if this module is compiled or not."""
return cython.compiled # type: ignore[no-any-return,unused-ignore]
# END GENERATED CYTHON IMPORT
if cython.compiled:
from cython.cimports.sqlalchemy.util._collections_cy import _get_id
else:
_get_id = id
@cython.cclass
class prefix_anon_map(Dict[str, str]):
"""A map that creates new keys for missing key access.
Considers keys of the form "<ident> <name>" to produce
new symbols "<name>_<index>", where "index" is an incrementing integer
corresponding to <name>.
Inlines the approach taken by :class:`sqlalchemy.util.PopulateDict` which
is otherwise usually used for this type of operation.
"""
def __missing__(self, key: str, /) -> str:
derived: str
value: str
self_dict: dict = self # type: ignore[type-arg]
derived = key.split(" ", 1)[1]
anonymous_counter: int = self_dict.get(derived, 1)
self_dict[derived] = anonymous_counter + 1
value = f"{derived}_{anonymous_counter}"
self_dict[key] = value
return value
_AM_KEY = Union[int, str, "CacheConst"]
_AM_VALUE = Union[int, Literal[True], "_CoreSingleExecuteParams"]
@cython.cclass
class anon_map(Dict[_AM_KEY, _AM_VALUE]):
"""A map that creates new keys for missing key access.
Produces an incrementing sequence given a series of unique keys.
This is similar to the compiler prefix_anon_map class although simpler.
Inlines the approach taken by :class:`sqlalchemy.util.PopulateDict` which
is otherwise usually used for this type of operation.
"""
if cython.compiled:
_index: cython.uint
def __cinit__(self): # type: ignore[no-untyped-def]
self._index = 0
else:
_index: int = 0 # type: ignore[no-redef]
@cython.cfunc
@cython.inline
def _add_missing(self: anon_map, key: _AM_KEY, /) -> object:
val: int = self._index
self._index += 1
self_dict: dict = self # type: ignore[type-arg]
self_dict[key] = val
return val
def get_anon(self: anon_map, obj: object, /) -> Tuple[int, bool]:
self_dict: dict = self # type: ignore[type-arg]
idself: int = _get_id(obj)
if idself in self_dict:
return self_dict[idself], True
else:
return self._add_missing(idself), False
if cython.compiled:
def __getitem__(self: anon_map, key: _AM_KEY, /) -> _AM_VALUE:
self_dict: dict = self # type: ignore[type-arg]
if key in self_dict:
return self_dict[key] # type: ignore[no-any-return]
else:
return self._add_missing(key) # type: ignore[no-any-return]
def __missing__(self: anon_map, key: _AM_KEY, /) -> int:
return self._add_missing(key) # type: ignore[no-any-return]
@@ -0,0 +1,590 @@
# sql/annotation.py
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
"""The :class:`.Annotated` class and related routines; creates hash-equivalent
copies of SQL constructs which contain context-specific markers and
associations.
Note that the :class:`.Annotated` concept as implemented in this module is not
related in any way to the pep-593 concept of "Annotated".
"""
from __future__ import annotations
from operator import itemgetter
import typing
from typing import Any
from typing import Callable
from typing import cast
from typing import Dict
from typing import FrozenSet
from typing import Literal
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 . import operators
from .cache_key import HasCacheKey
from .visitors import anon_map
from .visitors import ExternallyTraversible
from .visitors import InternalTraversal
from .. import util
from ..util.typing import Self
if TYPE_CHECKING:
from .base import _EntityNamespace
from .visitors import _TraverseInternalsType
_AnnotationDict = Mapping[str, Any]
EMPTY_ANNOTATIONS: util.immutabledict[str, Any] = util.EMPTY_DICT
class SupportsAnnotations(ExternallyTraversible):
__slots__ = ()
_annotations: util.immutabledict[str, Any] = EMPTY_ANNOTATIONS
proxy_set: util.generic_fn_descriptor[FrozenSet[Any]]
_is_immutable: bool
def _annotate(self, values: _AnnotationDict) -> Self:
raise NotImplementedError()
@overload
def _deannotate(
self,
values: Literal[None] = ...,
clone: bool = ...,
) -> Self: ...
@overload
def _deannotate(
self,
values: Sequence[str] = ...,
clone: bool = ...,
) -> SupportsAnnotations: ...
def _deannotate(
self,
values: Optional[Sequence[str]] = None,
clone: bool = False,
) -> SupportsAnnotations:
raise NotImplementedError()
@util.memoized_property
def _annotations_cache_key(self) -> Tuple[Any, ...]:
anon_map_ = anon_map()
return self._gen_annotations_cache_key(anon_map_)
def _gen_annotations_cache_key(
self, anon_map: anon_map
) -> Tuple[Any, ...]:
return (
"_annotations",
tuple(
(
key,
(
value._gen_cache_key(anon_map, [])
if isinstance(value, HasCacheKey)
else value
),
)
for key, value in sorted(
self._annotations.items(), key=_get_item0
)
),
)
_get_item0 = itemgetter(0)
class SupportsWrappingAnnotations(SupportsAnnotations):
__slots__ = ()
_constructor: Callable[..., SupportsWrappingAnnotations]
if TYPE_CHECKING:
@util.ro_non_memoized_property
def entity_namespace(self) -> _EntityNamespace: ...
def _annotate(self, values: _AnnotationDict) -> Self:
"""return a copy of this ClauseElement with annotations
updated by the given dictionary.
"""
return Annotated._as_annotated_instance(self, values) # type: ignore[return-value] # noqa: E501
def _with_annotations(self, values: _AnnotationDict) -> Self:
"""return a copy of this ClauseElement with annotations
replaced by the given dictionary.
"""
return Annotated._as_annotated_instance(self, values) # type: ignore[return-value] # noqa: E501
@overload
def _deannotate(
self,
values: Literal[None] = ...,
clone: bool = ...,
) -> Self: ...
@overload
def _deannotate(
self,
values: Sequence[str] = ...,
clone: bool = ...,
) -> SupportsAnnotations: ...
def _deannotate(
self,
values: Optional[Sequence[str]] = None,
clone: bool = False,
) -> SupportsAnnotations:
"""return a copy of this :class:`_expression.ClauseElement`
with annotations
removed.
:param values: optional tuple of individual values
to remove.
"""
if clone:
s = self._clone()
return s
else:
return self
class SupportsCloneAnnotations(SupportsWrappingAnnotations):
# SupportsCloneAnnotations extends from SupportsWrappingAnnotations
# to support the structure of having the base ClauseElement
# be a subclass of SupportsWrappingAnnotations. Any ClauseElement
# subclass that wants to extend from SupportsCloneAnnotations
# will inherently also be subclassing SupportsWrappingAnnotations, so
# make that specific here.
if not typing.TYPE_CHECKING:
__slots__ = ()
_clone_annotations_traverse_internals: _TraverseInternalsType = [
("_annotations", InternalTraversal.dp_annotations_key)
]
def _annotate(self, values: _AnnotationDict) -> Self:
"""return a copy of this ClauseElement with annotations
updated by the given dictionary.
"""
new = self._clone()
new._annotations = new._annotations.union(values)
new.__dict__.pop("_annotations_cache_key", None)
new.__dict__.pop("_generate_cache_key", None)
return new
def _with_annotations(self, values: _AnnotationDict) -> Self:
"""return a copy of this ClauseElement with annotations
replaced by the given dictionary.
"""
new = self._clone()
new._annotations = util.immutabledict(values)
new.__dict__.pop("_annotations_cache_key", None)
new.__dict__.pop("_generate_cache_key", None)
return new
@overload
def _deannotate(
self,
values: Literal[None] = ...,
clone: bool = ...,
) -> Self: ...
@overload
def _deannotate(
self,
values: Sequence[str] = ...,
clone: bool = ...,
) -> SupportsAnnotations: ...
def _deannotate(
self,
values: Optional[Sequence[str]] = None,
clone: bool = False,
) -> SupportsAnnotations:
"""return a copy of this :class:`_expression.ClauseElement`
with annotations
removed.
:param values: optional tuple of individual values
to remove.
"""
if clone or self._annotations:
# clone is used when we are also copying
# the expression for a deep deannotation
new = self._clone()
new._annotations = util.EMPTY_DICT
new.__dict__.pop("_annotations_cache_key", None)
return new
else:
return self
class Annotated(SupportsAnnotations):
"""clones a SupportsAnnotations and applies an 'annotations' dictionary.
Unlike regular clones, this clone also mimics __hash__() and
__eq__() of the original element so that it takes its place
in hashed collections.
A reference to the original element is maintained, for the important
reason of keeping its hash value current. When GC'ed, the
hash value may be reused, causing conflicts.
.. note:: The rationale for Annotated producing a brand new class,
rather than placing the functionality directly within ClauseElement,
is **performance**. The __hash__() method is absent on plain
ClauseElement which leads to significantly reduced function call
overhead, as the use of sets and dictionaries against ClauseElement
objects is prevalent, but most are not "annotated".
"""
_is_column_operators = False
@classmethod
def _as_annotated_instance(
cls, element: SupportsWrappingAnnotations, values: _AnnotationDict
) -> Annotated:
try:
cls = annotated_classes[element.__class__]
except KeyError:
cls = _new_annotation_type(element.__class__, cls)
return cls(element, values)
_annotations: util.immutabledict[str, Any]
__element: SupportsWrappingAnnotations
_hash: int
def __new__(cls: Type[Self], *args: Any) -> Self:
return object.__new__(cls)
def __init__(
self, element: SupportsWrappingAnnotations, values: _AnnotationDict
):
self.__dict__ = element.__dict__.copy()
self.__dict__.pop("_annotations_cache_key", None)
self.__dict__.pop("_generate_cache_key", None)
self.__element = element
self._annotations = util.immutabledict(values)
self._hash = hash(element)
def _annotate(self, values: _AnnotationDict) -> Self:
_values = self._annotations.union(values)
new = self._with_annotations(_values)
return new
def _with_annotations(self, values: _AnnotationDict) -> Self:
clone = self.__class__.__new__(self.__class__)
clone.__dict__ = self.__dict__.copy()
clone.__dict__.pop("_annotations_cache_key", None)
clone.__dict__.pop("_generate_cache_key", None)
clone._annotations = util.immutabledict(values)
return clone
@overload
def _deannotate(
self,
values: Literal[None] = ...,
clone: bool = ...,
) -> Self: ...
@overload
def _deannotate(
self,
values: Sequence[str] = ...,
clone: bool = ...,
) -> Annotated: ...
def _deannotate(
self,
values: Optional[Sequence[str]] = None,
clone: bool = True,
) -> SupportsAnnotations:
if values is None:
return self.__element
else:
return self._with_annotations(
util.immutabledict(
{
key: value
for key, value in self._annotations.items()
if key not in values
}
)
)
if not typing.TYPE_CHECKING:
# manually proxy some methods that need extra attention
def _compiler_dispatch(self, visitor: Any, **kw: Any) -> Any:
return self.__element.__class__._compiler_dispatch(
self, visitor, **kw
)
@property
def _constructor(self):
return self.__element._constructor
def _clone(self, **kw: Any) -> Self:
clone = self.__element._clone(**kw)
if clone is self.__element:
# detect immutable, don't change anything
return self
else:
# update the clone with any changes that have occurred
# to this object's __dict__.
clone.__dict__.update(self.__dict__)
return self.__class__(clone, self._annotations)
def __reduce__(self) -> Tuple[Type[Annotated], Tuple[Any, ...]]:
return self.__class__, (self.__element, self._annotations)
def __hash__(self) -> int:
return self._hash
def __eq__(self, other: Any) -> bool:
if self._is_column_operators:
return self.__element.__class__.__eq__(self, other)
else:
return hash(other) == hash(self)
@util.ro_non_memoized_property
def entity_namespace(self) -> _EntityNamespace:
if "entity_namespace" in self._annotations:
return cast(
SupportsWrappingAnnotations,
self._annotations["entity_namespace"],
).entity_namespace
else:
return self.__element.entity_namespace
# hard-generate Annotated subclasses. this technique
# is used instead of on-the-fly types (i.e. type.__new__())
# so that the resulting objects are pickleable; additionally, other
# decisions can be made up front about the type of object being annotated
# just once per class rather than per-instance.
annotated_classes: Dict[Type[SupportsWrappingAnnotations], Type[Annotated]] = (
{}
)
_SA = TypeVar("_SA", bound="SupportsAnnotations")
def _safe_annotate(to_annotate: _SA, annotations: _AnnotationDict) -> _SA:
try:
_annotate = to_annotate._annotate
except AttributeError:
# skip objects that don't actually have an `_annotate`
# attribute, namely QueryableAttribute inside of a join
# condition
return to_annotate
else:
return _annotate(annotations)
def _deep_annotate(
element: _SA,
annotations: _AnnotationDict,
exclude: Optional[Sequence[SupportsAnnotations]] = None,
*,
detect_subquery_cols: bool = False,
ind_cols_on_fromclause: bool = False,
annotate_callable: Optional[
Callable[[SupportsAnnotations, _AnnotationDict], SupportsAnnotations]
] = None,
) -> _SA:
"""Deep copy the given ClauseElement, annotating each element
with the given annotations dictionary.
Elements within the exclude collection will be cloned but not annotated.
"""
# annotated objects hack the __hash__() method so if we want to
# uniquely process them we have to use id()
cloned_ids: Dict[int, SupportsAnnotations] = {}
def clone(elem: SupportsAnnotations, **kw: Any) -> SupportsAnnotations:
# ind_cols_on_fromclause means make sure an AnnotatedFromClause
# has its own .c collection independent of that which its proxying.
# this is used specifically by orm.LoaderCriteriaOption to break
# a reference cycle that it's otherwise prone to building,
# see test_relationship_criteria->
# test_loader_criteria_subquery_w_same_entity. logic here was
# changed for #8796 and made explicit; previously it occurred
# by accident
kw["detect_subquery_cols"] = detect_subquery_cols
id_ = id(elem)
if id_ in cloned_ids:
return cloned_ids[id_]
if (
exclude
and hasattr(elem, "proxy_set")
and elem.proxy_set.intersection(exclude)
):
newelem = elem._clone(clone=clone, **kw)
elif annotations != elem._annotations:
if detect_subquery_cols and elem._is_immutable:
to_annotate = elem._clone(clone=clone, **kw)
else:
to_annotate = elem
if annotate_callable:
newelem = annotate_callable(to_annotate, annotations)
else:
newelem = _safe_annotate(to_annotate, annotations)
else:
newelem = elem
newelem._copy_internals(
clone=clone,
ind_cols_on_fromclause=ind_cols_on_fromclause,
_annotations_traversal=True,
)
cloned_ids[id_] = newelem
return newelem
if element is not None:
element = cast(_SA, clone(element))
clone = None # type: ignore[assignment] # remove gc cycles
return element
@overload
def _deep_deannotate(
element: Literal[None], values: Optional[Sequence[str]] = None
) -> Literal[None]: ...
@overload
def _deep_deannotate(
element: _SA, values: Optional[Sequence[str]] = None
) -> _SA: ...
def _deep_deannotate(
element: Optional[_SA], values: Optional[Sequence[str]] = None
) -> Optional[_SA]:
"""Deep copy the given element, removing annotations."""
cloned: Dict[Any, SupportsAnnotations] = {}
def clone(elem: SupportsAnnotations, **kw: Any) -> SupportsAnnotations:
key: Any
if values:
key = id(elem)
else:
key = elem
if key not in cloned:
newelem = elem._deannotate(values=values, clone=True)
newelem._copy_internals(clone=clone, _annotations_traversal=True)
cloned[key] = newelem
return newelem
else:
return cloned[key]
if element is not None:
element = cast(_SA, clone(element))
clone = None # type: ignore[assignment] # remove gc cycles
return element
def _shallow_annotate(element: _SA, annotations: _AnnotationDict) -> _SA:
"""Annotate the given ClauseElement and copy its internals so that
internal objects refer to the new annotated object.
Basically used to apply a "don't traverse" annotation to a
selectable, without digging throughout the whole
structure wasting time.
"""
element = element._annotate(annotations)
element._copy_internals(_annotations_traversal=True)
return element
def _new_annotation_type(
cls: Type[SupportsWrappingAnnotations], base_cls: Type[Annotated]
) -> Type[Annotated]:
"""Generates a new class that subclasses Annotated and proxies a given
element type.
"""
if issubclass(cls, Annotated):
return cls
elif cls in annotated_classes:
return annotated_classes[cls]
for super_ in cls.__mro__:
# check if an Annotated subclass more specific than
# the given base_cls is already registered, such
# as AnnotatedColumnElement.
if super_ in annotated_classes:
base_cls = annotated_classes[super_]
break
annotated_classes[cls] = anno_cls = cast(
Type[Annotated],
type("Annotated%s" % cls.__name__, (base_cls, cls), {}),
)
globals()["Annotated%s" % cls.__name__] = anno_cls
if "_traverse_internals" in cls.__dict__:
anno_cls._traverse_internals = list(cls._traverse_internals) + [
("_annotations", InternalTraversal.dp_annotations_key)
]
elif cls.__dict__.get("inherit_cache", False):
anno_cls._traverse_internals = list(cls._traverse_internals) + [
("_annotations", InternalTraversal.dp_annotations_key)
]
# some classes include this even if they have traverse_internals
# e.g. BindParameter, add it if present.
if cls.__dict__.get("inherit_cache", False):
anno_cls.inherit_cache = True # type: ignore[attr-defined]
elif "inherit_cache" in cls.__dict__:
anno_cls.inherit_cache = cls.__dict__["inherit_cache"] # type: ignore[attr-defined] # noqa: E501
anno_cls._is_column_operators = issubclass(cls, operators.ColumnOperators)
return anno_cls
def _prepare_annotations(
target_hierarchy: Type[SupportsWrappingAnnotations],
base_cls: Type[Annotated],
) -> None:
for cls in util.walk_subclasses(target_hierarchy):
_new_annotation_type(cls, base_cls)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,915 @@
# sql/cache_key.py
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
from __future__ import annotations
from itertools import zip_longest
import typing
from typing import Any
from typing import cast
from typing import Dict
from typing import Final
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Literal
from typing import MutableMapping
from typing import NamedTuple
from typing import Optional
from typing import Protocol
from typing import Sequence
from typing import Tuple
from typing import Type
from typing import Union
from . import _cache_key_cy
from ._cache_key_cy import CacheConst as CacheConst
from ._cache_key_cy import CacheTraverseTarget as CacheTraverseTarget
from .visitors import anon_map
from .visitors import HasTraversalDispatch
from .visitors import HasTraverseInternals
from .visitors import InternalTraversal
from .visitors import prefix_anon_map
from .. import util
from ..inspection import inspect
from ..util import HasMemoized
if typing.TYPE_CHECKING:
from .elements import BindParameter
from .elements import ClauseElement
from .elements import ColumnElement
from .visitors import _TraverseInternalsType
from ..engine.interfaces import _CoreSingleExecuteParams
class _CacheKeyTraversalDispatchType(Protocol):
def __call__(
s,
self: HasCacheKey,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
id_: int,
cls: Type[HasCacheKey],
) -> Optional[Tuple[Any, ...]]: ...
NO_CACHE: Final = CacheConst.NO_CACHE
_CacheKeyTraversalType = Union[
"_TraverseInternalsType", Literal[CacheConst.NO_CACHE], Literal[None]
]
CACHE_IN_PLACE: Final = CacheTraverseTarget.CACHE_IN_PLACE
CALL_GEN_CACHE_KEY: Final = CacheTraverseTarget.CALL_GEN_CACHE_KEY
STATIC_CACHE_KEY: Final = CacheTraverseTarget.STATIC_CACHE_KEY
PROPAGATE_ATTRS: Final = CacheTraverseTarget.PROPAGATE_ATTRS
ANON_NAME: Final = CacheTraverseTarget.ANON_NAME
class HasCacheKey(_cache_key_cy.BaseHasCacheKey):
"""Mixin for objects which can produce a cache key.
This class is usually in a hierarchy that starts with the
:class:`.HasTraverseInternals` base, but this is optional. Currently,
the class should be able to work on its own without including
:class:`.HasTraverseInternals`.
.. seealso::
:class:`.CacheKey`
:ref:`sql_caching`
"""
__slots__ = ()
_cache_key_traversal: _CacheKeyTraversalType = NO_CACHE
_is_has_cache_key = True
_hierarchy_supports_caching = True
"""private attribute which may be set to False to prevent the
inherit_cache warning from being emitted for a hierarchy of subclasses.
Currently applies to the :class:`.ExecutableDDLElement` hierarchy which
does not implement caching.
"""
inherit_cache: Optional[bool] = None
"""Indicate if this :class:`.HasCacheKey` instance should make use of the
cache key generation scheme used by its immediate superclass.
The attribute defaults to ``None``, which indicates that a construct has
not yet taken into account whether or not its appropriate for it to
participate in caching; this is functionally equivalent to setting the
value to ``False``, except that a warning is also emitted.
This flag can be set to ``True`` on a particular class, if the SQL that
corresponds to the object does not change based on attributes which
are local to this class, and not its superclass.
.. seealso::
:ref:`compilerext_caching` - General guideslines for setting the
:attr:`.HasCacheKey.inherit_cache` attribute for third-party or user
defined SQL constructs.
"""
__slots__ = ()
_generated_cache_key_traversal: Any
@classmethod
def _generate_cache_attrs(
cls,
) -> Union[_CacheKeyTraversalDispatchType, Literal[CacheConst.NO_CACHE]]:
"""generate cache key dispatcher for a new class.
This sets the _generated_cache_key_traversal attribute once called
so should only be called once per class.
"""
inherit_cache = cls.__dict__.get("inherit_cache", None)
inherit = bool(inherit_cache)
if inherit:
_cache_key_traversal = getattr(cls, "_cache_key_traversal", None)
if _cache_key_traversal is None:
try:
assert issubclass(cls, HasTraverseInternals)
_cache_key_traversal = cls._traverse_internals
except AttributeError:
cls._generated_cache_key_traversal = NO_CACHE
return NO_CACHE
assert _cache_key_traversal is not NO_CACHE, (
f"class {cls} has _cache_key_traversal=NO_CACHE, "
"which conflicts with inherit_cache=True"
)
# TODO: wouldn't we instead get this from our superclass?
# also, our superclass may not have this yet, but in any case,
# we'd generate for the superclass that has it. this is a little
# more complicated, so for the moment this is a little less
# efficient on startup but simpler.
return cast(
_CacheKeyTraversalDispatchType,
_cache_key_traversal_visitor.generate_class_attrs(
cls,
_cache_key_traversal,
"_generated_cache_key_traversal",
),
)
else:
_cache_key_traversal = cls.__dict__.get(
"_cache_key_traversal", None
)
if _cache_key_traversal is None:
_cache_key_traversal = cls.__dict__.get(
"_traverse_internals", None
)
if _cache_key_traversal is None:
cls._generated_cache_key_traversal = NO_CACHE
if (
inherit_cache is None
and cls._hierarchy_supports_caching
):
util.warn(
"Class %s will not make use of SQL compilation "
"caching as it does not set the 'inherit_cache' "
"attribute 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 object can make use of the cache "
"key generated by the superclass. Alternatively, "
"this attribute may be set to False which will "
"disable this warning." % (cls.__name__),
code="cprf",
)
return NO_CACHE
return cast(
_CacheKeyTraversalDispatchType,
_cache_key_traversal_visitor.generate_class_attrs(
cls,
_cache_key_traversal,
"_generated_cache_key_traversal",
),
)
def _generate_cache_key(self) -> Optional[CacheKey]:
"""return a cache key.
The cache key is a tuple which can contain any series of
objects that are hashable and also identifies
this object uniquely within the presence of a larger SQL expression
or statement, for the purposes of caching the resulting query.
The cache key should be based on the SQL compiled structure that would
ultimately be produced. That is, two structures that are composed in
exactly the same way should produce the same cache key; any difference
in the structures that would affect the SQL string or the type handlers
should result in a different cache key.
The cache key returned by this method is an instance of
:class:`.CacheKey`, which consists of a tuple representing the
cache key, as well as a list of :class:`.BindParameter` objects
which are extracted from the expression. While two expressions
that produce identical cache key tuples will themselves generate
identical SQL strings, the list of :class:`.BindParameter` objects
indicates the bound values which may have different values in
each one; these bound parameters must be consulted in order to
execute the statement with the correct parameters.
a :class:`_expression.ClauseElement` structure that does not implement
a :meth:`._gen_cache_key` method and does not implement a
:attr:`.traverse_internals` attribute will not be cacheable; when
such an element is embedded into a larger structure, this method
will return None, indicating no cache key is available.
"""
bindparams: List[BindParameter[Any]] = []
_anon_map = anon_map()
key = self._gen_cache_key(_anon_map, bindparams)
if NO_CACHE in _anon_map:
return None
else:
assert key is not None
return CacheKey(
key,
bindparams,
_anon_map.get(CacheConst.PARAMS), # type: ignore[arg-type]
)
class HasCacheKeyTraverse(HasTraverseInternals, HasCacheKey):
pass
class MemoizedHasCacheKey(HasCacheKey, HasMemoized):
__slots__ = ()
@HasMemoized.memoized_instancemethod
def _generate_cache_key(self) -> Optional[CacheKey]:
return HasCacheKey._generate_cache_key(self)
class SlotsMemoizedHasCacheKey(HasCacheKey, util.MemoizedSlots):
__slots__ = ()
def _memoized_method__generate_cache_key(self) -> Optional[CacheKey]:
return HasCacheKey._generate_cache_key(self)
class CacheKey(NamedTuple):
"""The key used to identify a SQL statement construct in the
SQL compilation cache.
.. seealso::
:ref:`sql_caching`
"""
key: Tuple[Any, ...]
bindparams: Sequence[BindParameter[Any]]
params: _CoreSingleExecuteParams | None
# can't set __hash__ attribute because it interferes
# with namedtuple
# can't use "if not TYPE_CHECKING" because mypy rejects it
# inside of a NamedTuple
def __hash__(self) -> Optional[int]: # type: ignore[override]
"""CacheKey itself is not hashable - hash the .key portion"""
return None
def to_offline_string(
self,
statement_cache: MutableMapping[Any, str],
statement: ClauseElement,
parameters: _CoreSingleExecuteParams,
) -> str:
"""Generate an "offline string" form of this :class:`.CacheKey`
The "offline string" is basically the string SQL for the
statement plus a repr of the bound parameter values in series.
Whereas the :class:`.CacheKey` object is dependent on in-memory
identities in order to work as a cache key, the "offline" version
is suitable for a cache that will work for other processes as well.
The given ``statement_cache`` is a dictionary-like object where the
string form of the statement itself will be cached. This dictionary
should be in a longer lived scope in order to reduce the time spent
stringifying statements.
"""
if self.key not in statement_cache:
statement_cache[self.key] = sql_str = str(statement)
else:
sql_str = statement_cache[self.key]
if not self.bindparams:
param_tuple = tuple(parameters[key] for key in sorted(parameters))
else:
param_tuple = tuple(
parameters.get(bindparam.key, bindparam.value)
for bindparam in self.bindparams
)
return repr((sql_str, param_tuple))
def __eq__(self, other: Any) -> bool:
return other is not None and bool(self.key == other.key)
def __ne__(self, other: Any) -> bool:
return other is None or not (self.key == other.key)
@classmethod
def _diff_tuples(cls, left: CacheKey, right: CacheKey) -> str:
ck1 = CacheKey(left, [], None)
ck2 = CacheKey(right, [], None)
return ck1._diff(ck2)
def _whats_different(self, other: CacheKey) -> Iterator[str]:
k1 = self.key
k2 = other.key
stack: List[int] = []
pickup_index = 0
while True:
s1, s2 = k1, k2
for idx in stack:
s1 = s1[idx]
s2 = s2[idx]
for idx, (e1, e2) in enumerate(zip_longest(s1, s2)):
if idx < pickup_index:
continue
if e1 != e2:
if isinstance(e1, tuple) and isinstance(e2, tuple):
stack.append(idx)
break
else:
yield "key%s[%d]: %s != %s" % (
"".join("[%d]" % id_ for id_ in stack),
idx,
e1,
e2,
)
else:
stack.pop(-1)
break
def _diff(self, other: CacheKey) -> str:
return ", ".join(self._whats_different(other))
def __str__(self) -> str:
stack: List[Union[Tuple[Any, ...], HasCacheKey]] = [self.key]
output = []
sentinel = object()
indent = -1
while stack:
elem = stack.pop(0)
if elem is sentinel:
output.append((" " * (indent * 2)) + "),")
indent -= 1
elif isinstance(elem, tuple):
if not elem:
output.append((" " * ((indent + 1) * 2)) + "()")
else:
indent += 1
stack = list(elem) + [sentinel] + stack
output.append((" " * (indent * 2)) + "(")
else:
if isinstance(elem, HasCacheKey):
repr_ = "<%s object at %s>" % (
type(elem).__name__,
hex(id(elem)),
)
else:
repr_ = repr(elem)
output.append((" " * (indent * 2)) + " " + repr_ + ", ")
return "CacheKey(key=%s)" % ("\n".join(output),)
def _generate_param_dict(self) -> Dict[str, Any]:
"""used for testing"""
_anon_map = prefix_anon_map()
return {b.key % _anon_map: b.effective_value for b in self.bindparams}
@util.preload_module("sqlalchemy.sql.elements")
def _apply_params_to_element(
self, original_cache_key: CacheKey, target_element: ColumnElement[Any]
) -> ColumnElement[Any]:
if target_element._is_immutable or original_cache_key is self:
return target_element
elements = util.preloaded.sql_elements
return elements._OverrideBinds(
target_element, self.bindparams, original_cache_key.bindparams
)
def _ad_hoc_cache_key_from_args(
tokens: Tuple[Any, ...],
traverse_args: Iterable[Tuple[str, InternalTraversal]],
args: Iterable[Any],
) -> Tuple[Any, ...]:
"""a quick cache key generator used by reflection.flexi_cache."""
bindparams: List[BindParameter[Any]] = []
_anon_map = anon_map()
tup = tokens
for (attrname, sym), arg in zip(traverse_args, args):
key = sym.name
visit_key = key.replace("dp_", "visit_")
if arg is None:
tup += (attrname, None)
continue
meth = getattr(_cache_key_traversal_visitor, visit_key)
if meth is CACHE_IN_PLACE:
tup += (attrname, arg)
elif meth in (
CALL_GEN_CACHE_KEY,
STATIC_CACHE_KEY,
ANON_NAME,
PROPAGATE_ATTRS,
):
raise NotImplementedError(
f"Haven't implemented symbol {meth} for ad-hoc key from args"
)
else:
tup += meth(attrname, arg, None, _anon_map, bindparams)
return tup
class _CacheKeyTraversal(
HasTraversalDispatch, _cache_key_cy._BaseCacheKeyTraversal
):
# the dispatch symbols below are resolved at class setup time by
# _generate_class_attrs(), which records the handler for each attribute
# as an integer "kind" that the compiled traversal switches on directly;
# only the symbols that have no such inline handler are left as actual
# methods on this class
visit_has_cache_key = visit_clauseelement = CALL_GEN_CACHE_KEY
visit_clauseelement_list = InternalTraversal.dp_clauseelement_list
visit_annotations_key = InternalTraversal.dp_annotations_key
visit_clauseelement_tuple = InternalTraversal.dp_clauseelement_tuple
visit_memoized_select_entities = (
InternalTraversal.dp_memoized_select_entities
)
visit_string = visit_boolean = visit_operator = visit_plain_obj = (
CACHE_IN_PLACE
)
visit_statement_hint_list = CACHE_IN_PLACE
visit_type = STATIC_CACHE_KEY
visit_anon_name = ANON_NAME
visit_propagate_attrs = PROPAGATE_ATTRS
def visit_compile_state_funcs(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return tuple([(fn.__code__, c_key) for fn, c_key in obj])
def visit_inspectable(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return (attrname, inspect(obj)._gen_cache_key(anon_map, bindparams))
def visit_string_list(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return tuple(obj)
def visit_multi(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return (
attrname,
(
obj._gen_cache_key(anon_map, bindparams)
if isinstance(obj, HasCacheKey)
else obj
),
)
def visit_multi_list(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return (
attrname,
tuple(
[
(
elem._gen_cache_key(anon_map, bindparams)
if isinstance(elem, HasCacheKey)
else elem
)
for elem in obj
]
),
)
def visit_has_cache_key_tuples(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return (
attrname,
tuple(
[
tuple(
[
elem._gen_cache_key(anon_map, bindparams)
for elem in tup_elem
]
)
for tup_elem in obj
]
),
)
def visit_has_cache_key_list(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return (
attrname,
tuple([elem._gen_cache_key(anon_map, bindparams) for elem in obj]),
)
def visit_executable_options(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return (
attrname,
tuple(
[
elem._gen_cache_key(anon_map, bindparams)
for elem in obj
if elem._is_has_cache_key
]
),
)
visit_clauseelement_tuples = visit_has_cache_key_tuples
def visit_fromclause_ordered_set(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return (
attrname,
tuple([elem._gen_cache_key(anon_map, bindparams) for elem in obj]),
)
def visit_clauseelement_unordered_set(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
cache_keys = [
elem._gen_cache_key(anon_map, bindparams) for elem in obj
]
return (
attrname,
tuple(
sorted(cache_keys)
), # cache keys all start with (id_, class)
)
def visit_named_ddl_element(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return (attrname, obj.name)
def visit_prefix_sequence(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return (
attrname,
tuple(
[
(clause._gen_cache_key(anon_map, bindparams), strval)
for clause, strval in obj
]
),
)
def visit_setup_join_tuple(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return tuple(
[
(
target._gen_cache_key(anon_map, bindparams),
(
onclause._gen_cache_key(anon_map, bindparams)
if onclause is not None
else None
),
(
from_._gen_cache_key(anon_map, bindparams)
if from_ is not None
else None
),
tuple([(key, flags[key]) for key in sorted(flags)]),
)
for (target, onclause, from_, flags) in obj
]
)
def visit_table_hint_list(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return (
attrname,
tuple(
[
(
clause._gen_cache_key(anon_map, bindparams),
dialect_name,
text,
)
for (clause, dialect_name), text in obj.items()
]
),
)
def visit_plain_dict(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return (attrname, tuple([(key, obj[key]) for key in sorted(obj)]))
def visit_dialect_options(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return (
attrname,
tuple(
[
(
dialect_name,
tuple(
[
(key, obj[dialect_name][key])
for key in sorted(obj[dialect_name])
]
),
)
for dialect_name in sorted(obj)
]
),
)
def visit_string_clauseelement_dict(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return (
attrname,
tuple(
[
(key, obj[key]._gen_cache_key(anon_map, bindparams))
for key in sorted(obj)
]
),
)
def visit_string_multi_dict(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return (
attrname,
tuple(
[
(
key,
(
value._gen_cache_key(anon_map, bindparams)
if isinstance(value, HasCacheKey)
else value
),
)
for key, value in [(key, obj[key]) for key in sorted(obj)]
]
),
)
def visit_fromclause_canonical_column_collection(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
# inlining into the internals of ColumnCollection
return (
attrname,
tuple(
[
col._gen_cache_key(anon_map, bindparams)
for k, col, _ in obj._collection
]
),
)
def visit_unknown_structure(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
anon_map[NO_CACHE] = True
return ()
def visit_dml_ordered_values(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
return (
attrname,
tuple(
[
(
(
key._gen_cache_key(anon_map, bindparams)
if hasattr(key, "__clause_element__")
else key
),
value._gen_cache_key(anon_map, bindparams),
)
for key, value in obj
]
),
)
def visit_dml_values(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
# in py37 we can assume two dictionaries created in the same
# insert ordering will retain that sorting
return (
attrname,
tuple(
[
(
(
k._gen_cache_key(anon_map, bindparams)
if hasattr(k, "__clause_element__")
else k
),
obj[k]._gen_cache_key(anon_map, bindparams),
)
for k in obj
]
),
)
def visit_dml_multi_values(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
# multivalues are simply not cacheable right now
anon_map[NO_CACHE] = True
return ()
def visit_params(
self,
attrname: str,
obj: Any,
parent: Any,
anon_map: anon_map,
bindparams: List[BindParameter[Any]],
) -> Tuple[Any, ...]:
if obj:
if CacheConst.PARAMS in anon_map:
to_set = anon_map[CacheConst.PARAMS] | obj
else:
to_set = obj
anon_map[CacheConst.PARAMS] = to_set
return ()
_cache_key_traversal_visitor = _CacheKeyTraversal()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,660 @@
# sql/default_comparator.py
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
"""Default implementation of SQL comparison operations."""
from __future__ import annotations
import typing
from typing import Any
from typing import Callable
from typing import NoReturn
from typing import Optional
from typing import Tuple
from typing import Type
from typing import Union
from . import coercions
from . import functions
from . import operators
from . import roles
from . import type_api
from .elements import and_
from .elements import BinaryExpression
from .elements import ClauseElement
from .elements import CollationClause
from .elements import CollectionAggregate
from .elements import ExpressionClauseList
from .elements import False_
from .elements import Null
from .elements import OperatorExpression
from .elements import or_
from .elements import True_
from .elements import UnaryExpression
from .operators import OperatorType
from .. import exc
from .. import util
_T = typing.TypeVar("_T", bound=Any)
if typing.TYPE_CHECKING:
from .elements import ColumnElement
from .operators import custom_op
from .type_api import TypeEngine
def _boolean_compare(
expr: ColumnElement[Any],
op: OperatorType,
obj: Any,
*,
negate_op: Optional[OperatorType] = None,
reverse: bool = False,
_python_is_types: Tuple[Type[Any], ...] = (type(None), bool),
result_type: Optional[TypeEngine[bool]] = None,
**kwargs: Any,
) -> OperatorExpression[bool]:
if result_type is None:
result_type = type_api.BOOLEANTYPE
if isinstance(obj, _python_is_types + (Null, True_, False_)):
# allow x ==/!= True/False to be treated as a literal.
# this comes out to "== / != true/false" or "1/0" if those
# constants aren't supported and works on all platforms
if op in (operators.eq, operators.ne) and isinstance(
obj, (bool, True_, False_)
):
return OperatorExpression._construct_for_op(
expr,
coercions.expect(roles.ConstExprRole, obj),
op,
type_=result_type,
negate=negate_op,
modifiers=kwargs,
)
elif op in (
operators.is_distinct_from,
operators.is_not_distinct_from,
):
return OperatorExpression._construct_for_op(
expr,
coercions.expect(roles.ConstExprRole, obj),
op,
type_=result_type,
negate=negate_op,
modifiers=kwargs,
)
elif expr._is_collection_aggregate:
obj = coercions.expect(
roles.ConstExprRole, element=obj, operator=op, expr=expr
)
else:
# all other None uses IS, IS NOT
if op in (operators.eq, operators.is_):
return OperatorExpression._construct_for_op(
expr,
coercions.expect(roles.ConstExprRole, obj),
operators.is_,
negate=operators.is_not,
type_=result_type,
)
elif op in (operators.ne, operators.is_not):
return OperatorExpression._construct_for_op(
expr,
coercions.expect(roles.ConstExprRole, obj),
operators.is_not,
negate=operators.is_,
type_=result_type,
)
else:
raise exc.ArgumentError(
"Only '=', '!=', 'is_()', 'is_not()', "
"'is_distinct_from()', 'is_not_distinct_from()' "
"operators can be used with None/True/False"
)
else:
obj = coercions.expect(
roles.BinaryElementRole, element=obj, operator=op, expr=expr
)
if reverse:
return OperatorExpression._construct_for_op(
obj,
expr,
op,
type_=result_type,
negate=negate_op,
modifiers=kwargs,
)
else:
return OperatorExpression._construct_for_op(
expr,
obj,
op,
type_=result_type,
negate=negate_op,
modifiers=kwargs,
)
def _custom_op_operate(
expr: ColumnElement[Any],
op: custom_op[Any],
obj: Any,
reverse: bool = False,
result_type: Optional[TypeEngine[Any]] = None,
**kw: Any,
) -> ColumnElement[Any]:
if result_type is None:
if op.return_type:
result_type = op.return_type
elif op.is_comparison:
result_type = type_api.BOOLEANTYPE
return _binary_operate(
expr, op, obj, reverse=reverse, result_type=result_type, **kw
)
def _binary_operate(
expr: ColumnElement[Any],
op: OperatorType,
obj: roles.BinaryElementRole[Any],
*,
reverse: bool = False,
result_type: Optional[TypeEngine[_T]] = None,
**kw: Any,
) -> OperatorExpression[_T]:
coerced_obj = coercions.expect(
roles.BinaryElementRole, obj, expr=expr, operator=op
)
if reverse:
left, right = coerced_obj, expr
else:
left, right = expr, coerced_obj
if result_type is None:
op, result_type = left.comparator._adapt_expression(
op, right.comparator
)
return OperatorExpression._construct_for_op(
left, right, op, type_=result_type, modifiers=kw
)
def _conjunction_operate(
expr: ColumnElement[Any], op: OperatorType, other: Any, **kw: Any
) -> ColumnElement[Any]:
if op is operators.and_:
return and_(expr, other)
elif op is operators.or_:
return or_(expr, other)
else:
raise NotImplementedError()
def _scalar(
expr: ColumnElement[Any],
op: OperatorType,
fn: Callable[[ColumnElement[Any]], ColumnElement[Any]],
**kw: Any,
) -> ColumnElement[Any]:
return fn(expr)
def _in_impl(
expr: ColumnElement[Any],
op: OperatorType,
seq_or_selectable: ClauseElement,
negate_op: OperatorType,
**kw: Any,
) -> ColumnElement[Any]:
seq_or_selectable = coercions.expect(
roles.InElementRole, seq_or_selectable, expr=expr, operator=op
)
if "in_ops" in seq_or_selectable._annotations:
op, negate_op = seq_or_selectable._annotations["in_ops"]
return _boolean_compare(
expr, op, seq_or_selectable, negate_op=negate_op, **kw
)
def _getitem_impl(
expr: ColumnElement[Any], op: OperatorType, other: Any, **kw: Any
) -> ColumnElement[Any]:
if (
isinstance(expr.type, type_api.INDEXABLE)
or isinstance(expr.type, type_api.TypeDecorator)
and isinstance(expr.type.impl_instance, type_api.INDEXABLE)
):
other = coercions.expect(
roles.BinaryElementRole, other, expr=expr, operator=op
)
return _binary_operate(expr, op, other, **kw)
else:
_unsupported_impl(expr, op, other, **kw)
def _unsupported_impl(
expr: ColumnElement[Any], op: OperatorType, *arg: Any, **kw: Any
) -> NoReturn:
raise NotImplementedError(
"Operator '%s' is not supported on this expression" % op.__name__
)
def _inv_impl(
expr: ColumnElement[Any], op: OperatorType, **kw: Any
) -> ColumnElement[Any]:
"""See :meth:`.ColumnOperators.__inv__`."""
# undocumented element currently used by the ORM for
# relationship.contains()
if hasattr(expr, "negation_clause"):
return expr.negation_clause
else:
return expr._negate()
def _neg_impl(
expr: ColumnElement[Any], op: OperatorType, **kw: Any
) -> ColumnElement[Any]:
"""See :meth:`.ColumnOperators.__neg__`."""
return UnaryExpression(expr, operator=operators.neg, type_=expr.type)
def _bitwise_not_impl(
expr: ColumnElement[Any], op: OperatorType, **kw: Any
) -> ColumnElement[Any]:
"""See :meth:`.ColumnOperators.bitwise_not`."""
return UnaryExpression(
expr, operator=operators.bitwise_not_op, type_=expr.type
)
def _match_impl(
expr: ColumnElement[Any], op: OperatorType, other: Any, **kw: Any
) -> ColumnElement[Any]:
"""See :meth:`.ColumnOperators.match`."""
return _boolean_compare(
expr,
operators.match_op,
coercions.expect(
roles.BinaryElementRole,
other,
expr=expr,
operator=operators.match_op,
),
result_type=type_api.MATCHTYPE,
negate_op=(
operators.not_match_op
if op is operators.match_op
else operators.match_op
),
**kw,
)
def _distinct_impl(
expr: ColumnElement[Any], op: OperatorType, **kw: Any
) -> ColumnElement[Any]:
"""See :meth:`.ColumnOperators.distinct`."""
return UnaryExpression(
expr, operator=operators.distinct_op, type_=expr.type
)
def _between_impl(
expr: ColumnElement[Any],
op: OperatorType,
cleft: Any,
cright: Any,
**kw: Any,
) -> ColumnElement[Any]:
"""See :meth:`.ColumnOperators.between`."""
return BinaryExpression(
expr,
ExpressionClauseList._construct_for_list(
operators.and_,
type_api.NULLTYPE,
coercions.expect(
roles.BinaryElementRole,
cleft,
expr=expr,
operator=operators.and_,
),
coercions.expect(
roles.BinaryElementRole,
cright,
expr=expr,
operator=operators.and_,
),
group=False,
),
op,
negate=(
operators.not_between_op
if op is operators.between_op
else operators.between_op
),
modifiers=kw,
)
def _pow_impl(
expr: ColumnElement[Any],
op: OperatorType,
other: Any,
reverse: bool = False,
**kw: Any,
) -> ColumnElement[Any]:
if reverse:
return functions.pow(other, expr)
else:
return functions.pow(expr, other)
def _collate_impl(
expr: ColumnElement[str],
op: OperatorType,
collation: str,
collation_schema: Optional[str] = None,
**kw: Any,
) -> ColumnElement[str]:
return CollationClause._create_collation_expression(
expr, collation, collation_schema
)
def _regexp_match_impl(
expr: ColumnElement[str],
op: OperatorType,
pattern: Any,
flags: Optional[str],
**kw: Any,
) -> ColumnElement[Any]:
return BinaryExpression(
expr,
coercions.expect(
roles.BinaryElementRole,
pattern,
expr=expr,
operator=operators.comma_op,
),
op,
negate=operators.not_regexp_match_op,
modifiers={"flags": flags},
)
def _regexp_replace_impl(
expr: ColumnElement[Any],
op: OperatorType,
pattern: Any,
replacement: Any,
flags: Optional[str],
**kw: Any,
) -> ColumnElement[Any]:
return BinaryExpression(
expr,
ExpressionClauseList._construct_for_list(
operators.comma_op,
type_api.NULLTYPE,
coercions.expect(
roles.BinaryElementRole,
pattern,
expr=expr,
operator=operators.comma_op,
),
coercions.expect(
roles.BinaryElementRole,
replacement,
expr=expr,
operator=operators.comma_op,
),
group=False,
),
op,
modifiers={"flags": flags},
)
operator_lookup: util.immutabledict[
str,
Tuple[
Callable[..., "ColumnElement[Any]"],
util.immutabledict[
str, Union["OperatorType", Callable[..., "ColumnElement[Any]"]]
],
],
] = util.immutabledict(
{
"any_op": (
_scalar,
util.immutabledict({"fn": CollectionAggregate._create_any}),
),
"all_op": (
_scalar,
util.immutabledict({"fn": CollectionAggregate._create_all}),
),
"lt": (
_boolean_compare,
util.immutabledict({"negate_op": operators.ge}),
),
"le": (
_boolean_compare,
util.immutabledict({"negate_op": operators.gt}),
),
"ne": (
_boolean_compare,
util.immutabledict({"negate_op": operators.eq}),
),
"gt": (
_boolean_compare,
util.immutabledict({"negate_op": operators.le}),
),
"ge": (
_boolean_compare,
util.immutabledict({"negate_op": operators.lt}),
),
"eq": (
_boolean_compare,
util.immutabledict({"negate_op": operators.ne}),
),
"is_distinct_from": (
_boolean_compare,
util.immutabledict({"negate_op": operators.is_not_distinct_from}),
),
"is_not_distinct_from": (
_boolean_compare,
util.immutabledict({"negate_op": operators.is_distinct_from}),
),
"in_op": (
_in_impl,
util.immutabledict({"negate_op": operators.not_in_op}),
),
"not_in_op": (
_in_impl,
util.immutabledict({"negate_op": operators.in_op}),
),
"is_": (
_boolean_compare,
util.immutabledict({"negate_op": operators.is_}),
),
"is_not": (
_boolean_compare,
util.immutabledict({"negate_op": operators.is_not}),
),
"between_op": (
_between_impl,
util.EMPTY_DICT,
),
"not_between_op": (
_between_impl,
util.EMPTY_DICT,
),
"desc_op": (
_scalar,
util.immutabledict({"fn": UnaryExpression._create_desc}),
),
"asc_op": (
_scalar,
util.immutabledict({"fn": UnaryExpression._create_asc}),
),
"nulls_first_op": (
_scalar,
util.immutabledict({"fn": UnaryExpression._create_nulls_first}),
),
"nulls_last_op": (
_scalar,
util.immutabledict({"fn": UnaryExpression._create_nulls_last}),
),
"distinct_op": (
_distinct_impl,
util.EMPTY_DICT,
),
"null_op": (_binary_operate, util.EMPTY_DICT),
"custom_op": (_custom_op_operate, util.EMPTY_DICT),
"and_": (
_conjunction_operate,
util.EMPTY_DICT,
),
"or_": (
_conjunction_operate,
util.EMPTY_DICT,
),
"inv": (
_inv_impl,
util.EMPTY_DICT,
),
"add": (
_binary_operate,
util.EMPTY_DICT,
),
"concat_op": (
_binary_operate,
util.EMPTY_DICT,
),
"getitem": (_getitem_impl, util.EMPTY_DICT),
"contains_op": (
_boolean_compare,
util.immutabledict({"negate_op": operators.not_contains_op}),
),
"icontains_op": (
_boolean_compare,
util.immutabledict({"negate_op": operators.not_icontains_op}),
),
"contains": (
_unsupported_impl,
util.EMPTY_DICT,
),
"like_op": (
_boolean_compare,
util.immutabledict({"negate_op": operators.not_like_op}),
),
"ilike_op": (
_boolean_compare,
util.immutabledict({"negate_op": operators.not_ilike_op}),
),
"not_like_op": (
_boolean_compare,
util.immutabledict({"negate_op": operators.like_op}),
),
"not_ilike_op": (
_boolean_compare,
util.immutabledict({"negate_op": operators.ilike_op}),
),
"startswith_op": (
_boolean_compare,
util.immutabledict({"negate_op": operators.not_startswith_op}),
),
"istartswith_op": (
_boolean_compare,
util.immutabledict({"negate_op": operators.not_istartswith_op}),
),
"endswith_op": (
_boolean_compare,
util.immutabledict({"negate_op": operators.not_endswith_op}),
),
"iendswith_op": (
_boolean_compare,
util.immutabledict({"negate_op": operators.not_iendswith_op}),
),
"collate": (
_collate_impl,
util.EMPTY_DICT,
),
"match_op": (_match_impl, util.EMPTY_DICT),
"not_match_op": (
_match_impl,
util.EMPTY_DICT,
),
"regexp_match_op": (
_regexp_match_impl,
util.EMPTY_DICT,
),
"not_regexp_match_op": (
_regexp_match_impl,
util.EMPTY_DICT,
),
"regexp_replace_op": (
_regexp_replace_impl,
util.EMPTY_DICT,
),
"lshift": (_unsupported_impl, util.EMPTY_DICT),
"rshift": (_unsupported_impl, util.EMPTY_DICT),
"bitwise_xor_op": (
_binary_operate,
util.EMPTY_DICT,
),
"bitwise_or_op": (
_binary_operate,
util.EMPTY_DICT,
),
"bitwise_and_op": (
_binary_operate,
util.EMPTY_DICT,
),
"bitwise_not_op": (
_bitwise_not_impl,
util.EMPTY_DICT,
),
"bitwise_lshift_op": (
_binary_operate,
util.EMPTY_DICT,
),
"bitwise_rshift_op": (
_binary_operate,
util.EMPTY_DICT,
),
"matmul": (_unsupported_impl, util.EMPTY_DICT),
"pow": (_pow_impl, util.EMPTY_DICT),
"neg": (_neg_impl, util.EMPTY_DICT),
"mul": (_binary_operate, util.EMPTY_DICT),
"sub": (
_binary_operate,
util.EMPTY_DICT,
),
"div": (_binary_operate, util.EMPTY_DICT),
"mod": (_binary_operate, util.EMPTY_DICT),
"truediv": (_binary_operate, util.EMPTY_DICT),
"floordiv": (_binary_operate, util.EMPTY_DICT),
"json_path_getitem_op": (
_binary_operate,
util.EMPTY_DICT,
),
"json_getitem_op": (
_binary_operate,
util.EMPTY_DICT,
),
}
)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,458 @@
# sql/events.py
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
from __future__ import annotations
from typing import Any
from typing import TYPE_CHECKING
from .base import SchemaEventTarget
from .. import event
if TYPE_CHECKING:
from .schema import Column
from .schema import Constraint
from .schema import SchemaItem
from .schema import Table
from ..engine.base import Connection
from ..engine.interfaces import ReflectedColumn
from ..engine.reflection import Inspector
class DDLEvents(event.Events[SchemaEventTarget]):
"""
Define event listeners for schema objects,
that is, :class:`.SchemaItem` and other :class:`.SchemaEventTarget`
subclasses, including :class:`_schema.MetaData`, :class:`_schema.Table`,
:class:`_schema.Column`, etc.
**Create / Drop Events**
Events emitted when CREATE and DROP commands are emitted to the database.
The event hooks in this category include :meth:`.DDLEvents.before_create`,
:meth:`.DDLEvents.after_create`, :meth:`.DDLEvents.before_drop`, and
:meth:`.DDLEvents.after_drop`.
These events are emitted when using schema-level methods such as
:meth:`.MetaData.create_all` and :meth:`.MetaData.drop_all`. Per-object
create/drop methods such as :meth:`.Table.create`, :meth:`.Table.drop`,
:meth:`.Index.create` are also included, as well as dialect-specific
methods such as :meth:`_postgresql.ENUM.create`.
.. versionadded:: 2.0 :class:`.DDLEvents` event hooks now take place
for non-table objects including constraints, indexes, and
dialect-specific schema types.
Event hooks may be attached directly to a :class:`_schema.Table` object or
to a :class:`_schema.MetaData` collection, as well as to any
:class:`.SchemaItem` class or object that can be individually created and
dropped using a distinct SQL command. Such classes include :class:`.Index`,
:class:`.Sequence`, and dialect-specific classes such as
:class:`_postgresql.ENUM`.
Example using the :meth:`.DDLEvents.after_create` event, where a custom
event hook will emit an ``ALTER TABLE`` command on the current connection,
after ``CREATE TABLE`` is emitted::
from sqlalchemy import create_engine
from sqlalchemy import event
from sqlalchemy import Table, Column, Metadata, Integer
m = MetaData()
some_table = Table("some_table", m, Column("data", Integer))
@event.listens_for(some_table, "after_create")
def after_create(target, connection, **kw):
connection.execute(
text("ALTER TABLE %s SET name=foo_%s" % (target.name, target.name))
)
some_engine = create_engine("postgresql://scott:tiger@host/test")
# will emit "CREATE TABLE some_table" as well as the above
# "ALTER TABLE" statement afterwards
m.create_all(some_engine)
Constraint objects such as :class:`.ForeignKeyConstraint`,
:class:`.UniqueConstraint`, :class:`.CheckConstraint` may also be
subscribed to these events, however they will **not** normally produce
events as these objects are usually rendered inline within an
enclosing ``CREATE TABLE`` statement and implicitly dropped from a
``DROP TABLE`` statement.
For the :class:`.Index` construct, the event hook will be emitted
for ``CREATE INDEX``, however SQLAlchemy does not normally emit
``DROP INDEX`` when dropping tables as this is again implicit within the
``DROP TABLE`` statement.
.. versionadded:: 2.0 Support for :class:`.SchemaItem` objects
for create/drop events was expanded from its previous support for
:class:`.MetaData` and :class:`.Table` to also include
:class:`.Constraint` and all subclasses, :class:`.Index`,
:class:`.Sequence` and some type-related constructs such as
:class:`_postgresql.ENUM`.
.. note:: These event hooks are only emitted within the scope of
SQLAlchemy's create/drop methods; they are not necessarily supported
by tools such as `alembic <https://alembic.sqlalchemy.org>`_.
**Attachment Events**
Attachment events are provided to customize
behavior whenever a child schema element is associated
with a parent, such as when a :class:`_schema.Column` is associated
with its :class:`_schema.Table`, when a
:class:`_schema.ForeignKeyConstraint`
is associated with a :class:`_schema.Table`, etc. These events include
:meth:`.DDLEvents.before_parent_attach` and
:meth:`.DDLEvents.after_parent_attach`.
**Reflection Events**
The :meth:`.DDLEvents.column_reflect` event is used to intercept
and modify the in-Python definition of database columns when
:term:`reflection` of database tables proceeds.
**Use with Generic DDL**
DDL events integrate closely with the
:class:`.DDL` class and the :class:`.ExecutableDDLElement` hierarchy
of DDL clause constructs, which are themselves appropriate
as listener callables::
from sqlalchemy import DDL
event.listen(
some_table,
"after_create",
DDL("ALTER TABLE %(table)s SET name=foo_%(table)s"),
)
**Event Propagation to MetaData Copies**
For all :class:`.DDLEvent` events, the ``propagate=True`` keyword argument
will ensure that a given event handler is propagated to copies of the
object, which are made when using the :meth:`_schema.Table.to_metadata`
method::
from sqlalchemy import DDL
metadata = MetaData()
some_table = Table("some_table", metadata, Column("data", Integer))
event.listen(
some_table,
"after_create",
DDL("ALTER TABLE %(table)s SET name=foo_%(table)s"),
propagate=True,
)
new_metadata = MetaData()
new_table = some_table.to_metadata(new_metadata)
The above :class:`.DDL` object will be associated with the
:meth:`.DDLEvents.after_create` event for both the ``some_table`` and
the ``new_table`` :class:`.Table` objects.
.. seealso::
:ref:`event_toplevel`
:class:`.ExecutableDDLElement`
:class:`.DDL`
:ref:`schema_ddl_sequences`
""" # noqa: E501
_target_class_doc = "SomeSchemaClassOrObject"
_dispatch_target = SchemaEventTarget
def before_create(
self, target: SchemaEventTarget, connection: Connection, **kw: Any
) -> None:
r"""Called before CREATE statements are emitted.
:param target: the :class:`.SchemaObject`, such as a
:class:`_schema.MetaData` or :class:`_schema.Table`
but also including all create/drop objects such as
:class:`.Index`, :class:`.Sequence`, etc.,
object which is the target of the event.
.. versionadded:: 2.0 Support for all :class:`.SchemaItem` objects
was added.
:param connection: the :class:`_engine.Connection` where the
CREATE statement or statements will be emitted.
:param \**kw: additional keyword arguments relevant
to the event. The contents of this dictionary
may vary across releases, and include the
list of tables being generated for a metadata-level
event, the checkfirst flag, and other
elements used by internal events.
:func:`.event.listen` accepts the ``propagate=True``
modifier for this event; when True, the listener function will
be established for any copies made of the target object,
i.e. those copies that are generated when
:meth:`_schema.Table.to_metadata` is used.
:func:`.event.listen` accepts the ``insert=True``
modifier for this event; when True, the listener function will
be prepended to the internal list of events upon discovery, and execute
before registered listener functions that do not pass this argument.
"""
def after_create(
self, target: SchemaEventTarget, connection: Connection, **kw: Any
) -> None:
r"""Called after CREATE statements are emitted.
:param target: the :class:`.SchemaObject`, such as a
:class:`_schema.MetaData` or :class:`_schema.Table`
but also including all create/drop objects such as
:class:`.Index`, :class:`.Sequence`, etc.,
object which is the target of the event.
.. versionadded:: 2.0 Support for all :class:`.SchemaItem` objects
was added.
:param connection: the :class:`_engine.Connection` where the
CREATE statement or statements have been emitted.
:param \**kw: additional keyword arguments relevant
to the event. The contents of this dictionary
may vary across releases, and include the
list of tables being generated for a metadata-level
event, the checkfirst flag, and other
elements used by internal events.
:func:`.event.listen` also accepts the ``propagate=True``
modifier for this event; when True, the listener function will
be established for any copies made of the target object,
i.e. those copies that are generated when
:meth:`_schema.Table.to_metadata` is used.
"""
def before_drop(
self, target: SchemaEventTarget, connection: Connection, **kw: Any
) -> None:
r"""Called before DROP statements are emitted.
:param target: the :class:`.SchemaObject`, such as a
:class:`_schema.MetaData` or :class:`_schema.Table`
but also including all create/drop objects such as
:class:`.Index`, :class:`.Sequence`, etc.,
object which is the target of the event.
.. versionadded:: 2.0 Support for all :class:`.SchemaItem` objects
was added.
:param connection: the :class:`_engine.Connection` where the
DROP statement or statements will be emitted.
:param \**kw: additional keyword arguments relevant
to the event. The contents of this dictionary
may vary across releases, and include the
list of tables being generated for a metadata-level
event, the checkfirst flag, and other
elements used by internal events.
:func:`.event.listen` also accepts the ``propagate=True``
modifier for this event; when True, the listener function will
be established for any copies made of the target object,
i.e. those copies that are generated when
:meth:`_schema.Table.to_metadata` is used.
"""
def after_drop(
self, target: SchemaEventTarget, connection: Connection, **kw: Any
) -> None:
r"""Called after DROP statements are emitted.
:param target: the :class:`.SchemaObject`, such as a
:class:`_schema.MetaData` or :class:`_schema.Table`
but also including all create/drop objects such as
:class:`.Index`, :class:`.Sequence`, etc.,
object which is the target of the event.
.. versionadded:: 2.0 Support for all :class:`.SchemaItem` objects
was added.
:param connection: the :class:`_engine.Connection` where the
DROP statement or statements have been emitted.
:param \**kw: additional keyword arguments relevant
to the event. The contents of this dictionary
may vary across releases, and include the
list of tables being generated for a metadata-level
event, the checkfirst flag, and other
elements used by internal events.
:func:`.event.listen` also accepts the ``propagate=True``
modifier for this event; when True, the listener function will
be established for any copies made of the target object,
i.e. those copies that are generated when
:meth:`_schema.Table.to_metadata` is used.
"""
def before_parent_attach(
self, target: SchemaEventTarget, parent: SchemaItem
) -> None:
"""Called before a :class:`.SchemaItem` is associated with
a parent :class:`.SchemaItem`.
:param target: the target object
:param parent: the parent to which the target is being attached.
:func:`.event.listen` also accepts the ``propagate=True``
modifier for this event; when True, the listener function will
be established for any copies made of the target object,
i.e. those copies that are generated when
:meth:`_schema.Table.to_metadata` is used.
"""
def after_parent_attach(
self, target: SchemaEventTarget, parent: SchemaItem
) -> None:
"""Called after a :class:`.SchemaItem` is associated with
a parent :class:`.SchemaItem`.
:param target: the target object
:param parent: the parent to which the target is being attached.
:func:`.event.listen` also accepts the ``propagate=True``
modifier for this event; when True, the listener function will
be established for any copies made of the target object,
i.e. those copies that are generated when
:meth:`_schema.Table.to_metadata` is used.
"""
def _sa_event_column_added_to_pk_constraint(
self, const: Constraint, col: Column[Any]
) -> None:
"""internal event hook used for primary key naming convention
updates.
"""
def column_reflect(
self, inspector: Inspector, table: Table, column_info: ReflectedColumn
) -> None:
"""Called for each unit of 'column info' retrieved when
a :class:`_schema.Table` is being reflected.
This event is most easily used by applying it to a specific
:class:`_schema.MetaData` instance, where it will take effect for
all :class:`_schema.Table` objects within that
:class:`_schema.MetaData` that undergo reflection::
metadata = MetaData()
@event.listens_for(metadata, "column_reflect")
def receive_column_reflect(inspector, table, column_info):
# receives for all Table objects that are reflected
# under this MetaData
...
# will use the above event hook
my_table = Table("my_table", metadata, autoload_with=some_engine)
.. versionadded:: 1.4.0b2 The :meth:`_events.DDLEvents.column_reflect`
hook may now be applied to a :class:`_schema.MetaData` object as
well as the :class:`_schema.MetaData` class itself where it will
take place for all :class:`_schema.Table` objects associated with
the targeted :class:`_schema.MetaData`.
It may also be applied to the :class:`_schema.Table` class across
the board::
from sqlalchemy import Table
@event.listens_for(Table, "column_reflect")
def receive_column_reflect(inspector, table, column_info):
# receives for all Table objects that are reflected
...
It can also be applied to a specific :class:`_schema.Table` at the
point that one is being reflected using the
:paramref:`_schema.Table.listeners` parameter::
t1 = Table(
"my_table",
autoload_with=some_engine,
listeners=[("column_reflect", receive_column_reflect)],
)
The dictionary of column information as returned by the
dialect is passed, and can be modified. The dictionary
is that returned in each element of the list returned
by :meth:`.reflection.Inspector.get_columns`:
* ``name`` - the column's name, is applied to the
:paramref:`_schema.Column.name` parameter
* ``type`` - the type of this column, which should be an instance
of :class:`~sqlalchemy.types.TypeEngine`, is applied to the
:paramref:`_schema.Column.type` parameter
* ``nullable`` - boolean flag if the column is NULL or NOT NULL,
is applied to the :paramref:`_schema.Column.nullable` parameter
* ``default`` - the column's server default value. This is
normally specified as a plain string SQL expression, however the
event can pass a :class:`.FetchedValue`, :class:`.DefaultClause`,
or :func:`_expression.text` object as well. Is applied to the
:paramref:`_schema.Column.server_default` parameter
The event is called before any action is taken against
this dictionary, and the contents can be modified; the following
additional keys may be added to the dictionary to further modify
how the :class:`_schema.Column` is constructed:
* ``key`` - the string key that will be used to access this
:class:`_schema.Column` in the ``.c`` collection; will be applied
to the :paramref:`_schema.Column.key` parameter. Is also used
for ORM mapping. See the section
:ref:`mapper_automated_reflection_schemes` for an example.
* ``quote`` - force or un-force quoting on the column name;
is applied to the :paramref:`_schema.Column.quote` parameter.
* ``info`` - a dictionary of arbitrary data to follow along with
the :class:`_schema.Column`, is applied to the
:paramref:`_schema.Column.info` parameter.
:func:`.event.listen` also accepts the ``propagate=True``
modifier for this event; when True, the listener function will
be established for any copies made of the target object,
i.e. those copies that are generated when
:meth:`_schema.Table.to_metadata` is used.
.. seealso::
:ref:`mapper_automated_reflection_schemes` -
in the ORM mapping documentation
:ref:`automap_intercepting_columns` -
in the :ref:`automap_toplevel` documentation
:ref:`metadata_reflection_dbagnostic_types` - in
the :ref:`metadata_reflection_toplevel` documentation
"""
@@ -0,0 +1,171 @@
# sql/expression.py
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
"""Defines the public namespace for SQL expression constructs."""
from __future__ import annotations
from ._dml_constructors import delete as delete
from ._dml_constructors import insert as insert
from ._dml_constructors import update as update
from ._elements_constructors import aggregate_order_by as aggregate_order_by
from ._elements_constructors import all_ as all_
from ._elements_constructors import and_ as and_
from ._elements_constructors import any_ as any_
from ._elements_constructors import asc as asc
from ._elements_constructors import between as between
from ._elements_constructors import bindparam as bindparam
from ._elements_constructors import bitwise_not as bitwise_not
from ._elements_constructors import case as case
from ._elements_constructors import cast as cast
from ._elements_constructors import collate as collate
from ._elements_constructors import column as column
from ._elements_constructors import desc as desc
from ._elements_constructors import distinct as distinct
from ._elements_constructors import extract as extract
from ._elements_constructors import false as false
from ._elements_constructors import from_dml_column as from_dml_column
from ._elements_constructors import funcfilter as funcfilter
from ._elements_constructors import label as label
from ._elements_constructors import not_ as not_
from ._elements_constructors import null as null
from ._elements_constructors import nulls_first as nulls_first
from ._elements_constructors import nulls_last as nulls_last
from ._elements_constructors import or_ as or_
from ._elements_constructors import outparam as outparam
from ._elements_constructors import over as over
from ._elements_constructors import text as text
from ._elements_constructors import true as true
from ._elements_constructors import try_cast as try_cast
from ._elements_constructors import tstring as tstring
from ._elements_constructors import tuple_ as tuple_
from ._elements_constructors import type_coerce as type_coerce
from ._elements_constructors import within_group as within_group
from ._selectable_constructors import alias as alias
from ._selectable_constructors import cte as cte
from ._selectable_constructors import except_ as except_
from ._selectable_constructors import except_all as except_all
from ._selectable_constructors import exists as exists
from ._selectable_constructors import intersect as intersect
from ._selectable_constructors import intersect_all as intersect_all
from ._selectable_constructors import join as join
from ._selectable_constructors import lateral as lateral
from ._selectable_constructors import outerjoin as outerjoin
from ._selectable_constructors import select as select
from ._selectable_constructors import table as table
from ._selectable_constructors import tablesample as tablesample
from ._selectable_constructors import union as union
from ._selectable_constructors import union_all as union_all
from ._selectable_constructors import values as values
from ._typing import ColumnExpressionArgument as ColumnExpressionArgument
from .base import _from_objects as _from_objects
from .base import _select_iterables as _select_iterables
from .base import ColumnCollection as ColumnCollection
from .base import Executable as Executable
from .base import ExecutableStatement as ExecutableStatement
from .base import WriteableColumnCollection as WriteableColumnCollection
from .cache_key import CacheKey as CacheKey
from .dml import Delete as Delete
from .dml import Insert as Insert
from .dml import Update as Update
from .dml import UpdateBase as UpdateBase
from .dml import ValuesBase as ValuesBase
from .elements import _truncated_label as _truncated_label
from .elements import AggregateOrderBy as AggregateOrderBy
from .elements import BinaryExpression as BinaryExpression
from .elements import BindParameter as BindParameter
from .elements import BooleanClauseList as BooleanClauseList
from .elements import Case as Case
from .elements import Cast as Cast
from .elements import ClauseElement as ClauseElement
from .elements import ClauseList as ClauseList
from .elements import CollectionAggregate as CollectionAggregate
from .elements import ColumnClause as ColumnClause
from .elements import ColumnElement as ColumnElement
from .elements import DMLTargetCopy as DMLTargetCopy
from .elements import ExpressionClauseList as ExpressionClauseList
from .elements import Extract as Extract
from .elements import False_ as False_
from .elements import FrameClause as FrameClause
from .elements import FrameClauseType as FrameClauseType
from .elements import FunctionFilter as FunctionFilter
from .elements import Grouping as Grouping
from .elements import Label as Label
from .elements import literal as literal
from .elements import literal_column as literal_column
from .elements import Null as Null
from .elements import OrderByList as OrderByList
from .elements import Over as Over
from .elements import quoted_name as quoted_name
from .elements import ReleaseSavepointClause as ReleaseSavepointClause
from .elements import RollbackToSavepointClause as RollbackToSavepointClause
from .elements import SavepointClause as SavepointClause
from .elements import SQLColumnExpression as SQLColumnExpression
from .elements import TableValuedColumn as TableValuedColumn
from .elements import TextClause as TextClause
from .elements import True_ as True_
from .elements import TryCast as TryCast
from .elements import TString as TString
from .elements import Tuple as Tuple
from .elements import TypeClause as TypeClause
from .elements import TypeCoerce as TypeCoerce
from .elements import UnaryExpression as UnaryExpression
from .elements import WithinGroup as WithinGroup
from .functions import func as func
from .functions import Function as Function
from .functions import FunctionElement as FunctionElement
from .functions import modifier as modifier
from .lambdas import lambda_stmt as lambda_stmt
from .lambdas import LambdaElement as LambdaElement
from .lambdas import StatementLambdaElement as StatementLambdaElement
from .operators import ColumnOperators as ColumnOperators
from .operators import custom_op as custom_op
from .operators import OperatorClass as OperatorClass
from .operators import Operators as Operators
from .selectable import Alias as Alias
from .selectable import AliasedReturnsRows as AliasedReturnsRows
from .selectable import CompoundSelect as CompoundSelect
from .selectable import CTE as CTE
from .selectable import Exists as Exists
from .selectable import FromClause as FromClause
from .selectable import FromGrouping as FromGrouping
from .selectable import GenerativeSelect as GenerativeSelect
from .selectable import HasCTE as HasCTE
from .selectable import HasPrefixes as HasPrefixes
from .selectable import HasSuffixes as HasSuffixes
from .selectable import Join as Join
from .selectable import LABEL_STYLE_DEFAULT as LABEL_STYLE_DEFAULT
from .selectable import (
LABEL_STYLE_DISAMBIGUATE_ONLY as LABEL_STYLE_DISAMBIGUATE_ONLY,
)
from .selectable import LABEL_STYLE_NONE as LABEL_STYLE_NONE
from .selectable import (
LABEL_STYLE_TABLENAME_PLUS_COL as LABEL_STYLE_TABLENAME_PLUS_COL,
)
from .selectable import Lateral as Lateral
from .selectable import ReturnsRows as ReturnsRows
from .selectable import ScalarSelect as ScalarSelect
from .selectable import ScalarValues as ScalarValues
from .selectable import Select as Select
from .selectable import Selectable as Selectable
from .selectable import SelectBase as SelectBase
from .selectable import SelectLabelStyle as SelectLabelStyle
from .selectable import Subquery as Subquery
from .selectable import TableClause as TableClause
from .selectable import TableSample as TableSample
from .selectable import TableValuedAlias as TableValuedAlias
from .selectable import TextAsFrom as TextAsFrom
from .selectable import TextualSelect as TextualSelect
from .selectable import Values as Values
from .visitors import Visitable as Visitable
nullsfirst = nulls_first
"""Synonym for the :func:`.nulls_first` function."""
nullslast = nulls_last
"""Synonym for the :func:`.nulls_last` function."""
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,204 @@
# sql/naming.py
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
# mypy: allow-untyped-defs, allow-untyped-calls
"""Establish constraint and index naming conventions."""
from __future__ import annotations
import re
from . import events # noqa
from .base import _NONE_NAME
from .elements import conv as conv
from .schema import CheckConstraint
from .schema import Column
from .schema import Constraint
from .schema import ForeignKeyConstraint
from .schema import Index
from .schema import PrimaryKeyConstraint
from .schema import Table
from .schema import UniqueConstraint
from .. import event
from .. import exc
class ConventionDict:
def __init__(self, const, table, convention):
self.const = const
self._is_fk = isinstance(const, ForeignKeyConstraint)
self.table = table
self.convention = convention
self._const_name = const.name
def _key_table_name(self):
return self.table.name
def _column_X(self, idx, attrname):
if self._is_fk:
try:
fk = self.const.elements[idx]
except IndexError:
return ""
else:
return getattr(fk.parent, attrname)
else:
cols = list(self.const.columns)
try:
col = cols[idx]
except IndexError:
return ""
else:
return getattr(col, attrname)
def _key_constraint_name(self):
if self._const_name in (None, _NONE_NAME):
raise exc.InvalidRequestError(
"Naming convention including "
"%(constraint_name)s token requires that "
"constraint is explicitly named."
)
if not isinstance(self._const_name, conv):
self.const.name = None
return self._const_name
def _key_column_X_key(self, idx):
# note this method was missing before
# [ticket:3989], meaning tokens like ``%(column_0_key)s`` weren't
# working even though documented.
return self._column_X(idx, "key")
def _key_column_X_name(self, idx):
return self._column_X(idx, "name")
def _key_column_X_label(self, idx):
return self._column_X(idx, "_ddl_label")
def _key_referred_table_name(self):
fk = self.const.elements[0]
return fk.target_tokens.table_name
def _key_referred_column_X_name(self, idx):
fk = self.const.elements[idx]
# note that before [ticket:3989], this method was returning
# the specification for the :class:`.ForeignKey` itself, which normally
# would be using the ``.key`` of the column, not the name.
return fk.column.name
def __getitem__(self, key):
if key in self.convention:
return self.convention[key](self.const, self.table)
elif hasattr(self, "_key_%s" % key):
return getattr(self, "_key_%s" % key)()
else:
col_template = re.match(r".*_?column_(\d+)(_?N)?_.+", key)
if col_template:
idx = col_template.group(1)
multiples = col_template.group(2)
if multiples:
if self._is_fk:
elems = self.const.elements
else:
elems = list(self.const.columns)
tokens = []
for idx, elem in enumerate(elems):
attr = "_key_" + key.replace("0" + multiples, "X")
try:
tokens.append(getattr(self, attr)(idx))
except AttributeError:
raise KeyError(key)
sep = "_" if multiples.startswith("_") else ""
return sep.join(tokens)
else:
attr = "_key_" + key.replace(idx, "X")
idx = int(idx)
if hasattr(self, attr):
return getattr(self, attr)(idx)
raise KeyError(key)
_prefix_dict = {
Index: "ix",
PrimaryKeyConstraint: "pk",
CheckConstraint: "ck",
UniqueConstraint: "uq",
ForeignKeyConstraint: "fk",
}
def _get_convention(dict_, key):
for super_ in key.__mro__:
if super_ in _prefix_dict and _prefix_dict[super_] in dict_:
return dict_[_prefix_dict[super_]]
elif super_ in dict_:
return dict_[super_]
else:
return None
def _constraint_name_for_table(const, table):
metadata = table.metadata
convention = _get_convention(metadata.naming_convention, type(const))
if isinstance(const.name, conv):
return const.name
elif (
convention is not None
and not isinstance(const.name, conv)
and (
const.name is None
or "constraint_name" in convention
or const.name is _NONE_NAME
)
):
return conv(
convention
% ConventionDict(const, table, metadata.naming_convention)
)
elif convention is _NONE_NAME:
return None
@event.listens_for(
PrimaryKeyConstraint, "_sa_event_column_added_to_pk_constraint"
)
def _column_added_to_pk_constraint(pk_constraint, col):
if pk_constraint._implicit_generated:
# only operate upon the "implicit" pk constraint for now,
# as we have to force the name to None to reset it. the
# "implicit" constraint will only have a naming convention name
# if at all.
table = pk_constraint.table
pk_constraint.name = None
newname = _constraint_name_for_table(pk_constraint, table)
if newname:
pk_constraint.name = newname
@event.listens_for(Constraint, "after_parent_attach")
@event.listens_for(Index, "after_parent_attach")
def _constraint_name(const, table):
if isinstance(table, Column):
# this path occurs for a CheckConstraint linked to a Column
# for column-attached constraint, set another event
# to link the column attached to the table as this constraint
# associated with the table.
event.listen(
table,
"after_parent_attach",
lambda col, table: _constraint_name(const, table),
)
elif isinstance(table, Table):
if isinstance(const.name, conv) or const.name is _NONE_NAME:
return
newname = _constraint_name_for_table(const, table)
if newname:
const.name = newname
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,332 @@
# sql/roles.py
# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
from __future__ import annotations
from typing import Any
from typing import Generic
from typing import Literal
from typing import Optional
from typing import TYPE_CHECKING
from typing import TypeVar
from .. import util
if TYPE_CHECKING:
from ._typing import _PropagateAttrsType
from .elements import Label
from .selectable import _SelectIterable
from .selectable import FromClause
from .selectable import Subquery
_T = TypeVar("_T", bound=Any)
_T_co = TypeVar("_T_co", bound=Any, covariant=True)
class SQLRole:
"""Define a "role" within a SQL statement structure.
Classes within SQL Core participate within SQLRole hierarchies in order
to more accurately indicate where they may be used within SQL statements
of all types.
.. versionadded:: 1.4
"""
__slots__ = ()
allows_lambda = False
uses_inspection = False
class SyntaxExtensionRole(SQLRole):
__slots__ = ()
_role_name = "Syntax extension construct"
class UsesInspection:
__slots__ = ()
_post_inspect: Literal[None] = None
uses_inspection = True
class AllowsLambdaRole:
__slots__ = ()
allows_lambda = True
class HasCacheKeyRole(SQLRole):
__slots__ = ()
_role_name = "Cacheable Core or ORM object"
class ExecutableOptionRole(SQLRole):
__slots__ = ()
_role_name = "ExecutionOption Core or ORM object"
class LiteralValueRole(SQLRole):
__slots__ = ()
_role_name = "Literal Python value"
class ColumnArgumentRole(SQLRole):
__slots__ = ()
_role_name = "Column expression"
class ColumnArgumentOrKeyRole(ColumnArgumentRole):
__slots__ = ()
_role_name = "Column expression or string key"
class StrAsPlainColumnRole(ColumnArgumentRole):
__slots__ = ()
_role_name = "Column expression or string key"
class ColumnListRole(SQLRole):
"""Elements suitable for forming comma separated lists of expressions."""
__slots__ = ()
class StringRole(SQLRole):
"""mixin indicating a role that results in strings"""
__slots__ = ()
class TruncatedLabelRole(StringRole, SQLRole):
__slots__ = ()
_role_name = "String SQL identifier"
class TStringElementRole(UsesInspection, SQLRole):
"""Role for elements that can be interpolated into a TString."""
__slots__ = ()
_role_name = "TString interpolatable element"
class ColumnsClauseRole(
TStringElementRole, AllowsLambdaRole, UsesInspection, ColumnListRole
):
__slots__ = ()
_role_name = (
"Column expression, FROM clause, or other columns clause element"
)
@property
def _select_iterable(self) -> _SelectIterable:
raise NotImplementedError()
class TypedColumnsClauseRole(Generic[_T_co], SQLRole):
"""element-typed form of ColumnsClauseRole"""
__slots__ = ()
class LimitOffsetRole(SQLRole):
__slots__ = ()
_role_name = "LIMIT / OFFSET expression"
class ByOfRole(ColumnListRole):
__slots__ = ()
_role_name = "GROUP BY / OF / etc. expression"
class GroupByRole(AllowsLambdaRole, UsesInspection, ByOfRole):
__slots__ = ()
# note there's a special case right now where you can pass a whole
# ORM entity to group_by() and it splits out. we may not want to keep
# this around
_role_name = "GROUP BY expression"
class OrderByRole(AllowsLambdaRole, ByOfRole):
__slots__ = ()
_role_name = "ORDER BY expression"
class StructuralRole(SQLRole):
__slots__ = ()
class StatementOptionRole(StructuralRole):
__slots__ = ()
_role_name = "statement sub-expression element"
class OnClauseRole(AllowsLambdaRole, StructuralRole):
__slots__ = ()
_role_name = (
"ON clause, typically a SQL expression or "
"ORM relationship attribute"
)
class WhereHavingRole(OnClauseRole):
__slots__ = ()
_role_name = "SQL expression for WHERE/HAVING role"
class ExpressionElementRole(TypedColumnsClauseRole[_T_co]):
# note when using generics for ExpressionElementRole,
# the generic type needs to be in
# sqlalchemy.sql.coercions._impl_lookup mapping also.
# these are set up for basic types like int, bool, str, float
# right now
__slots__ = ()
_role_name = "SQL expression element"
def label(self, name: Optional[str]) -> Label[_T]:
raise NotImplementedError()
class ConstExprRole(ExpressionElementRole[_T]):
__slots__ = ()
_role_name = "Constant True/False/None expression"
class LabeledColumnExprRole(ExpressionElementRole[_T]):
__slots__ = ()
class BinaryElementRole(ExpressionElementRole[_T]):
__slots__ = ()
_role_name = "SQL expression element or literal value"
class InElementRole(SQLRole):
__slots__ = ()
_role_name = (
"IN expression list, SELECT construct, or bound parameter object"
)
class JoinTargetRole(AllowsLambdaRole, UsesInspection, StructuralRole):
__slots__ = ()
_role_name = (
"Join target, typically a FROM expression, or ORM "
"relationship attribute"
)
class FromClauseRole(ColumnsClauseRole, JoinTargetRole):
__slots__ = ()
_role_name = "FROM expression, such as a Table or alias() object"
_is_subquery = False
named_with_column: bool
class AnonymizedFromClauseRole(FromClauseRole):
__slots__ = ()
if TYPE_CHECKING:
def _anonymous_fromclause(
self, *, name: Optional[str] = None, flat: bool = False
) -> FromClause: ...
class ReturnsRowsRole(SQLRole):
__slots__ = ()
_role_name = (
"Row returning expression such as a SELECT, a FROM clause, or an "
"INSERT/UPDATE/DELETE with RETURNING"
)
class StatementRole(SQLRole):
__slots__ = ()
_role_name = "Executable SQL or text() construct"
if TYPE_CHECKING:
@util.memoized_property
def _propagate_attrs(self) -> _PropagateAttrsType: ...
else:
_propagate_attrs = util.EMPTY_DICT
class SelectStatementRole(StatementRole, ReturnsRowsRole):
__slots__ = ()
_role_name = "SELECT construct or equivalent text() construct"
def subquery(self) -> Subquery:
raise NotImplementedError(
"All SelectStatementRole objects should implement a "
".subquery() method."
)
class HasCTERole(ReturnsRowsRole):
__slots__ = ()
class IsCTERole(SQLRole):
__slots__ = ()
_role_name = "CTE object"
class CompoundElementRole(AllowsLambdaRole, SQLRole):
"""SELECT statements inside a CompoundSelect, e.g. UNION, EXTRACT, etc."""
__slots__ = ()
_role_name = (
"SELECT construct for inclusion in a UNION or other set construct"
)
# TODO: are we using this?
class DMLRole(StatementRole):
__slots__ = ()
class DMLTableRole(FromClauseRole):
__slots__ = ()
_role_name = "subject table for an INSERT, UPDATE or DELETE"
class DMLColumnRole(SQLRole):
__slots__ = ()
_role_name = "SET/VALUES column expression or string key"
class DMLSelectRole(SQLRole):
"""A SELECT statement embedded in DML, typically INSERT from SELECT"""
__slots__ = ()
_role_name = "SELECT statement or equivalent textual object"
class DDLRole(StatementRole):
__slots__ = ()
class DDLExpressionRole(StructuralRole):
__slots__ = ()
_role_name = "SQL expression element for DDL constraint"
class DDLConstraintColumnRole(SQLRole):
__slots__ = ()
_role_name = "String column name or column expression for DDL constraint"
class DDLReferredColumnRole(DDLConstraintColumnRole):
__slots__ = ()
_role_name = (
"String column name or Column object for DDL foreign key constraint"
)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff