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_OnlyColumnArgument = Union[
190 "ColumnElement[_T]",
191 _HasClauseElement[_T],
192 roles.DMLColumnRole,
193]
194"""A narrow type that is looking for a ColumnClause (e.g. table column with a
195name) or an ORM element that produces this.
196
197This is used for constructs that need a named column to represent a
198position in a selectable, like TextClause().columns() or values(...).
199
200"""
201
202_ColumnExpressionArgument = Union[
203 "ColumnElement[_T]",
204 _HasClauseElement[_T],
205 "SQLCoreOperations[_T]",
206 roles.ExpressionElementRole[_T],
207 roles.TypedColumnsClauseRole[_T],
208 Callable[[], "ColumnElement[_T]"],
209 "LambdaElement",
210]
211"See docs in public alias ColumnExpressionArgument."
212
213ColumnExpressionArgument: TypeAlias = _ColumnExpressionArgument[_T]
214"""Narrower "column expression" argument.
215
216This type is used for all the other "column" kinds of expressions that
217typically represent a single SQL column expression, not a set of columns the
218way a table or ORM entity does.
219
220This includes ColumnElement, or ORM-mapped attributes that will have a
221``__clause_element__()`` method, it also has the ExpressionElementRole
222overall which brings in the TextClause object also.
223
224.. versionadded:: 2.0.13
225
226"""
227
228_ColumnExpressionOrLiteralArgument = Union[Any, _ColumnExpressionArgument[_T]]
229
230_ColumnExpressionOrStrLabelArgument = Union[str, _ColumnExpressionArgument[_T]]
231
232_ByArgument = Union[
233 Iterable[_ColumnExpressionOrStrLabelArgument[Any]],
234 _ColumnExpressionOrStrLabelArgument[Any],
235]
236"""Used for keyword-based ``order_by`` and ``partition_by`` parameters."""
237
238
239_InfoType = Dict[Any, Any]
240"""the .info dictionary accepted and used throughout Core /ORM"""
241
242_FromClauseArgument = Union[
243 roles.FromClauseRole,
244 Type[Any],
245 Inspectable[_HasClauseElement[Any]],
246 _HasClauseElement[Any],
247]
248"""A FROM clause, like we would send to select().select_from().
249
250Also accommodates ORM entities and related constructs.
251
252"""
253
254_JoinTargetArgument = Union[_FromClauseArgument, roles.JoinTargetRole]
255"""target for join() builds on _FromClauseArgument to include additional
256join target roles such as those which come from the ORM.
257
258"""
259
260_OnClauseArgument = Union[_ColumnExpressionArgument[Any], roles.OnClauseRole]
261"""target for an ON clause, includes additional roles such as those which
262come from the ORM.
263
264"""
265
266_SelectStatementForCompoundArgument = Union[
267 "Select[_TP]",
268 "CompoundSelect[_TP]",
269 roles.CompoundElementRole,
270]
271"""SELECT statement acceptable by ``union()`` and other SQL set operations"""
272
273_DMLColumnArgument = Union[
274 str,
275 _HasClauseElement[Any],
276 roles.DMLColumnRole,
277 "SQLCoreOperations[Any]",
278]
279"""A DML column expression. This is a "key" inside of insert().values(),
280update().values(), and related.
281
282These are usually strings or SQL table columns.
283
284There's also edge cases like JSON expression assignment, which we would want
285the DMLColumnRole to be able to accommodate.
286
287"""
288
289_DMLKey = TypeVar("_DMLKey", bound=_DMLColumnArgument)
290_DMLColumnKeyMapping = Mapping[_DMLKey, Any]
291
292
293_DDLColumnArgument = Union[str, "Column[Any]", roles.DDLConstraintColumnRole]
294"""DDL column.
295
296used for :class:`.PrimaryKeyConstraint`, :class:`.UniqueConstraint`, etc.
297
298"""
299
300_DMLTableArgument = Union[
301 "TableClause",
302 "Join",
303 "Alias",
304 "CTE",
305 Type[Any],
306 Inspectable[_HasClauseElement[Any]],
307 _HasClauseElement[Any],
308]
309
310_PropagateAttrsType = util.immutabledict[str, Any]
311
312_TypeEngineArgument = Union[Type["TypeEngine[_T]"], "TypeEngine[_T]"]
313
314_EquivalentColumnMap = Dict["ColumnElement[Any]", Set["ColumnElement[Any]"]]
315
316_LimitOffsetType = Union[int, _ColumnExpressionArgument[int], None]
317
318_AutoIncrementType = Union[bool, Literal["auto", "ignore_fk"]]
319
320_CreateDropBind = Union["Engine", "Connection", "MockConnection"]
321
322if TYPE_CHECKING:
323
324 def is_sql_compiler(c: Compiled) -> TypeGuard[SQLCompiler]: ...
325
326 def is_ddl_compiler(c: Compiled) -> TypeGuard[DDLCompiler]: ...
327
328 def is_named_from_clause(
329 t: FromClauseRole,
330 ) -> TypeGuard[NamedFromClause]: ...
331
332 def is_column_element(
333 c: ClauseElement,
334 ) -> TypeGuard[ColumnElement[Any]]: ...
335
336 def is_keyed_column_element(
337 c: ClauseElement,
338 ) -> TypeGuard[KeyedColumnElement[Any]]: ...
339
340 def is_text_clause(c: ClauseElement) -> TypeGuard[TextClause]: ...
341
342 def is_from_clause(c: ClauseElement) -> TypeGuard[FromClause]: ...
343
344 def is_tuple_type(t: TypeEngine[Any]) -> TypeGuard[TupleType]: ...
345
346 def is_table_value_type(
347 t: TypeEngine[Any],
348 ) -> TypeGuard[TableValueType]: ...
349
350 def is_selectable(t: Any) -> TypeGuard[Selectable]: ...
351
352 def is_select_base(
353 t: Union[Executable, ReturnsRows],
354 ) -> TypeGuard[SelectBase]: ...
355
356 def is_select_statement(
357 t: Union[Executable, ReturnsRows],
358 ) -> TypeGuard[Select[Any]]: ...
359
360 def is_table(t: FromClause) -> TypeGuard[TableClause]: ...
361
362 def is_subquery(t: FromClause) -> TypeGuard[Subquery]: ...
363
364 def is_dml(c: ClauseElement) -> TypeGuard[UpdateBase]: ...
365
366else:
367 is_sql_compiler = operator.attrgetter("is_sql")
368 is_ddl_compiler = operator.attrgetter("is_ddl")
369 is_named_from_clause = operator.attrgetter("named_with_column")
370 is_column_element = operator.attrgetter("_is_column_element")
371 is_keyed_column_element = operator.attrgetter("_is_keyed_column_element")
372 is_text_clause = operator.attrgetter("_is_text_clause")
373 is_from_clause = operator.attrgetter("_is_from_clause")
374 is_tuple_type = operator.attrgetter("_is_tuple_type")
375 is_table_value_type = operator.attrgetter("_is_table_value")
376 is_selectable = operator.attrgetter("is_selectable")
377 is_select_base = operator.attrgetter("_is_select_base")
378 is_select_statement = operator.attrgetter("_is_select_statement")
379 is_table = operator.attrgetter("_is_table")
380 is_subquery = operator.attrgetter("_is_subquery")
381 is_dml = operator.attrgetter("is_dml")
382
383
384def has_schema_attr(t: FromClauseRole) -> TypeGuard[TableClause]:
385 return hasattr(t, "schema")
386
387
388def is_quoted_name(s: str) -> TypeGuard[quoted_name]:
389 return hasattr(s, "quote")
390
391
392def is_has_clause_element(s: object) -> TypeGuard[_HasClauseElement[Any]]:
393 return hasattr(s, "__clause_element__")
394
395
396def is_insert_update(c: ClauseElement) -> TypeGuard[ValuesBase]:
397 return c.is_dml and (c.is_insert or c.is_update) # type: ignore
398
399
400def _no_kw() -> exc.ArgumentError:
401 return exc.ArgumentError(
402 "Additional keyword arguments are not accepted by this "
403 "function/method. The presence of **kw is for pep-484 typing purposes"
404 )
405
406
407def _unexpected_kw(methname: str, kw: Dict[str, Any]) -> NoReturn:
408 k = list(kw)[0]
409 raise TypeError(f"{methname} got an unexpected keyword argument '{k}'")
410
411
412@overload
413def Nullable(
414 val: "SQLCoreOperations[_T]",
415) -> "SQLCoreOperations[Optional[_T]]": ...
416
417
418@overload
419def Nullable(
420 val: roles.ExpressionElementRole[_T],
421) -> roles.ExpressionElementRole[Optional[_T]]: ...
422
423
424@overload
425def Nullable(val: Type[_T]) -> Type[Optional[_T]]: ...
426
427
428def Nullable(
429 val: _TypedColumnClauseArgument[_T],
430) -> _TypedColumnClauseArgument[Optional[_T]]:
431 """Types a column or ORM class as nullable.
432
433 This can be used in select and other contexts to express that the value of
434 a column can be null, for example due to an outer join::
435
436 stmt1 = select(A, Nullable(B)).outerjoin(A.bs)
437 stmt2 = select(A.data, Nullable(B.data)).outerjoin(A.bs)
438
439 At runtime this method returns the input unchanged.
440
441 .. versionadded:: 2.0.20
442 """
443 return val
444
445
446@overload
447def NotNullable(
448 val: "SQLCoreOperations[Optional[_T]]",
449) -> "SQLCoreOperations[_T]": ...
450
451
452@overload
453def NotNullable(
454 val: roles.ExpressionElementRole[Optional[_T]],
455) -> roles.ExpressionElementRole[_T]: ...
456
457
458@overload
459def NotNullable(val: Type[Optional[_T]]) -> Type[_T]: ...
460
461
462@overload
463def NotNullable(val: Optional[Type[_T]]) -> Type[_T]: ...
464
465
466def NotNullable(
467 val: Union[_TypedColumnClauseArgument[Optional[_T]], Optional[Type[_T]]],
468) -> _TypedColumnClauseArgument[_T]:
469 """Types a column or ORM class as not nullable.
470
471 This can be used in select and other contexts to express that the value of
472 a column cannot be null, for example due to a where condition on a
473 nullable column::
474
475 stmt = select(NotNullable(A.value)).where(A.value.is_not(None))
476
477 At runtime this method returns the input unchanged.
478
479 .. versionadded:: 2.0.20
480 """
481 return val # type: ignore