SQLAlchemy 2.0.38__py3-none-any.whl → 2.0.39__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlalchemy/__init__.py +1 -1
- sqlalchemy/dialects/mysql/base.py +25 -0
- sqlalchemy/dialects/postgresql/base.py +9 -6
- sqlalchemy/dialects/postgresql/json.py +52 -23
- sqlalchemy/dialects/postgresql/types.py +14 -4
- sqlalchemy/dialects/sqlite/base.py +12 -6
- sqlalchemy/engine/result.py +3 -3
- sqlalchemy/ext/asyncio/result.py +1 -0
- sqlalchemy/orm/context.py +102 -34
- sqlalchemy/orm/mapper.py +5 -4
- sqlalchemy/orm/query.py +11 -4
- sqlalchemy/pool/base.py +1 -0
- sqlalchemy/sql/_selectable_constructors.py +92 -16
- sqlalchemy/sql/_typing.py +4 -1
- sqlalchemy/sql/base.py +4 -1
- sqlalchemy/sql/compiler.py +19 -6
- sqlalchemy/sql/ddl.py +58 -8
- sqlalchemy/sql/dml.py +10 -0
- sqlalchemy/sql/elements.py +13 -2
- sqlalchemy/sql/schema.py +2 -2
- sqlalchemy/sql/selectable.py +38 -34
- sqlalchemy/sql/sqltypes.py +15 -6
- sqlalchemy/sql/type_api.py +16 -1
- sqlalchemy/testing/requirements.py +6 -0
- sqlalchemy/testing/suite/test_reflection.py +4 -0
- sqlalchemy/testing/suite/test_select.py +9 -0
- sqlalchemy/testing/suite/test_types.py +4 -0
- {SQLAlchemy-2.0.38.dist-info → sqlalchemy-2.0.39.dist-info}/METADATA +1 -1
- {SQLAlchemy-2.0.38.dist-info → sqlalchemy-2.0.39.dist-info}/RECORD +32 -32
- {SQLAlchemy-2.0.38.dist-info → sqlalchemy-2.0.39.dist-info}/WHEEL +1 -1
- {SQLAlchemy-2.0.38.dist-info → sqlalchemy-2.0.39.dist-info}/LICENSE +0 -0
- {SQLAlchemy-2.0.38.dist-info → sqlalchemy-2.0.39.dist-info}/top_level.txt +0 -0
sqlalchemy/__init__.py
CHANGED
|
@@ -182,6 +182,31 @@ For fully atomic transactions as well as support for foreign key
|
|
|
182
182
|
constraints, all participating ``CREATE TABLE`` statements must specify a
|
|
183
183
|
transactional engine, which in the vast majority of cases is ``InnoDB``.
|
|
184
184
|
|
|
185
|
+
Partitioning can similarly be specified using similar options.
|
|
186
|
+
In the example below the create table will specify ``PARTITION_BY``,
|
|
187
|
+
``PARTITIONS``, ``SUBPARTITIONS`` and ``SUBPARTITION_BY``::
|
|
188
|
+
|
|
189
|
+
# can also use mariadb_* prefix
|
|
190
|
+
Table(
|
|
191
|
+
"testtable",
|
|
192
|
+
MetaData(),
|
|
193
|
+
Column("id", Integer(), primary_key=True, autoincrement=True),
|
|
194
|
+
Column("other_id", Integer(), primary_key=True, autoincrement=False),
|
|
195
|
+
mysql_partitions="2",
|
|
196
|
+
mysql_partition_by="KEY(other_id)",
|
|
197
|
+
mysql_subpartition_by="HASH(some_expr)",
|
|
198
|
+
mysql_subpartitions="2",
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
This will render:
|
|
202
|
+
|
|
203
|
+
.. sourcecode:: sql
|
|
204
|
+
|
|
205
|
+
CREATE TABLE testtable (
|
|
206
|
+
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
207
|
+
other_id INTEGER NOT NULL,
|
|
208
|
+
PRIMARY KEY (id, other_id)
|
|
209
|
+
)PARTITION BY KEY(other_id) PARTITIONS 2 SUBPARTITION BY HASH(some_expr) SUBPARTITIONS 2
|
|
185
210
|
|
|
186
211
|
Case Sensitivity and Table Reflection
|
|
187
212
|
-------------------------------------
|
|
@@ -2001,9 +2001,10 @@ class PGCompiler(compiler.SQLCompiler):
|
|
|
2001
2001
|
for c in select._for_update_arg.of:
|
|
2002
2002
|
tables.update(sql_util.surface_selectables_only(c))
|
|
2003
2003
|
|
|
2004
|
+
of_kw = dict(kw)
|
|
2005
|
+
of_kw.update(ashint=True, use_schema=False)
|
|
2004
2006
|
tmp += " OF " + ", ".join(
|
|
2005
|
-
self.process(table,
|
|
2006
|
-
for table in tables
|
|
2007
|
+
self.process(table, **of_kw) for table in tables
|
|
2007
2008
|
)
|
|
2008
2009
|
|
|
2009
2010
|
if select._for_update_arg.nowait:
|
|
@@ -3601,6 +3602,7 @@ class PGDialect(default.DefaultDialect):
|
|
|
3601
3602
|
pg_catalog.pg_sequence.c.seqcache,
|
|
3602
3603
|
"cycle",
|
|
3603
3604
|
pg_catalog.pg_sequence.c.seqcycle,
|
|
3605
|
+
type_=sqltypes.JSON(),
|
|
3604
3606
|
)
|
|
3605
3607
|
)
|
|
3606
3608
|
.select_from(pg_catalog.pg_sequence)
|
|
@@ -5010,11 +5012,12 @@ class PGDialect(default.DefaultDialect):
|
|
|
5010
5012
|
key=lambda t: t[0],
|
|
5011
5013
|
)
|
|
5012
5014
|
for name, def_ in sorted_constraints:
|
|
5013
|
-
# constraint is in the form "CHECK (expression)"
|
|
5015
|
+
# constraint is in the form "CHECK (expression)"
|
|
5016
|
+
# or "NOT NULL". Ignore the "NOT NULL" and
|
|
5014
5017
|
# remove "CHECK (" and the tailing ")".
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
+
if def_.casefold().startswith("check"):
|
|
5019
|
+
check = def_[7:-1]
|
|
5020
|
+
constraints.append({"name": name, "check": check})
|
|
5018
5021
|
domain_rec: ReflectedDomain = {
|
|
5019
5022
|
"name": domain["name"],
|
|
5020
5023
|
"schema": domain["schema"],
|
|
@@ -4,8 +4,15 @@
|
|
|
4
4
|
#
|
|
5
5
|
# This module is part of SQLAlchemy and is released under
|
|
6
6
|
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
7
|
-
# mypy: ignore-errors
|
|
8
7
|
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
from typing import Callable
|
|
12
|
+
from typing import List
|
|
13
|
+
from typing import Optional
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
from typing import Union
|
|
9
16
|
|
|
10
17
|
from .array import ARRAY
|
|
11
18
|
from .array import array as _pg_array
|
|
@@ -21,13 +28,23 @@ from .operators import PATH_EXISTS
|
|
|
21
28
|
from .operators import PATH_MATCH
|
|
22
29
|
from ... import types as sqltypes
|
|
23
30
|
from ...sql import cast
|
|
31
|
+
from ...sql._typing import _T
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from ...engine.interfaces import Dialect
|
|
35
|
+
from ...sql.elements import ColumnElement
|
|
36
|
+
from ...sql.type_api import _BindProcessorType
|
|
37
|
+
from ...sql.type_api import _LiteralProcessorType
|
|
38
|
+
from ...sql.type_api import TypeEngine
|
|
24
39
|
|
|
25
40
|
__all__ = ("JSON", "JSONB")
|
|
26
41
|
|
|
27
42
|
|
|
28
43
|
class JSONPathType(sqltypes.JSON.JSONPathType):
|
|
29
|
-
def _processor(
|
|
30
|
-
|
|
44
|
+
def _processor(
|
|
45
|
+
self, dialect: Dialect, super_proc: Optional[Callable[[Any], Any]]
|
|
46
|
+
) -> Callable[[Any], Any]:
|
|
47
|
+
def process(value: Any) -> Any:
|
|
31
48
|
if isinstance(value, str):
|
|
32
49
|
# If it's already a string assume that it's in json path
|
|
33
50
|
# format. This allows using cast with json paths literals
|
|
@@ -44,11 +61,13 @@ class JSONPathType(sqltypes.JSON.JSONPathType):
|
|
|
44
61
|
|
|
45
62
|
return process
|
|
46
63
|
|
|
47
|
-
def bind_processor(self, dialect):
|
|
48
|
-
return self._processor(dialect, self.string_bind_processor(dialect))
|
|
64
|
+
def bind_processor(self, dialect: Dialect) -> _BindProcessorType[Any]:
|
|
65
|
+
return self._processor(dialect, self.string_bind_processor(dialect)) # type: ignore[return-value] # noqa: E501
|
|
49
66
|
|
|
50
|
-
def literal_processor(
|
|
51
|
-
|
|
67
|
+
def literal_processor(
|
|
68
|
+
self, dialect: Dialect
|
|
69
|
+
) -> _LiteralProcessorType[Any]:
|
|
70
|
+
return self._processor(dialect, self.string_literal_processor(dialect)) # type: ignore[return-value] # noqa: E501
|
|
52
71
|
|
|
53
72
|
|
|
54
73
|
class JSONPATH(JSONPathType):
|
|
@@ -148,9 +167,13 @@ class JSON(sqltypes.JSON):
|
|
|
148
167
|
""" # noqa
|
|
149
168
|
|
|
150
169
|
render_bind_cast = True
|
|
151
|
-
astext_type = sqltypes.Text()
|
|
170
|
+
astext_type: TypeEngine[str] = sqltypes.Text()
|
|
152
171
|
|
|
153
|
-
def __init__(
|
|
172
|
+
def __init__(
|
|
173
|
+
self,
|
|
174
|
+
none_as_null: bool = False,
|
|
175
|
+
astext_type: Optional[TypeEngine[str]] = None,
|
|
176
|
+
):
|
|
154
177
|
"""Construct a :class:`_types.JSON` type.
|
|
155
178
|
|
|
156
179
|
:param none_as_null: if True, persist the value ``None`` as a
|
|
@@ -175,11 +198,13 @@ class JSON(sqltypes.JSON):
|
|
|
175
198
|
if astext_type is not None:
|
|
176
199
|
self.astext_type = astext_type
|
|
177
200
|
|
|
178
|
-
class Comparator(sqltypes.JSON.Comparator):
|
|
201
|
+
class Comparator(sqltypes.JSON.Comparator[_T]):
|
|
179
202
|
"""Define comparison operations for :class:`_types.JSON`."""
|
|
180
203
|
|
|
204
|
+
type: JSON
|
|
205
|
+
|
|
181
206
|
@property
|
|
182
|
-
def astext(self):
|
|
207
|
+
def astext(self) -> ColumnElement[str]:
|
|
183
208
|
"""On an indexed expression, use the "astext" (e.g. "->>")
|
|
184
209
|
conversion when rendered in SQL.
|
|
185
210
|
|
|
@@ -193,13 +218,13 @@ class JSON(sqltypes.JSON):
|
|
|
193
218
|
|
|
194
219
|
"""
|
|
195
220
|
if isinstance(self.expr.right.type, sqltypes.JSON.JSONPathType):
|
|
196
|
-
return self.expr.left.operate(
|
|
221
|
+
return self.expr.left.operate( # type: ignore[no-any-return]
|
|
197
222
|
JSONPATH_ASTEXT,
|
|
198
223
|
self.expr.right,
|
|
199
224
|
result_type=self.type.astext_type,
|
|
200
225
|
)
|
|
201
226
|
else:
|
|
202
|
-
return self.expr.left.operate(
|
|
227
|
+
return self.expr.left.operate( # type: ignore[no-any-return]
|
|
203
228
|
ASTEXT, self.expr.right, result_type=self.type.astext_type
|
|
204
229
|
)
|
|
205
230
|
|
|
@@ -258,28 +283,30 @@ class JSONB(JSON):
|
|
|
258
283
|
|
|
259
284
|
__visit_name__ = "JSONB"
|
|
260
285
|
|
|
261
|
-
class Comparator(JSON.Comparator):
|
|
286
|
+
class Comparator(JSON.Comparator[_T]):
|
|
262
287
|
"""Define comparison operations for :class:`_types.JSON`."""
|
|
263
288
|
|
|
264
|
-
|
|
289
|
+
type: JSONB
|
|
290
|
+
|
|
291
|
+
def has_key(self, other: Any) -> ColumnElement[bool]:
|
|
265
292
|
"""Boolean expression. Test for presence of a key (equivalent of
|
|
266
293
|
the ``?`` operator). Note that the key may be a SQLA expression.
|
|
267
294
|
"""
|
|
268
295
|
return self.operate(HAS_KEY, other, result_type=sqltypes.Boolean)
|
|
269
296
|
|
|
270
|
-
def has_all(self, other):
|
|
297
|
+
def has_all(self, other: Any) -> ColumnElement[bool]:
|
|
271
298
|
"""Boolean expression. Test for presence of all keys in jsonb
|
|
272
299
|
(equivalent of the ``?&`` operator)
|
|
273
300
|
"""
|
|
274
301
|
return self.operate(HAS_ALL, other, result_type=sqltypes.Boolean)
|
|
275
302
|
|
|
276
|
-
def has_any(self, other):
|
|
303
|
+
def has_any(self, other: Any) -> ColumnElement[bool]:
|
|
277
304
|
"""Boolean expression. Test for presence of any key in jsonb
|
|
278
305
|
(equivalent of the ``?|`` operator)
|
|
279
306
|
"""
|
|
280
307
|
return self.operate(HAS_ANY, other, result_type=sqltypes.Boolean)
|
|
281
308
|
|
|
282
|
-
def contains(self, other, **kwargs):
|
|
309
|
+
def contains(self, other: Any, **kwargs: Any) -> ColumnElement[bool]:
|
|
283
310
|
"""Boolean expression. Test if keys (or array) are a superset
|
|
284
311
|
of/contained the keys of the argument jsonb expression
|
|
285
312
|
(equivalent of the ``@>`` operator).
|
|
@@ -289,7 +316,7 @@ class JSONB(JSON):
|
|
|
289
316
|
"""
|
|
290
317
|
return self.operate(CONTAINS, other, result_type=sqltypes.Boolean)
|
|
291
318
|
|
|
292
|
-
def contained_by(self, other):
|
|
319
|
+
def contained_by(self, other: Any) -> ColumnElement[bool]:
|
|
293
320
|
"""Boolean expression. Test if keys are a proper subset of the
|
|
294
321
|
keys of the argument jsonb expression
|
|
295
322
|
(equivalent of the ``<@`` operator).
|
|
@@ -298,7 +325,9 @@ class JSONB(JSON):
|
|
|
298
325
|
CONTAINED_BY, other, result_type=sqltypes.Boolean
|
|
299
326
|
)
|
|
300
327
|
|
|
301
|
-
def delete_path(
|
|
328
|
+
def delete_path(
|
|
329
|
+
self, array: Union[List[str], _pg_array[str]]
|
|
330
|
+
) -> ColumnElement[JSONB]:
|
|
302
331
|
"""JSONB expression. Deletes field or array element specified in
|
|
303
332
|
the argument array (equivalent of the ``#-`` operator).
|
|
304
333
|
|
|
@@ -308,11 +337,11 @@ class JSONB(JSON):
|
|
|
308
337
|
.. versionadded:: 2.0
|
|
309
338
|
"""
|
|
310
339
|
if not isinstance(array, _pg_array):
|
|
311
|
-
array = _pg_array(array)
|
|
340
|
+
array = _pg_array(array) # type: ignore[no-untyped-call]
|
|
312
341
|
right_side = cast(array, ARRAY(sqltypes.TEXT))
|
|
313
342
|
return self.operate(DELETE_PATH, right_side, result_type=JSONB)
|
|
314
343
|
|
|
315
|
-
def path_exists(self, other):
|
|
344
|
+
def path_exists(self, other: Any) -> ColumnElement[bool]:
|
|
316
345
|
"""Boolean expression. Test for presence of item given by the
|
|
317
346
|
argument JSONPath expression (equivalent of the ``@?`` operator).
|
|
318
347
|
|
|
@@ -322,7 +351,7 @@ class JSONB(JSON):
|
|
|
322
351
|
PATH_EXISTS, other, result_type=sqltypes.Boolean
|
|
323
352
|
)
|
|
324
353
|
|
|
325
|
-
def path_match(self, other):
|
|
354
|
+
def path_match(self, other: Any) -> ColumnElement[bool]:
|
|
326
355
|
"""Boolean expression. Test if JSONPath predicate given by the
|
|
327
356
|
argument JSONPath expression matches
|
|
328
357
|
(equivalent of the ``@@`` operator).
|
|
@@ -52,28 +52,38 @@ class BYTEA(sqltypes.LargeBinary):
|
|
|
52
52
|
__visit_name__ = "BYTEA"
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
class
|
|
55
|
+
class _NetworkAddressTypeMixin:
|
|
56
|
+
|
|
57
|
+
def coerce_compared_value(
|
|
58
|
+
self, op: Optional[OperatorType], value: Any
|
|
59
|
+
) -> TypeEngine[Any]:
|
|
60
|
+
if TYPE_CHECKING:
|
|
61
|
+
assert isinstance(self, TypeEngine)
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class INET(_NetworkAddressTypeMixin, sqltypes.TypeEngine[str]):
|
|
56
66
|
__visit_name__ = "INET"
|
|
57
67
|
|
|
58
68
|
|
|
59
69
|
PGInet = INET
|
|
60
70
|
|
|
61
71
|
|
|
62
|
-
class CIDR(sqltypes.TypeEngine[str]):
|
|
72
|
+
class CIDR(_NetworkAddressTypeMixin, sqltypes.TypeEngine[str]):
|
|
63
73
|
__visit_name__ = "CIDR"
|
|
64
74
|
|
|
65
75
|
|
|
66
76
|
PGCidr = CIDR
|
|
67
77
|
|
|
68
78
|
|
|
69
|
-
class MACADDR(sqltypes.TypeEngine[str]):
|
|
79
|
+
class MACADDR(_NetworkAddressTypeMixin, sqltypes.TypeEngine[str]):
|
|
70
80
|
__visit_name__ = "MACADDR"
|
|
71
81
|
|
|
72
82
|
|
|
73
83
|
PGMacAddr = MACADDR
|
|
74
84
|
|
|
75
85
|
|
|
76
|
-
class MACADDR8(sqltypes.TypeEngine[str]):
|
|
86
|
+
class MACADDR8(_NetworkAddressTypeMixin, sqltypes.TypeEngine[str]):
|
|
77
87
|
__visit_name__ = "MACADDR8"
|
|
78
88
|
|
|
79
89
|
|
|
@@ -1758,12 +1758,18 @@ class SQLiteDDLCompiler(compiler.DDLCompiler):
|
|
|
1758
1758
|
return text
|
|
1759
1759
|
|
|
1760
1760
|
def post_create_table(self, table):
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1761
|
+
table_options = []
|
|
1762
|
+
|
|
1763
|
+
if not table.dialect_options["sqlite"]["with_rowid"]:
|
|
1764
|
+
table_options.append("WITHOUT ROWID")
|
|
1765
|
+
|
|
1766
|
+
if table.dialect_options["sqlite"]["strict"]:
|
|
1767
|
+
table_options.append("STRICT")
|
|
1768
|
+
|
|
1769
|
+
if table_options:
|
|
1770
|
+
return "\n " + ",\n ".join(table_options)
|
|
1771
|
+
else:
|
|
1772
|
+
return ""
|
|
1767
1773
|
|
|
1768
1774
|
|
|
1769
1775
|
class SQLiteTypeCompiler(compiler.GenericTypeCompiler):
|
sqlalchemy/engine/result.py
CHANGED
|
@@ -52,11 +52,11 @@ else:
|
|
|
52
52
|
from sqlalchemy.cyextension.resultproxy import tuplegetter as tuplegetter
|
|
53
53
|
|
|
54
54
|
if typing.TYPE_CHECKING:
|
|
55
|
-
from ..sql.
|
|
55
|
+
from ..sql.elements import SQLCoreOperations
|
|
56
56
|
from ..sql.type_api import _ResultProcessorType
|
|
57
57
|
|
|
58
|
-
_KeyType = Union[str, "
|
|
59
|
-
_KeyIndexType = Union[
|
|
58
|
+
_KeyType = Union[str, "SQLCoreOperations[Any]"]
|
|
59
|
+
_KeyIndexType = Union[_KeyType, int]
|
|
60
60
|
|
|
61
61
|
# is overridden in cursor using _CursorKeyMapRecType
|
|
62
62
|
_KeyMapRecType = Any
|
sqlalchemy/ext/asyncio/result.py
CHANGED
|
@@ -93,6 +93,7 @@ class AsyncResult(_WithKeys, AsyncCommon[Row[_TP]]):
|
|
|
93
93
|
|
|
94
94
|
self._metadata = real_result._metadata
|
|
95
95
|
self._unique_filter_state = real_result._unique_filter_state
|
|
96
|
+
self._source_supports_scalars = real_result._source_supports_scalars
|
|
96
97
|
self._post_creational_filter = None
|
|
97
98
|
|
|
98
99
|
# BaseCursorResult pre-generates the "_row_getter". Use that
|
sqlalchemy/orm/context.py
CHANGED
|
@@ -148,10 +148,11 @@ class QueryContext:
|
|
|
148
148
|
def __init__(
|
|
149
149
|
self,
|
|
150
150
|
compile_state: CompileState,
|
|
151
|
-
statement: Union[Select[Any], FromStatement[Any]],
|
|
151
|
+
statement: Union[Select[Any], FromStatement[Any], UpdateBase],
|
|
152
152
|
user_passed_query: Union[
|
|
153
153
|
Select[Any],
|
|
154
154
|
FromStatement[Any],
|
|
155
|
+
UpdateBase,
|
|
155
156
|
],
|
|
156
157
|
params: _CoreSingleExecuteParams,
|
|
157
158
|
session: Session,
|
|
@@ -264,10 +265,10 @@ class AbstractORMCompileState(CompileState):
|
|
|
264
265
|
@classmethod
|
|
265
266
|
def create_for_statement(
|
|
266
267
|
cls,
|
|
267
|
-
statement:
|
|
268
|
-
compiler:
|
|
268
|
+
statement: Executable,
|
|
269
|
+
compiler: SQLCompiler,
|
|
269
270
|
**kw: Any,
|
|
270
|
-
) ->
|
|
271
|
+
) -> CompileState:
|
|
271
272
|
"""Create a context for a statement given a :class:`.Compiler`.
|
|
272
273
|
|
|
273
274
|
This method is always invoked in the context of SQLCompiler.process().
|
|
@@ -413,8 +414,8 @@ class ORMCompileState(AbstractORMCompileState):
|
|
|
413
414
|
attributes: Dict[Any, Any]
|
|
414
415
|
global_attributes: Dict[Any, Any]
|
|
415
416
|
|
|
416
|
-
statement: Union[Select[Any], FromStatement[Any]]
|
|
417
|
-
select_statement: Union[Select[Any], FromStatement[Any]]
|
|
417
|
+
statement: Union[Select[Any], FromStatement[Any], UpdateBase]
|
|
418
|
+
select_statement: Union[Select[Any], FromStatement[Any], UpdateBase]
|
|
418
419
|
_entities: List[_QueryEntity]
|
|
419
420
|
_polymorphic_adapters: Dict[_InternalEntityType, ORMAdapter]
|
|
420
421
|
compile_options: Union[
|
|
@@ -436,15 +437,30 @@ class ORMCompileState(AbstractORMCompileState):
|
|
|
436
437
|
def __init__(self, *arg, **kw):
|
|
437
438
|
raise NotImplementedError()
|
|
438
439
|
|
|
439
|
-
|
|
440
|
+
@classmethod
|
|
441
|
+
def create_for_statement(
|
|
442
|
+
cls,
|
|
443
|
+
statement: Executable,
|
|
444
|
+
compiler: SQLCompiler,
|
|
445
|
+
**kw: Any,
|
|
446
|
+
) -> ORMCompileState:
|
|
447
|
+
return cls._create_orm_context(
|
|
448
|
+
cast("Union[Select, FromStatement]", statement),
|
|
449
|
+
toplevel=not compiler.stack,
|
|
450
|
+
compiler=compiler,
|
|
451
|
+
**kw,
|
|
452
|
+
)
|
|
440
453
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
454
|
+
@classmethod
|
|
455
|
+
def _create_orm_context(
|
|
456
|
+
cls,
|
|
457
|
+
statement: Union[Select, FromStatement],
|
|
458
|
+
*,
|
|
459
|
+
toplevel: bool,
|
|
460
|
+
compiler: Optional[SQLCompiler],
|
|
461
|
+
**kw: Any,
|
|
462
|
+
) -> ORMCompileState:
|
|
463
|
+
raise NotImplementedError()
|
|
448
464
|
|
|
449
465
|
def _append_dedupe_col_collection(self, obj, col_collection):
|
|
450
466
|
dedupe = self.dedupe_columns
|
|
@@ -654,8 +670,8 @@ class ORMCompileState(AbstractORMCompileState):
|
|
|
654
670
|
)
|
|
655
671
|
|
|
656
672
|
|
|
657
|
-
class
|
|
658
|
-
"""an adapter used for the DML RETURNING
|
|
673
|
+
class _DMLReturningColFilter:
|
|
674
|
+
"""a base for an adapter used for the DML RETURNING cases
|
|
659
675
|
|
|
660
676
|
Has a subset of the interface used by
|
|
661
677
|
:class:`.ORMAdapter` and is used for :class:`._QueryEntity`
|
|
@@ -689,6 +705,21 @@ class DMLReturningColFilter:
|
|
|
689
705
|
else:
|
|
690
706
|
return None
|
|
691
707
|
|
|
708
|
+
def adapt_check_present(self, col):
|
|
709
|
+
raise NotImplementedError()
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
class _DMLBulkInsertReturningColFilter(_DMLReturningColFilter):
|
|
713
|
+
"""an adapter used for the DML RETURNING case specifically
|
|
714
|
+
for ORM bulk insert (or any hypothetical DML that is splitting out a class
|
|
715
|
+
hierarchy among multiple DML statements....ORM bulk insert is the only
|
|
716
|
+
example right now)
|
|
717
|
+
|
|
718
|
+
its main job is to limit the columns in a RETURNING to only a specific
|
|
719
|
+
mapped table in a hierarchy.
|
|
720
|
+
|
|
721
|
+
"""
|
|
722
|
+
|
|
692
723
|
def adapt_check_present(self, col):
|
|
693
724
|
mapper = self.mapper
|
|
694
725
|
prop = mapper._columntoproperty.get(col, None)
|
|
@@ -697,6 +728,30 @@ class DMLReturningColFilter:
|
|
|
697
728
|
return mapper.local_table.c.corresponding_column(col)
|
|
698
729
|
|
|
699
730
|
|
|
731
|
+
class _DMLUpdateDeleteReturningColFilter(_DMLReturningColFilter):
|
|
732
|
+
"""an adapter used for the DML RETURNING case specifically
|
|
733
|
+
for ORM enabled UPDATE/DELETE
|
|
734
|
+
|
|
735
|
+
its main job is to limit the columns in a RETURNING to include
|
|
736
|
+
only direct persisted columns from the immediate selectable, not
|
|
737
|
+
expressions like column_property(), or to also allow columns from other
|
|
738
|
+
mappers for the UPDATE..FROM use case.
|
|
739
|
+
|
|
740
|
+
"""
|
|
741
|
+
|
|
742
|
+
def adapt_check_present(self, col):
|
|
743
|
+
mapper = self.mapper
|
|
744
|
+
prop = mapper._columntoproperty.get(col, None)
|
|
745
|
+
if prop is not None:
|
|
746
|
+
# if the col is from the immediate mapper, only return a persisted
|
|
747
|
+
# column, not any kind of column_property expression
|
|
748
|
+
return mapper.persist_selectable.c.corresponding_column(col)
|
|
749
|
+
|
|
750
|
+
# if the col is from some other mapper, just return it, assume the
|
|
751
|
+
# user knows what they are doing
|
|
752
|
+
return col
|
|
753
|
+
|
|
754
|
+
|
|
700
755
|
@sql.base.CompileState.plugin_for("orm", "orm_from_statement")
|
|
701
756
|
class ORMFromStatementCompileState(ORMCompileState):
|
|
702
757
|
_from_obj_alias = None
|
|
@@ -715,12 +770,16 @@ class ORMFromStatementCompileState(ORMCompileState):
|
|
|
715
770
|
eager_joins = _EMPTY_DICT
|
|
716
771
|
|
|
717
772
|
@classmethod
|
|
718
|
-
def
|
|
773
|
+
def _create_orm_context(
|
|
719
774
|
cls,
|
|
720
|
-
|
|
775
|
+
statement: Union[Select, FromStatement],
|
|
776
|
+
*,
|
|
777
|
+
toplevel: bool,
|
|
721
778
|
compiler: Optional[SQLCompiler],
|
|
722
779
|
**kw: Any,
|
|
723
780
|
) -> ORMFromStatementCompileState:
|
|
781
|
+
statement_container = statement
|
|
782
|
+
|
|
724
783
|
assert isinstance(statement_container, FromStatement)
|
|
725
784
|
|
|
726
785
|
if compiler is not None and compiler.stack:
|
|
@@ -851,14 +910,24 @@ class ORMFromStatementCompileState(ORMCompileState):
|
|
|
851
910
|
return None
|
|
852
911
|
|
|
853
912
|
def setup_dml_returning_compile_state(self, dml_mapper):
|
|
854
|
-
"""used by BulkORMInsert
|
|
913
|
+
"""used by BulkORMInsert, Update, Delete to set up a handler
|
|
855
914
|
for RETURNING to return ORM objects and expressions
|
|
856
915
|
|
|
857
916
|
"""
|
|
858
917
|
target_mapper = self.statement._propagate_attrs.get(
|
|
859
918
|
"plugin_subject", None
|
|
860
919
|
)
|
|
861
|
-
|
|
920
|
+
|
|
921
|
+
if self.statement.is_insert:
|
|
922
|
+
adapter = _DMLBulkInsertReturningColFilter(
|
|
923
|
+
target_mapper, dml_mapper
|
|
924
|
+
)
|
|
925
|
+
elif self.statement.is_update or self.statement.is_delete:
|
|
926
|
+
adapter = _DMLUpdateDeleteReturningColFilter(
|
|
927
|
+
target_mapper, dml_mapper
|
|
928
|
+
)
|
|
929
|
+
else:
|
|
930
|
+
adapter = None
|
|
862
931
|
|
|
863
932
|
if self.compile_options._is_star and (len(self._entities) != 1):
|
|
864
933
|
raise sa_exc.CompileError(
|
|
@@ -1017,21 +1086,17 @@ class ORMSelectCompileState(ORMCompileState, SelectState):
|
|
|
1017
1086
|
_having_criteria = ()
|
|
1018
1087
|
|
|
1019
1088
|
@classmethod
|
|
1020
|
-
def
|
|
1089
|
+
def _create_orm_context(
|
|
1021
1090
|
cls,
|
|
1022
1091
|
statement: Union[Select, FromStatement],
|
|
1092
|
+
*,
|
|
1093
|
+
toplevel: bool,
|
|
1023
1094
|
compiler: Optional[SQLCompiler],
|
|
1024
1095
|
**kw: Any,
|
|
1025
1096
|
) -> ORMSelectCompileState:
|
|
1026
|
-
"""compiler hook, we arrive here from compiler.visit_select() only."""
|
|
1027
1097
|
|
|
1028
1098
|
self = cls.__new__(cls)
|
|
1029
1099
|
|
|
1030
|
-
if compiler is not None:
|
|
1031
|
-
toplevel = not compiler.stack
|
|
1032
|
-
else:
|
|
1033
|
-
toplevel = True
|
|
1034
|
-
|
|
1035
1100
|
select_statement = statement
|
|
1036
1101
|
|
|
1037
1102
|
# if we are a select() that was never a legacy Query, we won't
|
|
@@ -2535,7 +2600,7 @@ class _QueryEntity:
|
|
|
2535
2600
|
def setup_dml_returning_compile_state(
|
|
2536
2601
|
self,
|
|
2537
2602
|
compile_state: ORMCompileState,
|
|
2538
|
-
adapter:
|
|
2603
|
+
adapter: Optional[_DMLReturningColFilter],
|
|
2539
2604
|
) -> None:
|
|
2540
2605
|
raise NotImplementedError()
|
|
2541
2606
|
|
|
@@ -2737,7 +2802,7 @@ class _MapperEntity(_QueryEntity):
|
|
|
2737
2802
|
def setup_dml_returning_compile_state(
|
|
2738
2803
|
self,
|
|
2739
2804
|
compile_state: ORMCompileState,
|
|
2740
|
-
adapter:
|
|
2805
|
+
adapter: Optional[_DMLReturningColFilter],
|
|
2741
2806
|
) -> None:
|
|
2742
2807
|
loading._setup_entity_query(
|
|
2743
2808
|
compile_state,
|
|
@@ -2896,7 +2961,7 @@ class _BundleEntity(_QueryEntity):
|
|
|
2896
2961
|
def setup_dml_returning_compile_state(
|
|
2897
2962
|
self,
|
|
2898
2963
|
compile_state: ORMCompileState,
|
|
2899
|
-
adapter:
|
|
2964
|
+
adapter: Optional[_DMLReturningColFilter],
|
|
2900
2965
|
) -> None:
|
|
2901
2966
|
return self.setup_compile_state(compile_state)
|
|
2902
2967
|
|
|
@@ -3086,7 +3151,7 @@ class _RawColumnEntity(_ColumnEntity):
|
|
|
3086
3151
|
def setup_dml_returning_compile_state(
|
|
3087
3152
|
self,
|
|
3088
3153
|
compile_state: ORMCompileState,
|
|
3089
|
-
adapter:
|
|
3154
|
+
adapter: Optional[_DMLReturningColFilter],
|
|
3090
3155
|
) -> None:
|
|
3091
3156
|
return self.setup_compile_state(compile_state)
|
|
3092
3157
|
|
|
@@ -3203,10 +3268,13 @@ class _ORMColumnEntity(_ColumnEntity):
|
|
|
3203
3268
|
def setup_dml_returning_compile_state(
|
|
3204
3269
|
self,
|
|
3205
3270
|
compile_state: ORMCompileState,
|
|
3206
|
-
adapter:
|
|
3271
|
+
adapter: Optional[_DMLReturningColFilter],
|
|
3207
3272
|
) -> None:
|
|
3208
|
-
|
|
3209
|
-
column =
|
|
3273
|
+
|
|
3274
|
+
self._fetch_column = column = self.column
|
|
3275
|
+
if adapter:
|
|
3276
|
+
column = adapter(column, False)
|
|
3277
|
+
|
|
3210
3278
|
if column is not None:
|
|
3211
3279
|
compile_state.dedupe_columns.add(column)
|
|
3212
3280
|
compile_state.primary_columns.append(column)
|
sqlalchemy/orm/mapper.py
CHANGED
|
@@ -3440,7 +3440,7 @@ class Mapper(
|
|
|
3440
3440
|
|
|
3441
3441
|
def identity_key_from_row(
|
|
3442
3442
|
self,
|
|
3443
|
-
row:
|
|
3443
|
+
row: Union[Row[Any], RowMapping],
|
|
3444
3444
|
identity_token: Optional[Any] = None,
|
|
3445
3445
|
adapter: Optional[ORMAdapter] = None,
|
|
3446
3446
|
) -> _IdentityKeyType[_O]:
|
|
@@ -3459,14 +3459,15 @@ class Mapper(
|
|
|
3459
3459
|
if adapter:
|
|
3460
3460
|
pk_cols = [adapter.columns[c] for c in pk_cols]
|
|
3461
3461
|
|
|
3462
|
+
mapping: RowMapping
|
|
3462
3463
|
if hasattr(row, "_mapping"):
|
|
3463
|
-
mapping = row._mapping
|
|
3464
|
+
mapping = row._mapping
|
|
3464
3465
|
else:
|
|
3465
|
-
mapping =
|
|
3466
|
+
mapping = row # type: ignore[assignment]
|
|
3466
3467
|
|
|
3467
3468
|
return (
|
|
3468
3469
|
self._identity_class,
|
|
3469
|
-
tuple(mapping[column] for column in pk_cols),
|
|
3470
|
+
tuple(mapping[column] for column in pk_cols),
|
|
3470
3471
|
identity_token,
|
|
3471
3472
|
)
|
|
3472
3473
|
|