google-cloud-spanner 3.55.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. google/cloud/spanner.py +47 -0
  2. google/cloud/spanner_admin_database_v1/__init__.py +146 -0
  3. google/cloud/spanner_admin_database_v1/gapic_metadata.json +418 -0
  4. google/cloud/spanner_admin_database_v1/gapic_version.py +16 -0
  5. google/cloud/spanner_admin_database_v1/py.typed +2 -0
  6. google/cloud/spanner_admin_database_v1/services/__init__.py +15 -0
  7. google/cloud/spanner_admin_database_v1/services/database_admin/__init__.py +22 -0
  8. google/cloud/spanner_admin_database_v1/services/database_admin/async_client.py +4097 -0
  9. google/cloud/spanner_admin_database_v1/services/database_admin/client.py +4602 -0
  10. google/cloud/spanner_admin_database_v1/services/database_admin/pagers.py +989 -0
  11. google/cloud/spanner_admin_database_v1/services/database_admin/transports/__init__.py +38 -0
  12. google/cloud/spanner_admin_database_v1/services/database_admin/transports/base.py +820 -0
  13. google/cloud/spanner_admin_database_v1/services/database_admin/transports/grpc.py +1303 -0
  14. google/cloud/spanner_admin_database_v1/services/database_admin/transports/grpc_asyncio.py +1688 -0
  15. google/cloud/spanner_admin_database_v1/services/database_admin/transports/rest.py +6512 -0
  16. google/cloud/spanner_admin_database_v1/services/database_admin/transports/rest_base.py +1650 -0
  17. google/cloud/spanner_admin_database_v1/types/__init__.py +144 -0
  18. google/cloud/spanner_admin_database_v1/types/backup.py +1106 -0
  19. google/cloud/spanner_admin_database_v1/types/backup_schedule.py +369 -0
  20. google/cloud/spanner_admin_database_v1/types/common.py +180 -0
  21. google/cloud/spanner_admin_database_v1/types/spanner_database_admin.py +1303 -0
  22. google/cloud/spanner_admin_instance_v1/__init__.py +110 -0
  23. google/cloud/spanner_admin_instance_v1/gapic_metadata.json +343 -0
  24. google/cloud/spanner_admin_instance_v1/gapic_version.py +16 -0
  25. google/cloud/spanner_admin_instance_v1/py.typed +2 -0
  26. google/cloud/spanner_admin_instance_v1/services/__init__.py +15 -0
  27. google/cloud/spanner_admin_instance_v1/services/instance_admin/__init__.py +22 -0
  28. google/cloud/spanner_admin_instance_v1/services/instance_admin/async_client.py +3466 -0
  29. google/cloud/spanner_admin_instance_v1/services/instance_admin/client.py +3881 -0
  30. google/cloud/spanner_admin_instance_v1/services/instance_admin/pagers.py +856 -0
  31. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/__init__.py +38 -0
  32. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/base.py +545 -0
  33. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/grpc.py +1347 -0
  34. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/grpc_asyncio.py +1539 -0
  35. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/rest.py +4834 -0
  36. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/rest_base.py +1198 -0
  37. google/cloud/spanner_admin_instance_v1/types/__init__.py +104 -0
  38. google/cloud/spanner_admin_instance_v1/types/common.py +99 -0
  39. google/cloud/spanner_admin_instance_v1/types/spanner_instance_admin.py +2375 -0
  40. google/cloud/spanner_dbapi/__init__.py +93 -0
  41. google/cloud/spanner_dbapi/_helpers.py +113 -0
  42. google/cloud/spanner_dbapi/batch_dml_executor.py +135 -0
  43. google/cloud/spanner_dbapi/checksum.py +80 -0
  44. google/cloud/spanner_dbapi/client_side_statement_executor.py +140 -0
  45. google/cloud/spanner_dbapi/client_side_statement_parser.py +106 -0
  46. google/cloud/spanner_dbapi/connection.py +818 -0
  47. google/cloud/spanner_dbapi/cursor.py +609 -0
  48. google/cloud/spanner_dbapi/exceptions.py +172 -0
  49. google/cloud/spanner_dbapi/parse_utils.py +392 -0
  50. google/cloud/spanner_dbapi/parsed_statement.py +63 -0
  51. google/cloud/spanner_dbapi/parser.py +258 -0
  52. google/cloud/spanner_dbapi/partition_helper.py +41 -0
  53. google/cloud/spanner_dbapi/transaction_helper.py +294 -0
  54. google/cloud/spanner_dbapi/types.py +106 -0
  55. google/cloud/spanner_dbapi/utils.py +147 -0
  56. google/cloud/spanner_dbapi/version.py +20 -0
  57. google/cloud/spanner_v1/__init__.py +154 -0
  58. google/cloud/spanner_v1/_helpers.py +751 -0
  59. google/cloud/spanner_v1/_opentelemetry_tracing.py +165 -0
  60. google/cloud/spanner_v1/backup.py +397 -0
  61. google/cloud/spanner_v1/batch.py +433 -0
  62. google/cloud/spanner_v1/client.py +538 -0
  63. google/cloud/spanner_v1/data_types.py +350 -0
  64. google/cloud/spanner_v1/database.py +1968 -0
  65. google/cloud/spanner_v1/database_sessions_manager.py +249 -0
  66. google/cloud/spanner_v1/gapic_metadata.json +268 -0
  67. google/cloud/spanner_v1/gapic_version.py +16 -0
  68. google/cloud/spanner_v1/instance.py +735 -0
  69. google/cloud/spanner_v1/keyset.py +193 -0
  70. google/cloud/spanner_v1/merged_result_set.py +146 -0
  71. google/cloud/spanner_v1/metrics/constants.py +71 -0
  72. google/cloud/spanner_v1/metrics/metrics_capture.py +75 -0
  73. google/cloud/spanner_v1/metrics/metrics_exporter.py +384 -0
  74. google/cloud/spanner_v1/metrics/metrics_interceptor.py +156 -0
  75. google/cloud/spanner_v1/metrics/metrics_tracer.py +588 -0
  76. google/cloud/spanner_v1/metrics/metrics_tracer_factory.py +328 -0
  77. google/cloud/spanner_v1/metrics/spanner_metrics_tracer_factory.py +172 -0
  78. google/cloud/spanner_v1/param_types.py +110 -0
  79. google/cloud/spanner_v1/pool.py +813 -0
  80. google/cloud/spanner_v1/py.typed +2 -0
  81. google/cloud/spanner_v1/request_id_header.py +64 -0
  82. google/cloud/spanner_v1/services/__init__.py +15 -0
  83. google/cloud/spanner_v1/services/spanner/__init__.py +22 -0
  84. google/cloud/spanner_v1/services/spanner/async_client.py +2205 -0
  85. google/cloud/spanner_v1/services/spanner/client.py +2624 -0
  86. google/cloud/spanner_v1/services/spanner/pagers.py +196 -0
  87. google/cloud/spanner_v1/services/spanner/transports/__init__.py +38 -0
  88. google/cloud/spanner_v1/services/spanner/transports/base.py +520 -0
  89. google/cloud/spanner_v1/services/spanner/transports/grpc.py +911 -0
  90. google/cloud/spanner_v1/services/spanner/transports/grpc_asyncio.py +1144 -0
  91. google/cloud/spanner_v1/services/spanner/transports/rest.py +3468 -0
  92. google/cloud/spanner_v1/services/spanner/transports/rest_base.py +981 -0
  93. google/cloud/spanner_v1/session.py +631 -0
  94. google/cloud/spanner_v1/session_options.py +133 -0
  95. google/cloud/spanner_v1/snapshot.py +1057 -0
  96. google/cloud/spanner_v1/streamed.py +402 -0
  97. google/cloud/spanner_v1/table.py +181 -0
  98. google/cloud/spanner_v1/testing/__init__.py +0 -0
  99. google/cloud/spanner_v1/testing/database_test.py +121 -0
  100. google/cloud/spanner_v1/testing/interceptors.py +118 -0
  101. google/cloud/spanner_v1/testing/mock_database_admin.py +38 -0
  102. google/cloud/spanner_v1/testing/mock_spanner.py +261 -0
  103. google/cloud/spanner_v1/testing/spanner_database_admin_pb2_grpc.py +1267 -0
  104. google/cloud/spanner_v1/testing/spanner_pb2_grpc.py +882 -0
  105. google/cloud/spanner_v1/transaction.py +747 -0
  106. google/cloud/spanner_v1/types/__init__.py +118 -0
  107. google/cloud/spanner_v1/types/commit_response.py +94 -0
  108. google/cloud/spanner_v1/types/keys.py +248 -0
  109. google/cloud/spanner_v1/types/mutation.py +201 -0
  110. google/cloud/spanner_v1/types/query_plan.py +220 -0
  111. google/cloud/spanner_v1/types/result_set.py +379 -0
  112. google/cloud/spanner_v1/types/spanner.py +1815 -0
  113. google/cloud/spanner_v1/types/transaction.py +818 -0
  114. google/cloud/spanner_v1/types/type.py +288 -0
  115. google_cloud_spanner-3.55.0.dist-info/LICENSE +202 -0
  116. google_cloud_spanner-3.55.0.dist-info/METADATA +318 -0
  117. google_cloud_spanner-3.55.0.dist-info/RECORD +119 -0
  118. google_cloud_spanner-3.55.0.dist-info/WHEEL +5 -0
  119. google_cloud_spanner-3.55.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,747 @@
1
+ # Copyright 2016 Google LLC All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Spanner read-write transaction support."""
16
+ import functools
17
+ import threading
18
+ from google.protobuf.struct_pb2 import Struct
19
+ from typing import Optional
20
+
21
+ from google.cloud.spanner_v1._helpers import (
22
+ _make_value_pb,
23
+ _merge_query_options,
24
+ _metadata_with_prefix,
25
+ _metadata_with_leader_aware_routing,
26
+ _retry,
27
+ _check_rst_stream_error,
28
+ _merge_Transaction_Options,
29
+ )
30
+ from google.cloud.spanner_v1 import CommitRequest
31
+ from google.cloud.spanner_v1 import ExecuteBatchDmlRequest
32
+ from google.cloud.spanner_v1 import ExecuteSqlRequest
33
+ from google.cloud.spanner_v1 import TransactionSelector
34
+ from google.cloud.spanner_v1 import TransactionOptions
35
+ from google.cloud.spanner_v1._helpers import AtomicCounter
36
+ from google.cloud.spanner_v1.snapshot import _SnapshotBase
37
+ from google.cloud.spanner_v1.batch import _BatchBase
38
+ from google.cloud.spanner_v1._opentelemetry_tracing import add_span_event, trace_call
39
+ from google.cloud.spanner_v1 import RequestOptions
40
+ from google.cloud.spanner_v1.metrics.metrics_capture import MetricsCapture
41
+ from google.api_core import gapic_v1
42
+ from google.api_core.exceptions import InternalServerError
43
+ from dataclasses import dataclass, field
44
+ from typing import Any
45
+
46
+
47
+ class Transaction(_SnapshotBase, _BatchBase):
48
+ """Implement read-write transaction semantics for a session.
49
+
50
+ :type session: :class:`~google.cloud.spanner_v1.session.Session`
51
+ :param session: the session used to perform the commit
52
+
53
+ :raises ValueError: if session has an existing transaction
54
+ """
55
+
56
+ committed = None
57
+ """Timestamp at which the transaction was successfully committed."""
58
+ rolled_back = False
59
+ commit_stats = None
60
+ _multi_use = True
61
+ _execute_sql_count = 0
62
+ _lock = threading.Lock()
63
+ _read_only = False
64
+ exclude_txn_from_change_streams = False
65
+ isolation_level = TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED
66
+
67
+ def __init__(self, session):
68
+ if session._transaction is not None:
69
+ raise ValueError("Session has existing transaction.")
70
+
71
+ super(Transaction, self).__init__(session)
72
+
73
+ def _check_state(self):
74
+ """Helper for :meth:`commit` et al.
75
+
76
+ :raises: :exc:`ValueError` if the object's state is invalid for making
77
+ API requests.
78
+ """
79
+
80
+ if self.committed is not None:
81
+ raise ValueError("Transaction is already committed")
82
+
83
+ if self.rolled_back:
84
+ raise ValueError("Transaction is already rolled back")
85
+
86
+ def _make_txn_selector(self):
87
+ """Helper for :meth:`read`.
88
+
89
+ :rtype:
90
+ :class:`~.transaction_pb2.TransactionSelector`
91
+ :returns: a selector configured for read-write transaction semantics.
92
+ """
93
+ self._check_state()
94
+
95
+ if self._transaction_id is None:
96
+ txn_options = TransactionOptions(
97
+ read_write=TransactionOptions.ReadWrite(),
98
+ exclude_txn_from_change_streams=self.exclude_txn_from_change_streams,
99
+ isolation_level=self.isolation_level,
100
+ )
101
+
102
+ txn_options = _merge_Transaction_Options(
103
+ self._session._database.default_transaction_options.default_read_write_transaction_options,
104
+ txn_options,
105
+ )
106
+ return TransactionSelector(begin=txn_options)
107
+ else:
108
+ return TransactionSelector(id=self._transaction_id)
109
+
110
+ def _execute_request(
111
+ self,
112
+ method,
113
+ request,
114
+ metadata,
115
+ trace_name=None,
116
+ session=None,
117
+ attributes=None,
118
+ observability_options=None,
119
+ ):
120
+ """Helper method to execute request after fetching transaction selector.
121
+
122
+ :type method: callable
123
+ :param method: function returning iterator
124
+
125
+ :type request: proto
126
+ :param request: request proto to call the method with
127
+ """
128
+ transaction = self._make_txn_selector()
129
+ request.transaction = transaction
130
+ with trace_call(
131
+ trace_name,
132
+ session,
133
+ attributes,
134
+ observability_options=observability_options,
135
+ metadata=metadata,
136
+ ), MetricsCapture():
137
+ method = functools.partial(method, request=request)
138
+ response = _retry(
139
+ method,
140
+ allowed_exceptions={InternalServerError: _check_rst_stream_error},
141
+ )
142
+
143
+ return response
144
+
145
+ def begin(self):
146
+ """Begin a transaction on the database.
147
+
148
+ :rtype: bytes
149
+ :returns: the ID for the newly-begun transaction.
150
+ :raises ValueError:
151
+ if the transaction is already begun, committed, or rolled back.
152
+ """
153
+ if self._transaction_id is not None:
154
+ raise ValueError("Transaction already begun")
155
+
156
+ if self.committed is not None:
157
+ raise ValueError("Transaction already committed")
158
+
159
+ if self.rolled_back:
160
+ raise ValueError("Transaction is already rolled back")
161
+
162
+ database = self._session._database
163
+ api = database.spanner_api
164
+ metadata = _metadata_with_prefix(database.name)
165
+ if database._route_to_leader_enabled:
166
+ metadata.append(
167
+ _metadata_with_leader_aware_routing(database._route_to_leader_enabled)
168
+ )
169
+ txn_options = TransactionOptions(
170
+ read_write=TransactionOptions.ReadWrite(),
171
+ exclude_txn_from_change_streams=self.exclude_txn_from_change_streams,
172
+ isolation_level=self.isolation_level,
173
+ )
174
+ txn_options = _merge_Transaction_Options(
175
+ database.default_transaction_options.default_read_write_transaction_options,
176
+ txn_options,
177
+ )
178
+ observability_options = getattr(database, "observability_options", None)
179
+ with trace_call(
180
+ f"CloudSpanner.{type(self).__name__}.begin",
181
+ self._session,
182
+ observability_options=observability_options,
183
+ metadata=metadata,
184
+ ) as span, MetricsCapture():
185
+ attempt = AtomicCounter(0)
186
+ nth_request = database._next_nth_request
187
+
188
+ def wrapped_method(*args, **kwargs):
189
+ method = functools.partial(
190
+ api.begin_transaction,
191
+ session=self._session.name,
192
+ options=txn_options,
193
+ metadata=database.metadata_with_request_id(
194
+ nth_request,
195
+ attempt.increment(),
196
+ metadata,
197
+ span,
198
+ ),
199
+ )
200
+ return method(*args, **kwargs)
201
+
202
+ def beforeNextRetry(nthRetry, delayInSeconds):
203
+ add_span_event(
204
+ span,
205
+ "Transaction Begin Attempt Failed. Retrying",
206
+ {"attempt": nthRetry, "sleep_seconds": delayInSeconds},
207
+ )
208
+
209
+ response = _retry(
210
+ wrapped_method,
211
+ allowed_exceptions={InternalServerError: _check_rst_stream_error},
212
+ beforeNextRetry=beforeNextRetry,
213
+ )
214
+ self._transaction_id = response.id
215
+ return self._transaction_id
216
+
217
+ def rollback(self):
218
+ """Roll back a transaction on the database."""
219
+ self._check_state()
220
+
221
+ if self._transaction_id is not None:
222
+ database = self._session._database
223
+ api = database.spanner_api
224
+ metadata = _metadata_with_prefix(database.name)
225
+ if database._route_to_leader_enabled:
226
+ metadata.append(
227
+ _metadata_with_leader_aware_routing(
228
+ database._route_to_leader_enabled
229
+ )
230
+ )
231
+
232
+ observability_options = getattr(database, "observability_options", None)
233
+ with trace_call(
234
+ f"CloudSpanner.{type(self).__name__}.rollback",
235
+ self._session,
236
+ observability_options=observability_options,
237
+ metadata=metadata,
238
+ ) as span, MetricsCapture():
239
+ attempt = AtomicCounter(0)
240
+ nth_request = database._next_nth_request
241
+
242
+ def wrapped_method(*args, **kwargs):
243
+ attempt.increment()
244
+ method = functools.partial(
245
+ api.rollback,
246
+ session=self._session.name,
247
+ transaction_id=self._transaction_id,
248
+ metadata=database.metadata_with_request_id(
249
+ nth_request,
250
+ attempt.value,
251
+ metadata,
252
+ span,
253
+ ),
254
+ )
255
+ return method(*args, **kwargs)
256
+
257
+ _retry(
258
+ wrapped_method,
259
+ allowed_exceptions={InternalServerError: _check_rst_stream_error},
260
+ )
261
+
262
+ self.rolled_back = True
263
+ del self._session._transaction
264
+
265
+ def commit(
266
+ self, return_commit_stats=False, request_options=None, max_commit_delay=None
267
+ ):
268
+ """Commit mutations to the database.
269
+
270
+ :type return_commit_stats: bool
271
+ :param return_commit_stats:
272
+ If true, the response will return commit stats which can be accessed though commit_stats.
273
+
274
+ :type request_options:
275
+ :class:`google.cloud.spanner_v1.types.RequestOptions`
276
+ :param request_options:
277
+ (Optional) Common options for this request.
278
+ If a dict is provided, it must be of the same form as the protobuf
279
+ message :class:`~google.cloud.spanner_v1.types.RequestOptions`.
280
+
281
+ :type max_commit_delay: :class:`datetime.timedelta`
282
+ :param max_commit_delay:
283
+ (Optional) The amount of latency this request is willing to incur
284
+ in order to improve throughput.
285
+ :class:`~google.cloud.spanner_v1.types.MaxCommitDelay`.
286
+
287
+ :rtype: datetime
288
+ :returns: timestamp of the committed changes.
289
+ :raises ValueError: if there are no mutations to commit.
290
+ """
291
+ database = self._session._database
292
+ trace_attributes = {"num_mutations": len(self._mutations)}
293
+ observability_options = getattr(database, "observability_options", None)
294
+ api = database.spanner_api
295
+ metadata = _metadata_with_prefix(database.name)
296
+ if database._route_to_leader_enabled:
297
+ metadata.append(
298
+ _metadata_with_leader_aware_routing(database._route_to_leader_enabled)
299
+ )
300
+ with trace_call(
301
+ f"CloudSpanner.{type(self).__name__}.commit",
302
+ self._session,
303
+ trace_attributes,
304
+ observability_options,
305
+ metadata=metadata,
306
+ ) as span, MetricsCapture():
307
+ self._check_state()
308
+ if self._transaction_id is None and len(self._mutations) > 0:
309
+ self.begin()
310
+ elif self._transaction_id is None and len(self._mutations) == 0:
311
+ raise ValueError("Transaction is not begun")
312
+
313
+ if request_options is None:
314
+ request_options = RequestOptions()
315
+ elif type(request_options) is dict:
316
+ request_options = RequestOptions(request_options)
317
+ if self.transaction_tag is not None:
318
+ request_options.transaction_tag = self.transaction_tag
319
+
320
+ # Request tags are not supported for commit requests.
321
+ request_options.request_tag = None
322
+
323
+ request = CommitRequest(
324
+ session=self._session.name,
325
+ mutations=self._mutations,
326
+ transaction_id=self._transaction_id,
327
+ return_commit_stats=return_commit_stats,
328
+ max_commit_delay=max_commit_delay,
329
+ request_options=request_options,
330
+ )
331
+
332
+ add_span_event(span, "Starting Commit")
333
+
334
+ attempt = AtomicCounter(0)
335
+ nth_request = database._next_nth_request
336
+
337
+ def wrapped_method(*args, **kwargs):
338
+ attempt.increment()
339
+ method = functools.partial(
340
+ api.commit,
341
+ request=request,
342
+ metadata=database.metadata_with_request_id(
343
+ nth_request,
344
+ attempt.value,
345
+ metadata,
346
+ span,
347
+ ),
348
+ )
349
+ return method(*args, **kwargs)
350
+
351
+ def beforeNextRetry(nthRetry, delayInSeconds):
352
+ add_span_event(
353
+ span,
354
+ "Transaction Commit Attempt Failed. Retrying",
355
+ {"attempt": nthRetry, "sleep_seconds": delayInSeconds},
356
+ )
357
+
358
+ response = _retry(
359
+ wrapped_method,
360
+ allowed_exceptions={InternalServerError: _check_rst_stream_error},
361
+ beforeNextRetry=beforeNextRetry,
362
+ )
363
+
364
+ add_span_event(span, "Commit Done")
365
+
366
+ self.committed = response.commit_timestamp
367
+ if return_commit_stats:
368
+ self.commit_stats = response.commit_stats
369
+ del self._session._transaction
370
+ return self.committed
371
+
372
+ @staticmethod
373
+ def _make_params_pb(params, param_types):
374
+ """Helper for :meth:`execute_update`.
375
+
376
+ :type params: dict, {str -> column value}
377
+ :param params: values for parameter replacement. Keys must match
378
+ the names used in ``dml``.
379
+
380
+ :type param_types: dict[str -> Union[dict, .types.Type]]
381
+ :param param_types:
382
+ (Optional) maps explicit types for one or more param values;
383
+ required if parameters are passed.
384
+
385
+ :rtype: Union[None, :class:`Struct`]
386
+ :returns: a struct message for the passed params, or None
387
+ :raises ValueError:
388
+ If ``param_types`` is None but ``params`` is not None.
389
+ :raises ValueError:
390
+ If ``params`` is None but ``param_types`` is not None.
391
+ """
392
+ if params:
393
+ return Struct(
394
+ fields={key: _make_value_pb(value) for key, value in params.items()}
395
+ )
396
+
397
+ return {}
398
+
399
+ def execute_update(
400
+ self,
401
+ dml,
402
+ params=None,
403
+ param_types=None,
404
+ query_mode=None,
405
+ query_options=None,
406
+ request_options=None,
407
+ last_statement=False,
408
+ *,
409
+ retry=gapic_v1.method.DEFAULT,
410
+ timeout=gapic_v1.method.DEFAULT,
411
+ ):
412
+ """Perform an ``ExecuteSql`` API request with DML.
413
+
414
+ :type dml: str
415
+ :param dml: SQL DML statement
416
+
417
+ :type params: dict, {str -> column value}
418
+ :param params: values for parameter replacement. Keys must match
419
+ the names used in ``dml``.
420
+
421
+ :type param_types: dict[str -> Union[dict, .types.Type]]
422
+ :param param_types:
423
+ (Optional) maps explicit types for one or more param values;
424
+ required if parameters are passed.
425
+
426
+ :type query_mode:
427
+ :class:`~google.cloud.spanner_v1.types.ExecuteSqlRequest.QueryMode`
428
+ :param query_mode: Mode governing return of results / query plan.
429
+ See:
430
+ `QueryMode <https://cloud.google.com/spanner/reference/rpc/google.spanner.v1#google.spanner.v1.ExecuteSqlRequest.QueryMode>`_.
431
+
432
+ :type query_options:
433
+ :class:`~google.cloud.spanner_v1.types.ExecuteSqlRequest.QueryOptions`
434
+ or :class:`dict`
435
+ :param query_options: (Optional) Options that are provided for query plan stability.
436
+
437
+ :type request_options:
438
+ :class:`google.cloud.spanner_v1.types.RequestOptions`
439
+ :param request_options:
440
+ (Optional) Common options for this request.
441
+ If a dict is provided, it must be of the same form as the protobuf
442
+ message :class:`~google.cloud.spanner_v1.types.RequestOptions`.
443
+
444
+ :type last_statement: bool
445
+ :param last_statement:
446
+ If set to true, this option marks the end of the transaction. The
447
+ transaction should be committed or aborted after this statement
448
+ executes, and attempts to execute any other requests against this
449
+ transaction (including reads and queries) will be rejected. Mixing
450
+ mutations with statements that are marked as the last statement is
451
+ not allowed.
452
+ For DML statements, setting this option may cause some error
453
+ reporting to be deferred until commit time (e.g. validation of
454
+ unique constraints). Given this, successful execution of a DML
455
+ statement should not be assumed until the transaction commits.
456
+
457
+ :type retry: :class:`~google.api_core.retry.Retry`
458
+ :param retry: (Optional) The retry settings for this request.
459
+
460
+ :type timeout: float
461
+ :param timeout: (Optional) The timeout for this request.
462
+
463
+ :rtype: int
464
+ :returns: Count of rows affected by the DML statement.
465
+ """
466
+ params_pb = self._make_params_pb(params, param_types)
467
+ database = self._session._database
468
+ metadata = _metadata_with_prefix(database.name)
469
+ if database._route_to_leader_enabled:
470
+ metadata.append(
471
+ _metadata_with_leader_aware_routing(database._route_to_leader_enabled)
472
+ )
473
+ api = database.spanner_api
474
+
475
+ seqno, self._execute_sql_count = (
476
+ self._execute_sql_count,
477
+ self._execute_sql_count + 1,
478
+ )
479
+
480
+ # Query-level options have higher precedence than client-level and
481
+ # environment-level options
482
+ default_query_options = database._instance._client._query_options
483
+ query_options = _merge_query_options(default_query_options, query_options)
484
+ observability_options = getattr(
485
+ database._instance._client, "observability_options", None
486
+ )
487
+
488
+ if request_options is None:
489
+ request_options = RequestOptions()
490
+ elif type(request_options) is dict:
491
+ request_options = RequestOptions(request_options)
492
+ request_options.transaction_tag = self.transaction_tag
493
+
494
+ trace_attributes = {"db.statement": dml}
495
+
496
+ request = ExecuteSqlRequest(
497
+ session=self._session.name,
498
+ sql=dml,
499
+ params=params_pb,
500
+ param_types=param_types,
501
+ query_mode=query_mode,
502
+ query_options=query_options,
503
+ seqno=seqno,
504
+ request_options=request_options,
505
+ last_statement=last_statement,
506
+ )
507
+
508
+ nth_request = database._next_nth_request
509
+ attempt = AtomicCounter(0)
510
+
511
+ def wrapped_method(*args, **kwargs):
512
+ attempt.increment()
513
+ method = functools.partial(
514
+ api.execute_sql,
515
+ request=request,
516
+ metadata=database.metadata_with_request_id(
517
+ nth_request, attempt.value, metadata
518
+ ),
519
+ retry=retry,
520
+ timeout=timeout,
521
+ )
522
+ return method(*args, **kwargs)
523
+
524
+ if self._transaction_id is None:
525
+ # lock is added to handle the inline begin for first rpc
526
+ with self._lock:
527
+ response = self._execute_request(
528
+ wrapped_method,
529
+ request,
530
+ metadata,
531
+ f"CloudSpanner.{type(self).__name__}.execute_update",
532
+ self._session,
533
+ trace_attributes,
534
+ observability_options=observability_options,
535
+ )
536
+ # Setting the transaction id because the transaction begin was inlined for first rpc.
537
+ if (
538
+ self._transaction_id is None
539
+ and response is not None
540
+ and response.metadata is not None
541
+ and response.metadata.transaction is not None
542
+ ):
543
+ self._transaction_id = response.metadata.transaction.id
544
+ else:
545
+ response = self._execute_request(
546
+ wrapped_method,
547
+ request,
548
+ metadata,
549
+ f"CloudSpanner.{type(self).__name__}.execute_update",
550
+ self._session,
551
+ trace_attributes,
552
+ observability_options=observability_options,
553
+ )
554
+
555
+ return response.stats.row_count_exact
556
+
557
+ def batch_update(
558
+ self,
559
+ statements,
560
+ request_options=None,
561
+ last_statement=False,
562
+ *,
563
+ retry=gapic_v1.method.DEFAULT,
564
+ timeout=gapic_v1.method.DEFAULT,
565
+ ):
566
+ """Perform a batch of DML statements via an ``ExecuteBatchDml`` request.
567
+
568
+ :type statements:
569
+ Sequence[Union[ str, Tuple[str, Dict[str, Any], Dict[str, Union[dict, .types.Type]]]]]
570
+
571
+ :param statements:
572
+ List of DML statements, with optional params / param types.
573
+ If passed, 'params' is a dict mapping names to the values
574
+ for parameter replacement. Keys must match the names used in the
575
+ corresponding DML statement. If 'params' is passed, 'param_types'
576
+ must also be passed, as a dict mapping names to the type of
577
+ value passed in 'params'.
578
+
579
+ :type request_options:
580
+ :class:`google.cloud.spanner_v1.types.RequestOptions`
581
+ :param request_options:
582
+ (Optional) Common options for this request.
583
+ If a dict is provided, it must be of the same form as the protobuf
584
+ message :class:`~google.cloud.spanner_v1.types.RequestOptions`.
585
+
586
+ :type last_statement: bool
587
+ :param last_statement:
588
+ If set to true, this option marks the end of the transaction. The
589
+ transaction should be committed or aborted after this statement
590
+ executes, and attempts to execute any other requests against this
591
+ transaction (including reads and queries) will be rejected. Mixing
592
+ mutations with statements that are marked as the last statement is
593
+ not allowed.
594
+ For DML statements, setting this option may cause some error
595
+ reporting to be deferred until commit time (e.g. validation of
596
+ unique constraints). Given this, successful execution of a DML
597
+ statement should not be assumed until the transaction commits.
598
+
599
+ :type retry: :class:`~google.api_core.retry.Retry`
600
+ :param retry: (Optional) The retry settings for this request.
601
+
602
+ :type timeout: float
603
+ :param timeout: (Optional) The timeout for this request.
604
+
605
+ :rtype:
606
+ Tuple(status, Sequence[int])
607
+ :returns:
608
+ Status code, plus counts of rows affected by each completed DML
609
+ statement. Note that if the status code is not ``OK``, the
610
+ statement triggering the error will not have an entry in the
611
+ list, nor will any statements following that one.
612
+ """
613
+ parsed = []
614
+ for statement in statements:
615
+ if isinstance(statement, str):
616
+ parsed.append(ExecuteBatchDmlRequest.Statement(sql=statement))
617
+ else:
618
+ dml, params, param_types = statement
619
+ params_pb = self._make_params_pb(params, param_types)
620
+ parsed.append(
621
+ ExecuteBatchDmlRequest.Statement(
622
+ sql=dml, params=params_pb, param_types=param_types
623
+ )
624
+ )
625
+
626
+ database = self._session._database
627
+ metadata = _metadata_with_prefix(database.name)
628
+ if database._route_to_leader_enabled:
629
+ metadata.append(
630
+ _metadata_with_leader_aware_routing(database._route_to_leader_enabled)
631
+ )
632
+ api = database.spanner_api
633
+ observability_options = getattr(database, "observability_options", None)
634
+
635
+ seqno, self._execute_sql_count = (
636
+ self._execute_sql_count,
637
+ self._execute_sql_count + 1,
638
+ )
639
+
640
+ if request_options is None:
641
+ request_options = RequestOptions()
642
+ elif type(request_options) is dict:
643
+ request_options = RequestOptions(request_options)
644
+ request_options.transaction_tag = self.transaction_tag
645
+
646
+ trace_attributes = {
647
+ # Get just the queries from the DML statement batch
648
+ "db.statement": ";".join([statement.sql for statement in parsed])
649
+ }
650
+ request = ExecuteBatchDmlRequest(
651
+ session=self._session.name,
652
+ statements=parsed,
653
+ seqno=seqno,
654
+ request_options=request_options,
655
+ last_statements=last_statement,
656
+ )
657
+
658
+ nth_request = database._next_nth_request
659
+ attempt = AtomicCounter(0)
660
+
661
+ def wrapped_method(*args, **kwargs):
662
+ attempt.increment()
663
+ method = functools.partial(
664
+ api.execute_batch_dml,
665
+ request=request,
666
+ metadata=database.metadata_with_request_id(
667
+ nth_request, attempt.value, metadata
668
+ ),
669
+ retry=retry,
670
+ timeout=timeout,
671
+ )
672
+ return method(*args, **kwargs)
673
+
674
+ if self._transaction_id is None:
675
+ # lock is added to handle the inline begin for first rpc
676
+ with self._lock:
677
+ response = self._execute_request(
678
+ wrapped_method,
679
+ request,
680
+ metadata,
681
+ "CloudSpanner.DMLTransaction",
682
+ self._session,
683
+ trace_attributes,
684
+ observability_options=observability_options,
685
+ )
686
+ # Setting the transaction id because the transaction begin was inlined for first rpc.
687
+ for result_set in response.result_sets:
688
+ if (
689
+ self._transaction_id is None
690
+ and result_set.metadata is not None
691
+ and result_set.metadata.transaction is not None
692
+ ):
693
+ self._transaction_id = result_set.metadata.transaction.id
694
+ break
695
+ else:
696
+ response = self._execute_request(
697
+ wrapped_method,
698
+ request,
699
+ metadata,
700
+ "CloudSpanner.DMLTransaction",
701
+ self._session,
702
+ trace_attributes,
703
+ observability_options=observability_options,
704
+ )
705
+
706
+ row_counts = [
707
+ result_set.stats.row_count_exact for result_set in response.result_sets
708
+ ]
709
+
710
+ return response.status, row_counts
711
+
712
+ def __enter__(self):
713
+ """Begin ``with`` block."""
714
+ return self
715
+
716
+ def __exit__(self, exc_type, exc_val, exc_tb):
717
+ """End ``with`` block."""
718
+ if exc_type is None:
719
+ self.commit()
720
+ else:
721
+ self.rollback()
722
+
723
+
724
+ @dataclass
725
+ class BatchTransactionId:
726
+ transaction_id: str
727
+ session_id: str
728
+ read_timestamp: Any
729
+
730
+
731
+ @dataclass
732
+ class DefaultTransactionOptions:
733
+ isolation_level: str = TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED
734
+ _defaultReadWriteTransactionOptions: Optional[TransactionOptions] = field(
735
+ init=False, repr=False
736
+ )
737
+
738
+ def __post_init__(self):
739
+ """Initialize _defaultReadWriteTransactionOptions automatically"""
740
+ self._defaultReadWriteTransactionOptions = TransactionOptions(
741
+ isolation_level=self.isolation_level
742
+ )
743
+
744
+ @property
745
+ def default_read_write_transaction_options(self) -> TransactionOptions:
746
+ """Public accessor for _defaultReadWriteTransactionOptions"""
747
+ return self._defaultReadWriteTransactionOptions