diff options
Diffstat (limited to 'lib/sqlalchemy/sql/elements.py')
-rw-r--r-- | lib/sqlalchemy/sql/elements.py | 1978 |
1 files changed, 488 insertions, 1490 deletions
diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index a025cce35..705a89889 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -14,6 +14,16 @@ import itertools import operator import re import typing +from typing import Any +from typing import Callable +from typing import Generic +from typing import Optional +from typing import overload +from typing import Sequence +from typing import Text as typing_Text +from typing import Type +from typing import TypeVar +from typing import Union from . import coercions from . import operators @@ -31,7 +41,7 @@ from .base import NO_ARG from .base import SingletonConstant from .cache_key import MemoizedHasCacheKey from .cache_key import NO_CACHE -from .coercions import _document_text_coercion +from .coercions import _document_text_coercion # noqa from .traversals import HasCopyInternals from .visitors import cloned_traverse from .visitors import InternalTraversal @@ -41,86 +51,20 @@ from .. import exc from .. import inspection from .. import util +if typing.TYPE_CHECKING: + from decimal import Decimal -def collate(expression, collation): - """Return the clause ``expression COLLATE collation``. + from .selectable import FromClause + from .selectable import Select + from .sqltypes import Boolean # noqa + from .type_api import TypeEngine - e.g.:: +_NUMERIC = Union[complex, "Decimal"] - collate(mycolumn, 'utf8_bin') - - produces:: - - mycolumn COLLATE utf8_bin - - The collation expression is also quoted if it is a case sensitive - identifier, e.g. contains uppercase characters. - - .. versionchanged:: 1.2 quoting is automatically applied to COLLATE - expressions if they are case sensitive. - - """ - - expr = coercions.expect(roles.ExpressionElementRole, expression) - return BinaryExpression( - expr, CollationClause(collation), operators.collate, type_=expr.type - ) - - -def between(expr, lower_bound, upper_bound, symmetric=False): - """Produce a ``BETWEEN`` predicate clause. - - E.g.:: - - from sqlalchemy import between - stmt = select(users_table).where(between(users_table.c.id, 5, 7)) - - Would produce SQL resembling:: - - SELECT id, name FROM user WHERE id BETWEEN :id_1 AND :id_2 - - The :func:`.between` function is a standalone version of the - :meth:`_expression.ColumnElement.between` method available on all - SQL expressions, as in:: - - stmt = select(users_table).where(users_table.c.id.between(5, 7)) - - All arguments passed to :func:`.between`, including the left side - column expression, are coerced from Python scalar values if a - the value is not a :class:`_expression.ColumnElement` subclass. - For example, - three fixed values can be compared as in:: - - print(between(5, 3, 7)) - - Which would produce:: - - :param_1 BETWEEN :param_2 AND :param_3 - - :param expr: a column expression, typically a - :class:`_expression.ColumnElement` - instance or alternatively a Python scalar expression to be coerced - into a column expression, serving as the left side of the ``BETWEEN`` - expression. - - :param lower_bound: a column or Python scalar expression serving as the - lower bound of the right side of the ``BETWEEN`` expression. - - :param upper_bound: a column or Python scalar expression serving as the - upper bound of the right side of the ``BETWEEN`` expression. - - :param symmetric: if True, will render " BETWEEN SYMMETRIC ". Note - that not all databases support this syntax. - - .. versionadded:: 0.9.5 - - .. seealso:: - - :meth:`_expression.ColumnElement.between` - - """ - expr = coercions.expect(roles.ExpressionElementRole, expr) - return expr.between(lower_bound, upper_bound, symmetric=symmetric) +_T = TypeVar("_T", bound="Any") +_OPT = TypeVar("_OPT", bound="Any") +_NT = TypeVar("_NT", bound="_NUMERIC") +_ST = TypeVar("_ST", bound="typing_Text") def literal(value, type_=None): @@ -145,28 +89,40 @@ def literal(value, type_=None): return coercions.expect(roles.LiteralValueRole, value, type_=type_) -def outparam(key, type_=None): - """Create an 'OUT' parameter for usage in functions (stored procedures), - for databases which support them. +def literal_column(text, type_=None): + r"""Produce a :class:`.ColumnClause` object that has the + :paramref:`_expression.column.is_literal` flag set to True. + + :func:`_expression.literal_column` is similar to + :func:`_expression.column`, except that + it is more often used as a "standalone" column expression that renders + exactly as stated; while :func:`_expression.column` + stores a string name that + will be assumed to be part of a table and may be quoted as such, + :func:`_expression.literal_column` can be that, + or any other arbitrary column-oriented + expression. + + :param text: the text of the expression; can be any SQL expression. + Quoting rules will not be applied. To specify a column-name expression + which should be subject to quoting rules, use the :func:`column` + function. - The ``outparam`` can be used like a regular function parameter. - The "output" value will be available from the - :class:`~sqlalchemy.engine.CursorResult` object via its ``out_parameters`` - attribute, which returns a dictionary containing the values. + :param type\_: an optional :class:`~sqlalchemy.types.TypeEngine` + object which will + provide result-set translation and additional expression semantics for + this column. If left as ``None`` the type will be :class:`.NullType`. - """ - return BindParameter(key, None, type_=type_, unique=False, isoutparam=True) + .. seealso:: + :func:`_expression.column` -def not_(clause): - """Return a negation of the given clause, i.e. ``NOT(clause)``. + :func:`_expression.text` - The ``~`` operator is also overloaded on all - :class:`_expression.ColumnElement` subclasses to produce the - same result. + :ref:`sqlexpression_literal_column` """ - return operators.inv(coercions.expect(roles.ExpressionElementRole, clause)) + return ColumnClause(text, type_=type_, is_literal=True) class CompilerElement(Traversible): @@ -258,6 +214,9 @@ class CompilerElement(Traversible): return str(self.compile()) +SelfClauseElement = TypeVar("SelfClauseElement", bound="ClauseElement") + + @inspection._self_inspects class ClauseElement( SupportsWrappingAnnotations, @@ -313,7 +272,7 @@ class ClauseElement( self._propagate_attrs = util.immutabledict(values) return self - def _clone(self, **kw): + def _clone(self: SelfClauseElement, **kw) -> SelfClauseElement: """Create a shallow copy of this ClauseElement. This method may be used by a generative API. Its also used as @@ -624,8 +583,9 @@ class ColumnElement( roles.DMLColumnRole, roles.DDLConstraintColumnRole, roles.DDLExpressionRole, - operators.ColumnOperators, + operators.ColumnOperators["ColumnElement"], ClauseElement, + Generic[_T], ): """Represent a column-oriented SQL expression suitable for usage in the "columns" clause, WHERE clause etc. of a statement. @@ -841,11 +801,11 @@ class ColumnElement( return super(ColumnElement, self)._negate() @util.memoized_property - def type(self): + def type(self) -> "TypeEngine[_T]": return type_api.NULLTYPE @HasMemoized.memoized_attribute - def comparator(self): + def comparator(self) -> "TypeEngine.Comparator[_T]": try: comparator_factory = self.type.comparator_factory except AttributeError as err: @@ -869,10 +829,347 @@ class ColumnElement( ) ) from err - def operate(self, op, *other, **kwargs): + # annotations for comparison methods + # these are from operators->Operators / ColumnOperators, + # redefined with the specific types returned by ColumnElement hierarchies + if typing.TYPE_CHECKING: + + def op( + self, + opstring: Any, + precedence: int = 0, + is_comparison: bool = False, + return_type: Optional[ + Union[Type["TypeEngine[_OPT]"], "TypeEngine[_OPT]"] + ] = None, + python_impl=None, + ) -> Callable[[Any], "BinaryExpression[_OPT]"]: + ... + + def bool_op( + self, opstring: Any, precedence: int = 0, python_impl=None + ) -> Callable[[Any], "BinaryExpression[bool]"]: + ... + + def __and__(self, other: Any) -> "BooleanClauseList": + ... + + def __or__(self, other: Any) -> "BooleanClauseList": + ... + + def __invert__(self) -> "UnaryExpression[_T]": + ... + + def __lt__(self, other: Any) -> "BinaryExpression[bool]": + ... + + def __le__(self, other: Any) -> "BinaryExpression[bool]": + ... + + def __eq__(self, other: Any) -> "BinaryExpression[bool]": + ... + + def __ne__(self, other: Any) -> "BinaryExpression[bool]": + ... + + def is_distinct_from(self, other: Any) -> "BinaryExpression[bool]": + ... + + def is_not_distinct_from(self, other: Any) -> "BinaryExpression[bool]": + ... + + def __gt__(self, other: Any) -> "BinaryExpression[bool]": + ... + + def __ge__(self, other: Any) -> "BinaryExpression[bool]": + ... + + def __neg__(self) -> "UnaryExpression[_T]": + ... + + def __contains__(self, other: Any) -> "BinaryExpression[bool]": + ... + + def __getitem__(self, index: Any) -> "ColumnElement": + ... + + @overload + def concat(self, other: Any) -> "BinaryExpression[_ST]": + ... + + @overload + def concat(self, other: Any) -> "BinaryExpression": + ... + + def concat(self, other: Any) -> "BinaryExpression": + ... + + def like(self, other: Any, escape=None) -> "BinaryExpression[bool]": + ... + + def ilike(self, other: Any, escape=None) -> "BinaryExpression[bool]": + ... + + def in_( + self, + other: Union[Sequence[Any], "BindParameter", "Select"], + ) -> "BinaryExpression[bool]": + ... + + def not_in( + self, + other: Union[Sequence[Any], "BindParameter", "Select"], + ) -> "BinaryExpression[bool]": + ... + + def not_like( + self, other: Any, escape=None + ) -> "BinaryExpression[bool]": + ... + + def not_ilike( + self, other: Any, escape=None + ) -> "BinaryExpression[bool]": + ... + + def is_(self, other: Any) -> "BinaryExpression[bool]": + ... + + def is_not(self, other: Any) -> "BinaryExpression[bool]": + ... + + def startswith( + self, other: Any, escape=None, autoescape=False + ) -> "BinaryExpression[bool]": + ... + + def endswith( + self, other: Any, escape=None, autoescape=False + ) -> "BinaryExpression[bool]": + ... + + def contains( + self, other: Any, escape=None, autoescape=False + ) -> "BinaryExpression[bool]": + ... + + def match(self, other: Any, **kwargs) -> "BinaryExpression[bool]": + ... + + def regexp_match( + self, pattern, flags=None + ) -> "BinaryExpression[bool]": + ... + + def regexp_replace( + self, pattern, replacement, flags=None + ) -> "BinaryExpression": + ... + + def desc(self) -> "UnaryExpression[_T]": + ... + + def asc(self) -> "UnaryExpression[_T]": + ... + + def nulls_first(self) -> "UnaryExpression[_T]": + ... + + def nulls_last(self) -> "UnaryExpression[_T]": + ... + + def collate(self, collation) -> "CollationClause": + ... + + def between( + self, cleft, cright, symmetric=False + ) -> "BinaryExpression[bool]": + ... + + def distinct(self: "ColumnElement[_T]") -> "UnaryExpression[_T]": + ... + + def any_(self) -> "CollectionAggregate": + ... + + def all_(self) -> "CollectionAggregate": + ... + + # numeric overloads. These need more tweaking + + @overload + def __add__( + self: "ColumnElement[_NT]", other: "Union[ColumnElement[_NT], _NT]" + ) -> "BinaryExpression[_NT]": + ... + + @overload + def __add__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __add__( + self: "ColumnElement[_ST]", other: Any + ) -> "BinaryExpression[_ST]": + ... + + def __add__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __radd__(self, other: Any) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __radd__(self, other: Any) -> "BinaryExpression": + ... + + def __radd__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __sub__( + self: "ColumnElement[_NT]", other: "Union[ColumnElement[_NT], _NT]" + ) -> "BinaryExpression[_NT]": + ... + + @overload + def __sub__(self, other: Any) -> "BinaryExpression": + ... + + def __sub__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __rsub__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __rsub__(self, other: Any) -> "BinaryExpression": + ... + + def __rsub__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __mul__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __mul__(self, other: Any) -> "BinaryExpression": + ... + + def __mul__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __rmul__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __rmul__(self, other: Any) -> "BinaryExpression": + ... + + def __rmul__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __mod__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __mod__(self, other: Any) -> "BinaryExpression": + ... + + def __mod__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __rmod__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __rmod__(self, other: Any) -> "BinaryExpression": + ... + + def __rmod__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __truediv__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __truediv__(self, other: Any) -> "BinaryExpression": + ... + + def __truediv__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __rtruediv__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __rtruediv__(self, other: Any) -> "BinaryExpression": + ... + + def __rtruediv__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __floordiv__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __floordiv__(self, other: Any) -> "BinaryExpression": + ... + + def __floordiv__(self, other: Any) -> "BinaryExpression": + ... + + @overload + def __rfloordiv__( + self: "ColumnElement[_NT]", other: Any + ) -> "BinaryExpression[_NUMERIC]": + ... + + @overload + def __rfloordiv__(self, other: Any) -> "BinaryExpression": + ... + + def __rfloordiv__(self, other: Any) -> "BinaryExpression": + ... + + def operate( + self, + op: operators.OperatorType, + *other: Any, + **kwargs, + ) -> "ColumnElement": return op(self.comparator, *other, **kwargs) - def reverse_operate(self, op, other, **kwargs): + def reverse_operate( + self, op: operators.OperatorType, other: Any, **kwargs + ) -> "ColumnElement": return op(other, self.comparator, **kwargs) def _bind_param(self, operator, obj, type_=None, expanding=False): @@ -975,7 +1272,12 @@ class ColumnElement( return None def _make_proxy( - self, selectable, name=None, key=None, name_is_truncatable=False, **kw + self, + selectable, + name: Optional[str] = None, + key=None, + name_is_truncatable=False, + **kw, ): """Create a new :class:`_expression.ColumnElement` representing this :class:`_expression.ColumnElement` as it appears in the select list of @@ -1031,7 +1333,7 @@ class ColumnElement( """ return Label(name, self, self.type) - def _anon_label(self, seed, add_hash=None): + def _anon_label(self, seed, add_hash=None) -> "_anonymous_label": while self._is_clone_of is not None: self = self._is_clone_of @@ -1066,7 +1368,7 @@ class ColumnElement( return _anonymous_label.safe_construct(hash_value, seed or "anon") @util.memoized_property - def _anon_name_label(self): + def _anon_name_label(self) -> "_anonymous_label": """Provides a constant 'anonymous label' for this ColumnElement. This is a label() expression which will be named at compile time. @@ -1214,7 +1516,10 @@ class WrapsColumnExpression: return self._dedupe_anon_tq_label_idx(idx) -class BindParameter(roles.InElementRole, ColumnElement): +SelfBindParameter = TypeVar("SelfBindParameter", bound="BindParameter") + + +class BindParameter(roles.InElementRole, ColumnElement[_T]): r"""Represent a "bound expression". :class:`.BindParameter` is invoked explicitly using the @@ -1267,238 +1572,6 @@ class BindParameter(roles.InElementRole, ColumnElement): _compared_to_type=None, _is_crud=False, ): - r"""Produce a "bound expression". - - The return value is an instance of :class:`.BindParameter`; this - is a :class:`_expression.ColumnElement` - subclass which represents a so-called - "placeholder" value in a SQL expression, the value of which is - supplied at the point at which the statement in executed against a - database connection. - - In SQLAlchemy, the :func:`.bindparam` construct has - the ability to carry along the actual value that will be ultimately - used at expression time. In this way, it serves not just as - a "placeholder" for eventual population, but also as a means of - representing so-called "unsafe" values which should not be rendered - directly in a SQL statement, but rather should be passed along - to the :term:`DBAPI` as values which need to be correctly escaped - and potentially handled for type-safety. - - When using :func:`.bindparam` explicitly, the use case is typically - one of traditional deferment of parameters; the :func:`.bindparam` - construct accepts a name which can then be referred to at execution - time:: - - from sqlalchemy import bindparam - - stmt = select(users_table).\ - where(users_table.c.name == bindparam('username')) - - The above statement, when rendered, will produce SQL similar to:: - - SELECT id, name FROM user WHERE name = :username - - In order to populate the value of ``:username`` above, the value - would typically be applied at execution time to a method - like :meth:`_engine.Connection.execute`:: - - result = connection.execute(stmt, username='wendy') - - Explicit use of :func:`.bindparam` is also common when producing - UPDATE or DELETE statements that are to be invoked multiple times, - where the WHERE criterion of the statement is to change on each - invocation, such as:: - - stmt = (users_table.update(). - where(user_table.c.name == bindparam('username')). - values(fullname=bindparam('fullname')) - ) - - connection.execute( - stmt, [{"username": "wendy", "fullname": "Wendy Smith"}, - {"username": "jack", "fullname": "Jack Jones"}, - ] - ) - - SQLAlchemy's Core expression system makes wide use of - :func:`.bindparam` in an implicit sense. It is typical that Python - literal values passed to virtually all SQL expression functions are - coerced into fixed :func:`.bindparam` constructs. For example, given - a comparison operation such as:: - - expr = users_table.c.name == 'Wendy' - - The above expression will produce a :class:`.BinaryExpression` - construct, where the left side is the :class:`_schema.Column` object - representing the ``name`` column, and the right side is a - :class:`.BindParameter` representing the literal value:: - - print(repr(expr.right)) - BindParameter('%(4327771088 name)s', 'Wendy', type_=String()) - - The expression above will render SQL such as:: - - user.name = :name_1 - - Where the ``:name_1`` parameter name is an anonymous name. The - actual string ``Wendy`` is not in the rendered string, but is carried - along where it is later used within statement execution. If we - invoke a statement like the following:: - - stmt = select(users_table).where(users_table.c.name == 'Wendy') - result = connection.execute(stmt) - - We would see SQL logging output as:: - - SELECT "user".id, "user".name - FROM "user" - WHERE "user".name = %(name_1)s - {'name_1': 'Wendy'} - - Above, we see that ``Wendy`` is passed as a parameter to the database, - while the placeholder ``:name_1`` is rendered in the appropriate form - for the target database, in this case the PostgreSQL database. - - Similarly, :func:`.bindparam` is invoked automatically when working - with :term:`CRUD` statements as far as the "VALUES" portion is - concerned. The :func:`_expression.insert` construct produces an - ``INSERT`` expression which will, at statement execution time, generate - bound placeholders based on the arguments passed, as in:: - - stmt = users_table.insert() - result = connection.execute(stmt, name='Wendy') - - The above will produce SQL output as:: - - INSERT INTO "user" (name) VALUES (%(name)s) - {'name': 'Wendy'} - - The :class:`_expression.Insert` construct, at - compilation/execution time, rendered a single :func:`.bindparam` - mirroring the column name ``name`` as a result of the single ``name`` - parameter we passed to the :meth:`_engine.Connection.execute` method. - - :param key: - the key (e.g. the name) for this bind param. - Will be used in the generated - SQL statement for dialects that use named parameters. This - value may be modified when part of a compilation operation, - if other :class:`BindParameter` objects exist with the same - key, or if its length is too long and truncation is - required. - - :param value: - Initial value for this bind param. Will be used at statement - execution time as the value for this parameter passed to the - DBAPI, if no other value is indicated to the statement execution - method for this particular parameter name. Defaults to ``None``. - - :param callable\_: - A callable function that takes the place of "value". The function - will be called at statement execution time to determine the - ultimate value. Used for scenarios where the actual bind - value cannot be determined at the point at which the clause - construct is created, but embedded bind values are still desirable. - - :param type\_: - A :class:`.TypeEngine` class or instance representing an optional - datatype for this :func:`.bindparam`. If not passed, a type - may be determined automatically for the bind, based on the given - value; for example, trivial Python types such as ``str``, - ``int``, ``bool`` - may result in the :class:`.String`, :class:`.Integer` or - :class:`.Boolean` types being automatically selected. - - The type of a :func:`.bindparam` is significant especially in that - the type will apply pre-processing to the value before it is - passed to the database. For example, a :func:`.bindparam` which - refers to a datetime value, and is specified as holding the - :class:`.DateTime` type, may apply conversion needed to the - value (such as stringification on SQLite) before passing the value - to the database. - - :param unique: - if True, the key name of this :class:`.BindParameter` will be - modified if another :class:`.BindParameter` of the same name - already has been located within the containing - expression. This flag is used generally by the internals - when producing so-called "anonymous" bound expressions, it - isn't generally applicable to explicitly-named :func:`.bindparam` - constructs. - - :param required: - If ``True``, a value is required at execution time. If not passed, - it defaults to ``True`` if neither :paramref:`.bindparam.value` - or :paramref:`.bindparam.callable` were passed. If either of these - parameters are present, then :paramref:`.bindparam.required` - defaults to ``False``. - - :param quote: - True if this parameter name requires quoting and is not - currently known as a SQLAlchemy reserved word; this currently - only applies to the Oracle backend, where bound names must - sometimes be quoted. - - :param isoutparam: - if True, the parameter should be treated like a stored procedure - "OUT" parameter. This applies to backends such as Oracle which - support OUT parameters. - - :param expanding: - if True, this parameter will be treated as an "expanding" parameter - at execution time; the parameter value is expected to be a sequence, - rather than a scalar value, and the string SQL statement will - be transformed on a per-execution basis to accommodate the sequence - with a variable number of parameter slots passed to the DBAPI. - This is to allow statement caching to be used in conjunction with - an IN clause. - - .. seealso:: - - :meth:`.ColumnOperators.in_` - - :ref:`baked_in` - with baked queries - - .. note:: The "expanding" feature does not support "executemany"- - style parameter sets. - - .. versionadded:: 1.2 - - .. versionchanged:: 1.3 the "expanding" bound parameter feature now - supports empty lists. - - - .. seealso:: - - :ref:`coretutorial_bind_param` - - :ref:`coretutorial_insert_expressions` - - :func:`.outparam` - - :param literal_execute: - if True, the bound parameter will be rendered in the compile phase - with a special "POSTCOMPILE" token, and the SQLAlchemy compiler will - render the final value of the parameter into the SQL statement at - statement execution time, omitting the value from the parameter - dictionary / list passed to DBAPI ``cursor.execute()``. This - produces a similar effect as that of using the ``literal_binds``, - compilation flag, however takes place as the statement is sent to - the DBAPI ``cursor.execute()`` method, rather than when the statement - is compiled. The primary use of this - capability is for rendering LIMIT / OFFSET clauses for database - drivers that can't accommodate for bound parameters in these - contexts, while allowing SQL constructs to be cacheable at the - compilation level. - - .. versionadded:: 1.4 Added "post compile" bound parameters - - .. seealso:: - - :ref:`change_4808`. - - """ if required is NO_ARG: required = value is NO_ARG and callable_ is None if value is NO_ARG: @@ -1641,7 +1714,9 @@ class BindParameter(roles.InElementRole, ColumnElement): c.type = type_ return c - def _clone(self, maintain_key=False, **kw): + def _clone( + self: SelfBindParameter, maintain_key=False, **kw + ) -> SelfBindParameter: c = ClauseElement._clone(self, **kw) if not maintain_key and self.unique: c.key = _anonymous_label.safe_construct( @@ -1799,85 +1874,6 @@ class TextClause( # to the list of bindparams self.text = self._bind_params_regex.sub(repl, text) - @classmethod - @_document_text_coercion("text", ":func:`.text`", ":paramref:`.text.text`") - def _create_text(cls, text): - r"""Construct a new :class:`_expression.TextClause` clause, - representing - a textual SQL string directly. - - E.g.:: - - from sqlalchemy import text - - t = text("SELECT * FROM users") - result = connection.execute(t) - - The advantages :func:`_expression.text` - provides over a plain string are - backend-neutral support for bind parameters, per-statement - execution options, as well as - bind parameter and result-column typing behavior, allowing - SQLAlchemy type constructs to play a role when executing - a statement that is specified literally. The construct can also - be provided with a ``.c`` collection of column elements, allowing - it to be embedded in other SQL expression constructs as a subquery. - - Bind parameters are specified by name, using the format ``:name``. - E.g.:: - - t = text("SELECT * FROM users WHERE id=:user_id") - result = connection.execute(t, user_id=12) - - For SQL statements where a colon is required verbatim, as within - an inline string, use a backslash to escape:: - - t = text("SELECT * FROM users WHERE name='\:username'") - - The :class:`_expression.TextClause` - construct includes methods which can - provide information about the bound parameters as well as the column - values which would be returned from the textual statement, assuming - it's an executable SELECT type of statement. The - :meth:`_expression.TextClause.bindparams` - method is used to provide bound - parameter detail, and :meth:`_expression.TextClause.columns` - method allows - specification of return columns including names and types:: - - t = text("SELECT * FROM users WHERE id=:user_id").\ - bindparams(user_id=7).\ - columns(id=Integer, name=String) - - for id, name in connection.execute(t): - print(id, name) - - The :func:`_expression.text` construct is used in cases when - a literal string SQL fragment is specified as part of a larger query, - such as for the WHERE clause of a SELECT statement:: - - s = select(users.c.id, users.c.name).where(text("id=:user_id")) - result = connection.execute(s, user_id=12) - - :func:`_expression.text` is also used for the construction - of a full, standalone statement using plain text. - As such, SQLAlchemy refers - to it as an :class:`.Executable` object and may be used - like any other statement passed to an ``.execute()`` method. - - :param text: - the text of the SQL statement to be created. Use ``:<param>`` - to specify bind parameters; they will be compiled to their - engine-specific format. - - .. seealso:: - - :ref:`sqlexpression_text` - in the Core tutorial - - - """ - return TextClause(text) - @_generative def bindparams( self: SelfTextClause, *binds, **names_to_values @@ -2204,40 +2200,6 @@ class False_(SingletonConstant, roles.ConstExprRole, ColumnElement): @classmethod def _instance(cls): - """Return a :class:`.False_` construct. - - E.g.:: - - >>> from sqlalchemy import false - >>> print(select(t.c.x).where(false())) - SELECT x FROM t WHERE false - - A backend which does not support true/false constants will render as - an expression against 1 or 0:: - - >>> print(select(t.c.x).where(false())) - SELECT x FROM t WHERE 0 = 1 - - The :func:`.true` and :func:`.false` constants also feature - "short circuit" operation within an :func:`.and_` or :func:`.or_` - conjunction:: - - >>> print(select(t.c.x).where(or_(t.c.x > 5, true()))) - SELECT x FROM t WHERE true - - >>> print(select(t.c.x).where(and_(t.c.x > 5, false()))) - SELECT x FROM t WHERE false - - .. versionchanged:: 0.9 :func:`.true` and :func:`.false` feature - better integrated behavior within conjunctions and on dialects - that don't support true/false constants. - - .. seealso:: - - :func:`.true` - - """ - return False_() @@ -2272,40 +2234,6 @@ class True_(SingletonConstant, roles.ConstExprRole, ColumnElement): @classmethod def _instance(cls): - """Return a constant :class:`.True_` construct. - - E.g.:: - - >>> from sqlalchemy import true - >>> print(select(t.c.x).where(true())) - SELECT x FROM t WHERE true - - A backend which does not support true/false constants will render as - an expression against 1 or 0:: - - >>> print(select(t.c.x).where(true())) - SELECT x FROM t WHERE 1 = 1 - - The :func:`.true` and :func:`.false` constants also feature - "short circuit" operation within an :func:`.and_` or :func:`.or_` - conjunction:: - - >>> print(select(t.c.x).where(or_(t.c.x > 5, true()))) - SELECT x FROM t WHERE true - - >>> print(select(t.c.x).where(and_(t.c.x > 5, false()))) - SELECT x FROM t WHERE false - - .. versionchanged:: 0.9 :func:`.true` and :func:`.false` feature - better integrated behavior within conjunctions and on dialects - that don't support true/false constants. - - .. seealso:: - - :func:`.false` - - """ - return True_() @@ -2334,15 +2262,23 @@ class ClauseList( ("operator", InternalTraversal.dp_operator), ] - def __init__(self, *clauses, **kwargs): - self.operator = kwargs.pop("operator", operators.comma_op) - self.group = kwargs.pop("group", True) - self.group_contents = kwargs.pop("group_contents", True) - if kwargs.pop("_flatten_sub_clauses", False): + def __init__( + self, + *clauses, + operator=operators.comma_op, + group=True, + group_contents=True, + _flatten_sub_clauses=False, + _literal_as_text_role: Type[roles.SQLRole] = roles.WhereHavingRole, + ): + self.operator = operator + self.group = group + self.group_contents = group_contents + if _flatten_sub_clauses: clauses = util.flatten_iterator(clauses) - self._text_converter_role = text_converter_role = kwargs.pop( - "_literal_as_text_role", roles.WhereHavingRole - ) + self._text_converter_role: Type[roles.SQLRole] = _literal_as_text_role + text_converter_role: Type[roles.SQLRole] = _literal_as_text_role + if self.group_contents: self.clauses = [ coercions.expect( @@ -2404,7 +2340,7 @@ class ClauseList( return self -class BooleanClauseList(ClauseList, ColumnElement): +class BooleanClauseList(ClauseList, ColumnElement[bool]): __visit_name__ = "clauselist" inherit_cache = True @@ -2531,60 +2467,7 @@ class BooleanClauseList(ClauseList, ColumnElement): def and_(cls, *clauses): r"""Produce a conjunction of expressions joined by ``AND``. - E.g.:: - - from sqlalchemy import and_ - - stmt = select(users_table).where( - and_( - users_table.c.name == 'wendy', - users_table.c.enrolled == True - ) - ) - - The :func:`.and_` conjunction is also available using the - Python ``&`` operator (though note that compound expressions - need to be parenthesized in order to function with Python - operator precedence behavior):: - - stmt = select(users_table).where( - (users_table.c.name == 'wendy') & - (users_table.c.enrolled == True) - ) - - The :func:`.and_` operation is also implicit in some cases; - the :meth:`_expression.Select.where` - method for example can be invoked multiple - times against a statement, which will have the effect of each - clause being combined using :func:`.and_`:: - - stmt = select(users_table).\ - where(users_table.c.name == 'wendy').\ - where(users_table.c.enrolled == True) - - The :func:`.and_` construct must be given at least one positional - argument in order to be valid; a :func:`.and_` construct with no - arguments is ambiguous. To produce an "empty" or dynamically - generated :func:`.and_` expression, from a given list of expressions, - a "default" element of ``True`` should be specified:: - - criteria = and_(True, *expressions) - - The above expression will compile to SQL as the expression ``true`` - or ``1 = 1``, depending on backend, if no other expressions are - present. If expressions are present, then the ``True`` value is - ignored as it does not affect the outcome of an AND expression that - has other elements. - - .. deprecated:: 1.4 The :func:`.and_` element now requires that at - least one argument is passed; creating the :func:`.and_` construct - with no arguments is deprecated, and will emit a deprecation warning - while continuing to produce a blank SQL string. - - .. seealso:: - - :func:`.or_` - + See :func:`_sql.and_` for full documentation. """ return cls._construct( operators.and_, True_._singleton, False_._singleton, *clauses @@ -2594,50 +2477,7 @@ class BooleanClauseList(ClauseList, ColumnElement): def or_(cls, *clauses): """Produce a conjunction of expressions joined by ``OR``. - E.g.:: - - from sqlalchemy import or_ - - stmt = select(users_table).where( - or_( - users_table.c.name == 'wendy', - users_table.c.name == 'jack' - ) - ) - - The :func:`.or_` conjunction is also available using the - Python ``|`` operator (though note that compound expressions - need to be parenthesized in order to function with Python - operator precedence behavior):: - - stmt = select(users_table).where( - (users_table.c.name == 'wendy') | - (users_table.c.name == 'jack') - ) - - The :func:`.or_` construct must be given at least one positional - argument in order to be valid; a :func:`.or_` construct with no - arguments is ambiguous. To produce an "empty" or dynamically - generated :func:`.or_` expression, from a given list of expressions, - a "default" element of ``False`` should be specified:: - - or_criteria = or_(False, *expressions) - - The above expression will compile to SQL as the expression ``false`` - or ``0 = 1``, depending on backend, if no other expressions are - present. If expressions are present, then the ``False`` value is - ignored as it does not affect the outcome of an OR expression which - has other elements. - - .. deprecated:: 1.4 The :func:`.or_` element now requires that at - least one argument is passed; creating the :func:`.or_` construct - with no arguments is deprecated, and will emit a deprecation warning - while continuing to produce a blank SQL string. - - .. seealso:: - - :func:`.and_` - + See :func:`_sql.or_` for full documentation. """ return cls._construct( operators.or_, False_._singleton, True_._singleton, *clauses @@ -2669,32 +2509,9 @@ class Tuple(ClauseList, ColumnElement): _traverse_internals = ClauseList._traverse_internals + [] @util.preload_module("sqlalchemy.sql.sqltypes") - def __init__(self, *clauses, **kw): - """Return a :class:`.Tuple`. - - Main usage is to produce a composite IN construct using - :meth:`.ColumnOperators.in_` :: - - from sqlalchemy import tuple_ - - tuple_(table.c.col1, table.c.col2).in_( - [(1, 2), (5, 12), (10, 19)] - ) - - .. versionchanged:: 1.3.6 Added support for SQLite IN tuples. - - .. warning:: - - The composite IN construct is not supported by all backends, and is - currently known to work on PostgreSQL, MySQL, and SQLite. - Unsupported backends will raise a subclass of - :class:`~sqlalchemy.exc.DBAPIError` when such an expression is - invoked. - - """ + def __init__(self, *clauses, types=None): sqltypes = util.preloaded.sql_sqltypes - types = kw.pop("types", None) if types is None: clauses = [ coercions.expect(roles.ExpressionElementRole, c) @@ -2716,7 +2533,7 @@ class Tuple(ClauseList, ColumnElement): ] self.type = sqltypes.TupleType(*[arg.type for arg in clauses]) - super(Tuple, self).__init__(*clauses, **kw) + super(Tuple, self).__init__(*clauses) @property def _select_iterable(self): @@ -2752,7 +2569,7 @@ class Tuple(ClauseList, ColumnElement): return self -class Case(ColumnElement): +class Case(ColumnElement[_T]): """Represent a ``CASE`` expression. :class:`.Case` is produced using the :func:`.case` factory function, @@ -2785,127 +2602,10 @@ class Case(ColumnElement): ("else_", InternalTraversal.dp_clauseelement), ] - def __init__(self, *whens, value=None, else_=None): - r"""Produce a ``CASE`` expression. - - The ``CASE`` construct in SQL is a conditional object that - acts somewhat analogously to an "if/then" construct in other - languages. It returns an instance of :class:`.Case`. - - :func:`.case` in its usual form is passed a series of "when" - constructs, that is, a list of conditions and results as tuples:: - - from sqlalchemy import case - - stmt = select(users_table).\ - where( - case( - (users_table.c.name == 'wendy', 'W'), - (users_table.c.name == 'jack', 'J'), - else_='E' - ) - ) - - The above statement will produce SQL resembling:: - - SELECT id, name FROM user - WHERE CASE - WHEN (name = :name_1) THEN :param_1 - WHEN (name = :name_2) THEN :param_2 - ELSE :param_3 - END - - When simple equality expressions of several values against a single - parent column are needed, :func:`.case` also has a "shorthand" format - used via the - :paramref:`.case.value` parameter, which is passed a column - expression to be compared. In this form, the :paramref:`.case.whens` - parameter is passed as a dictionary containing expressions to be - compared against keyed to result expressions. The statement below is - equivalent to the preceding statement:: - - stmt = select(users_table).\ - where( - case( - {"wendy": "W", "jack": "J"}, - value=users_table.c.name, - else_='E' - ) - ) - - The values which are accepted as result values in - :paramref:`.case.whens` as well as with :paramref:`.case.else_` are - coerced from Python literals into :func:`.bindparam` constructs. - SQL expressions, e.g. :class:`_expression.ColumnElement` constructs, - are accepted - as well. To coerce a literal string expression into a constant - expression rendered inline, use the :func:`_expression.literal_column` - construct, - as in:: - - from sqlalchemy import case, literal_column - - case( - ( - orderline.c.qty > 100, - literal_column("'greaterthan100'") - ), - ( - orderline.c.qty > 10, - literal_column("'greaterthan10'") - ), - else_=literal_column("'lessthan10'") - ) - - The above will render the given constants without using bound - parameters for the result values (but still for the comparison - values), as in:: - - CASE - WHEN (orderline.qty > :qty_1) THEN 'greaterthan100' - WHEN (orderline.qty > :qty_2) THEN 'greaterthan10' - ELSE 'lessthan10' - END - - :param \*whens: The criteria to be compared against, - :paramref:`.case.whens` accepts two different forms, based on - whether or not :paramref:`.case.value` is used. - - .. versionchanged:: 1.4 the :func:`_sql.case` - function now accepts the series of WHEN conditions positionally - - In the first form, it accepts a list of 2-tuples; each 2-tuple - consists of ``(<sql expression>, <value>)``, where the SQL - expression is a boolean expression and "value" is a resulting value, - e.g.:: - - case( - (users_table.c.name == 'wendy', 'W'), - (users_table.c.name == 'jack', 'J') - ) - - In the second form, it accepts a Python dictionary of comparison - values mapped to a resulting value; this form requires - :paramref:`.case.value` to be present, and values will be compared - using the ``==`` operator, e.g.:: - - case( - {"wendy": "W", "jack": "J"}, - value=users_table.c.name - ) - - :param value: An optional SQL expression which will be used as a - fixed "comparison point" for candidate values within a dictionary - passed to :paramref:`.case.whens`. - - :param else\_: An optional SQL expression which will be the evaluated - result of the ``CASE`` construct if all expressions within - :paramref:`.case.whens` evaluate to false. When omitted, most - databases will produce a result of NULL if none of the "when" - expressions evaluate to true. + # for case(), the type is derived from the whens. so for the moment + # users would have to cast() the case to get a specific type - - """ + def __init__(self, *whens, value=None, else_=None): whens = coercions._expression_collection_was_a_list( "whens", "case", whens @@ -2952,43 +2652,7 @@ class Case(ColumnElement): ) -def literal_column(text, type_=None): - r"""Produce a :class:`.ColumnClause` object that has the - :paramref:`_expression.column.is_literal` flag set to True. - - :func:`_expression.literal_column` is similar to - :func:`_expression.column`, except that - it is more often used as a "standalone" column expression that renders - exactly as stated; while :func:`_expression.column` - stores a string name that - will be assumed to be part of a table and may be quoted as such, - :func:`_expression.literal_column` can be that, - or any other arbitrary column-oriented - expression. - - :param text: the text of the expression; can be any SQL expression. - Quoting rules will not be applied. To specify a column-name expression - which should be subject to quoting rules, use the :func:`column` - function. - - :param type\_: an optional :class:`~sqlalchemy.types.TypeEngine` - object which will - provide result-set translation and additional expression semantics for - this column. If left as ``None`` the type will be :class:`.NullType`. - - .. seealso:: - - :func:`_expression.column` - - :func:`_expression.text` - - :ref:`sqlexpression_literal_column` - - """ - return ColumnClause(text, type_=type_, is_literal=True) - - -class Cast(WrapsColumnExpression, ColumnElement): +class Cast(WrapsColumnExpression, ColumnElement[_T]): """Represent a ``CAST`` expression. :class:`.Cast` is produced using the :func:`.cast` factory function, @@ -3020,57 +2684,6 @@ class Cast(WrapsColumnExpression, ColumnElement): ] def __init__(self, expression, type_): - r"""Produce a ``CAST`` expression. - - :func:`.cast` returns an instance of :class:`.Cast`. - - E.g.:: - - from sqlalchemy import cast, Numeric - - stmt = select(cast(product_table.c.unit_price, Numeric(10, 4))) - - The above statement will produce SQL resembling:: - - SELECT CAST(unit_price AS NUMERIC(10, 4)) FROM product - - The :func:`.cast` function performs two distinct functions when - used. The first is that it renders the ``CAST`` expression within - the resulting SQL string. The second is that it associates the given - type (e.g. :class:`.TypeEngine` class or instance) with the column - expression on the Python side, which means the expression will take - on the expression operator behavior associated with that type, - as well as the bound-value handling and result-row-handling behavior - of the type. - - .. versionchanged:: 0.9.0 :func:`.cast` now applies the given type - to the expression such that it takes effect on the bound-value, - e.g. the Python-to-database direction, in addition to the - result handling, e.g. database-to-Python, direction. - - An alternative to :func:`.cast` is the :func:`.type_coerce` function. - This function performs the second task of associating an expression - with a specific type, but does not render the ``CAST`` expression - in SQL. - - :param expression: A SQL expression, such as a - :class:`_expression.ColumnElement` - expression or a Python string which will be coerced into a bound - literal value. - - :param type\_: A :class:`.TypeEngine` class or instance indicating - the type to which the ``CAST`` should apply. - - .. seealso:: - - :ref:`coretutorial_casts` - - :func:`.type_coerce` - an alternative to CAST that coerces the type - on the Python side only, which is often sufficient to generate the - correct SQL and data coercion. - - - """ self.type = type_api.to_instance(type_) self.clause = coercions.expect( roles.ExpressionElementRole, @@ -3089,7 +2702,7 @@ class Cast(WrapsColumnExpression, ColumnElement): return self.clause -class TypeCoerce(WrapsColumnExpression, ColumnElement): +class TypeCoerce(WrapsColumnExpression, ColumnElement[_T]): """Represent a Python-side type-coercion wrapper. :class:`.TypeCoerce` supplies the :func:`_expression.type_coerce` @@ -3115,80 +2728,6 @@ class TypeCoerce(WrapsColumnExpression, ColumnElement): ] def __init__(self, expression, type_): - r"""Associate a SQL expression with a particular type, without rendering - ``CAST``. - - E.g.:: - - from sqlalchemy import type_coerce - - stmt = select(type_coerce(log_table.date_string, StringDateTime())) - - The above construct will produce a :class:`.TypeCoerce` object, which - does not modify the rendering in any way on the SQL side, with the - possible exception of a generated label if used in a columns clause - context:: - - SELECT date_string AS date_string FROM log - - When result rows are fetched, the ``StringDateTime`` type processor - will be applied to result rows on behalf of the ``date_string`` column. - - .. note:: the :func:`.type_coerce` construct does not render any - SQL syntax of its own, including that it does not imply - parenthesization. Please use :meth:`.TypeCoerce.self_group` - if explicit parenthesization is required. - - In order to provide a named label for the expression, use - :meth:`_expression.ColumnElement.label`:: - - stmt = select( - type_coerce(log_table.date_string, StringDateTime()).label('date') - ) - - - A type that features bound-value handling will also have that behavior - take effect when literal values or :func:`.bindparam` constructs are - passed to :func:`.type_coerce` as targets. - For example, if a type implements the - :meth:`.TypeEngine.bind_expression` - method or :meth:`.TypeEngine.bind_processor` method or equivalent, - these functions will take effect at statement compilation/execution - time when a literal value is passed, as in:: - - # bound-value handling of MyStringType will be applied to the - # literal value "some string" - stmt = select(type_coerce("some string", MyStringType)) - - When using :func:`.type_coerce` with composed expressions, note that - **parenthesis are not applied**. If :func:`.type_coerce` is being - used in an operator context where the parenthesis normally present from - CAST are necessary, use the :meth:`.TypeCoerce.self_group` method:: - - >>> some_integer = column("someint", Integer) - >>> some_string = column("somestr", String) - >>> expr = type_coerce(some_integer + 5, String) + some_string - >>> print(expr) - someint + :someint_1 || somestr - >>> expr = type_coerce(some_integer + 5, String).self_group() + some_string - >>> print(expr) - (someint + :someint_1) || somestr - - :param expression: A SQL expression, such as a - :class:`_expression.ColumnElement` - expression or a Python string which will be coerced into a bound - literal value. - - :param type\_: A :class:`.TypeEngine` class or instance indicating - the type to which the expression is coerced. - - .. seealso:: - - :ref:`coretutorial_casts` - - :func:`.cast` - - """ # noqa self.type = type_api.to_instance(type_) self.clause = coercions.expect( roles.ExpressionElementRole, @@ -3222,7 +2761,7 @@ class TypeCoerce(WrapsColumnExpression, ColumnElement): return self -class Extract(ColumnElement): +class Extract(ColumnElement[_T]): """Represent a SQL EXTRACT clause, ``extract(field FROM expr)``.""" __visit_name__ = "extract" @@ -3232,44 +2771,7 @@ class Extract(ColumnElement): ("field", InternalTraversal.dp_string), ] - def __init__(self, field, expr, **kwargs): - """Return a :class:`.Extract` construct. - - This is typically available as :func:`.extract` - as well as ``func.extract`` from the - :data:`.func` namespace. - - :param field: The field to extract. - - :param expr: A column or Python scalar expression serving as the - right side of the ``EXTRACT`` expression. - - E.g.:: - - from sqlalchemy import extract - from sqlalchemy import table, column - - logged_table = table("user", - column("id"), - column("date_created"), - ) - - stmt = select(logged_table.c.id).where( - extract("YEAR", logged_table.c.date_created) == 2021 - ) - - In the above example, the statement is used to select ids from the - database where the ``YEAR`` component matches a specific value. - - Similarly, one can also select an extracted component:: - - stmt = select( - extract("YEAR", logged_table.c.date_created) - ).where(logged_table.c.id == 1) - - The implementation of ``EXTRACT`` may vary across database backends. - Users are reminded to consult their database documentation. - """ + def __init__(self, field, expr): self.type = type_api.INTEGERTYPE self.field = field self.expr = coercions.expect(roles.ExpressionElementRole, expr) @@ -3314,10 +2816,10 @@ class _textual_label_reference(ColumnElement): @util.memoized_property def _text_clause(self): - return TextClause._create_text(self.element) + return TextClause(self.element) -class UnaryExpression(ColumnElement): +class UnaryExpression(ColumnElement[_T]): """Define a 'unary' expression. A unary expression has a single column expression @@ -3344,7 +2846,7 @@ class UnaryExpression(ColumnElement): element, operator=None, modifier=None, - type_=None, + type_: Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"] = None, wraps_column_expression=False, ): self.operator = operator @@ -3353,51 +2855,11 @@ class UnaryExpression(ColumnElement): self.element = element.self_group( against=self.operator or self.modifier ) - self.type = type_api.to_instance(type_) + self.type: TypeEngine[_T] = type_api.to_instance(type_) self.wraps_column_expression = wraps_column_expression @classmethod def _create_nulls_first(cls, column): - """Produce the ``NULLS FIRST`` modifier for an ``ORDER BY`` expression. - - :func:`.nulls_first` is intended to modify the expression produced - by :func:`.asc` or :func:`.desc`, and indicates how NULL values - should be handled when they are encountered during ordering:: - - - from sqlalchemy import desc, nulls_first - - stmt = select(users_table).order_by( - nulls_first(desc(users_table.c.name))) - - The SQL expression from the above would resemble:: - - SELECT id, name FROM user ORDER BY name DESC NULLS FIRST - - Like :func:`.asc` and :func:`.desc`, :func:`.nulls_first` is typically - invoked from the column expression itself using - :meth:`_expression.ColumnElement.nulls_first`, - rather than as its standalone - function version, as in:: - - stmt = select(users_table).order_by( - users_table.c.name.desc().nulls_first()) - - .. versionchanged:: 1.4 :func:`.nulls_first` is renamed from - :func:`.nullsfirst` in previous releases. - The previous name remains available for backwards compatibility. - - .. seealso:: - - :func:`.asc` - - :func:`.desc` - - :func:`.nulls_last` - - :meth:`_expression.Select.order_by` - - """ return UnaryExpression( coercions.expect(roles.ByOfRole, column), modifier=operators.nulls_first_op, @@ -3406,46 +2868,6 @@ class UnaryExpression(ColumnElement): @classmethod def _create_nulls_last(cls, column): - """Produce the ``NULLS LAST`` modifier for an ``ORDER BY`` expression. - - :func:`.nulls_last` is intended to modify the expression produced - by :func:`.asc` or :func:`.desc`, and indicates how NULL values - should be handled when they are encountered during ordering:: - - - from sqlalchemy import desc, nulls_last - - stmt = select(users_table).order_by( - nulls_last(desc(users_table.c.name))) - - The SQL expression from the above would resemble:: - - SELECT id, name FROM user ORDER BY name DESC NULLS LAST - - Like :func:`.asc` and :func:`.desc`, :func:`.nulls_last` is typically - invoked from the column expression itself using - :meth:`_expression.ColumnElement.nulls_last`, - rather than as its standalone - function version, as in:: - - stmt = select(users_table).order_by( - users_table.c.name.desc().nulls_last()) - - .. versionchanged:: 1.4 :func:`.nulls_last` is renamed from - :func:`.nullslast` in previous releases. - The previous name remains available for backwards compatibility. - - .. seealso:: - - :func:`.asc` - - :func:`.desc` - - :func:`.nulls_first` - - :meth:`_expression.Select.order_by` - - """ return UnaryExpression( coercions.expect(roles.ByOfRole, column), modifier=operators.nulls_last_op, @@ -3454,41 +2876,6 @@ class UnaryExpression(ColumnElement): @classmethod def _create_desc(cls, column): - """Produce a descending ``ORDER BY`` clause element. - - e.g.:: - - from sqlalchemy import desc - - stmt = select(users_table).order_by(desc(users_table.c.name)) - - will produce SQL as:: - - SELECT id, name FROM user ORDER BY name DESC - - The :func:`.desc` function is a standalone version of the - :meth:`_expression.ColumnElement.desc` - method available on all SQL expressions, - e.g.:: - - - stmt = select(users_table).order_by(users_table.c.name.desc()) - - :param column: A :class:`_expression.ColumnElement` (e.g. - scalar SQL expression) - with which to apply the :func:`.desc` operation. - - .. seealso:: - - :func:`.asc` - - :func:`.nulls_first` - - :func:`.nulls_last` - - :meth:`_expression.Select.order_by` - - """ return UnaryExpression( coercions.expect(roles.ByOfRole, column), modifier=operators.desc_op, @@ -3497,40 +2884,6 @@ class UnaryExpression(ColumnElement): @classmethod def _create_asc(cls, column): - """Produce an ascending ``ORDER BY`` clause element. - - e.g.:: - - from sqlalchemy import asc - stmt = select(users_table).order_by(asc(users_table.c.name)) - - will produce SQL as:: - - SELECT id, name FROM user ORDER BY name ASC - - The :func:`.asc` function is a standalone version of the - :meth:`_expression.ColumnElement.asc` - method available on all SQL expressions, - e.g.:: - - - stmt = select(users_table).order_by(users_table.c.name.asc()) - - :param column: A :class:`_expression.ColumnElement` (e.g. - scalar SQL expression) - with which to apply the :func:`.asc` operation. - - .. seealso:: - - :func:`.desc` - - :func:`.nulls_first` - - :func:`.nulls_last` - - :meth:`_expression.Select.order_by` - - """ return UnaryExpression( coercions.expect(roles.ByOfRole, column), modifier=operators.asc_op, @@ -3539,41 +2892,6 @@ class UnaryExpression(ColumnElement): @classmethod def _create_distinct(cls, expr): - """Produce an column-expression-level unary ``DISTINCT`` clause. - - This applies the ``DISTINCT`` keyword to an individual column - expression, and is typically contained within an aggregate function, - as in:: - - from sqlalchemy import distinct, func - stmt = select(func.count(distinct(users_table.c.name))) - - The above would produce an expression resembling:: - - SELECT COUNT(DISTINCT name) FROM user - - The :func:`.distinct` function is also available as a column-level - method, e.g. :meth:`_expression.ColumnElement.distinct`, as in:: - - stmt = select(func.count(users_table.c.name.distinct())) - - The :func:`.distinct` operator is different from the - :meth:`_expression.Select.distinct` method of - :class:`_expression.Select`, - which produces a ``SELECT`` statement - with ``DISTINCT`` applied to the result set as a whole, - e.g. a ``SELECT DISTINCT`` expression. See that method for further - information. - - .. seealso:: - - :meth:`_expression.ColumnElement.distinct` - - :meth:`_expression.Select.distinct` - - :data:`.func` - - """ expr = coercions.expect(roles.ExpressionElementRole, expr) return UnaryExpression( expr, @@ -3625,57 +2943,6 @@ class CollectionAggregate(UnaryExpression): @classmethod def _create_any(cls, expr): - """Produce an ANY expression. - - For dialects such as that of PostgreSQL, this operator applies - to usage of the :class:`_types.ARRAY` datatype, for that of - MySQL, it may apply to a subquery. e.g.:: - - # renders on PostgreSQL: - # '5 = ANY (somearray)' - expr = 5 == any_(mytable.c.somearray) - - # renders on MySQL: - # '5 = ANY (SELECT value FROM table)' - expr = 5 == any_(select(table.c.value)) - - Comparison to NULL may work using ``None`` or :func:`_sql.null`:: - - None == any_(mytable.c.somearray) - - The any_() / all_() operators also feature a special "operand flipping" - behavior such that if any_() / all_() are used on the left side of a - comparison using a standalone operator such as ``==``, ``!=``, etc. - (not including operator methods such as - :meth:`_sql.ColumnOperators.is_`) the rendered expression is flipped:: - - # would render '5 = ANY (column)` - any_(mytable.c.column) == 5 - - Or with ``None``, which note will not perform - the usual step of rendering "IS" as is normally the case for NULL:: - - # would render 'NULL = ANY(somearray)' - any_(mytable.c.somearray) == None - - .. versionchanged:: 1.4.26 repaired the use of any_() / all_() - comparing to NULL on the right side to be flipped to the left. - - The column-level :meth:`_sql.ColumnElement.any_` method (not to be - confused with :class:`_types.ARRAY` level - :meth:`_types.ARRAY.Comparator.any`) is shorthand for - ``any_(col)``:: - - 5 = mytable.c.somearray.any_() - - .. seealso:: - - :meth:`_sql.ColumnOperators.any_` - - :func:`_expression.all_` - - """ - expr = coercions.expect(roles.ExpressionElementRole, expr) expr = expr.self_group() @@ -3688,56 +2955,6 @@ class CollectionAggregate(UnaryExpression): @classmethod def _create_all(cls, expr): - """Produce an ALL expression. - - For dialects such as that of PostgreSQL, this operator applies - to usage of the :class:`_types.ARRAY` datatype, for that of - MySQL, it may apply to a subquery. e.g.:: - - # renders on PostgreSQL: - # '5 = ALL (somearray)' - expr = 5 == all_(mytable.c.somearray) - - # renders on MySQL: - # '5 = ALL (SELECT value FROM table)' - expr = 5 == all_(select(table.c.value)) - - Comparison to NULL may work using ``None``:: - - None == all_(mytable.c.somearray) - - The any_() / all_() operators also feature a special "operand flipping" - behavior such that if any_() / all_() are used on the left side of a - comparison using a standalone operator such as ``==``, ``!=``, etc. - (not including operator methods such as - :meth:`_sql.ColumnOperators.is_`) the rendered expression is flipped:: - - # would render '5 = ALL (column)` - all_(mytable.c.column) == 5 - - Or with ``None``, which note will not perform - the usual step of rendering "IS" as is normally the case for NULL:: - - # would render 'NULL = ALL(somearray)' - all_(mytable.c.somearray) == None - - .. versionchanged:: 1.4.26 repaired the use of any_() / all_() - comparing to NULL on the right side to be flipped to the left. - - The column-level :meth:`_sql.ColumnElement.all_` method (not to be - confused with :class:`_types.ARRAY` level - :meth:`_types.ARRAY.Comparator.all`) is shorthand for - ``all_(col)``:: - - 5 == mytable.c.somearray.all_() - - .. seealso:: - - :meth:`_sql.ColumnOperators.all_` - - :func:`_expression.any_` - - """ expr = coercions.expect(roles.ExpressionElementRole, expr) expr = expr.self_group() return CollectionAggregate( @@ -3792,7 +3009,7 @@ class AsBoolean(WrapsColumnExpression, UnaryExpression): return AsBoolean(self.element, self.negate, self.operator) -class BinaryExpression(ColumnElement): +class BinaryExpression(ColumnElement[_T]): """Represent an expression that is ``LEFT <operator> RIGHT``. A :class:`.BinaryExpression` is generated automatically @@ -3827,7 +3044,15 @@ class BinaryExpression(ColumnElement): """ def __init__( - self, left, right, operator, type_=None, negate=None, modifiers=None + self, + left: ColumnElement, + right: Union[ColumnElement, ClauseList], + operator, + type_: Optional[ + Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"] + ] = None, + negate=None, + modifiers=None, ): # allow compatibility with libraries that # refer to BinaryExpression directly and pass strings @@ -3838,7 +3063,7 @@ class BinaryExpression(ColumnElement): self.left = left.self_group(against=operator) self.right = right.self_group(against=operator) self.operator = operator - self.type = type_api.to_instance(type_) + self.type: TypeEngine[_T] = type_api.to_instance(type_) self.negate = negate self._is_implicitly_boolean = operators.is_boolean(operator) @@ -3855,6 +3080,13 @@ class BinaryExpression(ColumnElement): __nonzero__ = __bool__ + if typing.TYPE_CHECKING: + + def __invert__( + self: "BinaryExpression[_T]", + ) -> "BinaryExpression[_T]": + ... + @property def is_comparison(self): return operators.is_comparison(self.operator) @@ -3996,7 +3228,7 @@ RANGE_UNBOUNDED = util.symbol("RANGE_UNBOUNDED") RANGE_CURRENT = util.symbol("RANGE_CURRENT") -class Over(ColumnElement): +class Over(ColumnElement[_T]): """Represent an OVER clause. This is a special operator against a so-called @@ -4026,88 +3258,6 @@ class Over(ColumnElement): def __init__( self, element, partition_by=None, order_by=None, range_=None, rows=None ): - r"""Produce an :class:`.Over` object against a function. - - Used against aggregate or so-called "window" functions, - for database backends that support window functions. - - :func:`_expression.over` is usually called using - the :meth:`.FunctionElement.over` method, e.g.:: - - func.row_number().over(order_by=mytable.c.some_column) - - Would produce:: - - ROW_NUMBER() OVER(ORDER BY some_column) - - Ranges are also possible using the :paramref:`.expression.over.range_` - and :paramref:`.expression.over.rows` parameters. These - mutually-exclusive parameters each accept a 2-tuple, which contains - a combination of integers and None:: - - func.row_number().over( - order_by=my_table.c.some_column, range_=(None, 0)) - - The above would produce:: - - ROW_NUMBER() OVER(ORDER BY some_column - RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - - A value of ``None`` indicates "unbounded", a - value of zero indicates "current row", and negative / positive - integers indicate "preceding" and "following": - - * RANGE BETWEEN 5 PRECEDING AND 10 FOLLOWING:: - - func.row_number().over(order_by='x', range_=(-5, 10)) - - * ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:: - - func.row_number().over(order_by='x', rows=(None, 0)) - - * RANGE BETWEEN 2 PRECEDING AND UNBOUNDED FOLLOWING:: - - func.row_number().over(order_by='x', range_=(-2, None)) - - * RANGE BETWEEN 1 FOLLOWING AND 3 FOLLOWING:: - - func.row_number().over(order_by='x', range_=(1, 3)) - - .. versionadded:: 1.1 support for RANGE / ROWS within a window - - - :param element: a :class:`.FunctionElement`, :class:`.WithinGroup`, - or other compatible construct. - :param partition_by: a column element or string, or a list - of such, that will be used as the PARTITION BY clause - of the OVER construct. - :param order_by: a column element or string, or a list - of such, that will be used as the ORDER BY clause - of the OVER construct. - :param range\_: optional range clause for the window. This is a - tuple value which can contain integer values or ``None``, - and will render a RANGE BETWEEN PRECEDING / FOLLOWING clause. - - .. versionadded:: 1.1 - - :param rows: optional rows clause for the window. This is a tuple - value which can contain integer values or None, and will render - a ROWS BETWEEN PRECEDING / FOLLOWING clause. - - .. versionadded:: 1.1 - - This function is also available from the :data:`~.expression.func` - construct itself via the :meth:`.FunctionElement.over` method. - - .. seealso:: - - :ref:`tutorial_window_functions` - in the :ref:`unified_tutorial` - - :data:`.expression.func` - - :func:`_expression.within_group` - - """ self.element = element if order_by is not None: self.order_by = ClauseList( @@ -4191,7 +3341,7 @@ class Over(ColumnElement): ) -class WithinGroup(ColumnElement): +class WithinGroup(ColumnElement[_T]): """Represent a WITHIN GROUP (ORDER BY) clause. This is a special operator against so-called @@ -4218,44 +3368,6 @@ class WithinGroup(ColumnElement): order_by = None def __init__(self, element, *order_by): - r"""Produce a :class:`.WithinGroup` object against a function. - - Used against so-called "ordered set aggregate" and "hypothetical - set aggregate" functions, including :class:`.percentile_cont`, - :class:`.rank`, :class:`.dense_rank`, etc. - - :func:`_expression.within_group` is usually called using - the :meth:`.FunctionElement.within_group` method, e.g.:: - - from sqlalchemy import within_group - stmt = select( - department.c.id, - func.percentile_cont(0.5).within_group( - department.c.salary.desc() - ) - ) - - The above statement would produce SQL similar to - ``SELECT department.id, percentile_cont(0.5) - WITHIN GROUP (ORDER BY department.salary DESC)``. - - :param element: a :class:`.FunctionElement` construct, typically - generated by :data:`~.expression.func`. - :param \*order_by: one or more column elements that will be used - as the ORDER BY clause of the WITHIN GROUP construct. - - .. versionadded:: 1.1 - - .. seealso:: - - :ref:`tutorial_functions_within_group` - in the - :ref:`unified_tutorial` - - :data:`.expression.func` - - :func:`_expression.over` - - """ self.element = element if order_by is not None: self.order_by = ClauseList( @@ -4332,31 +3444,6 @@ class FunctionFilter(ColumnElement): criterion = None def __init__(self, func, *criterion): - """Produce a :class:`.FunctionFilter` object against a function. - - Used against aggregate and window functions, - for database backends that support the "FILTER" clause. - - E.g.:: - - from sqlalchemy import funcfilter - funcfilter(func.count(1), MyClass.name == 'some name') - - Would produce "COUNT(1) FILTER (WHERE myclass.name = 'some name')". - - This function is also available from the :data:`~.expression.func` - construct itself via the :meth:`.FunctionElement.filter` method. - - .. versionadded:: 1.0.0 - - .. seealso:: - - :ref:`tutorial_functions_within_group` - in the - :ref:`unified_tutorial` - - :meth:`.FunctionElement.filter` - - """ self.func = func self.filter(*criterion) @@ -4431,7 +3518,7 @@ class FunctionFilter(ColumnElement): ) -class Label(roles.LabeledColumnExprRole, ColumnElement): +class Label(roles.LabeledColumnExprRole, ColumnElement[_T]): """Represents a column label (AS). Represent a label, as typically applied to any column-level @@ -4448,22 +3535,6 @@ class Label(roles.LabeledColumnExprRole, ColumnElement): ] def __init__(self, name, element, type_=None): - """Return a :class:`Label` object for the - given :class:`_expression.ColumnElement`. - - A label changes the name of an element in the columns clause of a - ``SELECT`` statement, typically via the ``AS`` SQL keyword. - - This functionality is more conveniently available via the - :meth:`_expression.ColumnElement.label` method on - :class:`_expression.ColumnElement`. - - :param name: label name - - :param obj: a :class:`_expression.ColumnElement`. - - """ - orig_element = element element = coercions.expect( roles.ExpressionElementRole, @@ -4583,7 +3654,7 @@ class Label(roles.LabeledColumnExprRole, ColumnElement): return self.key, e -class NamedColumn(ColumnElement): +class NamedColumn(ColumnElement[_T]): is_literal = False table = None @@ -4673,7 +3744,7 @@ class ColumnClause( roles.LabeledColumnExprRole, roles.StrAsPlainColumnRole, Immutable, - NamedColumn, + NamedColumn[_T], ): """Represents a column expression from any textual string. @@ -4728,101 +3799,18 @@ class ColumnClause( _is_multiparam_column = False - def __init__(self, text, type_=None, is_literal=False, _selectable=None): - """Produce a :class:`.ColumnClause` object. - - The :class:`.ColumnClause` is a lightweight analogue to the - :class:`_schema.Column` class. The :func:`_expression.column` - function can - be invoked with just a name alone, as in:: - - from sqlalchemy import column - - id, name = column("id"), column("name") - stmt = select(id, name).select_from("user") - - The above statement would produce SQL like:: - - SELECT id, name FROM user - - Once constructed, :func:`_expression.column` - may be used like any other SQL - expression element such as within :func:`_expression.select` - constructs:: - - from sqlalchemy.sql import column - - id, name = column("id"), column("name") - stmt = select(id, name).select_from("user") - - The text handled by :func:`_expression.column` - is assumed to be handled - like the name of a database column; if the string contains mixed case, - special characters, or matches a known reserved word on the target - backend, the column expression will render using the quoting - behavior determined by the backend. To produce a textual SQL - expression that is rendered exactly without any quoting, - use :func:`_expression.literal_column` instead, - or pass ``True`` as the - value of :paramref:`_expression.column.is_literal`. Additionally, - full SQL - statements are best handled using the :func:`_expression.text` - construct. - - :func:`_expression.column` can be used in a table-like - fashion by combining it with the :func:`.table` function - (which is the lightweight analogue to :class:`_schema.Table` - ) to produce - a working table construct with minimal boilerplate:: - - from sqlalchemy import table, column, select - - user = table("user", - column("id"), - column("name"), - column("description"), - ) - - stmt = select(user.c.description).where(user.c.name == 'wendy') - - A :func:`_expression.column` / :func:`.table` - construct like that illustrated - above can be created in an - ad-hoc fashion and is not associated with any - :class:`_schema.MetaData`, DDL, or events, unlike its - :class:`_schema.Table` counterpart. - - .. versionchanged:: 1.0.0 :func:`_expression.column` can now - be imported from the plain ``sqlalchemy`` namespace like any - other SQL element. - - :param text: the text of the element. - - :param type: :class:`_types.TypeEngine` object which can associate - this :class:`.ColumnClause` with a type. - - :param is_literal: if True, the :class:`.ColumnClause` is assumed to - be an exact expression that will be delivered to the output with no - quoting rules applied regardless of case sensitive settings. the - :func:`_expression.literal_column()` function essentially invokes - :func:`_expression.column` while passing ``is_literal=True``. - - .. seealso:: - - :class:`_schema.Column` - - :func:`_expression.literal_column` - - :func:`.table` - - :func:`_expression.text` - - :ref:`sqlexpression_literal_column` - - """ + def __init__( + self, + text: str, + type_: Optional[ + Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"] + ] = None, + is_literal: bool = False, + _selectable: Optional["FromClause"] = None, + ): self.key = self.name = text self.table = _selectable - self.type = type_api.to_instance(type_) + self.type: TypeEngine[_T] = type_api.to_instance(type_) self.is_literal = is_literal def get_children(self, column_tables=False, **kw): @@ -5010,6 +3998,16 @@ class CollationClause(ColumnElement): _traverse_internals = [("collation", InternalTraversal.dp_string)] + @classmethod + def _create_collation_expression(cls, expression, collation): + expr = coercions.expect(roles.ExpressionElementRole, expression) + return BinaryExpression( + expr, + CollationClause(collation), + operators.collate, + type_=expression.type, + ) + def __init__(self, collation): self.collation = collation @@ -5280,7 +4278,7 @@ class _anonymous_label(_truncated_label): @classmethod def safe_construct( cls, seed, body, enclosing_label=None, sanitize_key=False - ): + ) -> "_anonymous_label": if sanitize_key: body = re.sub(r"[%\(\) \$]+", "_", body).strip("_") |