1# sql/_typing.py
2# Copyright (C) 2022-2025 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7
8from __future__ import annotations
9
10import operator
11from typing import Any
12from typing import Callable
13from typing import Dict
14from typing import Generic
15from typing import Iterable
16from typing import Mapping
17from typing import NoReturn
18from typing import Optional
19from typing import overload
20from typing import Set
21from typing import Tuple
22from typing import Type
23from typing import TYPE_CHECKING
24from typing import TypeVar
25from typing import Union
26
27from . import roles
28from .. import exc
29from .. import util
30from ..inspection import Inspectable
31from ..util.typing import Literal
32from ..util.typing import Protocol
33from ..util.typing import TypeAlias
34
35if TYPE_CHECKING:
36 from datetime import date
37 from datetime import datetime
38 from datetime import time
39 from datetime import timedelta
40 from decimal import Decimal
41 from uuid import UUID
42
43 from .base import Executable
44 from .compiler import Compiled
45 from .compiler import DDLCompiler
46 from .compiler import SQLCompiler
47 from .dml import UpdateBase
48 from .dml import ValuesBase
49 from .elements import ClauseElement
50 from .elements import ColumnElement
51 from .elements import KeyedColumnElement
52 from .elements import quoted_name
53 from .elements import SQLCoreOperations
54 from .elements import TextClause
55 from .lambdas import LambdaElement
56 from .roles import FromClauseRole
57 from .schema import Column
58 from .selectable import Alias
59 from .selectable import CompoundSelect
60 from .selectable import CTE
61 from .selectable import FromClause
62 from .selectable import Join
63 from .selectable import NamedFromClause
64 from .selectable import ReturnsRows
65 from .selectable import Select
66 from .selectable import Selectable
67 from .selectable import SelectBase
68 from .selectable import Subquery
69 from .selectable import TableClause
70 from .sqltypes import TableValueType
71 from .sqltypes import TupleType
72 from .type_api import TypeEngine
73 from ..engine import Connection
74 from ..engine import Dialect
75 from ..engine import Engine
76 from ..engine.mock import MockConnection
77 from ..util.typing import TypeGuard
78
79_T = TypeVar("_T", bound=Any)
80_T_co = TypeVar("_T_co", bound=Any, covariant=True)
81
82
83_CE = TypeVar("_CE", bound="ColumnElement[Any]")
84
85_CLE = TypeVar("_CLE", bound="ClauseElement")
86
87
88class _HasClauseElement(Protocol, Generic[_T_co]):
89 """indicates a class that has a __clause_element__() method"""
90
91 def __clause_element__(self) -> roles.ExpressionElementRole[_T_co]: ...
92
93
94class _CoreAdapterProto(Protocol):
95 """protocol for the ClauseAdapter/ColumnAdapter.traverse() method."""
96
97 def __call__(self, obj: _CE) -> _CE: ...
98
99
100class _HasDialect(Protocol):
101 """protocol for Engine/Connection-like objects that have dialect
102 attribute.
103 """
104
105 @property
106 def dialect(self) -> Dialect: ...
107
108
109# match column types that are not ORM entities
110_NOT_ENTITY = TypeVar(
111 "_NOT_ENTITY",
112 int,
113 str,
114 bool,
115 "datetime",
116 "date",
117 "time",
118 "timedelta",
119 "UUID",
120 float,
121 "Decimal",
122)
123
124_StarOrOne = Literal["*", 1]
125
126_MAYBE_ENTITY = TypeVar(
127 "_MAYBE_ENTITY",
128 roles.ColumnsClauseRole,
129 _StarOrOne,
130 Type[Any],
131 Inspectable[_HasClauseElement[Any]],
132 _HasClauseElement[Any],
133)
134
135
136# convention:
137# XYZArgument - something that the end user is passing to a public API method
138# XYZElement - the internal representation that we use for the thing.
139# the coercions system is responsible for converting from XYZArgument to
140# XYZElement.
141
142_TextCoercedExpressionArgument = Union[
143 str,
144 "TextClause",
145 "ColumnElement[_T]",
146 _HasClauseElement[_T],
147 roles.ExpressionElementRole[_T],
148]
149
150_ColumnsClauseArgument = Union[
151 roles.TypedColumnsClauseRole[_T],
152 roles.ColumnsClauseRole,
153 "SQLCoreOperations[_T]",
154 _StarOrOne,
155 Type[_T],
156 Inspectable[_HasClauseElement[_T]],
157 _HasClauseElement[_T],
158]
159"""open-ended SELECT columns clause argument.
160
161Includes column expressions, tables, ORM mapped entities, a few literal values.
162
163This type is used for lists of columns / entities to be returned in result
164sets; select(...), insert().returning(...), etc.
165
166
167"""
168
169_TypedColumnClauseArgument = Union[
170 roles.TypedColumnsClauseRole[_T],
171 "SQLCoreOperations[_T]",
172 Type[_T],
173]
174
175_TP = TypeVar("_TP", bound=Tuple[Any, ...])
176
177_T0 = TypeVar("_T0", bound=Any)
178_T1 = TypeVar("_T1", bound=Any)
179_T2 = TypeVar("_T2", bound=Any)
180_T3 = TypeVar("_T3", bound=Any)
181_T4 = TypeVar("_T4", bound=Any)
182_T5 = TypeVar("_T5", bound=Any)
183_T6 = TypeVar("_T6", bound=Any)
184_T7 = TypeVar("_T7", bound=Any)
185_T8 = TypeVar("_T8", bound=Any)
186_T9 = TypeVar("_T9", bound=Any)
187
188
189_ColumnExpressionArgument = Union[
190 "ColumnElement[_T]",
191 _HasClauseElement[_T],
192 "SQLCoreOperations[_T]",
193 roles.ExpressionElementRole[_T],
194 roles.TypedColumnsClauseRole[_T],
195 Callable[[], "ColumnElement[_T]"],
196 "LambdaElement",
197]
198"See docs in public alias ColumnExpressionArgument."
199
200ColumnExpressionArgument: TypeAlias = _ColumnExpressionArgument[_T]
201"""Narrower "column expression" argument.
202
203This type is used for all the other "column" kinds of expressions that
204typically represent a single SQL column expression, not a set of columns the
205way a table or ORM entity does.
206
207This includes ColumnElement, or ORM-mapped attributes that will have a
208``__clause_element__()`` method, it also has the ExpressionElementRole
209overall which brings in the TextClause object also.
210
211.. versionadded:: 2.0.13
212
213"""
214
215_ColumnExpressionOrLiteralArgument = Union[Any, _ColumnExpressionArgument[_T]]
216
217_ColumnExpressionOrStrLabelArgument = Union[str, _ColumnExpressionArgument[_T]]
218
219_ByArgument = Union[
220 Iterable[_ColumnExpressionOrStrLabelArgument[Any]],
221 _ColumnExpressionOrStrLabelArgument[Any],
222]
223"""Used for keyword-based ``order_by`` and ``partition_by`` parameters."""
224
225
226_InfoType = Dict[Any, Any]
227"""the .info dictionary accepted and used throughout Core /ORM"""
228
229_FromClauseArgument = Union[
230 roles.FromClauseRole,
231 Type[Any],
232 Inspectable[_HasClauseElement[Any]],
233 _HasClauseElement[Any],
234]
235"""A FROM clause, like we would send to select().select_from().
236
237Also accommodates ORM entities and related constructs.
238
239"""
240
241_JoinTargetArgument = Union[_FromClauseArgument, roles.JoinTargetRole]
242"""target for join() builds on _FromClauseArgument to include additional
243join target roles such as those which come from the ORM.
244
245"""
246
247_OnClauseArgument = Union[_ColumnExpressionArgument[Any], roles.OnClauseRole]
248"""target for an ON clause, includes additional roles such as those which
249come from the ORM.
250
251"""
252
253_SelectStatementForCompoundArgument = Union[
254 "Select[_TP]",
255 "CompoundSelect[_TP]",
256 roles.CompoundElementRole,
257]
258"""SELECT statement acceptable by ``union()`` and other SQL set operations"""
259
260_DMLColumnArgument = Union[
261 str,
262 _HasClauseElement[Any],
263 roles.DMLColumnRole,
264 "SQLCoreOperations[Any]",
265]
266"""A DML column expression. This is a "key" inside of insert().values(),
267update().values(), and related.
268
269These are usually strings or SQL table columns.
270
271There's also edge cases like JSON expression assignment, which we would want
272the DMLColumnRole to be able to accommodate.
273
274"""
275
276_DMLKey = TypeVar("_DMLKey", bound=_DMLColumnArgument)
277_DMLColumnKeyMapping = Mapping[_DMLKey, Any]
278
279
280_DDLColumnArgument = Union[str, "Column[Any]", roles.DDLConstraintColumnRole]
281"""DDL column.
282
283used for :class:`.PrimaryKeyConstraint`, :class:`.UniqueConstraint`, etc.
284
285"""
286
287_DMLTableArgument = Union[
288 "TableClause",
289 "Join",
290 "Alias",
291 "CTE",
292 Type[Any],
293 Inspectable[_HasClauseElement[Any]],
294 _HasClauseElement[Any],
295]
296
297_PropagateAttrsType = util.immutabledict[str, Any]
298
299_TypeEngineArgument = Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"]
300
301_EquivalentColumnMap = Dict["ColumnElement[Any]", Set["ColumnElement[Any]"]]
302
303_LimitOffsetType = Union[int, _ColumnExpressionArgument[int], None]
304
305_AutoIncrementType = Union[bool, Literal["auto", "ignore_fk"]]
306
307_CreateDropBind = Union["Engine", "Connection", "MockConnection"]
308
309if TYPE_CHECKING:
310
311 def is_sql_compiler(c: Compiled) -> TypeGuard[SQLCompiler]: ...
312
313 def is_ddl_compiler(c: Compiled) -> TypeGuard[DDLCompiler]: ...
314
315 def is_named_from_clause(
316 t: FromClauseRole,
317 ) -> TypeGuard[NamedFromClause]: ...
318
319 def is_column_element(
320 c: ClauseElement,
321 ) -> TypeGuard[ColumnElement[Any]]: ...
322
323 def is_keyed_column_element(
324 c: ClauseElement,
325 ) -> TypeGuard[KeyedColumnElement[Any]]: ...
326
327 def is_text_clause(c: ClauseElement) -> TypeGuard[TextClause]: ...
328
329 def is_from_clause(c: ClauseElement) -> TypeGuard[FromClause]: ...
330
331 def is_tuple_type(t: TypeEngine[Any]) -> TypeGuard[TupleType]: ...
332
333 def is_table_value_type(
334 t: TypeEngine[Any],
335 ) -> TypeGuard[TableValueType]: ...
336
337 def is_selectable(t: Any) -> TypeGuard[Selectable]: ...
338
339 def is_select_base(
340 t: Union[Executable, ReturnsRows]
341 ) -> TypeGuard[SelectBase]: ...
342
343 def is_select_statement(
344 t: Union[Executable, ReturnsRows]
345 ) -> TypeGuard[Select[Any]]: ...
346
347 def is_table(t: FromClause) -> TypeGuard[TableClause]: ...
348
349 def is_subquery(t: FromClause) -> TypeGuard[Subquery]: ...
350
351 def is_dml(c: ClauseElement) -> TypeGuard[UpdateBase]: ...
352
353else:
354 is_sql_compiler = operator.attrgetter("is_sql")
355 is_ddl_compiler = operator.attrgetter("is_ddl")
356 is_named_from_clause = operator.attrgetter("named_with_column")
357 is_column_element = operator.attrgetter("_is_column_element")
358 is_keyed_column_element = operator.attrgetter("_is_keyed_column_element")
359 is_text_clause = operator.attrgetter("_is_text_clause")
360 is_from_clause = operator.attrgetter("_is_from_clause")
361 is_tuple_type = operator.attrgetter("_is_tuple_type")
362 is_table_value_type = operator.attrgetter("_is_table_value")
363 is_selectable = operator.attrgetter("is_selectable")
364 is_select_base = operator.attrgetter("_is_select_base")
365 is_select_statement = operator.attrgetter("_is_select_statement")
366 is_table = operator.attrgetter("_is_table")
367 is_subquery = operator.attrgetter("_is_subquery")
368 is_dml = operator.attrgetter("is_dml")
369
370
371def has_schema_attr(t: FromClauseRole) -> TypeGuard[TableClause]:
372 return hasattr(t, "schema")
373
374
375def is_quoted_name(s: str) -> TypeGuard[quoted_name]:
376 return hasattr(s, "quote")
377
378
379def is_has_clause_element(s: object) -> TypeGuard[_HasClauseElement[Any]]:
380 return hasattr(s, "__clause_element__")
381
382
383def is_insert_update(c: ClauseElement) -> TypeGuard[ValuesBase]:
384 return c.is_dml and (c.is_insert or c.is_update) # type: ignore
385
386
387def _no_kw() -> exc.ArgumentError:
388 return exc.ArgumentError(
389 "Additional keyword arguments are not accepted by this "
390 "function/method. The presence of **kw is for pep-484 typing purposes"
391 )
392
393
394def _unexpected_kw(methname: str, kw: Dict[str, Any]) -> NoReturn:
395 k = list(kw)[0]
396 raise TypeError(f"{methname} got an unexpected keyword argument '{k}'")
397
398
399@overload
400def Nullable(
401 val: "SQLCoreOperations[_T]",
402) -> "SQLCoreOperations[Optional[_T]]": ...
403
404
405@overload
406def Nullable(
407 val: roles.ExpressionElementRole[_T],
408) -> roles.ExpressionElementRole[Optional[_T]]: ...
409
410
411@overload
412def Nullable(val: Type[_T]) -> Type[Optional[_T]]: ...
413
414
415def Nullable(
416 val: _TypedColumnClauseArgument[_T],
417) -> _TypedColumnClauseArgument[Optional[_T]]:
418 """Types a column or ORM class as nullable.
419
420 This can be used in select and other contexts to express that the value of
421 a column can be null, for example due to an outer join::
422
423 stmt1 = select(A, Nullable(B)).outerjoin(A.bs)
424 stmt2 = select(A.data, Nullable(B.data)).outerjoin(A.bs)
425
426 At runtime this method returns the input unchanged.
427
428 .. versionadded:: 2.0.20
429 """
430 return val
431
432
433@overload
434def NotNullable(
435 val: "SQLCoreOperations[Optional[_T]]",
436) -> "SQLCoreOperations[_T]": ...
437
438
439@overload
440def NotNullable(
441 val: roles.ExpressionElementRole[Optional[_T]],
442) -> roles.ExpressionElementRole[_T]: ...
443
444
445@overload
446def NotNullable(val: Type[Optional[_T]]) -> Type[_T]: ...
447
448
449@overload
450def NotNullable(val: Optional[Type[_T]]) -> Type[_T]: ...
451
452
453def NotNullable(
454 val: Union[_TypedColumnClauseArgument[Optional[_T]], Optional[Type[_T]]],
455) -> _TypedColumnClauseArgument[_T]:
456 """Types a column or ORM class as not nullable.
457
458 This can be used in select and other contexts to express that the value of
459 a column cannot be null, for example due to a where condition on a
460 nullable column::
461
462 stmt = select(NotNullable(A.value)).where(A.value.is_not(None))
463
464 At runtime this method returns the input unchanged.
465
466 .. versionadded:: 2.0.20
467 """
468 return val # type: ignore