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.
- google/cloud/spanner.py +47 -0
- google/cloud/spanner_admin_database_v1/__init__.py +146 -0
- google/cloud/spanner_admin_database_v1/gapic_metadata.json +418 -0
- google/cloud/spanner_admin_database_v1/gapic_version.py +16 -0
- google/cloud/spanner_admin_database_v1/py.typed +2 -0
- google/cloud/spanner_admin_database_v1/services/__init__.py +15 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/__init__.py +22 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/async_client.py +4097 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/client.py +4602 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/pagers.py +989 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/__init__.py +38 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/base.py +820 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/grpc.py +1303 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/grpc_asyncio.py +1688 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/rest.py +6512 -0
- google/cloud/spanner_admin_database_v1/services/database_admin/transports/rest_base.py +1650 -0
- google/cloud/spanner_admin_database_v1/types/__init__.py +144 -0
- google/cloud/spanner_admin_database_v1/types/backup.py +1106 -0
- google/cloud/spanner_admin_database_v1/types/backup_schedule.py +369 -0
- google/cloud/spanner_admin_database_v1/types/common.py +180 -0
- google/cloud/spanner_admin_database_v1/types/spanner_database_admin.py +1303 -0
- google/cloud/spanner_admin_instance_v1/__init__.py +110 -0
- google/cloud/spanner_admin_instance_v1/gapic_metadata.json +343 -0
- google/cloud/spanner_admin_instance_v1/gapic_version.py +16 -0
- google/cloud/spanner_admin_instance_v1/py.typed +2 -0
- google/cloud/spanner_admin_instance_v1/services/__init__.py +15 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/__init__.py +22 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/async_client.py +3466 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/client.py +3881 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/pagers.py +856 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/__init__.py +38 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/base.py +545 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/grpc.py +1347 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/grpc_asyncio.py +1539 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/rest.py +4834 -0
- google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/rest_base.py +1198 -0
- google/cloud/spanner_admin_instance_v1/types/__init__.py +104 -0
- google/cloud/spanner_admin_instance_v1/types/common.py +99 -0
- google/cloud/spanner_admin_instance_v1/types/spanner_instance_admin.py +2375 -0
- google/cloud/spanner_dbapi/__init__.py +93 -0
- google/cloud/spanner_dbapi/_helpers.py +113 -0
- google/cloud/spanner_dbapi/batch_dml_executor.py +135 -0
- google/cloud/spanner_dbapi/checksum.py +80 -0
- google/cloud/spanner_dbapi/client_side_statement_executor.py +140 -0
- google/cloud/spanner_dbapi/client_side_statement_parser.py +106 -0
- google/cloud/spanner_dbapi/connection.py +818 -0
- google/cloud/spanner_dbapi/cursor.py +609 -0
- google/cloud/spanner_dbapi/exceptions.py +172 -0
- google/cloud/spanner_dbapi/parse_utils.py +392 -0
- google/cloud/spanner_dbapi/parsed_statement.py +63 -0
- google/cloud/spanner_dbapi/parser.py +258 -0
- google/cloud/spanner_dbapi/partition_helper.py +41 -0
- google/cloud/spanner_dbapi/transaction_helper.py +294 -0
- google/cloud/spanner_dbapi/types.py +106 -0
- google/cloud/spanner_dbapi/utils.py +147 -0
- google/cloud/spanner_dbapi/version.py +20 -0
- google/cloud/spanner_v1/__init__.py +154 -0
- google/cloud/spanner_v1/_helpers.py +751 -0
- google/cloud/spanner_v1/_opentelemetry_tracing.py +165 -0
- google/cloud/spanner_v1/backup.py +397 -0
- google/cloud/spanner_v1/batch.py +433 -0
- google/cloud/spanner_v1/client.py +538 -0
- google/cloud/spanner_v1/data_types.py +350 -0
- google/cloud/spanner_v1/database.py +1968 -0
- google/cloud/spanner_v1/database_sessions_manager.py +249 -0
- google/cloud/spanner_v1/gapic_metadata.json +268 -0
- google/cloud/spanner_v1/gapic_version.py +16 -0
- google/cloud/spanner_v1/instance.py +735 -0
- google/cloud/spanner_v1/keyset.py +193 -0
- google/cloud/spanner_v1/merged_result_set.py +146 -0
- google/cloud/spanner_v1/metrics/constants.py +71 -0
- google/cloud/spanner_v1/metrics/metrics_capture.py +75 -0
- google/cloud/spanner_v1/metrics/metrics_exporter.py +384 -0
- google/cloud/spanner_v1/metrics/metrics_interceptor.py +156 -0
- google/cloud/spanner_v1/metrics/metrics_tracer.py +588 -0
- google/cloud/spanner_v1/metrics/metrics_tracer_factory.py +328 -0
- google/cloud/spanner_v1/metrics/spanner_metrics_tracer_factory.py +172 -0
- google/cloud/spanner_v1/param_types.py +110 -0
- google/cloud/spanner_v1/pool.py +813 -0
- google/cloud/spanner_v1/py.typed +2 -0
- google/cloud/spanner_v1/request_id_header.py +64 -0
- google/cloud/spanner_v1/services/__init__.py +15 -0
- google/cloud/spanner_v1/services/spanner/__init__.py +22 -0
- google/cloud/spanner_v1/services/spanner/async_client.py +2205 -0
- google/cloud/spanner_v1/services/spanner/client.py +2624 -0
- google/cloud/spanner_v1/services/spanner/pagers.py +196 -0
- google/cloud/spanner_v1/services/spanner/transports/__init__.py +38 -0
- google/cloud/spanner_v1/services/spanner/transports/base.py +520 -0
- google/cloud/spanner_v1/services/spanner/transports/grpc.py +911 -0
- google/cloud/spanner_v1/services/spanner/transports/grpc_asyncio.py +1144 -0
- google/cloud/spanner_v1/services/spanner/transports/rest.py +3468 -0
- google/cloud/spanner_v1/services/spanner/transports/rest_base.py +981 -0
- google/cloud/spanner_v1/session.py +631 -0
- google/cloud/spanner_v1/session_options.py +133 -0
- google/cloud/spanner_v1/snapshot.py +1057 -0
- google/cloud/spanner_v1/streamed.py +402 -0
- google/cloud/spanner_v1/table.py +181 -0
- google/cloud/spanner_v1/testing/__init__.py +0 -0
- google/cloud/spanner_v1/testing/database_test.py +121 -0
- google/cloud/spanner_v1/testing/interceptors.py +118 -0
- google/cloud/spanner_v1/testing/mock_database_admin.py +38 -0
- google/cloud/spanner_v1/testing/mock_spanner.py +261 -0
- google/cloud/spanner_v1/testing/spanner_database_admin_pb2_grpc.py +1267 -0
- google/cloud/spanner_v1/testing/spanner_pb2_grpc.py +882 -0
- google/cloud/spanner_v1/transaction.py +747 -0
- google/cloud/spanner_v1/types/__init__.py +118 -0
- google/cloud/spanner_v1/types/commit_response.py +94 -0
- google/cloud/spanner_v1/types/keys.py +248 -0
- google/cloud/spanner_v1/types/mutation.py +201 -0
- google/cloud/spanner_v1/types/query_plan.py +220 -0
- google/cloud/spanner_v1/types/result_set.py +379 -0
- google/cloud/spanner_v1/types/spanner.py +1815 -0
- google/cloud/spanner_v1/types/transaction.py +818 -0
- google/cloud/spanner_v1/types/type.py +288 -0
- google_cloud_spanner-3.55.0.dist-info/LICENSE +202 -0
- google_cloud_spanner-3.55.0.dist-info/METADATA +318 -0
- google_cloud_spanner-3.55.0.dist-info/RECORD +119 -0
- google_cloud_spanner-3.55.0.dist-info/WHEEL +5 -0
- 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
|