1# sql/roles.py
2# Copyright (C) 2005-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
7from __future__ import annotations
8
9from typing import Any
10from typing import Generic
11from typing import Optional
12from typing import TYPE_CHECKING
13from typing import TypeVar
14
15from .. import util
16from ..util.typing import Literal
17
18if TYPE_CHECKING:
19 from ._typing import _PropagateAttrsType
20 from .elements import Label
21 from .selectable import _SelectIterable
22 from .selectable import FromClause
23 from .selectable import Subquery
24
25_T = TypeVar("_T", bound=Any)
26_T_co = TypeVar("_T_co", bound=Any, covariant=True)
27
28
29class SQLRole:
30 """Define a "role" within a SQL statement structure.
31
32 Classes within SQL Core participate within SQLRole hierarchies in order
33 to more accurately indicate where they may be used within SQL statements
34 of all types.
35
36 .. versionadded:: 1.4
37
38 """
39
40 __slots__ = ()
41 allows_lambda = False
42 uses_inspection = False
43
44
45class SyntaxExtensionRole(SQLRole):
46 __slots__ = ()
47 _role_name = "Syntax extension construct"
48
49
50class UsesInspection:
51 __slots__ = ()
52 _post_inspect: Literal[None] = None
53 uses_inspection = True
54
55
56class AllowsLambdaRole:
57 __slots__ = ()
58 allows_lambda = True
59
60
61class HasCacheKeyRole(SQLRole):
62 __slots__ = ()
63 _role_name = "Cacheable Core or ORM object"
64
65
66class ExecutableOptionRole(SQLRole):
67 __slots__ = ()
68 _role_name = "ExecutionOption Core or ORM object"
69
70
71class LiteralValueRole(SQLRole):
72 __slots__ = ()
73 _role_name = "Literal Python value"
74
75
76class ColumnArgumentRole(SQLRole):
77 __slots__ = ()
78 _role_name = "Column expression"
79
80
81class ColumnArgumentOrKeyRole(ColumnArgumentRole):
82 __slots__ = ()
83 _role_name = "Column expression or string key"
84
85
86class StrAsPlainColumnRole(ColumnArgumentRole):
87 __slots__ = ()
88 _role_name = "Column expression or string key"
89
90
91class ColumnListRole(SQLRole):
92 """Elements suitable for forming comma separated lists of expressions."""
93
94 __slots__ = ()
95
96
97class StringRole(SQLRole):
98 """mixin indicating a role that results in strings"""
99
100 __slots__ = ()
101
102
103class TruncatedLabelRole(StringRole, SQLRole):
104 __slots__ = ()
105 _role_name = "String SQL identifier"
106
107
108class ColumnsClauseRole(AllowsLambdaRole, UsesInspection, ColumnListRole):
109 __slots__ = ()
110 _role_name = (
111 "Column expression, FROM clause, or other columns clause element"
112 )
113
114 @property
115 def _select_iterable(self) -> _SelectIterable:
116 raise NotImplementedError()
117
118
119class TypedColumnsClauseRole(Generic[_T_co], SQLRole):
120 """element-typed form of ColumnsClauseRole"""
121
122 __slots__ = ()
123
124
125class LimitOffsetRole(SQLRole):
126 __slots__ = ()
127 _role_name = "LIMIT / OFFSET expression"
128
129
130class ByOfRole(ColumnListRole):
131 __slots__ = ()
132 _role_name = "GROUP BY / OF / etc. expression"
133
134
135class GroupByRole(AllowsLambdaRole, UsesInspection, ByOfRole):
136 __slots__ = ()
137 # note there's a special case right now where you can pass a whole
138 # ORM entity to group_by() and it splits out. we may not want to keep
139 # this around
140
141 _role_name = "GROUP BY expression"
142
143
144class OrderByRole(AllowsLambdaRole, ByOfRole):
145 __slots__ = ()
146 _role_name = "ORDER BY expression"
147
148
149class StructuralRole(SQLRole):
150 __slots__ = ()
151
152
153class StatementOptionRole(StructuralRole):
154 __slots__ = ()
155 _role_name = "statement sub-expression element"
156
157
158class OnClauseRole(AllowsLambdaRole, StructuralRole):
159 __slots__ = ()
160 _role_name = (
161 "ON clause, typically a SQL expression or "
162 "ORM relationship attribute"
163 )
164
165
166class WhereHavingRole(OnClauseRole):
167 __slots__ = ()
168 _role_name = "SQL expression for WHERE/HAVING role"
169
170
171class ExpressionElementRole(TypedColumnsClauseRole[_T_co]):
172 # note when using generics for ExpressionElementRole,
173 # the generic type needs to be in
174 # sqlalchemy.sql.coercions._impl_lookup mapping also.
175 # these are set up for basic types like int, bool, str, float
176 # right now
177
178 __slots__ = ()
179 _role_name = "SQL expression element"
180
181 def label(self, name: Optional[str]) -> Label[_T]:
182 raise NotImplementedError()
183
184
185class ConstExprRole(ExpressionElementRole[_T]):
186 __slots__ = ()
187 _role_name = "Constant True/False/None expression"
188
189
190class LabeledColumnExprRole(ExpressionElementRole[_T]):
191 __slots__ = ()
192
193
194class BinaryElementRole(ExpressionElementRole[_T]):
195 __slots__ = ()
196 _role_name = "SQL expression element or literal value"
197
198
199class InElementRole(SQLRole):
200 __slots__ = ()
201 _role_name = (
202 "IN expression list, SELECT construct, or bound parameter object"
203 )
204
205
206class JoinTargetRole(AllowsLambdaRole, UsesInspection, StructuralRole):
207 __slots__ = ()
208 _role_name = (
209 "Join target, typically a FROM expression, or ORM "
210 "relationship attribute"
211 )
212
213
214class FromClauseRole(ColumnsClauseRole, JoinTargetRole):
215 __slots__ = ()
216 _role_name = "FROM expression, such as a Table or alias() object"
217
218 _is_subquery = False
219
220 named_with_column: bool
221
222
223class AnonymizedFromClauseRole(FromClauseRole):
224 __slots__ = ()
225
226 if TYPE_CHECKING:
227
228 def _anonymous_fromclause(
229 self, *, name: Optional[str] = None, flat: bool = False
230 ) -> FromClause: ...
231
232
233class ReturnsRowsRole(SQLRole):
234 __slots__ = ()
235 _role_name = (
236 "Row returning expression such as a SELECT, a FROM clause, or an "
237 "INSERT/UPDATE/DELETE with RETURNING"
238 )
239
240
241class StatementRole(SQLRole):
242 __slots__ = ()
243 _role_name = "Executable SQL or text() construct"
244
245 if TYPE_CHECKING:
246
247 @util.memoized_property
248 def _propagate_attrs(self) -> _PropagateAttrsType: ...
249
250 else:
251 _propagate_attrs = util.EMPTY_DICT
252
253
254class SelectStatementRole(StatementRole, ReturnsRowsRole):
255 __slots__ = ()
256 _role_name = "SELECT construct or equivalent text() construct"
257
258 def subquery(self) -> Subquery:
259 raise NotImplementedError(
260 "All SelectStatementRole objects should implement a "
261 ".subquery() method."
262 )
263
264
265class HasCTERole(ReturnsRowsRole):
266 __slots__ = ()
267
268
269class IsCTERole(SQLRole):
270 __slots__ = ()
271 _role_name = "CTE object"
272
273
274class CompoundElementRole(AllowsLambdaRole, SQLRole):
275 """SELECT statements inside a CompoundSelect, e.g. UNION, EXTRACT, etc."""
276
277 __slots__ = ()
278 _role_name = (
279 "SELECT construct for inclusion in a UNION or other set construct"
280 )
281
282
283# TODO: are we using this?
284class DMLRole(StatementRole):
285 __slots__ = ()
286
287
288class DMLTableRole(FromClauseRole):
289 __slots__ = ()
290 _role_name = "subject table for an INSERT, UPDATE or DELETE"
291
292
293class DMLColumnRole(SQLRole):
294 __slots__ = ()
295 _role_name = "SET/VALUES column expression or string key"
296
297
298class DMLSelectRole(SQLRole):
299 """A SELECT statement embedded in DML, typically INSERT from SELECT"""
300
301 __slots__ = ()
302 _role_name = "SELECT statement or equivalent textual object"
303
304
305class DDLRole(StatementRole):
306 __slots__ = ()
307
308
309class DDLExpressionRole(StructuralRole):
310 __slots__ = ()
311 _role_name = "SQL expression element for DDL constraint"
312
313
314class DDLConstraintColumnRole(SQLRole):
315 __slots__ = ()
316 _role_name = "String column name or column expression for DDL constraint"
317
318
319class DDLReferredColumnRole(DDLConstraintColumnRole):
320 __slots__ = ()
321 _role_name = (
322 "String column name or Column object for DDL foreign key constraint"
323 )