SQLAlchemy 2.0.47__cp313-cp313t-win32.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 +283 -0
- sqlalchemy/connectors/__init__.py +18 -0
- sqlalchemy/connectors/aioodbc.py +184 -0
- sqlalchemy/connectors/asyncio.py +429 -0
- sqlalchemy/connectors/pyodbc.py +250 -0
- sqlalchemy/cyextension/__init__.py +6 -0
- sqlalchemy/cyextension/collections.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/collections.pyx +409 -0
- sqlalchemy/cyextension/immutabledict.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/immutabledict.pxd +8 -0
- sqlalchemy/cyextension/immutabledict.pyx +133 -0
- sqlalchemy/cyextension/processors.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/processors.pyx +68 -0
- sqlalchemy/cyextension/resultproxy.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/resultproxy.pyx +102 -0
- sqlalchemy/cyextension/util.cp313t-win32.pyd +0 -0
- sqlalchemy/cyextension/util.pyx +90 -0
- sqlalchemy/dialects/__init__.py +62 -0
- sqlalchemy/dialects/_typing.py +30 -0
- sqlalchemy/dialects/mssql/__init__.py +88 -0
- sqlalchemy/dialects/mssql/aioodbc.py +63 -0
- sqlalchemy/dialects/mssql/base.py +4093 -0
- sqlalchemy/dialects/mssql/information_schema.py +285 -0
- sqlalchemy/dialects/mssql/json.py +129 -0
- sqlalchemy/dialects/mssql/provision.py +185 -0
- sqlalchemy/dialects/mssql/pymssql.py +126 -0
- sqlalchemy/dialects/mssql/pyodbc.py +760 -0
- sqlalchemy/dialects/mysql/__init__.py +104 -0
- sqlalchemy/dialects/mysql/aiomysql.py +250 -0
- sqlalchemy/dialects/mysql/asyncmy.py +231 -0
- sqlalchemy/dialects/mysql/base.py +3949 -0
- sqlalchemy/dialects/mysql/cymysql.py +106 -0
- sqlalchemy/dialects/mysql/dml.py +225 -0
- sqlalchemy/dialects/mysql/enumerated.py +282 -0
- sqlalchemy/dialects/mysql/expression.py +146 -0
- sqlalchemy/dialects/mysql/json.py +91 -0
- sqlalchemy/dialects/mysql/mariadb.py +72 -0
- sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
- sqlalchemy/dialects/mysql/mysqldb.py +314 -0
- sqlalchemy/dialects/mysql/provision.py +153 -0
- sqlalchemy/dialects/mysql/pymysql.py +158 -0
- sqlalchemy/dialects/mysql/pyodbc.py +157 -0
- sqlalchemy/dialects/mysql/reflection.py +727 -0
- sqlalchemy/dialects/mysql/reserved_words.py +570 -0
- sqlalchemy/dialects/mysql/types.py +835 -0
- sqlalchemy/dialects/oracle/__init__.py +81 -0
- sqlalchemy/dialects/oracle/base.py +3802 -0
- sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
- sqlalchemy/dialects/oracle/dictionary.py +507 -0
- sqlalchemy/dialects/oracle/oracledb.py +941 -0
- sqlalchemy/dialects/oracle/provision.py +297 -0
- sqlalchemy/dialects/oracle/types.py +316 -0
- sqlalchemy/dialects/oracle/vector.py +365 -0
- sqlalchemy/dialects/postgresql/__init__.py +167 -0
- sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
- sqlalchemy/dialects/postgresql/array.py +519 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
- sqlalchemy/dialects/postgresql/base.py +5378 -0
- sqlalchemy/dialects/postgresql/dml.py +339 -0
- sqlalchemy/dialects/postgresql/ext.py +540 -0
- sqlalchemy/dialects/postgresql/hstore.py +406 -0
- sqlalchemy/dialects/postgresql/json.py +404 -0
- sqlalchemy/dialects/postgresql/named_types.py +524 -0
- sqlalchemy/dialects/postgresql/operators.py +129 -0
- sqlalchemy/dialects/postgresql/pg8000.py +669 -0
- sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
- sqlalchemy/dialects/postgresql/provision.py +183 -0
- sqlalchemy/dialects/postgresql/psycopg.py +862 -0
- sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
- sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
- sqlalchemy/dialects/postgresql/ranges.py +1031 -0
- sqlalchemy/dialects/postgresql/types.py +313 -0
- sqlalchemy/dialects/sqlite/__init__.py +57 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
- sqlalchemy/dialects/sqlite/base.py +3056 -0
- sqlalchemy/dialects/sqlite/dml.py +263 -0
- sqlalchemy/dialects/sqlite/json.py +92 -0
- sqlalchemy/dialects/sqlite/provision.py +229 -0
- sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
- sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
- sqlalchemy/engine/__init__.py +62 -0
- sqlalchemy/engine/_py_processors.py +136 -0
- sqlalchemy/engine/_py_row.py +128 -0
- sqlalchemy/engine/_py_util.py +74 -0
- sqlalchemy/engine/base.py +3390 -0
- sqlalchemy/engine/characteristics.py +155 -0
- sqlalchemy/engine/create.py +893 -0
- sqlalchemy/engine/cursor.py +2298 -0
- sqlalchemy/engine/default.py +2394 -0
- sqlalchemy/engine/events.py +965 -0
- sqlalchemy/engine/interfaces.py +3471 -0
- sqlalchemy/engine/mock.py +134 -0
- sqlalchemy/engine/processors.py +61 -0
- sqlalchemy/engine/reflection.py +2102 -0
- sqlalchemy/engine/result.py +2399 -0
- sqlalchemy/engine/row.py +400 -0
- sqlalchemy/engine/strategies.py +16 -0
- sqlalchemy/engine/url.py +924 -0
- sqlalchemy/engine/util.py +167 -0
- sqlalchemy/event/__init__.py +26 -0
- sqlalchemy/event/api.py +220 -0
- sqlalchemy/event/attr.py +676 -0
- sqlalchemy/event/base.py +472 -0
- sqlalchemy/event/legacy.py +258 -0
- sqlalchemy/event/registry.py +390 -0
- sqlalchemy/events.py +17 -0
- sqlalchemy/exc.py +832 -0
- sqlalchemy/ext/__init__.py +11 -0
- sqlalchemy/ext/associationproxy.py +2027 -0
- sqlalchemy/ext/asyncio/__init__.py +25 -0
- sqlalchemy/ext/asyncio/base.py +281 -0
- sqlalchemy/ext/asyncio/engine.py +1471 -0
- sqlalchemy/ext/asyncio/exc.py +21 -0
- sqlalchemy/ext/asyncio/result.py +965 -0
- sqlalchemy/ext/asyncio/scoping.py +1599 -0
- sqlalchemy/ext/asyncio/session.py +1947 -0
- sqlalchemy/ext/automap.py +1701 -0
- sqlalchemy/ext/baked.py +570 -0
- sqlalchemy/ext/compiler.py +600 -0
- sqlalchemy/ext/declarative/__init__.py +65 -0
- sqlalchemy/ext/declarative/extensions.py +564 -0
- sqlalchemy/ext/horizontal_shard.py +478 -0
- sqlalchemy/ext/hybrid.py +1535 -0
- sqlalchemy/ext/indexable.py +364 -0
- sqlalchemy/ext/instrumentation.py +450 -0
- sqlalchemy/ext/mutable.py +1085 -0
- sqlalchemy/ext/mypy/__init__.py +6 -0
- sqlalchemy/ext/mypy/apply.py +324 -0
- sqlalchemy/ext/mypy/decl_class.py +515 -0
- sqlalchemy/ext/mypy/infer.py +590 -0
- sqlalchemy/ext/mypy/names.py +335 -0
- sqlalchemy/ext/mypy/plugin.py +303 -0
- sqlalchemy/ext/mypy/util.py +357 -0
- sqlalchemy/ext/orderinglist.py +439 -0
- sqlalchemy/ext/serializer.py +185 -0
- sqlalchemy/future/__init__.py +16 -0
- sqlalchemy/future/engine.py +15 -0
- sqlalchemy/inspection.py +174 -0
- sqlalchemy/log.py +288 -0
- sqlalchemy/orm/__init__.py +171 -0
- sqlalchemy/orm/_orm_constructors.py +2661 -0
- sqlalchemy/orm/_typing.py +179 -0
- sqlalchemy/orm/attributes.py +2845 -0
- sqlalchemy/orm/base.py +971 -0
- sqlalchemy/orm/bulk_persistence.py +2135 -0
- sqlalchemy/orm/clsregistry.py +571 -0
- sqlalchemy/orm/collections.py +1627 -0
- sqlalchemy/orm/context.py +3334 -0
- sqlalchemy/orm/decl_api.py +2004 -0
- sqlalchemy/orm/decl_base.py +2192 -0
- sqlalchemy/orm/dependency.py +1302 -0
- sqlalchemy/orm/descriptor_props.py +1092 -0
- sqlalchemy/orm/dynamic.py +300 -0
- sqlalchemy/orm/evaluator.py +379 -0
- sqlalchemy/orm/events.py +3252 -0
- sqlalchemy/orm/exc.py +237 -0
- sqlalchemy/orm/identity.py +302 -0
- sqlalchemy/orm/instrumentation.py +754 -0
- sqlalchemy/orm/interfaces.py +1496 -0
- sqlalchemy/orm/loading.py +1686 -0
- sqlalchemy/orm/mapped_collection.py +557 -0
- sqlalchemy/orm/mapper.py +4444 -0
- sqlalchemy/orm/path_registry.py +809 -0
- sqlalchemy/orm/persistence.py +1788 -0
- sqlalchemy/orm/properties.py +935 -0
- sqlalchemy/orm/query.py +3459 -0
- sqlalchemy/orm/relationships.py +3508 -0
- sqlalchemy/orm/scoping.py +2148 -0
- sqlalchemy/orm/session.py +5280 -0
- sqlalchemy/orm/state.py +1168 -0
- sqlalchemy/orm/state_changes.py +196 -0
- sqlalchemy/orm/strategies.py +3470 -0
- sqlalchemy/orm/strategy_options.py +2568 -0
- sqlalchemy/orm/sync.py +164 -0
- sqlalchemy/orm/unitofwork.py +796 -0
- sqlalchemy/orm/util.py +2403 -0
- sqlalchemy/orm/writeonly.py +674 -0
- sqlalchemy/pool/__init__.py +44 -0
- sqlalchemy/pool/base.py +1524 -0
- sqlalchemy/pool/events.py +375 -0
- sqlalchemy/pool/impl.py +588 -0
- sqlalchemy/py.typed +0 -0
- sqlalchemy/schema.py +69 -0
- sqlalchemy/sql/__init__.py +145 -0
- sqlalchemy/sql/_dml_constructors.py +132 -0
- sqlalchemy/sql/_elements_constructors.py +1872 -0
- sqlalchemy/sql/_orm_types.py +20 -0
- sqlalchemy/sql/_py_util.py +75 -0
- sqlalchemy/sql/_selectable_constructors.py +763 -0
- sqlalchemy/sql/_typing.py +482 -0
- sqlalchemy/sql/annotation.py +587 -0
- sqlalchemy/sql/base.py +2293 -0
- sqlalchemy/sql/cache_key.py +1057 -0
- sqlalchemy/sql/coercions.py +1404 -0
- sqlalchemy/sql/compiler.py +8081 -0
- sqlalchemy/sql/crud.py +1752 -0
- sqlalchemy/sql/ddl.py +1444 -0
- sqlalchemy/sql/default_comparator.py +551 -0
- sqlalchemy/sql/dml.py +1850 -0
- sqlalchemy/sql/elements.py +5589 -0
- sqlalchemy/sql/events.py +458 -0
- sqlalchemy/sql/expression.py +159 -0
- sqlalchemy/sql/functions.py +2158 -0
- sqlalchemy/sql/lambdas.py +1442 -0
- sqlalchemy/sql/naming.py +209 -0
- sqlalchemy/sql/operators.py +2623 -0
- sqlalchemy/sql/roles.py +323 -0
- sqlalchemy/sql/schema.py +6222 -0
- sqlalchemy/sql/selectable.py +7265 -0
- sqlalchemy/sql/sqltypes.py +3930 -0
- sqlalchemy/sql/traversals.py +1024 -0
- sqlalchemy/sql/type_api.py +2368 -0
- sqlalchemy/sql/util.py +1485 -0
- sqlalchemy/sql/visitors.py +1164 -0
- sqlalchemy/testing/__init__.py +96 -0
- sqlalchemy/testing/assertions.py +994 -0
- sqlalchemy/testing/assertsql.py +520 -0
- sqlalchemy/testing/asyncio.py +135 -0
- sqlalchemy/testing/config.py +434 -0
- sqlalchemy/testing/engines.py +483 -0
- sqlalchemy/testing/entities.py +117 -0
- sqlalchemy/testing/exclusions.py +476 -0
- sqlalchemy/testing/fixtures/__init__.py +28 -0
- sqlalchemy/testing/fixtures/base.py +384 -0
- sqlalchemy/testing/fixtures/mypy.py +332 -0
- sqlalchemy/testing/fixtures/orm.py +227 -0
- sqlalchemy/testing/fixtures/sql.py +482 -0
- sqlalchemy/testing/pickleable.py +155 -0
- sqlalchemy/testing/plugin/__init__.py +6 -0
- sqlalchemy/testing/plugin/bootstrap.py +51 -0
- sqlalchemy/testing/plugin/plugin_base.py +828 -0
- sqlalchemy/testing/plugin/pytestplugin.py +892 -0
- sqlalchemy/testing/profiling.py +329 -0
- sqlalchemy/testing/provision.py +603 -0
- sqlalchemy/testing/requirements.py +1945 -0
- sqlalchemy/testing/schema.py +198 -0
- sqlalchemy/testing/suite/__init__.py +19 -0
- sqlalchemy/testing/suite/test_cte.py +237 -0
- sqlalchemy/testing/suite/test_ddl.py +389 -0
- sqlalchemy/testing/suite/test_deprecations.py +153 -0
- sqlalchemy/testing/suite/test_dialect.py +776 -0
- sqlalchemy/testing/suite/test_insert.py +630 -0
- sqlalchemy/testing/suite/test_reflection.py +3557 -0
- sqlalchemy/testing/suite/test_results.py +504 -0
- sqlalchemy/testing/suite/test_rowcount.py +258 -0
- sqlalchemy/testing/suite/test_select.py +2010 -0
- sqlalchemy/testing/suite/test_sequence.py +317 -0
- sqlalchemy/testing/suite/test_types.py +2147 -0
- sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
- sqlalchemy/testing/suite/test_update_delete.py +139 -0
- sqlalchemy/testing/util.py +535 -0
- sqlalchemy/testing/warnings.py +52 -0
- sqlalchemy/types.py +74 -0
- sqlalchemy/util/__init__.py +162 -0
- sqlalchemy/util/_collections.py +712 -0
- sqlalchemy/util/_concurrency_py3k.py +288 -0
- sqlalchemy/util/_has_cy.py +40 -0
- sqlalchemy/util/_py_collections.py +541 -0
- sqlalchemy/util/compat.py +421 -0
- sqlalchemy/util/concurrency.py +110 -0
- sqlalchemy/util/deprecations.py +401 -0
- sqlalchemy/util/langhelpers.py +2203 -0
- sqlalchemy/util/preloaded.py +150 -0
- sqlalchemy/util/queue.py +322 -0
- sqlalchemy/util/tool_support.py +201 -0
- sqlalchemy/util/topological.py +120 -0
- sqlalchemy/util/typing.py +734 -0
- sqlalchemy-2.0.47.dist-info/METADATA +243 -0
- sqlalchemy-2.0.47.dist-info/RECORD +274 -0
- sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
- sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
- sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1788 @@
|
|
|
1
|
+
# orm/persistence.py
|
|
2
|
+
# Copyright (C) 2005-2026 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
|
+
# mypy: ignore-errors
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
"""private module containing functions used to emit INSERT, UPDATE
|
|
11
|
+
and DELETE statements on behalf of a :class:`_orm.Mapper` and its descending
|
|
12
|
+
mappers.
|
|
13
|
+
|
|
14
|
+
The functions here are called only by the unit of work functions
|
|
15
|
+
in unitofwork.py.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from itertools import chain
|
|
21
|
+
from itertools import groupby
|
|
22
|
+
from itertools import zip_longest
|
|
23
|
+
import operator
|
|
24
|
+
|
|
25
|
+
from . import attributes
|
|
26
|
+
from . import exc as orm_exc
|
|
27
|
+
from . import loading
|
|
28
|
+
from . import sync
|
|
29
|
+
from .base import state_str
|
|
30
|
+
from .. import exc as sa_exc
|
|
31
|
+
from .. import future
|
|
32
|
+
from .. import sql
|
|
33
|
+
from .. import util
|
|
34
|
+
from ..engine import cursor as _cursor
|
|
35
|
+
from ..sql import operators
|
|
36
|
+
from ..sql.elements import BooleanClauseList
|
|
37
|
+
from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def save_obj(base_mapper, states, uowtransaction, single=False):
|
|
41
|
+
"""Issue ``INSERT`` and/or ``UPDATE`` statements for a list
|
|
42
|
+
of objects.
|
|
43
|
+
|
|
44
|
+
This is called within the context of a UOWTransaction during a
|
|
45
|
+
flush operation, given a list of states to be flushed. The
|
|
46
|
+
base mapper in an inheritance hierarchy handles the inserts/
|
|
47
|
+
updates for all descendant mappers.
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
# if batch=false, call _save_obj separately for each object
|
|
52
|
+
if not single and not base_mapper.batch:
|
|
53
|
+
for state in _sort_states(base_mapper, states):
|
|
54
|
+
save_obj(base_mapper, [state], uowtransaction, single=True)
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
states_to_update = []
|
|
58
|
+
states_to_insert = []
|
|
59
|
+
|
|
60
|
+
for (
|
|
61
|
+
state,
|
|
62
|
+
dict_,
|
|
63
|
+
mapper,
|
|
64
|
+
connection,
|
|
65
|
+
has_identity,
|
|
66
|
+
row_switch,
|
|
67
|
+
update_version_id,
|
|
68
|
+
) in _organize_states_for_save(base_mapper, states, uowtransaction):
|
|
69
|
+
if has_identity or row_switch:
|
|
70
|
+
states_to_update.append(
|
|
71
|
+
(state, dict_, mapper, connection, update_version_id)
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
states_to_insert.append((state, dict_, mapper, connection))
|
|
75
|
+
|
|
76
|
+
for table, mapper in base_mapper._sorted_tables.items():
|
|
77
|
+
if table not in mapper._pks_by_table:
|
|
78
|
+
continue
|
|
79
|
+
insert = _collect_insert_commands(table, states_to_insert)
|
|
80
|
+
|
|
81
|
+
update = _collect_update_commands(
|
|
82
|
+
uowtransaction, table, states_to_update
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
_emit_update_statements(
|
|
86
|
+
base_mapper,
|
|
87
|
+
uowtransaction,
|
|
88
|
+
mapper,
|
|
89
|
+
table,
|
|
90
|
+
update,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
_emit_insert_statements(
|
|
94
|
+
base_mapper,
|
|
95
|
+
uowtransaction,
|
|
96
|
+
mapper,
|
|
97
|
+
table,
|
|
98
|
+
insert,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
_finalize_insert_update_commands(
|
|
102
|
+
base_mapper,
|
|
103
|
+
uowtransaction,
|
|
104
|
+
chain(
|
|
105
|
+
(
|
|
106
|
+
(state, state_dict, mapper, connection, False)
|
|
107
|
+
for (state, state_dict, mapper, connection) in states_to_insert
|
|
108
|
+
),
|
|
109
|
+
(
|
|
110
|
+
(state, state_dict, mapper, connection, True)
|
|
111
|
+
for (
|
|
112
|
+
state,
|
|
113
|
+
state_dict,
|
|
114
|
+
mapper,
|
|
115
|
+
connection,
|
|
116
|
+
update_version_id,
|
|
117
|
+
) in states_to_update
|
|
118
|
+
),
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def post_update(base_mapper, states, uowtransaction, post_update_cols):
|
|
124
|
+
"""Issue UPDATE statements on behalf of a relationship() which
|
|
125
|
+
specifies post_update.
|
|
126
|
+
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
states_to_update = list(
|
|
130
|
+
_organize_states_for_post_update(base_mapper, states, uowtransaction)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
for table, mapper in base_mapper._sorted_tables.items():
|
|
134
|
+
if table not in mapper._pks_by_table:
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
update = (
|
|
138
|
+
(
|
|
139
|
+
state,
|
|
140
|
+
state_dict,
|
|
141
|
+
sub_mapper,
|
|
142
|
+
connection,
|
|
143
|
+
(
|
|
144
|
+
mapper._get_committed_state_attr_by_column(
|
|
145
|
+
state, state_dict, mapper.version_id_col
|
|
146
|
+
)
|
|
147
|
+
if mapper.version_id_col is not None
|
|
148
|
+
else None
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
for state, state_dict, sub_mapper, connection in states_to_update
|
|
152
|
+
if table in sub_mapper._pks_by_table
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
update = _collect_post_update_commands(
|
|
156
|
+
base_mapper, uowtransaction, table, update, post_update_cols
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
_emit_post_update_statements(
|
|
160
|
+
base_mapper,
|
|
161
|
+
uowtransaction,
|
|
162
|
+
mapper,
|
|
163
|
+
table,
|
|
164
|
+
update,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def delete_obj(base_mapper, states, uowtransaction):
|
|
169
|
+
"""Issue ``DELETE`` statements for a list of objects.
|
|
170
|
+
|
|
171
|
+
This is called within the context of a UOWTransaction during a
|
|
172
|
+
flush operation.
|
|
173
|
+
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
states_to_delete = list(
|
|
177
|
+
_organize_states_for_delete(base_mapper, states, uowtransaction)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
table_to_mapper = base_mapper._sorted_tables
|
|
181
|
+
|
|
182
|
+
for table in reversed(list(table_to_mapper.keys())):
|
|
183
|
+
mapper = table_to_mapper[table]
|
|
184
|
+
if table not in mapper._pks_by_table:
|
|
185
|
+
continue
|
|
186
|
+
elif mapper.inherits and mapper.passive_deletes:
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
delete = _collect_delete_commands(
|
|
190
|
+
base_mapper, uowtransaction, table, states_to_delete
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
_emit_delete_statements(
|
|
194
|
+
base_mapper,
|
|
195
|
+
uowtransaction,
|
|
196
|
+
mapper,
|
|
197
|
+
table,
|
|
198
|
+
delete,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
for (
|
|
202
|
+
state,
|
|
203
|
+
state_dict,
|
|
204
|
+
mapper,
|
|
205
|
+
connection,
|
|
206
|
+
update_version_id,
|
|
207
|
+
) in states_to_delete:
|
|
208
|
+
mapper.dispatch.after_delete(mapper, connection, state)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _organize_states_for_save(base_mapper, states, uowtransaction):
|
|
212
|
+
"""Make an initial pass across a set of states for INSERT or
|
|
213
|
+
UPDATE.
|
|
214
|
+
|
|
215
|
+
This includes splitting out into distinct lists for
|
|
216
|
+
each, calling before_insert/before_update, obtaining
|
|
217
|
+
key information for each state including its dictionary,
|
|
218
|
+
mapper, the connection to use for the execution per state,
|
|
219
|
+
and the identity flag.
|
|
220
|
+
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
for state, dict_, mapper, connection in _connections_for_states(
|
|
224
|
+
base_mapper, uowtransaction, states
|
|
225
|
+
):
|
|
226
|
+
has_identity = bool(state.key)
|
|
227
|
+
|
|
228
|
+
instance_key = state.key or mapper._identity_key_from_state(state)
|
|
229
|
+
|
|
230
|
+
row_switch = update_version_id = None
|
|
231
|
+
|
|
232
|
+
# call before_XXX extensions
|
|
233
|
+
if not has_identity:
|
|
234
|
+
mapper.dispatch.before_insert(mapper, connection, state)
|
|
235
|
+
else:
|
|
236
|
+
mapper.dispatch.before_update(mapper, connection, state)
|
|
237
|
+
|
|
238
|
+
if mapper._validate_polymorphic_identity:
|
|
239
|
+
mapper._validate_polymorphic_identity(mapper, state, dict_)
|
|
240
|
+
|
|
241
|
+
# detect if we have a "pending" instance (i.e. has
|
|
242
|
+
# no instance_key attached to it), and another instance
|
|
243
|
+
# with the same identity key already exists as persistent.
|
|
244
|
+
# convert to an UPDATE if so.
|
|
245
|
+
if (
|
|
246
|
+
not has_identity
|
|
247
|
+
and instance_key in uowtransaction.session.identity_map
|
|
248
|
+
):
|
|
249
|
+
instance = uowtransaction.session.identity_map[instance_key]
|
|
250
|
+
existing = attributes.instance_state(instance)
|
|
251
|
+
|
|
252
|
+
if not uowtransaction.was_already_deleted(existing):
|
|
253
|
+
if not uowtransaction.is_deleted(existing):
|
|
254
|
+
util.warn(
|
|
255
|
+
"New instance %s with identity key %s conflicts "
|
|
256
|
+
"with persistent instance %s"
|
|
257
|
+
% (state_str(state), instance_key, state_str(existing))
|
|
258
|
+
)
|
|
259
|
+
else:
|
|
260
|
+
base_mapper._log_debug(
|
|
261
|
+
"detected row switch for identity %s. "
|
|
262
|
+
"will update %s, remove %s from "
|
|
263
|
+
"transaction",
|
|
264
|
+
instance_key,
|
|
265
|
+
state_str(state),
|
|
266
|
+
state_str(existing),
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# remove the "delete" flag from the existing element
|
|
270
|
+
uowtransaction.remove_state_actions(existing)
|
|
271
|
+
row_switch = existing
|
|
272
|
+
|
|
273
|
+
if (has_identity or row_switch) and mapper.version_id_col is not None:
|
|
274
|
+
update_version_id = mapper._get_committed_state_attr_by_column(
|
|
275
|
+
row_switch if row_switch else state,
|
|
276
|
+
row_switch.dict if row_switch else dict_,
|
|
277
|
+
mapper.version_id_col,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
yield (
|
|
281
|
+
state,
|
|
282
|
+
dict_,
|
|
283
|
+
mapper,
|
|
284
|
+
connection,
|
|
285
|
+
has_identity,
|
|
286
|
+
row_switch,
|
|
287
|
+
update_version_id,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _organize_states_for_post_update(base_mapper, states, uowtransaction):
|
|
292
|
+
"""Make an initial pass across a set of states for UPDATE
|
|
293
|
+
corresponding to post_update.
|
|
294
|
+
|
|
295
|
+
This includes obtaining key information for each state
|
|
296
|
+
including its dictionary, mapper, the connection to use for
|
|
297
|
+
the execution per state.
|
|
298
|
+
|
|
299
|
+
"""
|
|
300
|
+
return _connections_for_states(base_mapper, uowtransaction, states)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _organize_states_for_delete(base_mapper, states, uowtransaction):
|
|
304
|
+
"""Make an initial pass across a set of states for DELETE.
|
|
305
|
+
|
|
306
|
+
This includes calling out before_delete and obtaining
|
|
307
|
+
key information for each state including its dictionary,
|
|
308
|
+
mapper, the connection to use for the execution per state.
|
|
309
|
+
|
|
310
|
+
"""
|
|
311
|
+
for state, dict_, mapper, connection in _connections_for_states(
|
|
312
|
+
base_mapper, uowtransaction, states
|
|
313
|
+
):
|
|
314
|
+
mapper.dispatch.before_delete(mapper, connection, state)
|
|
315
|
+
|
|
316
|
+
if mapper.version_id_col is not None:
|
|
317
|
+
update_version_id = mapper._get_committed_state_attr_by_column(
|
|
318
|
+
state, dict_, mapper.version_id_col
|
|
319
|
+
)
|
|
320
|
+
else:
|
|
321
|
+
update_version_id = None
|
|
322
|
+
|
|
323
|
+
yield (state, dict_, mapper, connection, update_version_id)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _collect_insert_commands(
|
|
327
|
+
table,
|
|
328
|
+
states_to_insert,
|
|
329
|
+
*,
|
|
330
|
+
bulk=False,
|
|
331
|
+
return_defaults=False,
|
|
332
|
+
render_nulls=False,
|
|
333
|
+
include_bulk_keys=(),
|
|
334
|
+
):
|
|
335
|
+
"""Identify sets of values to use in INSERT statements for a
|
|
336
|
+
list of states.
|
|
337
|
+
|
|
338
|
+
"""
|
|
339
|
+
for state, state_dict, mapper, connection in states_to_insert:
|
|
340
|
+
if table not in mapper._pks_by_table:
|
|
341
|
+
continue
|
|
342
|
+
|
|
343
|
+
params = {}
|
|
344
|
+
value_params = {}
|
|
345
|
+
|
|
346
|
+
propkey_to_col = mapper._propkey_to_col[table]
|
|
347
|
+
|
|
348
|
+
eval_none = mapper._insert_cols_evaluating_none[table]
|
|
349
|
+
|
|
350
|
+
for propkey in set(propkey_to_col).intersection(state_dict):
|
|
351
|
+
value = state_dict[propkey]
|
|
352
|
+
col = propkey_to_col[propkey]
|
|
353
|
+
if value is None and col not in eval_none and not render_nulls:
|
|
354
|
+
continue
|
|
355
|
+
elif not bulk and (
|
|
356
|
+
hasattr(value, "__clause_element__")
|
|
357
|
+
or isinstance(value, sql.ClauseElement)
|
|
358
|
+
):
|
|
359
|
+
value_params[col] = (
|
|
360
|
+
value.__clause_element__()
|
|
361
|
+
if hasattr(value, "__clause_element__")
|
|
362
|
+
else value
|
|
363
|
+
)
|
|
364
|
+
else:
|
|
365
|
+
params[col.key] = value
|
|
366
|
+
|
|
367
|
+
if not bulk:
|
|
368
|
+
# for all the columns that have no default and we don't have
|
|
369
|
+
# a value and where "None" is not a special value, add
|
|
370
|
+
# explicit None to the INSERT. This is a legacy behavior
|
|
371
|
+
# which might be worth removing, as it should not be necessary
|
|
372
|
+
# and also produces confusion, given that "missing" and None
|
|
373
|
+
# now have distinct meanings
|
|
374
|
+
for colkey in (
|
|
375
|
+
mapper._insert_cols_as_none[table]
|
|
376
|
+
.difference(params)
|
|
377
|
+
.difference([c.key for c in value_params])
|
|
378
|
+
):
|
|
379
|
+
params[colkey] = None
|
|
380
|
+
|
|
381
|
+
if not bulk or return_defaults:
|
|
382
|
+
# params are in terms of Column key objects, so
|
|
383
|
+
# compare to pk_keys_by_table
|
|
384
|
+
has_all_pks = mapper._pk_keys_by_table[table].issubset(params)
|
|
385
|
+
|
|
386
|
+
if mapper.base_mapper._prefer_eager_defaults(
|
|
387
|
+
connection.dialect, table
|
|
388
|
+
):
|
|
389
|
+
has_all_defaults = mapper._server_default_col_keys[
|
|
390
|
+
table
|
|
391
|
+
].issubset(params)
|
|
392
|
+
else:
|
|
393
|
+
has_all_defaults = True
|
|
394
|
+
else:
|
|
395
|
+
has_all_defaults = has_all_pks = True
|
|
396
|
+
|
|
397
|
+
if (
|
|
398
|
+
mapper.version_id_generator is not False
|
|
399
|
+
and mapper.version_id_col is not None
|
|
400
|
+
and mapper.version_id_col in mapper._cols_by_table[table]
|
|
401
|
+
):
|
|
402
|
+
params[mapper.version_id_col.key] = mapper.version_id_generator(
|
|
403
|
+
None
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
if bulk:
|
|
407
|
+
if mapper._set_polymorphic_identity:
|
|
408
|
+
params.setdefault(
|
|
409
|
+
mapper._polymorphic_attr_key, mapper.polymorphic_identity
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
if include_bulk_keys:
|
|
413
|
+
params.update((k, state_dict[k]) for k in include_bulk_keys)
|
|
414
|
+
|
|
415
|
+
yield (
|
|
416
|
+
state,
|
|
417
|
+
state_dict,
|
|
418
|
+
params,
|
|
419
|
+
mapper,
|
|
420
|
+
connection,
|
|
421
|
+
value_params,
|
|
422
|
+
has_all_pks,
|
|
423
|
+
has_all_defaults,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _collect_update_commands(
|
|
428
|
+
uowtransaction,
|
|
429
|
+
table,
|
|
430
|
+
states_to_update,
|
|
431
|
+
*,
|
|
432
|
+
bulk=False,
|
|
433
|
+
use_orm_update_stmt=None,
|
|
434
|
+
include_bulk_keys=(),
|
|
435
|
+
):
|
|
436
|
+
"""Identify sets of values to use in UPDATE statements for a
|
|
437
|
+
list of states.
|
|
438
|
+
|
|
439
|
+
This function works intricately with the history system
|
|
440
|
+
to determine exactly what values should be updated
|
|
441
|
+
as well as how the row should be matched within an UPDATE
|
|
442
|
+
statement. Includes some tricky scenarios where the primary
|
|
443
|
+
key of an object might have been changed.
|
|
444
|
+
|
|
445
|
+
"""
|
|
446
|
+
|
|
447
|
+
for (
|
|
448
|
+
state,
|
|
449
|
+
state_dict,
|
|
450
|
+
mapper,
|
|
451
|
+
connection,
|
|
452
|
+
update_version_id,
|
|
453
|
+
) in states_to_update:
|
|
454
|
+
if table not in mapper._pks_by_table:
|
|
455
|
+
continue
|
|
456
|
+
|
|
457
|
+
pks = mapper._pks_by_table[table]
|
|
458
|
+
|
|
459
|
+
if use_orm_update_stmt is not None:
|
|
460
|
+
# TODO: ordered values, etc
|
|
461
|
+
value_params = use_orm_update_stmt._values
|
|
462
|
+
else:
|
|
463
|
+
value_params = {}
|
|
464
|
+
|
|
465
|
+
propkey_to_col = mapper._propkey_to_col[table]
|
|
466
|
+
|
|
467
|
+
if bulk:
|
|
468
|
+
# keys here are mapped attribute keys, so
|
|
469
|
+
# look at mapper attribute keys for pk
|
|
470
|
+
params = {
|
|
471
|
+
propkey_to_col[propkey].key: state_dict[propkey]
|
|
472
|
+
for propkey in set(propkey_to_col)
|
|
473
|
+
.intersection(state_dict)
|
|
474
|
+
.difference(mapper._pk_attr_keys_by_table[table])
|
|
475
|
+
}
|
|
476
|
+
has_all_defaults = True
|
|
477
|
+
else:
|
|
478
|
+
params = {}
|
|
479
|
+
for propkey in set(propkey_to_col).intersection(
|
|
480
|
+
state.committed_state
|
|
481
|
+
):
|
|
482
|
+
value = state_dict[propkey]
|
|
483
|
+
col = propkey_to_col[propkey]
|
|
484
|
+
|
|
485
|
+
if hasattr(value, "__clause_element__") or isinstance(
|
|
486
|
+
value, sql.ClauseElement
|
|
487
|
+
):
|
|
488
|
+
value_params[col] = (
|
|
489
|
+
value.__clause_element__()
|
|
490
|
+
if hasattr(value, "__clause_element__")
|
|
491
|
+
else value
|
|
492
|
+
)
|
|
493
|
+
# guard against values that generate non-__nonzero__
|
|
494
|
+
# objects for __eq__()
|
|
495
|
+
elif (
|
|
496
|
+
state.manager[propkey].impl.is_equal(
|
|
497
|
+
value, state.committed_state[propkey]
|
|
498
|
+
)
|
|
499
|
+
is not True
|
|
500
|
+
):
|
|
501
|
+
params[col.key] = value
|
|
502
|
+
|
|
503
|
+
if mapper.base_mapper.eager_defaults is True:
|
|
504
|
+
has_all_defaults = (
|
|
505
|
+
mapper._server_onupdate_default_col_keys[table]
|
|
506
|
+
).issubset(params)
|
|
507
|
+
else:
|
|
508
|
+
has_all_defaults = True
|
|
509
|
+
|
|
510
|
+
if (
|
|
511
|
+
update_version_id is not None
|
|
512
|
+
and mapper.version_id_col in mapper._cols_by_table[table]
|
|
513
|
+
):
|
|
514
|
+
if not bulk and not (params or value_params):
|
|
515
|
+
# HACK: check for history in other tables, in case the
|
|
516
|
+
# history is only in a different table than the one
|
|
517
|
+
# where the version_id_col is. This logic was lost
|
|
518
|
+
# from 0.9 -> 1.0.0 and restored in 1.0.6.
|
|
519
|
+
for prop in mapper._columntoproperty.values():
|
|
520
|
+
history = state.manager[prop.key].impl.get_history(
|
|
521
|
+
state, state_dict, attributes.PASSIVE_NO_INITIALIZE
|
|
522
|
+
)
|
|
523
|
+
if history.added:
|
|
524
|
+
break
|
|
525
|
+
else:
|
|
526
|
+
# no net change, break
|
|
527
|
+
continue
|
|
528
|
+
|
|
529
|
+
col = mapper.version_id_col
|
|
530
|
+
no_params = not params and not value_params
|
|
531
|
+
params[col._label] = update_version_id
|
|
532
|
+
|
|
533
|
+
if (
|
|
534
|
+
bulk or col.key not in params
|
|
535
|
+
) and mapper.version_id_generator is not False:
|
|
536
|
+
val = mapper.version_id_generator(update_version_id)
|
|
537
|
+
params[col.key] = val
|
|
538
|
+
elif mapper.version_id_generator is False and no_params:
|
|
539
|
+
# no version id generator, no values set on the table,
|
|
540
|
+
# and version id wasn't manually incremented.
|
|
541
|
+
# set version id to itself so we get an UPDATE
|
|
542
|
+
# statement
|
|
543
|
+
params[col.key] = update_version_id
|
|
544
|
+
|
|
545
|
+
elif not (params or value_params):
|
|
546
|
+
continue
|
|
547
|
+
|
|
548
|
+
has_all_pks = True
|
|
549
|
+
expect_pk_cascaded = False
|
|
550
|
+
if bulk:
|
|
551
|
+
# keys here are mapped attribute keys, so
|
|
552
|
+
# look at mapper attribute keys for pk
|
|
553
|
+
pk_params = {
|
|
554
|
+
propkey_to_col[propkey]._label: state_dict.get(propkey)
|
|
555
|
+
for propkey in set(propkey_to_col).intersection(
|
|
556
|
+
mapper._pk_attr_keys_by_table[table]
|
|
557
|
+
)
|
|
558
|
+
}
|
|
559
|
+
if util.NONE_SET.intersection(pk_params.values()):
|
|
560
|
+
raise sa_exc.InvalidRequestError(
|
|
561
|
+
f"No primary key value supplied for column(s) "
|
|
562
|
+
f"""{
|
|
563
|
+
', '.join(
|
|
564
|
+
str(c) for c in pks if pk_params[c._label] is None
|
|
565
|
+
)
|
|
566
|
+
}; """
|
|
567
|
+
"per-row ORM Bulk UPDATE by Primary Key requires that "
|
|
568
|
+
"records contain primary key values",
|
|
569
|
+
code="bupq",
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
else:
|
|
573
|
+
pk_params = {}
|
|
574
|
+
for col in pks:
|
|
575
|
+
propkey = mapper._columntoproperty[col].key
|
|
576
|
+
|
|
577
|
+
history = state.manager[propkey].impl.get_history(
|
|
578
|
+
state, state_dict, attributes.PASSIVE_OFF
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
if history.added:
|
|
582
|
+
if (
|
|
583
|
+
not history.deleted
|
|
584
|
+
or ("pk_cascaded", state, col)
|
|
585
|
+
in uowtransaction.attributes
|
|
586
|
+
):
|
|
587
|
+
expect_pk_cascaded = True
|
|
588
|
+
pk_params[col._label] = history.added[0]
|
|
589
|
+
params.pop(col.key, None)
|
|
590
|
+
else:
|
|
591
|
+
# else, use the old value to locate the row
|
|
592
|
+
pk_params[col._label] = history.deleted[0]
|
|
593
|
+
if col in value_params:
|
|
594
|
+
has_all_pks = False
|
|
595
|
+
else:
|
|
596
|
+
pk_params[col._label] = history.unchanged[0]
|
|
597
|
+
if pk_params[col._label] is None:
|
|
598
|
+
raise orm_exc.FlushError(
|
|
599
|
+
"Can't update table %s using NULL for primary "
|
|
600
|
+
"key value on column %s" % (table, col)
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
if include_bulk_keys:
|
|
604
|
+
params.update((k, state_dict[k]) for k in include_bulk_keys)
|
|
605
|
+
|
|
606
|
+
if params or value_params:
|
|
607
|
+
params.update(pk_params)
|
|
608
|
+
yield (
|
|
609
|
+
state,
|
|
610
|
+
state_dict,
|
|
611
|
+
params,
|
|
612
|
+
mapper,
|
|
613
|
+
connection,
|
|
614
|
+
value_params,
|
|
615
|
+
has_all_defaults,
|
|
616
|
+
has_all_pks,
|
|
617
|
+
)
|
|
618
|
+
elif expect_pk_cascaded:
|
|
619
|
+
# no UPDATE occurs on this table, but we expect that CASCADE rules
|
|
620
|
+
# have changed the primary key of the row; propagate this event to
|
|
621
|
+
# other columns that expect to have been modified. this normally
|
|
622
|
+
# occurs after the UPDATE is emitted however we invoke it here
|
|
623
|
+
# explicitly in the absence of our invoking an UPDATE
|
|
624
|
+
for m, equated_pairs in mapper._table_to_equated[table]:
|
|
625
|
+
sync.populate(
|
|
626
|
+
state,
|
|
627
|
+
m,
|
|
628
|
+
state,
|
|
629
|
+
m,
|
|
630
|
+
equated_pairs,
|
|
631
|
+
uowtransaction,
|
|
632
|
+
mapper.passive_updates,
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def _collect_post_update_commands(
|
|
637
|
+
base_mapper, uowtransaction, table, states_to_update, post_update_cols
|
|
638
|
+
):
|
|
639
|
+
"""Identify sets of values to use in UPDATE statements for a
|
|
640
|
+
list of states within a post_update operation.
|
|
641
|
+
|
|
642
|
+
"""
|
|
643
|
+
|
|
644
|
+
for (
|
|
645
|
+
state,
|
|
646
|
+
state_dict,
|
|
647
|
+
mapper,
|
|
648
|
+
connection,
|
|
649
|
+
update_version_id,
|
|
650
|
+
) in states_to_update:
|
|
651
|
+
# assert table in mapper._pks_by_table
|
|
652
|
+
|
|
653
|
+
pks = mapper._pks_by_table[table]
|
|
654
|
+
params = {}
|
|
655
|
+
hasdata = False
|
|
656
|
+
|
|
657
|
+
for col in mapper._cols_by_table[table]:
|
|
658
|
+
if col in pks:
|
|
659
|
+
params[col._label] = mapper._get_state_attr_by_column(
|
|
660
|
+
state, state_dict, col, passive=attributes.PASSIVE_OFF
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
elif col in post_update_cols or col.onupdate is not None:
|
|
664
|
+
prop = mapper._columntoproperty[col]
|
|
665
|
+
history = state.manager[prop.key].impl.get_history(
|
|
666
|
+
state, state_dict, attributes.PASSIVE_NO_INITIALIZE
|
|
667
|
+
)
|
|
668
|
+
if history.added:
|
|
669
|
+
value = history.added[0]
|
|
670
|
+
params[col.key] = value
|
|
671
|
+
hasdata = True
|
|
672
|
+
if hasdata:
|
|
673
|
+
if (
|
|
674
|
+
update_version_id is not None
|
|
675
|
+
and mapper.version_id_col in mapper._cols_by_table[table]
|
|
676
|
+
):
|
|
677
|
+
col = mapper.version_id_col
|
|
678
|
+
params[col._label] = update_version_id
|
|
679
|
+
|
|
680
|
+
if (
|
|
681
|
+
bool(state.key)
|
|
682
|
+
and col.key not in params
|
|
683
|
+
and mapper.version_id_generator is not False
|
|
684
|
+
):
|
|
685
|
+
val = mapper.version_id_generator(update_version_id)
|
|
686
|
+
params[col.key] = val
|
|
687
|
+
yield state, state_dict, mapper, connection, params
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def _collect_delete_commands(
|
|
691
|
+
base_mapper, uowtransaction, table, states_to_delete
|
|
692
|
+
):
|
|
693
|
+
"""Identify values to use in DELETE statements for a list of
|
|
694
|
+
states to be deleted."""
|
|
695
|
+
|
|
696
|
+
for (
|
|
697
|
+
state,
|
|
698
|
+
state_dict,
|
|
699
|
+
mapper,
|
|
700
|
+
connection,
|
|
701
|
+
update_version_id,
|
|
702
|
+
) in states_to_delete:
|
|
703
|
+
if table not in mapper._pks_by_table:
|
|
704
|
+
continue
|
|
705
|
+
|
|
706
|
+
params = {}
|
|
707
|
+
for col in mapper._pks_by_table[table]:
|
|
708
|
+
params[col.key] = value = (
|
|
709
|
+
mapper._get_committed_state_attr_by_column(
|
|
710
|
+
state, state_dict, col
|
|
711
|
+
)
|
|
712
|
+
)
|
|
713
|
+
if value is None:
|
|
714
|
+
raise orm_exc.FlushError(
|
|
715
|
+
"Can't delete from table %s "
|
|
716
|
+
"using NULL for primary "
|
|
717
|
+
"key value on column %s" % (table, col)
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
if (
|
|
721
|
+
update_version_id is not None
|
|
722
|
+
and mapper.version_id_col in mapper._cols_by_table[table]
|
|
723
|
+
):
|
|
724
|
+
params[mapper.version_id_col.key] = update_version_id
|
|
725
|
+
yield params, connection
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def _emit_update_statements(
|
|
729
|
+
base_mapper,
|
|
730
|
+
uowtransaction,
|
|
731
|
+
mapper,
|
|
732
|
+
table,
|
|
733
|
+
update,
|
|
734
|
+
*,
|
|
735
|
+
bookkeeping=True,
|
|
736
|
+
use_orm_update_stmt=None,
|
|
737
|
+
enable_check_rowcount=True,
|
|
738
|
+
):
|
|
739
|
+
"""Emit UPDATE statements corresponding to value lists collected
|
|
740
|
+
by _collect_update_commands()."""
|
|
741
|
+
|
|
742
|
+
needs_version_id = (
|
|
743
|
+
mapper.version_id_col is not None
|
|
744
|
+
and mapper.version_id_col in mapper._cols_by_table[table]
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
execution_options = {"compiled_cache": base_mapper._compiled_cache}
|
|
748
|
+
|
|
749
|
+
def update_stmt(existing_stmt=None):
|
|
750
|
+
clauses = BooleanClauseList._construct_raw(operators.and_)
|
|
751
|
+
|
|
752
|
+
for col in mapper._pks_by_table[table]:
|
|
753
|
+
clauses._append_inplace(
|
|
754
|
+
col == sql.bindparam(col._label, type_=col.type)
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
if needs_version_id:
|
|
758
|
+
clauses._append_inplace(
|
|
759
|
+
mapper.version_id_col
|
|
760
|
+
== sql.bindparam(
|
|
761
|
+
mapper.version_id_col._label,
|
|
762
|
+
type_=mapper.version_id_col.type,
|
|
763
|
+
)
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
if existing_stmt is not None:
|
|
767
|
+
stmt = existing_stmt.where(clauses)
|
|
768
|
+
else:
|
|
769
|
+
stmt = table.update().where(clauses)
|
|
770
|
+
return stmt
|
|
771
|
+
|
|
772
|
+
if use_orm_update_stmt is not None:
|
|
773
|
+
cached_stmt = update_stmt(use_orm_update_stmt)
|
|
774
|
+
|
|
775
|
+
else:
|
|
776
|
+
cached_stmt = base_mapper._memo(("update", table), update_stmt)
|
|
777
|
+
|
|
778
|
+
for (
|
|
779
|
+
(connection, paramkeys, hasvalue, has_all_defaults, has_all_pks),
|
|
780
|
+
records,
|
|
781
|
+
) in groupby(
|
|
782
|
+
update,
|
|
783
|
+
lambda rec: (
|
|
784
|
+
rec[4], # connection
|
|
785
|
+
set(rec[2]), # set of parameter keys
|
|
786
|
+
bool(rec[5]), # whether or not we have "value" parameters
|
|
787
|
+
rec[6], # has_all_defaults
|
|
788
|
+
rec[7], # has all pks
|
|
789
|
+
),
|
|
790
|
+
):
|
|
791
|
+
rows = 0
|
|
792
|
+
records = list(records)
|
|
793
|
+
|
|
794
|
+
statement = cached_stmt
|
|
795
|
+
|
|
796
|
+
if use_orm_update_stmt is not None:
|
|
797
|
+
statement = statement._annotate(
|
|
798
|
+
{
|
|
799
|
+
"_emit_update_table": table,
|
|
800
|
+
"_emit_update_mapper": mapper,
|
|
801
|
+
}
|
|
802
|
+
)
|
|
803
|
+
|
|
804
|
+
return_defaults = False
|
|
805
|
+
|
|
806
|
+
if not has_all_pks:
|
|
807
|
+
statement = statement.return_defaults(*mapper._pks_by_table[table])
|
|
808
|
+
return_defaults = True
|
|
809
|
+
|
|
810
|
+
if (
|
|
811
|
+
bookkeeping
|
|
812
|
+
and not has_all_defaults
|
|
813
|
+
and mapper.base_mapper.eager_defaults is True
|
|
814
|
+
# change as of #8889 - if RETURNING is not going to be used anyway,
|
|
815
|
+
# (applies to MySQL, MariaDB which lack UPDATE RETURNING) ensure
|
|
816
|
+
# we can do an executemany UPDATE which is more efficient
|
|
817
|
+
and table.implicit_returning
|
|
818
|
+
and connection.dialect.update_returning
|
|
819
|
+
):
|
|
820
|
+
statement = statement.return_defaults(
|
|
821
|
+
*mapper._server_onupdate_default_cols[table]
|
|
822
|
+
)
|
|
823
|
+
return_defaults = True
|
|
824
|
+
|
|
825
|
+
if mapper._version_id_has_server_side_value:
|
|
826
|
+
statement = statement.return_defaults(mapper.version_id_col)
|
|
827
|
+
return_defaults = True
|
|
828
|
+
|
|
829
|
+
assert_singlerow = connection.dialect.supports_sane_rowcount
|
|
830
|
+
|
|
831
|
+
assert_multirow = (
|
|
832
|
+
assert_singlerow
|
|
833
|
+
and connection.dialect.supports_sane_multi_rowcount
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
# change as of #8889 - if RETURNING is not going to be used anyway,
|
|
837
|
+
# (applies to MySQL, MariaDB which lack UPDATE RETURNING) ensure
|
|
838
|
+
# we can do an executemany UPDATE which is more efficient
|
|
839
|
+
allow_executemany = not return_defaults and not needs_version_id
|
|
840
|
+
|
|
841
|
+
if hasvalue:
|
|
842
|
+
for (
|
|
843
|
+
state,
|
|
844
|
+
state_dict,
|
|
845
|
+
params,
|
|
846
|
+
mapper,
|
|
847
|
+
connection,
|
|
848
|
+
value_params,
|
|
849
|
+
has_all_defaults,
|
|
850
|
+
has_all_pks,
|
|
851
|
+
) in records:
|
|
852
|
+
c = connection.execute(
|
|
853
|
+
statement.values(value_params),
|
|
854
|
+
params,
|
|
855
|
+
execution_options=execution_options,
|
|
856
|
+
)
|
|
857
|
+
if bookkeeping:
|
|
858
|
+
_postfetch(
|
|
859
|
+
mapper,
|
|
860
|
+
uowtransaction,
|
|
861
|
+
table,
|
|
862
|
+
state,
|
|
863
|
+
state_dict,
|
|
864
|
+
c,
|
|
865
|
+
c.context.compiled_parameters[0],
|
|
866
|
+
value_params,
|
|
867
|
+
True,
|
|
868
|
+
c.returned_defaults,
|
|
869
|
+
)
|
|
870
|
+
rows += c.rowcount
|
|
871
|
+
check_rowcount = enable_check_rowcount and assert_singlerow
|
|
872
|
+
else:
|
|
873
|
+
if not allow_executemany:
|
|
874
|
+
check_rowcount = enable_check_rowcount and assert_singlerow
|
|
875
|
+
for (
|
|
876
|
+
state,
|
|
877
|
+
state_dict,
|
|
878
|
+
params,
|
|
879
|
+
mapper,
|
|
880
|
+
connection,
|
|
881
|
+
value_params,
|
|
882
|
+
has_all_defaults,
|
|
883
|
+
has_all_pks,
|
|
884
|
+
) in records:
|
|
885
|
+
c = connection.execute(
|
|
886
|
+
statement, params, execution_options=execution_options
|
|
887
|
+
)
|
|
888
|
+
|
|
889
|
+
# TODO: why with bookkeeping=False?
|
|
890
|
+
if bookkeeping:
|
|
891
|
+
_postfetch(
|
|
892
|
+
mapper,
|
|
893
|
+
uowtransaction,
|
|
894
|
+
table,
|
|
895
|
+
state,
|
|
896
|
+
state_dict,
|
|
897
|
+
c,
|
|
898
|
+
c.context.compiled_parameters[0],
|
|
899
|
+
value_params,
|
|
900
|
+
True,
|
|
901
|
+
c.returned_defaults,
|
|
902
|
+
)
|
|
903
|
+
rows += c.rowcount
|
|
904
|
+
else:
|
|
905
|
+
multiparams = [rec[2] for rec in records]
|
|
906
|
+
|
|
907
|
+
check_rowcount = enable_check_rowcount and (
|
|
908
|
+
assert_multirow
|
|
909
|
+
or (assert_singlerow and len(multiparams) == 1)
|
|
910
|
+
)
|
|
911
|
+
|
|
912
|
+
c = connection.execute(
|
|
913
|
+
statement, multiparams, execution_options=execution_options
|
|
914
|
+
)
|
|
915
|
+
|
|
916
|
+
rows += c.rowcount
|
|
917
|
+
|
|
918
|
+
for (
|
|
919
|
+
state,
|
|
920
|
+
state_dict,
|
|
921
|
+
params,
|
|
922
|
+
mapper,
|
|
923
|
+
connection,
|
|
924
|
+
value_params,
|
|
925
|
+
has_all_defaults,
|
|
926
|
+
has_all_pks,
|
|
927
|
+
) in records:
|
|
928
|
+
if bookkeeping:
|
|
929
|
+
_postfetch(
|
|
930
|
+
mapper,
|
|
931
|
+
uowtransaction,
|
|
932
|
+
table,
|
|
933
|
+
state,
|
|
934
|
+
state_dict,
|
|
935
|
+
c,
|
|
936
|
+
c.context.compiled_parameters[0],
|
|
937
|
+
value_params,
|
|
938
|
+
True,
|
|
939
|
+
(
|
|
940
|
+
c.returned_defaults
|
|
941
|
+
if not c.context.executemany
|
|
942
|
+
else None
|
|
943
|
+
),
|
|
944
|
+
)
|
|
945
|
+
|
|
946
|
+
if check_rowcount:
|
|
947
|
+
if rows != len(records):
|
|
948
|
+
raise orm_exc.StaleDataError(
|
|
949
|
+
"UPDATE statement on table '%s' expected to "
|
|
950
|
+
"update %d row(s); %d were matched."
|
|
951
|
+
% (table.description, len(records), rows)
|
|
952
|
+
)
|
|
953
|
+
|
|
954
|
+
elif needs_version_id:
|
|
955
|
+
util.warn(
|
|
956
|
+
"Dialect %s does not support updated rowcount "
|
|
957
|
+
"- versioning cannot be verified."
|
|
958
|
+
% c.dialect.dialect_description
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
def _emit_insert_statements(
|
|
963
|
+
base_mapper,
|
|
964
|
+
uowtransaction,
|
|
965
|
+
mapper,
|
|
966
|
+
table,
|
|
967
|
+
insert,
|
|
968
|
+
*,
|
|
969
|
+
bookkeeping=True,
|
|
970
|
+
use_orm_insert_stmt=None,
|
|
971
|
+
execution_options=None,
|
|
972
|
+
):
|
|
973
|
+
"""Emit INSERT statements corresponding to value lists collected
|
|
974
|
+
by _collect_insert_commands()."""
|
|
975
|
+
|
|
976
|
+
if use_orm_insert_stmt is not None:
|
|
977
|
+
cached_stmt = use_orm_insert_stmt
|
|
978
|
+
exec_opt = util.EMPTY_DICT
|
|
979
|
+
|
|
980
|
+
# if a user query with RETURNING was passed, we definitely need
|
|
981
|
+
# to use RETURNING.
|
|
982
|
+
returning_is_required_anyway = bool(use_orm_insert_stmt._returning)
|
|
983
|
+
deterministic_results_reqd = (
|
|
984
|
+
returning_is_required_anyway
|
|
985
|
+
and use_orm_insert_stmt._sort_by_parameter_order
|
|
986
|
+
) or bookkeeping
|
|
987
|
+
else:
|
|
988
|
+
returning_is_required_anyway = False
|
|
989
|
+
deterministic_results_reqd = bookkeeping
|
|
990
|
+
cached_stmt = base_mapper._memo(("insert", table), table.insert)
|
|
991
|
+
exec_opt = {"compiled_cache": base_mapper._compiled_cache}
|
|
992
|
+
|
|
993
|
+
if execution_options:
|
|
994
|
+
execution_options = util.EMPTY_DICT.merge_with(
|
|
995
|
+
exec_opt, execution_options
|
|
996
|
+
)
|
|
997
|
+
else:
|
|
998
|
+
execution_options = exec_opt
|
|
999
|
+
|
|
1000
|
+
return_result = None
|
|
1001
|
+
|
|
1002
|
+
for (
|
|
1003
|
+
(connection, _, hasvalue, has_all_pks, has_all_defaults),
|
|
1004
|
+
records,
|
|
1005
|
+
) in groupby(
|
|
1006
|
+
insert,
|
|
1007
|
+
lambda rec: (
|
|
1008
|
+
rec[4], # connection
|
|
1009
|
+
set(rec[2]), # parameter keys
|
|
1010
|
+
bool(rec[5]), # whether we have "value" parameters
|
|
1011
|
+
rec[6],
|
|
1012
|
+
rec[7],
|
|
1013
|
+
),
|
|
1014
|
+
):
|
|
1015
|
+
statement = cached_stmt
|
|
1016
|
+
|
|
1017
|
+
if use_orm_insert_stmt is not None:
|
|
1018
|
+
statement = statement._annotate(
|
|
1019
|
+
{
|
|
1020
|
+
"_emit_insert_table": table,
|
|
1021
|
+
"_emit_insert_mapper": mapper,
|
|
1022
|
+
}
|
|
1023
|
+
)
|
|
1024
|
+
|
|
1025
|
+
if (
|
|
1026
|
+
(
|
|
1027
|
+
not bookkeeping
|
|
1028
|
+
or (
|
|
1029
|
+
has_all_defaults
|
|
1030
|
+
or not base_mapper._prefer_eager_defaults(
|
|
1031
|
+
connection.dialect, table
|
|
1032
|
+
)
|
|
1033
|
+
or not table.implicit_returning
|
|
1034
|
+
or not connection.dialect.insert_returning
|
|
1035
|
+
)
|
|
1036
|
+
)
|
|
1037
|
+
and not returning_is_required_anyway
|
|
1038
|
+
and has_all_pks
|
|
1039
|
+
and not hasvalue
|
|
1040
|
+
):
|
|
1041
|
+
# the "we don't need newly generated values back" section.
|
|
1042
|
+
# here we have all the PKs, all the defaults or we don't want
|
|
1043
|
+
# to fetch them, or the dialect doesn't support RETURNING at all
|
|
1044
|
+
# so we have to post-fetch / use lastrowid anyway.
|
|
1045
|
+
records = list(records)
|
|
1046
|
+
multiparams = [rec[2] for rec in records]
|
|
1047
|
+
|
|
1048
|
+
result = connection.execute(
|
|
1049
|
+
statement, multiparams, execution_options=execution_options
|
|
1050
|
+
)
|
|
1051
|
+
if bookkeeping:
|
|
1052
|
+
for (
|
|
1053
|
+
(
|
|
1054
|
+
state,
|
|
1055
|
+
state_dict,
|
|
1056
|
+
params,
|
|
1057
|
+
mapper_rec,
|
|
1058
|
+
conn,
|
|
1059
|
+
value_params,
|
|
1060
|
+
has_all_pks,
|
|
1061
|
+
has_all_defaults,
|
|
1062
|
+
),
|
|
1063
|
+
last_inserted_params,
|
|
1064
|
+
) in zip(records, result.context.compiled_parameters):
|
|
1065
|
+
if state:
|
|
1066
|
+
_postfetch(
|
|
1067
|
+
mapper_rec,
|
|
1068
|
+
uowtransaction,
|
|
1069
|
+
table,
|
|
1070
|
+
state,
|
|
1071
|
+
state_dict,
|
|
1072
|
+
result,
|
|
1073
|
+
last_inserted_params,
|
|
1074
|
+
value_params,
|
|
1075
|
+
False,
|
|
1076
|
+
(
|
|
1077
|
+
result.returned_defaults
|
|
1078
|
+
if not result.context.executemany
|
|
1079
|
+
else None
|
|
1080
|
+
),
|
|
1081
|
+
)
|
|
1082
|
+
else:
|
|
1083
|
+
_postfetch_bulk_save(mapper_rec, state_dict, table)
|
|
1084
|
+
|
|
1085
|
+
else:
|
|
1086
|
+
# here, we need defaults and/or pk values back or we otherwise
|
|
1087
|
+
# know that we are using RETURNING in any case
|
|
1088
|
+
|
|
1089
|
+
records = list(records)
|
|
1090
|
+
|
|
1091
|
+
if returning_is_required_anyway or (
|
|
1092
|
+
table.implicit_returning and not hasvalue and len(records) > 1
|
|
1093
|
+
):
|
|
1094
|
+
if (
|
|
1095
|
+
deterministic_results_reqd
|
|
1096
|
+
and connection.dialect.insert_executemany_returning_sort_by_parameter_order # noqa: E501
|
|
1097
|
+
) or (
|
|
1098
|
+
not deterministic_results_reqd
|
|
1099
|
+
and connection.dialect.insert_executemany_returning
|
|
1100
|
+
):
|
|
1101
|
+
do_executemany = True
|
|
1102
|
+
elif returning_is_required_anyway:
|
|
1103
|
+
if deterministic_results_reqd:
|
|
1104
|
+
dt = " with RETURNING and sort by parameter order"
|
|
1105
|
+
else:
|
|
1106
|
+
dt = " with RETURNING"
|
|
1107
|
+
raise sa_exc.InvalidRequestError(
|
|
1108
|
+
f"Can't use explicit RETURNING for bulk INSERT "
|
|
1109
|
+
f"operation with "
|
|
1110
|
+
f"{connection.dialect.dialect_description} backend; "
|
|
1111
|
+
f"executemany{dt} is not enabled for this dialect."
|
|
1112
|
+
)
|
|
1113
|
+
else:
|
|
1114
|
+
do_executemany = False
|
|
1115
|
+
else:
|
|
1116
|
+
do_executemany = False
|
|
1117
|
+
|
|
1118
|
+
if use_orm_insert_stmt is None:
|
|
1119
|
+
if (
|
|
1120
|
+
not has_all_defaults
|
|
1121
|
+
and base_mapper._prefer_eager_defaults(
|
|
1122
|
+
connection.dialect, table
|
|
1123
|
+
)
|
|
1124
|
+
):
|
|
1125
|
+
statement = statement.return_defaults(
|
|
1126
|
+
*mapper._server_default_cols[table],
|
|
1127
|
+
sort_by_parameter_order=bookkeeping,
|
|
1128
|
+
)
|
|
1129
|
+
|
|
1130
|
+
if mapper.version_id_col is not None:
|
|
1131
|
+
statement = statement.return_defaults(
|
|
1132
|
+
mapper.version_id_col,
|
|
1133
|
+
sort_by_parameter_order=bookkeeping,
|
|
1134
|
+
)
|
|
1135
|
+
elif do_executemany:
|
|
1136
|
+
statement = statement.return_defaults(
|
|
1137
|
+
*table.primary_key, sort_by_parameter_order=bookkeeping
|
|
1138
|
+
)
|
|
1139
|
+
|
|
1140
|
+
if do_executemany:
|
|
1141
|
+
multiparams = [rec[2] for rec in records]
|
|
1142
|
+
|
|
1143
|
+
result = connection.execute(
|
|
1144
|
+
statement, multiparams, execution_options=execution_options
|
|
1145
|
+
)
|
|
1146
|
+
|
|
1147
|
+
if use_orm_insert_stmt is not None:
|
|
1148
|
+
if return_result is None:
|
|
1149
|
+
return_result = result
|
|
1150
|
+
else:
|
|
1151
|
+
return_result = return_result.splice_vertically(result)
|
|
1152
|
+
|
|
1153
|
+
if bookkeeping:
|
|
1154
|
+
for (
|
|
1155
|
+
(
|
|
1156
|
+
state,
|
|
1157
|
+
state_dict,
|
|
1158
|
+
params,
|
|
1159
|
+
mapper_rec,
|
|
1160
|
+
conn,
|
|
1161
|
+
value_params,
|
|
1162
|
+
has_all_pks,
|
|
1163
|
+
has_all_defaults,
|
|
1164
|
+
),
|
|
1165
|
+
last_inserted_params,
|
|
1166
|
+
inserted_primary_key,
|
|
1167
|
+
returned_defaults,
|
|
1168
|
+
) in zip_longest(
|
|
1169
|
+
records,
|
|
1170
|
+
result.context.compiled_parameters,
|
|
1171
|
+
result.inserted_primary_key_rows,
|
|
1172
|
+
result.returned_defaults_rows or (),
|
|
1173
|
+
):
|
|
1174
|
+
if inserted_primary_key is None:
|
|
1175
|
+
# this is a real problem and means that we didn't
|
|
1176
|
+
# get back as many PK rows. we can't continue
|
|
1177
|
+
# since this indicates PK rows were missing, which
|
|
1178
|
+
# means we likely mis-populated records starting
|
|
1179
|
+
# at that point with incorrectly matched PK
|
|
1180
|
+
# values.
|
|
1181
|
+
raise orm_exc.FlushError(
|
|
1182
|
+
"Multi-row INSERT statement for %s did not "
|
|
1183
|
+
"produce "
|
|
1184
|
+
"the correct number of INSERTed rows for "
|
|
1185
|
+
"RETURNING. Ensure there are no triggers or "
|
|
1186
|
+
"special driver issues preventing INSERT from "
|
|
1187
|
+
"functioning properly." % mapper_rec
|
|
1188
|
+
)
|
|
1189
|
+
|
|
1190
|
+
for pk, col in zip(
|
|
1191
|
+
inserted_primary_key,
|
|
1192
|
+
mapper._pks_by_table[table],
|
|
1193
|
+
):
|
|
1194
|
+
prop = mapper_rec._columntoproperty[col]
|
|
1195
|
+
if state_dict.get(prop.key) is None:
|
|
1196
|
+
state_dict[prop.key] = pk
|
|
1197
|
+
|
|
1198
|
+
if state:
|
|
1199
|
+
_postfetch(
|
|
1200
|
+
mapper_rec,
|
|
1201
|
+
uowtransaction,
|
|
1202
|
+
table,
|
|
1203
|
+
state,
|
|
1204
|
+
state_dict,
|
|
1205
|
+
result,
|
|
1206
|
+
last_inserted_params,
|
|
1207
|
+
value_params,
|
|
1208
|
+
False,
|
|
1209
|
+
returned_defaults,
|
|
1210
|
+
)
|
|
1211
|
+
else:
|
|
1212
|
+
_postfetch_bulk_save(mapper_rec, state_dict, table)
|
|
1213
|
+
else:
|
|
1214
|
+
assert not returning_is_required_anyway
|
|
1215
|
+
|
|
1216
|
+
for (
|
|
1217
|
+
state,
|
|
1218
|
+
state_dict,
|
|
1219
|
+
params,
|
|
1220
|
+
mapper_rec,
|
|
1221
|
+
connection,
|
|
1222
|
+
value_params,
|
|
1223
|
+
has_all_pks,
|
|
1224
|
+
has_all_defaults,
|
|
1225
|
+
) in records:
|
|
1226
|
+
if value_params:
|
|
1227
|
+
result = connection.execute(
|
|
1228
|
+
statement.values(value_params),
|
|
1229
|
+
params,
|
|
1230
|
+
execution_options=execution_options,
|
|
1231
|
+
)
|
|
1232
|
+
else:
|
|
1233
|
+
result = connection.execute(
|
|
1234
|
+
statement,
|
|
1235
|
+
params,
|
|
1236
|
+
execution_options=execution_options,
|
|
1237
|
+
)
|
|
1238
|
+
|
|
1239
|
+
primary_key = result.inserted_primary_key
|
|
1240
|
+
if primary_key is None:
|
|
1241
|
+
raise orm_exc.FlushError(
|
|
1242
|
+
"Single-row INSERT statement for %s "
|
|
1243
|
+
"did not produce a "
|
|
1244
|
+
"new primary key result "
|
|
1245
|
+
"being invoked. Ensure there are no triggers or "
|
|
1246
|
+
"special driver issues preventing INSERT from "
|
|
1247
|
+
"functioning properly." % (mapper_rec,)
|
|
1248
|
+
)
|
|
1249
|
+
for pk, col in zip(
|
|
1250
|
+
primary_key, mapper._pks_by_table[table]
|
|
1251
|
+
):
|
|
1252
|
+
prop = mapper_rec._columntoproperty[col]
|
|
1253
|
+
if (
|
|
1254
|
+
col in value_params
|
|
1255
|
+
or state_dict.get(prop.key) is None
|
|
1256
|
+
):
|
|
1257
|
+
state_dict[prop.key] = pk
|
|
1258
|
+
if bookkeeping:
|
|
1259
|
+
if state:
|
|
1260
|
+
_postfetch(
|
|
1261
|
+
mapper_rec,
|
|
1262
|
+
uowtransaction,
|
|
1263
|
+
table,
|
|
1264
|
+
state,
|
|
1265
|
+
state_dict,
|
|
1266
|
+
result,
|
|
1267
|
+
result.context.compiled_parameters[0],
|
|
1268
|
+
value_params,
|
|
1269
|
+
False,
|
|
1270
|
+
(
|
|
1271
|
+
result.returned_defaults
|
|
1272
|
+
if not result.context.executemany
|
|
1273
|
+
else None
|
|
1274
|
+
),
|
|
1275
|
+
)
|
|
1276
|
+
else:
|
|
1277
|
+
_postfetch_bulk_save(mapper_rec, state_dict, table)
|
|
1278
|
+
|
|
1279
|
+
if use_orm_insert_stmt is not None:
|
|
1280
|
+
if return_result is None:
|
|
1281
|
+
return _cursor.null_dml_result()
|
|
1282
|
+
else:
|
|
1283
|
+
return return_result
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
def _emit_post_update_statements(
|
|
1287
|
+
base_mapper, uowtransaction, mapper, table, update
|
|
1288
|
+
):
|
|
1289
|
+
"""Emit UPDATE statements corresponding to value lists collected
|
|
1290
|
+
by _collect_post_update_commands()."""
|
|
1291
|
+
|
|
1292
|
+
execution_options = {"compiled_cache": base_mapper._compiled_cache}
|
|
1293
|
+
|
|
1294
|
+
needs_version_id = (
|
|
1295
|
+
mapper.version_id_col is not None
|
|
1296
|
+
and mapper.version_id_col in mapper._cols_by_table[table]
|
|
1297
|
+
)
|
|
1298
|
+
|
|
1299
|
+
def update_stmt():
|
|
1300
|
+
clauses = BooleanClauseList._construct_raw(operators.and_)
|
|
1301
|
+
|
|
1302
|
+
for col in mapper._pks_by_table[table]:
|
|
1303
|
+
clauses._append_inplace(
|
|
1304
|
+
col == sql.bindparam(col._label, type_=col.type)
|
|
1305
|
+
)
|
|
1306
|
+
|
|
1307
|
+
if needs_version_id:
|
|
1308
|
+
clauses._append_inplace(
|
|
1309
|
+
mapper.version_id_col
|
|
1310
|
+
== sql.bindparam(
|
|
1311
|
+
mapper.version_id_col._label,
|
|
1312
|
+
type_=mapper.version_id_col.type,
|
|
1313
|
+
)
|
|
1314
|
+
)
|
|
1315
|
+
|
|
1316
|
+
stmt = table.update().where(clauses)
|
|
1317
|
+
|
|
1318
|
+
return stmt
|
|
1319
|
+
|
|
1320
|
+
statement = base_mapper._memo(("post_update", table), update_stmt)
|
|
1321
|
+
|
|
1322
|
+
if mapper._version_id_has_server_side_value:
|
|
1323
|
+
statement = statement.return_defaults(mapper.version_id_col)
|
|
1324
|
+
|
|
1325
|
+
# execute each UPDATE in the order according to the original
|
|
1326
|
+
# list of states to guarantee row access order, but
|
|
1327
|
+
# also group them into common (connection, cols) sets
|
|
1328
|
+
# to support executemany().
|
|
1329
|
+
for key, records in groupby(
|
|
1330
|
+
update,
|
|
1331
|
+
lambda rec: (rec[3], set(rec[4])), # connection # parameter keys
|
|
1332
|
+
):
|
|
1333
|
+
rows = 0
|
|
1334
|
+
|
|
1335
|
+
records = list(records)
|
|
1336
|
+
connection = key[0]
|
|
1337
|
+
|
|
1338
|
+
assert_singlerow = connection.dialect.supports_sane_rowcount
|
|
1339
|
+
assert_multirow = (
|
|
1340
|
+
assert_singlerow
|
|
1341
|
+
and connection.dialect.supports_sane_multi_rowcount
|
|
1342
|
+
)
|
|
1343
|
+
allow_executemany = not needs_version_id or assert_multirow
|
|
1344
|
+
|
|
1345
|
+
if not allow_executemany:
|
|
1346
|
+
check_rowcount = assert_singlerow
|
|
1347
|
+
for state, state_dict, mapper_rec, connection, params in records:
|
|
1348
|
+
c = connection.execute(
|
|
1349
|
+
statement, params, execution_options=execution_options
|
|
1350
|
+
)
|
|
1351
|
+
|
|
1352
|
+
_postfetch_post_update(
|
|
1353
|
+
mapper_rec,
|
|
1354
|
+
uowtransaction,
|
|
1355
|
+
table,
|
|
1356
|
+
state,
|
|
1357
|
+
state_dict,
|
|
1358
|
+
c,
|
|
1359
|
+
c.context.compiled_parameters[0],
|
|
1360
|
+
)
|
|
1361
|
+
rows += c.rowcount
|
|
1362
|
+
else:
|
|
1363
|
+
multiparams = [
|
|
1364
|
+
params
|
|
1365
|
+
for state, state_dict, mapper_rec, conn, params in records
|
|
1366
|
+
]
|
|
1367
|
+
|
|
1368
|
+
check_rowcount = assert_multirow or (
|
|
1369
|
+
assert_singlerow and len(multiparams) == 1
|
|
1370
|
+
)
|
|
1371
|
+
|
|
1372
|
+
c = connection.execute(
|
|
1373
|
+
statement, multiparams, execution_options=execution_options
|
|
1374
|
+
)
|
|
1375
|
+
|
|
1376
|
+
rows += c.rowcount
|
|
1377
|
+
for i, (
|
|
1378
|
+
state,
|
|
1379
|
+
state_dict,
|
|
1380
|
+
mapper_rec,
|
|
1381
|
+
connection,
|
|
1382
|
+
params,
|
|
1383
|
+
) in enumerate(records):
|
|
1384
|
+
_postfetch_post_update(
|
|
1385
|
+
mapper_rec,
|
|
1386
|
+
uowtransaction,
|
|
1387
|
+
table,
|
|
1388
|
+
state,
|
|
1389
|
+
state_dict,
|
|
1390
|
+
c,
|
|
1391
|
+
c.context.compiled_parameters[i],
|
|
1392
|
+
)
|
|
1393
|
+
|
|
1394
|
+
if check_rowcount:
|
|
1395
|
+
if rows != len(records):
|
|
1396
|
+
raise orm_exc.StaleDataError(
|
|
1397
|
+
"UPDATE statement on table '%s' expected to "
|
|
1398
|
+
"update %d row(s); %d were matched."
|
|
1399
|
+
% (table.description, len(records), rows)
|
|
1400
|
+
)
|
|
1401
|
+
|
|
1402
|
+
elif needs_version_id:
|
|
1403
|
+
util.warn(
|
|
1404
|
+
"Dialect %s does not support updated rowcount "
|
|
1405
|
+
"- versioning cannot be verified."
|
|
1406
|
+
% c.dialect.dialect_description
|
|
1407
|
+
)
|
|
1408
|
+
|
|
1409
|
+
|
|
1410
|
+
def _emit_delete_statements(
|
|
1411
|
+
base_mapper, uowtransaction, mapper, table, delete
|
|
1412
|
+
):
|
|
1413
|
+
"""Emit DELETE statements corresponding to value lists collected
|
|
1414
|
+
by _collect_delete_commands()."""
|
|
1415
|
+
|
|
1416
|
+
need_version_id = (
|
|
1417
|
+
mapper.version_id_col is not None
|
|
1418
|
+
and mapper.version_id_col in mapper._cols_by_table[table]
|
|
1419
|
+
)
|
|
1420
|
+
|
|
1421
|
+
def delete_stmt():
|
|
1422
|
+
clauses = BooleanClauseList._construct_raw(operators.and_)
|
|
1423
|
+
|
|
1424
|
+
for col in mapper._pks_by_table[table]:
|
|
1425
|
+
clauses._append_inplace(
|
|
1426
|
+
col == sql.bindparam(col.key, type_=col.type)
|
|
1427
|
+
)
|
|
1428
|
+
|
|
1429
|
+
if need_version_id:
|
|
1430
|
+
clauses._append_inplace(
|
|
1431
|
+
mapper.version_id_col
|
|
1432
|
+
== sql.bindparam(
|
|
1433
|
+
mapper.version_id_col.key, type_=mapper.version_id_col.type
|
|
1434
|
+
)
|
|
1435
|
+
)
|
|
1436
|
+
|
|
1437
|
+
return table.delete().where(clauses)
|
|
1438
|
+
|
|
1439
|
+
statement = base_mapper._memo(("delete", table), delete_stmt)
|
|
1440
|
+
for connection, recs in groupby(delete, lambda rec: rec[1]): # connection
|
|
1441
|
+
del_objects = [params for params, connection in recs]
|
|
1442
|
+
|
|
1443
|
+
execution_options = {"compiled_cache": base_mapper._compiled_cache}
|
|
1444
|
+
expected = len(del_objects)
|
|
1445
|
+
rows_matched = -1
|
|
1446
|
+
only_warn = False
|
|
1447
|
+
|
|
1448
|
+
if (
|
|
1449
|
+
need_version_id
|
|
1450
|
+
and not connection.dialect.supports_sane_multi_rowcount
|
|
1451
|
+
):
|
|
1452
|
+
if connection.dialect.supports_sane_rowcount:
|
|
1453
|
+
rows_matched = 0
|
|
1454
|
+
# execute deletes individually so that versioned
|
|
1455
|
+
# rows can be verified
|
|
1456
|
+
for params in del_objects:
|
|
1457
|
+
c = connection.execute(
|
|
1458
|
+
statement, params, execution_options=execution_options
|
|
1459
|
+
)
|
|
1460
|
+
rows_matched += c.rowcount
|
|
1461
|
+
else:
|
|
1462
|
+
util.warn(
|
|
1463
|
+
"Dialect %s does not support deleted rowcount "
|
|
1464
|
+
"- versioning cannot be verified."
|
|
1465
|
+
% connection.dialect.dialect_description
|
|
1466
|
+
)
|
|
1467
|
+
connection.execute(
|
|
1468
|
+
statement, del_objects, execution_options=execution_options
|
|
1469
|
+
)
|
|
1470
|
+
else:
|
|
1471
|
+
c = connection.execute(
|
|
1472
|
+
statement, del_objects, execution_options=execution_options
|
|
1473
|
+
)
|
|
1474
|
+
|
|
1475
|
+
if not need_version_id:
|
|
1476
|
+
only_warn = True
|
|
1477
|
+
|
|
1478
|
+
rows_matched = c.rowcount
|
|
1479
|
+
|
|
1480
|
+
if (
|
|
1481
|
+
base_mapper.confirm_deleted_rows
|
|
1482
|
+
and rows_matched > -1
|
|
1483
|
+
and expected != rows_matched
|
|
1484
|
+
and (
|
|
1485
|
+
connection.dialect.supports_sane_multi_rowcount
|
|
1486
|
+
or len(del_objects) == 1
|
|
1487
|
+
)
|
|
1488
|
+
):
|
|
1489
|
+
# TODO: why does this "only warn" if versioning is turned off,
|
|
1490
|
+
# whereas the UPDATE raises?
|
|
1491
|
+
if only_warn:
|
|
1492
|
+
util.warn(
|
|
1493
|
+
"DELETE statement on table '%s' expected to "
|
|
1494
|
+
"delete %d row(s); %d were matched. Please set "
|
|
1495
|
+
"confirm_deleted_rows=False within the mapper "
|
|
1496
|
+
"configuration to prevent this warning."
|
|
1497
|
+
% (table.description, expected, rows_matched)
|
|
1498
|
+
)
|
|
1499
|
+
else:
|
|
1500
|
+
raise orm_exc.StaleDataError(
|
|
1501
|
+
"DELETE statement on table '%s' expected to "
|
|
1502
|
+
"delete %d row(s); %d were matched. Please set "
|
|
1503
|
+
"confirm_deleted_rows=False within the mapper "
|
|
1504
|
+
"configuration to prevent this warning."
|
|
1505
|
+
% (table.description, expected, rows_matched)
|
|
1506
|
+
)
|
|
1507
|
+
|
|
1508
|
+
|
|
1509
|
+
def _finalize_insert_update_commands(base_mapper, uowtransaction, states):
|
|
1510
|
+
"""finalize state on states that have been inserted or updated,
|
|
1511
|
+
including calling after_insert/after_update events.
|
|
1512
|
+
|
|
1513
|
+
"""
|
|
1514
|
+
for state, state_dict, mapper, connection, has_identity in states:
|
|
1515
|
+
if mapper._readonly_props:
|
|
1516
|
+
readonly = state.unmodified_intersection(
|
|
1517
|
+
[
|
|
1518
|
+
p.key
|
|
1519
|
+
for p in mapper._readonly_props
|
|
1520
|
+
if (
|
|
1521
|
+
p.expire_on_flush
|
|
1522
|
+
and (not p.deferred or p.key in state.dict)
|
|
1523
|
+
)
|
|
1524
|
+
or (
|
|
1525
|
+
not p.expire_on_flush
|
|
1526
|
+
and not p.deferred
|
|
1527
|
+
and p.key not in state.dict
|
|
1528
|
+
)
|
|
1529
|
+
]
|
|
1530
|
+
)
|
|
1531
|
+
if readonly:
|
|
1532
|
+
state._expire_attributes(state.dict, readonly)
|
|
1533
|
+
|
|
1534
|
+
# if eager_defaults option is enabled, load
|
|
1535
|
+
# all expired cols. Else if we have a version_id_col, make sure
|
|
1536
|
+
# it isn't expired.
|
|
1537
|
+
toload_now = []
|
|
1538
|
+
|
|
1539
|
+
# this is specifically to emit a second SELECT for eager_defaults,
|
|
1540
|
+
# so only if it's set to True, not "auto"
|
|
1541
|
+
if base_mapper.eager_defaults is True:
|
|
1542
|
+
toload_now.extend(
|
|
1543
|
+
state._unloaded_non_object.intersection(
|
|
1544
|
+
mapper._server_default_plus_onupdate_propkeys
|
|
1545
|
+
)
|
|
1546
|
+
)
|
|
1547
|
+
|
|
1548
|
+
if (
|
|
1549
|
+
mapper.version_id_col is not None
|
|
1550
|
+
and mapper.version_id_generator is False
|
|
1551
|
+
):
|
|
1552
|
+
if mapper._version_id_prop.key in state.unloaded:
|
|
1553
|
+
toload_now.extend([mapper._version_id_prop.key])
|
|
1554
|
+
|
|
1555
|
+
if toload_now:
|
|
1556
|
+
state.key = base_mapper._identity_key_from_state(state)
|
|
1557
|
+
stmt = future.select(mapper).set_label_style(
|
|
1558
|
+
LABEL_STYLE_TABLENAME_PLUS_COL
|
|
1559
|
+
)
|
|
1560
|
+
loading.load_on_ident(
|
|
1561
|
+
uowtransaction.session,
|
|
1562
|
+
stmt,
|
|
1563
|
+
state.key,
|
|
1564
|
+
refresh_state=state,
|
|
1565
|
+
only_load_props=toload_now,
|
|
1566
|
+
)
|
|
1567
|
+
|
|
1568
|
+
# call after_XXX extensions
|
|
1569
|
+
if not has_identity:
|
|
1570
|
+
mapper.dispatch.after_insert(mapper, connection, state)
|
|
1571
|
+
else:
|
|
1572
|
+
mapper.dispatch.after_update(mapper, connection, state)
|
|
1573
|
+
|
|
1574
|
+
if (
|
|
1575
|
+
mapper.version_id_generator is False
|
|
1576
|
+
and mapper.version_id_col is not None
|
|
1577
|
+
):
|
|
1578
|
+
if state_dict[mapper._version_id_prop.key] is None:
|
|
1579
|
+
raise orm_exc.FlushError(
|
|
1580
|
+
"Instance does not contain a non-NULL version value"
|
|
1581
|
+
)
|
|
1582
|
+
|
|
1583
|
+
|
|
1584
|
+
def _postfetch_post_update(
|
|
1585
|
+
mapper, uowtransaction, table, state, dict_, result, params
|
|
1586
|
+
):
|
|
1587
|
+
needs_version_id = (
|
|
1588
|
+
mapper.version_id_col is not None
|
|
1589
|
+
and mapper.version_id_col in mapper._cols_by_table[table]
|
|
1590
|
+
)
|
|
1591
|
+
|
|
1592
|
+
if not uowtransaction.is_deleted(state):
|
|
1593
|
+
# post updating after a regular INSERT or UPDATE, do a full postfetch
|
|
1594
|
+
prefetch_cols = result.context.compiled.prefetch
|
|
1595
|
+
postfetch_cols = result.context.compiled.postfetch
|
|
1596
|
+
elif needs_version_id:
|
|
1597
|
+
# post updating before a DELETE with a version_id_col, need to
|
|
1598
|
+
# postfetch just version_id_col
|
|
1599
|
+
prefetch_cols = postfetch_cols = ()
|
|
1600
|
+
else:
|
|
1601
|
+
# post updating before a DELETE without a version_id_col,
|
|
1602
|
+
# don't need to postfetch
|
|
1603
|
+
return
|
|
1604
|
+
|
|
1605
|
+
if needs_version_id:
|
|
1606
|
+
prefetch_cols = list(prefetch_cols) + [mapper.version_id_col]
|
|
1607
|
+
|
|
1608
|
+
refresh_flush = bool(mapper.class_manager.dispatch.refresh_flush)
|
|
1609
|
+
if refresh_flush:
|
|
1610
|
+
load_evt_attrs = []
|
|
1611
|
+
|
|
1612
|
+
for c in prefetch_cols:
|
|
1613
|
+
if c.key in params and c in mapper._columntoproperty:
|
|
1614
|
+
dict_[mapper._columntoproperty[c].key] = params[c.key]
|
|
1615
|
+
if refresh_flush:
|
|
1616
|
+
load_evt_attrs.append(mapper._columntoproperty[c].key)
|
|
1617
|
+
|
|
1618
|
+
if refresh_flush and load_evt_attrs:
|
|
1619
|
+
mapper.class_manager.dispatch.refresh_flush(
|
|
1620
|
+
state, uowtransaction, load_evt_attrs
|
|
1621
|
+
)
|
|
1622
|
+
|
|
1623
|
+
if postfetch_cols:
|
|
1624
|
+
state._expire_attributes(
|
|
1625
|
+
state.dict,
|
|
1626
|
+
[
|
|
1627
|
+
mapper._columntoproperty[c].key
|
|
1628
|
+
for c in postfetch_cols
|
|
1629
|
+
if c in mapper._columntoproperty
|
|
1630
|
+
],
|
|
1631
|
+
)
|
|
1632
|
+
|
|
1633
|
+
|
|
1634
|
+
def _postfetch(
|
|
1635
|
+
mapper,
|
|
1636
|
+
uowtransaction,
|
|
1637
|
+
table,
|
|
1638
|
+
state,
|
|
1639
|
+
dict_,
|
|
1640
|
+
result,
|
|
1641
|
+
params,
|
|
1642
|
+
value_params,
|
|
1643
|
+
isupdate,
|
|
1644
|
+
returned_defaults,
|
|
1645
|
+
):
|
|
1646
|
+
"""Expire attributes in need of newly persisted database state,
|
|
1647
|
+
after an INSERT or UPDATE statement has proceeded for that
|
|
1648
|
+
state."""
|
|
1649
|
+
|
|
1650
|
+
prefetch_cols = result.context.compiled.prefetch
|
|
1651
|
+
postfetch_cols = result.context.compiled.postfetch
|
|
1652
|
+
returning_cols = result.context.compiled.effective_returning
|
|
1653
|
+
|
|
1654
|
+
if (
|
|
1655
|
+
mapper.version_id_col is not None
|
|
1656
|
+
and mapper.version_id_col in mapper._cols_by_table[table]
|
|
1657
|
+
):
|
|
1658
|
+
prefetch_cols = list(prefetch_cols) + [mapper.version_id_col]
|
|
1659
|
+
|
|
1660
|
+
refresh_flush = bool(mapper.class_manager.dispatch.refresh_flush)
|
|
1661
|
+
if refresh_flush:
|
|
1662
|
+
load_evt_attrs = []
|
|
1663
|
+
|
|
1664
|
+
if returning_cols:
|
|
1665
|
+
row = returned_defaults
|
|
1666
|
+
if row is not None:
|
|
1667
|
+
for row_value, col in zip(row, returning_cols):
|
|
1668
|
+
# pk cols returned from insert are handled
|
|
1669
|
+
# distinctly, don't step on the values here
|
|
1670
|
+
if col.primary_key and result.context.isinsert:
|
|
1671
|
+
continue
|
|
1672
|
+
|
|
1673
|
+
# note that columns can be in the "return defaults" that are
|
|
1674
|
+
# not mapped to this mapper, typically because they are
|
|
1675
|
+
# "excluded", which can be specified directly or also occurs
|
|
1676
|
+
# when using declarative w/ single table inheritance
|
|
1677
|
+
prop = mapper._columntoproperty.get(col)
|
|
1678
|
+
if prop:
|
|
1679
|
+
dict_[prop.key] = row_value
|
|
1680
|
+
if refresh_flush:
|
|
1681
|
+
load_evt_attrs.append(prop.key)
|
|
1682
|
+
|
|
1683
|
+
for c in prefetch_cols:
|
|
1684
|
+
if c.key in params and c in mapper._columntoproperty:
|
|
1685
|
+
pkey = mapper._columntoproperty[c].key
|
|
1686
|
+
|
|
1687
|
+
# set prefetched value in dict and also pop from committed_state,
|
|
1688
|
+
# since this is new database state that replaces whatever might
|
|
1689
|
+
# have previously been fetched (see #10800). this is essentially a
|
|
1690
|
+
# shorthand version of set_committed_value(), which could also be
|
|
1691
|
+
# used here directly (with more overhead)
|
|
1692
|
+
dict_[pkey] = params[c.key]
|
|
1693
|
+
state.committed_state.pop(pkey, None)
|
|
1694
|
+
|
|
1695
|
+
if refresh_flush:
|
|
1696
|
+
load_evt_attrs.append(pkey)
|
|
1697
|
+
|
|
1698
|
+
if refresh_flush and load_evt_attrs:
|
|
1699
|
+
mapper.class_manager.dispatch.refresh_flush(
|
|
1700
|
+
state, uowtransaction, load_evt_attrs
|
|
1701
|
+
)
|
|
1702
|
+
|
|
1703
|
+
if isupdate and value_params:
|
|
1704
|
+
# explicitly suit the use case specified by
|
|
1705
|
+
# [ticket:3801], PK SQL expressions for UPDATE on non-RETURNING
|
|
1706
|
+
# database which are set to themselves in order to do a version bump.
|
|
1707
|
+
postfetch_cols.extend(
|
|
1708
|
+
[
|
|
1709
|
+
col
|
|
1710
|
+
for col in value_params
|
|
1711
|
+
if col.primary_key and col not in returning_cols
|
|
1712
|
+
]
|
|
1713
|
+
)
|
|
1714
|
+
|
|
1715
|
+
if postfetch_cols:
|
|
1716
|
+
state._expire_attributes(
|
|
1717
|
+
state.dict,
|
|
1718
|
+
[
|
|
1719
|
+
mapper._columntoproperty[c].key
|
|
1720
|
+
for c in postfetch_cols
|
|
1721
|
+
if c in mapper._columntoproperty
|
|
1722
|
+
],
|
|
1723
|
+
)
|
|
1724
|
+
|
|
1725
|
+
# synchronize newly inserted ids from one table to the next
|
|
1726
|
+
# TODO: this still goes a little too often. would be nice to
|
|
1727
|
+
# have definitive list of "columns that changed" here
|
|
1728
|
+
for m, equated_pairs in mapper._table_to_equated[table]:
|
|
1729
|
+
sync.populate(
|
|
1730
|
+
state,
|
|
1731
|
+
m,
|
|
1732
|
+
state,
|
|
1733
|
+
m,
|
|
1734
|
+
equated_pairs,
|
|
1735
|
+
uowtransaction,
|
|
1736
|
+
mapper.passive_updates,
|
|
1737
|
+
)
|
|
1738
|
+
|
|
1739
|
+
|
|
1740
|
+
def _postfetch_bulk_save(mapper, dict_, table):
|
|
1741
|
+
for m, equated_pairs in mapper._table_to_equated[table]:
|
|
1742
|
+
sync.bulk_populate_inherit_keys(dict_, m, equated_pairs)
|
|
1743
|
+
|
|
1744
|
+
|
|
1745
|
+
def _connections_for_states(base_mapper, uowtransaction, states):
|
|
1746
|
+
"""Return an iterator of (state, state.dict, mapper, connection).
|
|
1747
|
+
|
|
1748
|
+
The states are sorted according to _sort_states, then paired
|
|
1749
|
+
with the connection they should be using for the given
|
|
1750
|
+
unit of work transaction.
|
|
1751
|
+
|
|
1752
|
+
"""
|
|
1753
|
+
# if session has a connection callable,
|
|
1754
|
+
# organize individual states with the connection
|
|
1755
|
+
# to use for update
|
|
1756
|
+
if uowtransaction.session.connection_callable:
|
|
1757
|
+
connection_callable = uowtransaction.session.connection_callable
|
|
1758
|
+
else:
|
|
1759
|
+
connection = uowtransaction.transaction.connection(base_mapper)
|
|
1760
|
+
connection_callable = None
|
|
1761
|
+
|
|
1762
|
+
for state in _sort_states(base_mapper, states):
|
|
1763
|
+
if connection_callable:
|
|
1764
|
+
connection = connection_callable(base_mapper, state.obj())
|
|
1765
|
+
|
|
1766
|
+
mapper = state.manager.mapper
|
|
1767
|
+
|
|
1768
|
+
yield state, state.dict, mapper, connection
|
|
1769
|
+
|
|
1770
|
+
|
|
1771
|
+
def _sort_states(mapper, states):
|
|
1772
|
+
pending = set(states)
|
|
1773
|
+
persistent = {s for s in pending if s.key is not None}
|
|
1774
|
+
pending.difference_update(persistent)
|
|
1775
|
+
|
|
1776
|
+
try:
|
|
1777
|
+
persistent_sorted = sorted(
|
|
1778
|
+
persistent, key=mapper._persistent_sortkey_fn
|
|
1779
|
+
)
|
|
1780
|
+
except TypeError as err:
|
|
1781
|
+
raise sa_exc.InvalidRequestError(
|
|
1782
|
+
"Could not sort objects by primary key; primary key "
|
|
1783
|
+
"values must be sortable in Python (was: %s)" % err
|
|
1784
|
+
) from err
|
|
1785
|
+
return (
|
|
1786
|
+
sorted(pending, key=operator.attrgetter("insert_order"))
|
|
1787
|
+
+ persistent_sorted
|
|
1788
|
+
)
|