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.
Files changed (274) hide show
  1. sqlalchemy/__init__.py +283 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +184 -0
  4. sqlalchemy/connectors/asyncio.py +429 -0
  5. sqlalchemy/connectors/pyodbc.py +250 -0
  6. sqlalchemy/cyextension/__init__.py +6 -0
  7. sqlalchemy/cyextension/collections.cp313t-win32.pyd +0 -0
  8. sqlalchemy/cyextension/collections.pyx +409 -0
  9. sqlalchemy/cyextension/immutabledict.cp313t-win32.pyd +0 -0
  10. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  11. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  12. sqlalchemy/cyextension/processors.cp313t-win32.pyd +0 -0
  13. sqlalchemy/cyextension/processors.pyx +68 -0
  14. sqlalchemy/cyextension/resultproxy.cp313t-win32.pyd +0 -0
  15. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  16. sqlalchemy/cyextension/util.cp313t-win32.pyd +0 -0
  17. sqlalchemy/cyextension/util.pyx +90 -0
  18. sqlalchemy/dialects/__init__.py +62 -0
  19. sqlalchemy/dialects/_typing.py +30 -0
  20. sqlalchemy/dialects/mssql/__init__.py +88 -0
  21. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  22. sqlalchemy/dialects/mssql/base.py +4093 -0
  23. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  24. sqlalchemy/dialects/mssql/json.py +129 -0
  25. sqlalchemy/dialects/mssql/provision.py +185 -0
  26. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  27. sqlalchemy/dialects/mssql/pyodbc.py +760 -0
  28. sqlalchemy/dialects/mysql/__init__.py +104 -0
  29. sqlalchemy/dialects/mysql/aiomysql.py +250 -0
  30. sqlalchemy/dialects/mysql/asyncmy.py +231 -0
  31. sqlalchemy/dialects/mysql/base.py +3949 -0
  32. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  33. sqlalchemy/dialects/mysql/dml.py +225 -0
  34. sqlalchemy/dialects/mysql/enumerated.py +282 -0
  35. sqlalchemy/dialects/mysql/expression.py +146 -0
  36. sqlalchemy/dialects/mysql/json.py +91 -0
  37. sqlalchemy/dialects/mysql/mariadb.py +72 -0
  38. sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
  39. sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
  40. sqlalchemy/dialects/mysql/mysqldb.py +314 -0
  41. sqlalchemy/dialects/mysql/provision.py +153 -0
  42. sqlalchemy/dialects/mysql/pymysql.py +158 -0
  43. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  44. sqlalchemy/dialects/mysql/reflection.py +727 -0
  45. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  46. sqlalchemy/dialects/mysql/types.py +835 -0
  47. sqlalchemy/dialects/oracle/__init__.py +81 -0
  48. sqlalchemy/dialects/oracle/base.py +3802 -0
  49. sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
  50. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  51. sqlalchemy/dialects/oracle/oracledb.py +941 -0
  52. sqlalchemy/dialects/oracle/provision.py +297 -0
  53. sqlalchemy/dialects/oracle/types.py +316 -0
  54. sqlalchemy/dialects/oracle/vector.py +365 -0
  55. sqlalchemy/dialects/postgresql/__init__.py +167 -0
  56. sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
  57. sqlalchemy/dialects/postgresql/array.py +519 -0
  58. sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
  59. sqlalchemy/dialects/postgresql/base.py +5378 -0
  60. sqlalchemy/dialects/postgresql/dml.py +339 -0
  61. sqlalchemy/dialects/postgresql/ext.py +540 -0
  62. sqlalchemy/dialects/postgresql/hstore.py +406 -0
  63. sqlalchemy/dialects/postgresql/json.py +404 -0
  64. sqlalchemy/dialects/postgresql/named_types.py +524 -0
  65. sqlalchemy/dialects/postgresql/operators.py +129 -0
  66. sqlalchemy/dialects/postgresql/pg8000.py +669 -0
  67. sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
  68. sqlalchemy/dialects/postgresql/provision.py +183 -0
  69. sqlalchemy/dialects/postgresql/psycopg.py +862 -0
  70. sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
  71. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  72. sqlalchemy/dialects/postgresql/ranges.py +1031 -0
  73. sqlalchemy/dialects/postgresql/types.py +313 -0
  74. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  75. sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
  76. sqlalchemy/dialects/sqlite/base.py +3056 -0
  77. sqlalchemy/dialects/sqlite/dml.py +263 -0
  78. sqlalchemy/dialects/sqlite/json.py +92 -0
  79. sqlalchemy/dialects/sqlite/provision.py +229 -0
  80. sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
  81. sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
  82. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  83. sqlalchemy/engine/__init__.py +62 -0
  84. sqlalchemy/engine/_py_processors.py +136 -0
  85. sqlalchemy/engine/_py_row.py +128 -0
  86. sqlalchemy/engine/_py_util.py +74 -0
  87. sqlalchemy/engine/base.py +3390 -0
  88. sqlalchemy/engine/characteristics.py +155 -0
  89. sqlalchemy/engine/create.py +893 -0
  90. sqlalchemy/engine/cursor.py +2298 -0
  91. sqlalchemy/engine/default.py +2394 -0
  92. sqlalchemy/engine/events.py +965 -0
  93. sqlalchemy/engine/interfaces.py +3471 -0
  94. sqlalchemy/engine/mock.py +134 -0
  95. sqlalchemy/engine/processors.py +61 -0
  96. sqlalchemy/engine/reflection.py +2102 -0
  97. sqlalchemy/engine/result.py +2399 -0
  98. sqlalchemy/engine/row.py +400 -0
  99. sqlalchemy/engine/strategies.py +16 -0
  100. sqlalchemy/engine/url.py +924 -0
  101. sqlalchemy/engine/util.py +167 -0
  102. sqlalchemy/event/__init__.py +26 -0
  103. sqlalchemy/event/api.py +220 -0
  104. sqlalchemy/event/attr.py +676 -0
  105. sqlalchemy/event/base.py +472 -0
  106. sqlalchemy/event/legacy.py +258 -0
  107. sqlalchemy/event/registry.py +390 -0
  108. sqlalchemy/events.py +17 -0
  109. sqlalchemy/exc.py +832 -0
  110. sqlalchemy/ext/__init__.py +11 -0
  111. sqlalchemy/ext/associationproxy.py +2027 -0
  112. sqlalchemy/ext/asyncio/__init__.py +25 -0
  113. sqlalchemy/ext/asyncio/base.py +281 -0
  114. sqlalchemy/ext/asyncio/engine.py +1471 -0
  115. sqlalchemy/ext/asyncio/exc.py +21 -0
  116. sqlalchemy/ext/asyncio/result.py +965 -0
  117. sqlalchemy/ext/asyncio/scoping.py +1599 -0
  118. sqlalchemy/ext/asyncio/session.py +1947 -0
  119. sqlalchemy/ext/automap.py +1701 -0
  120. sqlalchemy/ext/baked.py +570 -0
  121. sqlalchemy/ext/compiler.py +600 -0
  122. sqlalchemy/ext/declarative/__init__.py +65 -0
  123. sqlalchemy/ext/declarative/extensions.py +564 -0
  124. sqlalchemy/ext/horizontal_shard.py +478 -0
  125. sqlalchemy/ext/hybrid.py +1535 -0
  126. sqlalchemy/ext/indexable.py +364 -0
  127. sqlalchemy/ext/instrumentation.py +450 -0
  128. sqlalchemy/ext/mutable.py +1085 -0
  129. sqlalchemy/ext/mypy/__init__.py +6 -0
  130. sqlalchemy/ext/mypy/apply.py +324 -0
  131. sqlalchemy/ext/mypy/decl_class.py +515 -0
  132. sqlalchemy/ext/mypy/infer.py +590 -0
  133. sqlalchemy/ext/mypy/names.py +335 -0
  134. sqlalchemy/ext/mypy/plugin.py +303 -0
  135. sqlalchemy/ext/mypy/util.py +357 -0
  136. sqlalchemy/ext/orderinglist.py +439 -0
  137. sqlalchemy/ext/serializer.py +185 -0
  138. sqlalchemy/future/__init__.py +16 -0
  139. sqlalchemy/future/engine.py +15 -0
  140. sqlalchemy/inspection.py +174 -0
  141. sqlalchemy/log.py +288 -0
  142. sqlalchemy/orm/__init__.py +171 -0
  143. sqlalchemy/orm/_orm_constructors.py +2661 -0
  144. sqlalchemy/orm/_typing.py +179 -0
  145. sqlalchemy/orm/attributes.py +2845 -0
  146. sqlalchemy/orm/base.py +971 -0
  147. sqlalchemy/orm/bulk_persistence.py +2135 -0
  148. sqlalchemy/orm/clsregistry.py +571 -0
  149. sqlalchemy/orm/collections.py +1627 -0
  150. sqlalchemy/orm/context.py +3334 -0
  151. sqlalchemy/orm/decl_api.py +2004 -0
  152. sqlalchemy/orm/decl_base.py +2192 -0
  153. sqlalchemy/orm/dependency.py +1302 -0
  154. sqlalchemy/orm/descriptor_props.py +1092 -0
  155. sqlalchemy/orm/dynamic.py +300 -0
  156. sqlalchemy/orm/evaluator.py +379 -0
  157. sqlalchemy/orm/events.py +3252 -0
  158. sqlalchemy/orm/exc.py +237 -0
  159. sqlalchemy/orm/identity.py +302 -0
  160. sqlalchemy/orm/instrumentation.py +754 -0
  161. sqlalchemy/orm/interfaces.py +1496 -0
  162. sqlalchemy/orm/loading.py +1686 -0
  163. sqlalchemy/orm/mapped_collection.py +557 -0
  164. sqlalchemy/orm/mapper.py +4444 -0
  165. sqlalchemy/orm/path_registry.py +809 -0
  166. sqlalchemy/orm/persistence.py +1788 -0
  167. sqlalchemy/orm/properties.py +935 -0
  168. sqlalchemy/orm/query.py +3459 -0
  169. sqlalchemy/orm/relationships.py +3508 -0
  170. sqlalchemy/orm/scoping.py +2148 -0
  171. sqlalchemy/orm/session.py +5280 -0
  172. sqlalchemy/orm/state.py +1168 -0
  173. sqlalchemy/orm/state_changes.py +196 -0
  174. sqlalchemy/orm/strategies.py +3470 -0
  175. sqlalchemy/orm/strategy_options.py +2568 -0
  176. sqlalchemy/orm/sync.py +164 -0
  177. sqlalchemy/orm/unitofwork.py +796 -0
  178. sqlalchemy/orm/util.py +2403 -0
  179. sqlalchemy/orm/writeonly.py +674 -0
  180. sqlalchemy/pool/__init__.py +44 -0
  181. sqlalchemy/pool/base.py +1524 -0
  182. sqlalchemy/pool/events.py +375 -0
  183. sqlalchemy/pool/impl.py +588 -0
  184. sqlalchemy/py.typed +0 -0
  185. sqlalchemy/schema.py +69 -0
  186. sqlalchemy/sql/__init__.py +145 -0
  187. sqlalchemy/sql/_dml_constructors.py +132 -0
  188. sqlalchemy/sql/_elements_constructors.py +1872 -0
  189. sqlalchemy/sql/_orm_types.py +20 -0
  190. sqlalchemy/sql/_py_util.py +75 -0
  191. sqlalchemy/sql/_selectable_constructors.py +763 -0
  192. sqlalchemy/sql/_typing.py +482 -0
  193. sqlalchemy/sql/annotation.py +587 -0
  194. sqlalchemy/sql/base.py +2293 -0
  195. sqlalchemy/sql/cache_key.py +1057 -0
  196. sqlalchemy/sql/coercions.py +1404 -0
  197. sqlalchemy/sql/compiler.py +8081 -0
  198. sqlalchemy/sql/crud.py +1752 -0
  199. sqlalchemy/sql/ddl.py +1444 -0
  200. sqlalchemy/sql/default_comparator.py +551 -0
  201. sqlalchemy/sql/dml.py +1850 -0
  202. sqlalchemy/sql/elements.py +5589 -0
  203. sqlalchemy/sql/events.py +458 -0
  204. sqlalchemy/sql/expression.py +159 -0
  205. sqlalchemy/sql/functions.py +2158 -0
  206. sqlalchemy/sql/lambdas.py +1442 -0
  207. sqlalchemy/sql/naming.py +209 -0
  208. sqlalchemy/sql/operators.py +2623 -0
  209. sqlalchemy/sql/roles.py +323 -0
  210. sqlalchemy/sql/schema.py +6222 -0
  211. sqlalchemy/sql/selectable.py +7265 -0
  212. sqlalchemy/sql/sqltypes.py +3930 -0
  213. sqlalchemy/sql/traversals.py +1024 -0
  214. sqlalchemy/sql/type_api.py +2368 -0
  215. sqlalchemy/sql/util.py +1485 -0
  216. sqlalchemy/sql/visitors.py +1164 -0
  217. sqlalchemy/testing/__init__.py +96 -0
  218. sqlalchemy/testing/assertions.py +994 -0
  219. sqlalchemy/testing/assertsql.py +520 -0
  220. sqlalchemy/testing/asyncio.py +135 -0
  221. sqlalchemy/testing/config.py +434 -0
  222. sqlalchemy/testing/engines.py +483 -0
  223. sqlalchemy/testing/entities.py +117 -0
  224. sqlalchemy/testing/exclusions.py +476 -0
  225. sqlalchemy/testing/fixtures/__init__.py +28 -0
  226. sqlalchemy/testing/fixtures/base.py +384 -0
  227. sqlalchemy/testing/fixtures/mypy.py +332 -0
  228. sqlalchemy/testing/fixtures/orm.py +227 -0
  229. sqlalchemy/testing/fixtures/sql.py +482 -0
  230. sqlalchemy/testing/pickleable.py +155 -0
  231. sqlalchemy/testing/plugin/__init__.py +6 -0
  232. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  233. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  234. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  235. sqlalchemy/testing/profiling.py +329 -0
  236. sqlalchemy/testing/provision.py +603 -0
  237. sqlalchemy/testing/requirements.py +1945 -0
  238. sqlalchemy/testing/schema.py +198 -0
  239. sqlalchemy/testing/suite/__init__.py +19 -0
  240. sqlalchemy/testing/suite/test_cte.py +237 -0
  241. sqlalchemy/testing/suite/test_ddl.py +389 -0
  242. sqlalchemy/testing/suite/test_deprecations.py +153 -0
  243. sqlalchemy/testing/suite/test_dialect.py +776 -0
  244. sqlalchemy/testing/suite/test_insert.py +630 -0
  245. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  246. sqlalchemy/testing/suite/test_results.py +504 -0
  247. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  248. sqlalchemy/testing/suite/test_select.py +2010 -0
  249. sqlalchemy/testing/suite/test_sequence.py +317 -0
  250. sqlalchemy/testing/suite/test_types.py +2147 -0
  251. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  252. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  253. sqlalchemy/testing/util.py +535 -0
  254. sqlalchemy/testing/warnings.py +52 -0
  255. sqlalchemy/types.py +74 -0
  256. sqlalchemy/util/__init__.py +162 -0
  257. sqlalchemy/util/_collections.py +712 -0
  258. sqlalchemy/util/_concurrency_py3k.py +288 -0
  259. sqlalchemy/util/_has_cy.py +40 -0
  260. sqlalchemy/util/_py_collections.py +541 -0
  261. sqlalchemy/util/compat.py +421 -0
  262. sqlalchemy/util/concurrency.py +110 -0
  263. sqlalchemy/util/deprecations.py +401 -0
  264. sqlalchemy/util/langhelpers.py +2203 -0
  265. sqlalchemy/util/preloaded.py +150 -0
  266. sqlalchemy/util/queue.py +322 -0
  267. sqlalchemy/util/tool_support.py +201 -0
  268. sqlalchemy/util/topological.py +120 -0
  269. sqlalchemy/util/typing.py +734 -0
  270. sqlalchemy-2.0.47.dist-info/METADATA +243 -0
  271. sqlalchemy-2.0.47.dist-info/RECORD +274 -0
  272. sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
  273. sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
  274. 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
+ )