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