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,1057 @@
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
+ """Model a set of read-only queries to a database as a snapshot."""
16
+
17
+ from datetime import datetime
18
+ import functools
19
+ import threading
20
+ from google.protobuf.struct_pb2 import Struct
21
+ from google.cloud.spanner_v1 import ExecuteSqlRequest
22
+ from google.cloud.spanner_v1 import ReadRequest
23
+ from google.cloud.spanner_v1 import TransactionOptions
24
+ from google.cloud.spanner_v1 import TransactionSelector
25
+ from google.cloud.spanner_v1 import PartitionOptions
26
+ from google.cloud.spanner_v1 import PartitionQueryRequest
27
+ from google.cloud.spanner_v1 import PartitionReadRequest
28
+
29
+ from google.api_core.exceptions import InternalServerError
30
+ from google.api_core.exceptions import ServiceUnavailable
31
+ from google.api_core.exceptions import InvalidArgument
32
+ from google.api_core import gapic_v1
33
+ from google.cloud.spanner_v1._helpers import (
34
+ _make_value_pb,
35
+ _merge_query_options,
36
+ _metadata_with_prefix,
37
+ _metadata_with_leader_aware_routing,
38
+ _retry,
39
+ _check_rst_stream_error,
40
+ _SessionWrapper,
41
+ AtomicCounter,
42
+ )
43
+ from google.cloud.spanner_v1._opentelemetry_tracing import trace_call
44
+ from google.cloud.spanner_v1.streamed import StreamedResultSet
45
+ from google.cloud.spanner_v1 import RequestOptions
46
+
47
+ from google.cloud.spanner_v1.metrics.metrics_capture import MetricsCapture
48
+
49
+ _STREAM_RESUMPTION_INTERNAL_ERROR_MESSAGES = (
50
+ "RST_STREAM",
51
+ "Received unexpected EOS on DATA frame from server",
52
+ )
53
+
54
+
55
+ def _restart_on_unavailable(
56
+ method,
57
+ request,
58
+ metadata=None,
59
+ trace_name=None,
60
+ session=None,
61
+ attributes=None,
62
+ transaction=None,
63
+ transaction_selector=None,
64
+ observability_options=None,
65
+ request_id_manager=None,
66
+ ):
67
+ """Restart iteration after :exc:`.ServiceUnavailable`.
68
+
69
+ :type method: callable
70
+ :param method: function returning iterator
71
+
72
+ :type request: proto
73
+ :param request: request proto to call the method with
74
+
75
+ :type transaction: :class:`google.cloud.spanner_v1.snapshot._SnapshotBase`
76
+ :param transaction: Snapshot or Transaction class object based on the type of transaction
77
+
78
+ :type transaction_selector: :class:`transaction_pb2.TransactionSelector`
79
+ :param transaction_selector: Transaction selector object to be used in request if transaction is not passed,
80
+ if both transaction_selector and transaction are passed, then transaction is given priority.
81
+ """
82
+
83
+ resume_token = b""
84
+ item_buffer = []
85
+
86
+ if transaction is not None:
87
+ transaction_selector = transaction._make_txn_selector()
88
+ elif transaction_selector is None:
89
+ raise InvalidArgument(
90
+ "Either transaction or transaction_selector should be set"
91
+ )
92
+
93
+ request.transaction = transaction_selector
94
+ iterator = None
95
+ attempt = 1
96
+ nth_request = getattr(request_id_manager, "_next_nth_request", 0)
97
+
98
+ while True:
99
+ try:
100
+ if iterator is None:
101
+ with trace_call(
102
+ trace_name,
103
+ session,
104
+ attributes,
105
+ observability_options=observability_options,
106
+ metadata=metadata,
107
+ ) as span, MetricsCapture():
108
+ iterator = method(
109
+ request=request,
110
+ metadata=request_id_manager.metadata_with_request_id(
111
+ nth_request,
112
+ attempt,
113
+ metadata,
114
+ span,
115
+ ),
116
+ )
117
+ for item in iterator:
118
+ item_buffer.append(item)
119
+ # Setting the transaction id because the transaction begin was inlined for first rpc.
120
+ if (
121
+ transaction is not None
122
+ and transaction._transaction_id is None
123
+ and item.metadata is not None
124
+ and item.metadata.transaction is not None
125
+ and item.metadata.transaction.id is not None
126
+ ):
127
+ transaction._transaction_id = item.metadata.transaction.id
128
+ if item.resume_token:
129
+ resume_token = item.resume_token
130
+ break
131
+ except ServiceUnavailable:
132
+ del item_buffer[:]
133
+ with trace_call(
134
+ trace_name,
135
+ session,
136
+ attributes,
137
+ observability_options=observability_options,
138
+ metadata=metadata,
139
+ ) as span, MetricsCapture():
140
+ request.resume_token = resume_token
141
+ if transaction is not None:
142
+ transaction_selector = transaction._make_txn_selector()
143
+ request.transaction = transaction_selector
144
+ attempt += 1
145
+ iterator = method(
146
+ request=request,
147
+ metadata=request_id_manager.metadata_with_request_id(
148
+ nth_request,
149
+ attempt,
150
+ metadata,
151
+ span,
152
+ ),
153
+ )
154
+ continue
155
+ except InternalServerError as exc:
156
+ resumable_error = any(
157
+ resumable_message in exc.message
158
+ for resumable_message in _STREAM_RESUMPTION_INTERNAL_ERROR_MESSAGES
159
+ )
160
+ if not resumable_error:
161
+ raise
162
+ del item_buffer[:]
163
+ with trace_call(
164
+ trace_name,
165
+ session,
166
+ attributes,
167
+ observability_options=observability_options,
168
+ metadata=metadata,
169
+ ) as span, MetricsCapture():
170
+ request.resume_token = resume_token
171
+ if transaction is not None:
172
+ transaction_selector = transaction._make_txn_selector()
173
+ attempt += 1
174
+ request.transaction = transaction_selector
175
+ iterator = method(
176
+ request=request,
177
+ metadata=request_id_manager.metadata_with_request_id(
178
+ nth_request,
179
+ attempt,
180
+ metadata,
181
+ span,
182
+ ),
183
+ )
184
+ continue
185
+
186
+ if len(item_buffer) == 0:
187
+ break
188
+
189
+ for item in item_buffer:
190
+ yield item
191
+
192
+ del item_buffer[:]
193
+
194
+
195
+ class _SnapshotBase(_SessionWrapper):
196
+ """Base class for Snapshot.
197
+
198
+ Allows reuse of API request methods with different transaction selector.
199
+
200
+ :type session: :class:`~google.cloud.spanner_v1.session.Session`
201
+ :param session: the session used to perform the commit
202
+ """
203
+
204
+ _multi_use = False
205
+ _read_only: bool = True
206
+ _transaction_id = None
207
+ _read_request_count = 0
208
+ _execute_sql_count = 0
209
+ _lock = threading.Lock()
210
+
211
+ def _make_txn_selector(self):
212
+ """Helper for :meth:`read` / :meth:`execute_sql`.
213
+
214
+ Subclasses must override, returning an instance of
215
+ :class:`transaction_pb2.TransactionSelector`
216
+ appropriate for making ``read`` / ``execute_sql`` requests
217
+
218
+ :raises: NotImplementedError, always
219
+ """
220
+ raise NotImplementedError
221
+
222
+ def read(
223
+ self,
224
+ table,
225
+ columns,
226
+ keyset,
227
+ index="",
228
+ limit=0,
229
+ partition=None,
230
+ request_options=None,
231
+ data_boost_enabled=False,
232
+ directed_read_options=None,
233
+ *,
234
+ retry=gapic_v1.method.DEFAULT,
235
+ timeout=gapic_v1.method.DEFAULT,
236
+ column_info=None,
237
+ lazy_decode=False,
238
+ ):
239
+ """Perform a ``StreamingRead`` API request for rows in a table.
240
+
241
+ :type table: str
242
+ :param table: name of the table from which to fetch data
243
+
244
+ :type columns: list of str
245
+ :param columns: names of columns to be retrieved
246
+
247
+ :type keyset: :class:`~google.cloud.spanner_v1.keyset.KeySet`
248
+ :param keyset: keys / ranges identifying rows to be retrieved
249
+
250
+ :type index: str
251
+ :param index: (Optional) name of index to use, rather than the
252
+ table's primary key
253
+
254
+ :type limit: int
255
+ :param limit: (Optional) maximum number of rows to return.
256
+ Incompatible with ``partition``.
257
+
258
+ :type partition: bytes
259
+ :param partition: (Optional) one of the partition tokens returned
260
+ from :meth:`partition_read`. Incompatible with
261
+ ``limit``.
262
+
263
+ :type request_options:
264
+ :class:`google.cloud.spanner_v1.types.RequestOptions`
265
+ :param request_options:
266
+ (Optional) Common options for this request.
267
+ If a dict is provided, it must be of the same form as the protobuf
268
+ message :class:`~google.cloud.spanner_v1.types.RequestOptions`.
269
+ Please note, the `transactionTag` setting will be ignored for
270
+ snapshot as it's not supported for read-only transactions.
271
+
272
+ :type retry: :class:`~google.api_core.retry.Retry`
273
+ :param retry: (Optional) The retry settings for this request.
274
+
275
+ :type timeout: float
276
+ :param timeout: (Optional) The timeout for this request.
277
+
278
+ :type data_boost_enabled:
279
+ :param data_boost_enabled:
280
+ (Optional) If this is for a partitioned read and this field is
281
+ set ``true``, the request will be executed via offline access.
282
+ If the field is set to ``true`` but the request does not set
283
+ ``partition_token``, the API will return an
284
+ ``INVALID_ARGUMENT`` error.
285
+
286
+ :type directed_read_options: :class:`~google.cloud.spanner_v1.DirectedReadOptions`
287
+ or :class:`dict`
288
+ :param directed_read_options: (Optional) Request level option used to set the directed_read_options
289
+ for all ReadRequests and ExecuteSqlRequests that indicates which replicas
290
+ or regions should be used for non-transactional reads or queries.
291
+
292
+ :type column_info: dict
293
+ :param column_info: (Optional) dict of mapping between column names and additional column information.
294
+ An object where column names as keys and custom objects as corresponding
295
+ values for deserialization. It's specifically useful for data types like
296
+ protobuf where deserialization logic is on user-specific code. When provided,
297
+ the custom object enables deserialization of backend-received column data.
298
+ If not provided, data remains serialized as bytes for Proto Messages and
299
+ integer for Proto Enums.
300
+
301
+ :type lazy_decode: bool
302
+ :param lazy_decode:
303
+ (Optional) If this argument is set to ``true``, the iterator
304
+ returns the underlying protobuf values instead of decoded Python
305
+ objects. This reduces the time that is needed to iterate through
306
+ large result sets. The application is responsible for decoding
307
+ the data that is needed. The returned row iterator contains two
308
+ functions that can be used for this. ``iterator.decode_row(row)``
309
+ decodes all the columns in the given row to an array of Python
310
+ objects. ``iterator.decode_column(row, column_index)`` decodes one
311
+ specific column in the given row.
312
+
313
+ :rtype: :class:`~google.cloud.spanner_v1.streamed.StreamedResultSet`
314
+ :returns: a result set instance which can be used to consume rows.
315
+
316
+ :raises ValueError:
317
+ for reuse of single-use snapshots, or if a transaction ID is
318
+ already pending for multiple-use snapshots.
319
+ """
320
+ if self._read_request_count > 0:
321
+ if not self._multi_use:
322
+ raise ValueError("Cannot re-use single-use snapshot.")
323
+ if self._transaction_id is None and self._read_only:
324
+ raise ValueError("Transaction ID pending.")
325
+
326
+ database = self._session._database
327
+ api = database.spanner_api
328
+ metadata = _metadata_with_prefix(database.name)
329
+ if not self._read_only and database._route_to_leader_enabled:
330
+ metadata.append(
331
+ _metadata_with_leader_aware_routing(database._route_to_leader_enabled)
332
+ )
333
+
334
+ if request_options is None:
335
+ request_options = RequestOptions()
336
+ elif type(request_options) is dict:
337
+ request_options = RequestOptions(request_options)
338
+
339
+ if self._read_only:
340
+ # Transaction tags are not supported for read only transactions.
341
+ request_options.transaction_tag = None
342
+ if (
343
+ directed_read_options is None
344
+ and database._directed_read_options is not None
345
+ ):
346
+ directed_read_options = database._directed_read_options
347
+ elif self.transaction_tag is not None:
348
+ request_options.transaction_tag = self.transaction_tag
349
+
350
+ request = ReadRequest(
351
+ session=self._session.name,
352
+ table=table,
353
+ columns=columns,
354
+ key_set=keyset._to_pb(),
355
+ index=index,
356
+ limit=limit,
357
+ partition_token=partition,
358
+ request_options=request_options,
359
+ data_boost_enabled=data_boost_enabled,
360
+ directed_read_options=directed_read_options,
361
+ )
362
+
363
+ restart = functools.partial(
364
+ api.streaming_read,
365
+ request=request,
366
+ metadata=metadata,
367
+ retry=retry,
368
+ timeout=timeout,
369
+ )
370
+
371
+ trace_attributes = {"table_id": table, "columns": columns}
372
+ observability_options = getattr(database, "observability_options", None)
373
+
374
+ if self._transaction_id is None:
375
+ # lock is added to handle the inline begin for first rpc
376
+ with self._lock:
377
+ iterator = _restart_on_unavailable(
378
+ restart,
379
+ request,
380
+ metadata,
381
+ f"CloudSpanner.{type(self).__name__}.read",
382
+ self._session,
383
+ trace_attributes,
384
+ transaction=self,
385
+ observability_options=observability_options,
386
+ request_id_manager=self._session._database,
387
+ )
388
+ self._read_request_count += 1
389
+ if self._multi_use:
390
+ return StreamedResultSet(
391
+ iterator,
392
+ source=self,
393
+ column_info=column_info,
394
+ lazy_decode=lazy_decode,
395
+ )
396
+ else:
397
+ return StreamedResultSet(
398
+ iterator, column_info=column_info, lazy_decode=lazy_decode
399
+ )
400
+ else:
401
+ iterator = _restart_on_unavailable(
402
+ restart,
403
+ request,
404
+ metadata,
405
+ f"CloudSpanner.{type(self).__name__}.read",
406
+ self._session,
407
+ trace_attributes,
408
+ transaction=self,
409
+ observability_options=observability_options,
410
+ request_id_manager=self._session._database,
411
+ )
412
+
413
+ self._read_request_count += 1
414
+ self._session._last_use_time = datetime.now()
415
+
416
+ if self._multi_use:
417
+ return StreamedResultSet(
418
+ iterator, source=self, column_info=column_info, lazy_decode=lazy_decode
419
+ )
420
+ else:
421
+ return StreamedResultSet(
422
+ iterator, column_info=column_info, lazy_decode=lazy_decode
423
+ )
424
+
425
+ def execute_sql(
426
+ self,
427
+ sql,
428
+ params=None,
429
+ param_types=None,
430
+ query_mode=None,
431
+ query_options=None,
432
+ request_options=None,
433
+ last_statement=False,
434
+ partition=None,
435
+ retry=gapic_v1.method.DEFAULT,
436
+ timeout=gapic_v1.method.DEFAULT,
437
+ data_boost_enabled=False,
438
+ directed_read_options=None,
439
+ column_info=None,
440
+ lazy_decode=False,
441
+ ):
442
+ """Perform an ``ExecuteStreamingSql`` API request.
443
+
444
+ :type sql: str
445
+ :param sql: SQL query statement
446
+
447
+ :type params: dict, {str -> column value}
448
+ :param params: values for parameter replacement. Keys must match
449
+ the names used in ``sql``.
450
+
451
+ :type param_types: dict[str -> Union[dict, .types.Type]]
452
+ :param param_types:
453
+ (Optional) maps explicit types for one or more param values;
454
+ required if parameters are passed.
455
+
456
+ :type query_mode:
457
+ :class:`~google.cloud.spanner_v1.types.ExecuteSqlRequest.QueryMode`
458
+ :param query_mode: Mode governing return of results / query plan.
459
+ See:
460
+ `QueryMode <https://cloud.google.com/spanner/reference/rpc/google.spanner.v1#google.spanner.v1.ExecuteSqlRequest.QueryMode>`_.
461
+
462
+ :type query_options:
463
+ :class:`~google.cloud.spanner_v1.types.ExecuteSqlRequest.QueryOptions`
464
+ or :class:`dict`
465
+ :param query_options:
466
+ (Optional) Query optimizer configuration to use for the given query.
467
+ If a dict is provided, it must be of the same form as the protobuf
468
+ message :class:`~google.cloud.spanner_v1.types.QueryOptions`
469
+
470
+ :type request_options:
471
+ :class:`google.cloud.spanner_v1.types.RequestOptions`
472
+ :param request_options:
473
+ (Optional) Common options for this request.
474
+ If a dict is provided, it must be of the same form as the protobuf
475
+ message :class:`~google.cloud.spanner_v1.types.RequestOptions`.
476
+
477
+ :type last_statement: bool
478
+ :param last_statement:
479
+ If set to true, this option marks the end of the transaction. The
480
+ transaction should be committed or aborted after this statement
481
+ executes, and attempts to execute any other requests against this
482
+ transaction (including reads and queries) will be rejected. Mixing
483
+ mutations with statements that are marked as the last statement is
484
+ not allowed.
485
+ For DML statements, setting this option may cause some error
486
+ reporting to be deferred until commit time (e.g. validation of
487
+ unique constraints). Given this, successful execution of a DML
488
+ statement should not be assumed until the transaction commits.
489
+
490
+ :type partition: bytes
491
+ :param partition: (Optional) one of the partition tokens returned
492
+ from :meth:`partition_query`.
493
+
494
+ :rtype: :class:`~google.cloud.spanner_v1.streamed.StreamedResultSet`
495
+ :returns: a result set instance which can be used to consume rows.
496
+
497
+ :type retry: :class:`~google.api_core.retry.Retry`
498
+ :param retry: (Optional) The retry settings for this request.
499
+
500
+ :type timeout: float
501
+ :param timeout: (Optional) The timeout for this request.
502
+
503
+ :type data_boost_enabled:
504
+ :param data_boost_enabled:
505
+ (Optional) If this is for a partitioned query and this field is
506
+ set ``true``, the request will be executed via offline access.
507
+ If the field is set to ``true`` but the request does not set
508
+ ``partition_token``, the API will return an
509
+ ``INVALID_ARGUMENT`` error.
510
+
511
+ :type directed_read_options: :class:`~google.cloud.spanner_v1.DirectedReadOptions`
512
+ or :class:`dict`
513
+ :param directed_read_options: (Optional) Request level option used to set the directed_read_options
514
+ for all ReadRequests and ExecuteSqlRequests that indicates which replicas
515
+ or regions should be used for non-transactional reads or queries.
516
+
517
+ :type column_info: dict
518
+ :param column_info: (Optional) dict of mapping between column names and additional column information.
519
+ An object where column names as keys and custom objects as corresponding
520
+ values for deserialization. It's specifically useful for data types like
521
+ protobuf where deserialization logic is on user-specific code. When provided,
522
+ the custom object enables deserialization of backend-received column data.
523
+ If not provided, data remains serialized as bytes for Proto Messages and
524
+ integer for Proto Enums.
525
+
526
+ :type lazy_decode: bool
527
+ :param lazy_decode:
528
+ (Optional) If this argument is set to ``true``, the iterator
529
+ returns the underlying protobuf values instead of decoded Python
530
+ objects. This reduces the time that is needed to iterate through
531
+ large result sets. The application is responsible for decoding
532
+ the data that is needed. The returned row iterator contains two
533
+ functions that can be used for this. ``iterator.decode_row(row)``
534
+ decodes all the columns in the given row to an array of Python
535
+ objects. ``iterator.decode_column(row, column_index)`` decodes one
536
+ specific column in the given row.
537
+
538
+ :raises ValueError:
539
+ for reuse of single-use snapshots, or if a transaction ID is
540
+ already pending for multiple-use snapshots.
541
+ """
542
+ if self._read_request_count > 0:
543
+ if not self._multi_use:
544
+ raise ValueError("Cannot re-use single-use snapshot.")
545
+ if self._transaction_id is None and self._read_only:
546
+ raise ValueError("Transaction ID pending.")
547
+
548
+ if params is not None:
549
+ params_pb = Struct(
550
+ fields={key: _make_value_pb(value) for key, value in params.items()}
551
+ )
552
+ else:
553
+ params_pb = {}
554
+
555
+ database = self._session._database
556
+ metadata = _metadata_with_prefix(database.name)
557
+ if not self._read_only and database._route_to_leader_enabled:
558
+ metadata.append(
559
+ _metadata_with_leader_aware_routing(database._route_to_leader_enabled)
560
+ )
561
+
562
+ api = database.spanner_api
563
+
564
+ # Query-level options have higher precedence than client-level and
565
+ # environment-level options
566
+ default_query_options = database._instance._client._query_options
567
+ query_options = _merge_query_options(default_query_options, query_options)
568
+
569
+ if request_options is None:
570
+ request_options = RequestOptions()
571
+ elif type(request_options) is dict:
572
+ request_options = RequestOptions(request_options)
573
+ if self._read_only:
574
+ # Transaction tags are not supported for read only transactions.
575
+ request_options.transaction_tag = None
576
+ if (
577
+ directed_read_options is None
578
+ and database._directed_read_options is not None
579
+ ):
580
+ directed_read_options = database._directed_read_options
581
+ elif self.transaction_tag is not None:
582
+ request_options.transaction_tag = self.transaction_tag
583
+
584
+ request = ExecuteSqlRequest(
585
+ session=self._session.name,
586
+ sql=sql,
587
+ params=params_pb,
588
+ param_types=param_types,
589
+ query_mode=query_mode,
590
+ partition_token=partition,
591
+ seqno=self._execute_sql_count,
592
+ query_options=query_options,
593
+ request_options=request_options,
594
+ last_statement=last_statement,
595
+ data_boost_enabled=data_boost_enabled,
596
+ directed_read_options=directed_read_options,
597
+ )
598
+
599
+ def wrapped_restart(*args, **kwargs):
600
+ restart = functools.partial(
601
+ api.execute_streaming_sql,
602
+ request=request,
603
+ metadata=kwargs.get("metadata", metadata),
604
+ retry=retry,
605
+ timeout=timeout,
606
+ )
607
+ return restart(*args, **kwargs)
608
+
609
+ trace_attributes = {"db.statement": sql}
610
+ observability_options = getattr(database, "observability_options", None)
611
+
612
+ if self._transaction_id is None:
613
+ # lock is added to handle the inline begin for first rpc
614
+ with self._lock:
615
+ return self._get_streamed_result_set(
616
+ wrapped_restart,
617
+ request,
618
+ metadata,
619
+ trace_attributes,
620
+ column_info,
621
+ observability_options,
622
+ lazy_decode=lazy_decode,
623
+ )
624
+ else:
625
+ return self._get_streamed_result_set(
626
+ wrapped_restart,
627
+ request,
628
+ metadata,
629
+ trace_attributes,
630
+ column_info,
631
+ observability_options,
632
+ lazy_decode=lazy_decode,
633
+ )
634
+
635
+ def _get_streamed_result_set(
636
+ self,
637
+ restart,
638
+ request,
639
+ metadata,
640
+ trace_attributes,
641
+ column_info,
642
+ observability_options=None,
643
+ lazy_decode=False,
644
+ ):
645
+ iterator = _restart_on_unavailable(
646
+ restart,
647
+ request,
648
+ metadata,
649
+ f"CloudSpanner.{type(self).__name__}.execute_sql",
650
+ self._session,
651
+ trace_attributes,
652
+ transaction=self,
653
+ observability_options=observability_options,
654
+ request_id_manager=self._session._database,
655
+ )
656
+ self._read_request_count += 1
657
+ self._execute_sql_count += 1
658
+
659
+ if self._multi_use:
660
+ return StreamedResultSet(
661
+ iterator, source=self, column_info=column_info, lazy_decode=lazy_decode
662
+ )
663
+ else:
664
+ return StreamedResultSet(
665
+ iterator, column_info=column_info, lazy_decode=lazy_decode
666
+ )
667
+
668
+ def partition_read(
669
+ self,
670
+ table,
671
+ columns,
672
+ keyset,
673
+ index="",
674
+ partition_size_bytes=None,
675
+ max_partitions=None,
676
+ *,
677
+ retry=gapic_v1.method.DEFAULT,
678
+ timeout=gapic_v1.method.DEFAULT,
679
+ ):
680
+ """Perform a ``PartitionRead`` API request for rows in a table.
681
+
682
+ :type table: str
683
+ :param table: name of the table from which to fetch data
684
+
685
+ :type columns: list of str
686
+ :param columns: names of columns to be retrieved
687
+
688
+ :type keyset: :class:`~google.cloud.spanner_v1.keyset.KeySet`
689
+ :param keyset: keys / ranges identifying rows to be retrieved
690
+
691
+ :type index: str
692
+ :param index: (Optional) name of index to use, rather than the
693
+ table's primary key
694
+
695
+ :type partition_size_bytes: int
696
+ :param partition_size_bytes:
697
+ (Optional) desired size for each partition generated. The service
698
+ uses this as a hint, the actual partition size may differ.
699
+
700
+ :type max_partitions: int
701
+ :param max_partitions:
702
+ (Optional) desired maximum number of partitions generated. The
703
+ service uses this as a hint, the actual number of partitions may
704
+ differ.
705
+
706
+ :type retry: :class:`~google.api_core.retry.Retry`
707
+ :param retry: (Optional) The retry settings for this request.
708
+
709
+ :type timeout: float
710
+ :param timeout: (Optional) The timeout for this request.
711
+
712
+ :rtype: iterable of bytes
713
+ :returns: a sequence of partition tokens
714
+
715
+ :raises ValueError:
716
+ for single-use snapshots, or if a transaction ID is
717
+ already associated with the snapshot.
718
+ """
719
+ if not self._multi_use:
720
+ raise ValueError("Cannot use single-use snapshot.")
721
+
722
+ if self._transaction_id is None:
723
+ raise ValueError("Transaction not started.")
724
+
725
+ database = self._session._database
726
+ api = database.spanner_api
727
+ metadata = _metadata_with_prefix(database.name)
728
+ if database._route_to_leader_enabled:
729
+ metadata.append(
730
+ _metadata_with_leader_aware_routing(database._route_to_leader_enabled)
731
+ )
732
+ transaction = self._make_txn_selector()
733
+ partition_options = PartitionOptions(
734
+ partition_size_bytes=partition_size_bytes, max_partitions=max_partitions
735
+ )
736
+ request = PartitionReadRequest(
737
+ session=self._session.name,
738
+ table=table,
739
+ columns=columns,
740
+ key_set=keyset._to_pb(),
741
+ transaction=transaction,
742
+ index=index,
743
+ partition_options=partition_options,
744
+ )
745
+
746
+ trace_attributes = {"table_id": table, "columns": columns}
747
+ can_include_index = (index != "") and (index is not None)
748
+ if can_include_index:
749
+ trace_attributes["index"] = index
750
+
751
+ with trace_call(
752
+ f"CloudSpanner.{type(self).__name__}.partition_read",
753
+ self._session,
754
+ extra_attributes=trace_attributes,
755
+ observability_options=getattr(database, "observability_options", None),
756
+ metadata=metadata,
757
+ ) as span, MetricsCapture():
758
+ nth_request = getattr(database, "_next_nth_request", 0)
759
+ attempt = AtomicCounter()
760
+
761
+ def attempt_tracking_method():
762
+ all_metadata = database.metadata_with_request_id(
763
+ nth_request,
764
+ attempt.increment(),
765
+ metadata,
766
+ span,
767
+ )
768
+ method = functools.partial(
769
+ api.partition_read,
770
+ request=request,
771
+ metadata=all_metadata,
772
+ retry=retry,
773
+ timeout=timeout,
774
+ )
775
+ return method()
776
+
777
+ response = _retry(
778
+ attempt_tracking_method,
779
+ allowed_exceptions={InternalServerError: _check_rst_stream_error},
780
+ )
781
+
782
+ return [partition.partition_token for partition in response.partitions]
783
+
784
+ def partition_query(
785
+ self,
786
+ sql,
787
+ params=None,
788
+ param_types=None,
789
+ partition_size_bytes=None,
790
+ max_partitions=None,
791
+ *,
792
+ retry=gapic_v1.method.DEFAULT,
793
+ timeout=gapic_v1.method.DEFAULT,
794
+ ):
795
+ """Perform a ``PartitionQuery`` API request.
796
+
797
+ :type sql: str
798
+ :param sql: SQL query statement
799
+
800
+ :type params: dict, {str -> column value}
801
+ :param params: values for parameter replacement. Keys must match
802
+ the names used in ``sql``.
803
+
804
+ :type param_types: dict[str -> Union[dict, .types.Type]]
805
+ :param param_types:
806
+ (Optional) maps explicit types for one or more param values;
807
+ required if parameters are passed.
808
+
809
+ :type partition_size_bytes: int
810
+ :param partition_size_bytes:
811
+ (Optional) desired size for each partition generated. The service
812
+ uses this as a hint, the actual partition size may differ.
813
+
814
+ :type max_partitions: int
815
+ :param max_partitions:
816
+ (Optional) desired maximum number of partitions generated. The
817
+ service uses this as a hint, the actual number of partitions may
818
+ differ.
819
+
820
+ :type retry: :class:`~google.api_core.retry.Retry`
821
+ :param retry: (Optional) The retry settings for this request.
822
+
823
+ :type timeout: float
824
+ :param timeout: (Optional) The timeout for this request.
825
+
826
+ :rtype: iterable of bytes
827
+ :returns: a sequence of partition tokens
828
+
829
+ :raises ValueError:
830
+ for single-use snapshots, or if a transaction ID is
831
+ already associated with the snapshot.
832
+ """
833
+ if not self._multi_use:
834
+ raise ValueError("Cannot use single-use snapshot.")
835
+
836
+ if self._transaction_id is None:
837
+ raise ValueError("Transaction not started.")
838
+
839
+ if params is not None:
840
+ params_pb = Struct(
841
+ fields={key: _make_value_pb(value) for (key, value) in params.items()}
842
+ )
843
+ else:
844
+ params_pb = Struct()
845
+
846
+ database = self._session._database
847
+ api = database.spanner_api
848
+ metadata = _metadata_with_prefix(database.name)
849
+ if database._route_to_leader_enabled:
850
+ metadata.append(
851
+ _metadata_with_leader_aware_routing(database._route_to_leader_enabled)
852
+ )
853
+ transaction = self._make_txn_selector()
854
+ partition_options = PartitionOptions(
855
+ partition_size_bytes=partition_size_bytes, max_partitions=max_partitions
856
+ )
857
+ request = PartitionQueryRequest(
858
+ session=self._session.name,
859
+ sql=sql,
860
+ transaction=transaction,
861
+ params=params_pb,
862
+ param_types=param_types,
863
+ partition_options=partition_options,
864
+ )
865
+
866
+ trace_attributes = {"db.statement": sql}
867
+ with trace_call(
868
+ f"CloudSpanner.{type(self).__name__}.partition_query",
869
+ self._session,
870
+ trace_attributes,
871
+ observability_options=getattr(database, "observability_options", None),
872
+ metadata=metadata,
873
+ ) as span, MetricsCapture():
874
+ nth_request = getattr(database, "_next_nth_request", 0)
875
+ attempt = AtomicCounter()
876
+
877
+ def attempt_tracking_method():
878
+ all_metadata = database.metadata_with_request_id(
879
+ nth_request,
880
+ attempt.increment(),
881
+ metadata,
882
+ span,
883
+ )
884
+ method = functools.partial(
885
+ api.partition_query,
886
+ request=request,
887
+ metadata=all_metadata,
888
+ retry=retry,
889
+ timeout=timeout,
890
+ )
891
+ return method()
892
+
893
+ response = _retry(
894
+ attempt_tracking_method,
895
+ allowed_exceptions={InternalServerError: _check_rst_stream_error},
896
+ )
897
+
898
+ return [partition.partition_token for partition in response.partitions]
899
+
900
+
901
+ class Snapshot(_SnapshotBase):
902
+ """Allow a set of reads / SQL statements with shared staleness.
903
+
904
+ See
905
+ https://cloud.google.com/spanner/reference/rpc/google.spanner.v1#google.spanner.v1.TransactionOptions.ReadOnly
906
+
907
+ If no options are passed, reads will use the ``strong`` model, reading
908
+ at a timestamp where all previously committed transactions are visible.
909
+
910
+ :type session: :class:`~google.cloud.spanner_v1.session.Session`
911
+ :param session: The session used to perform the commit.
912
+
913
+ :type read_timestamp: :class:`datetime.datetime`
914
+ :param read_timestamp: Execute all reads at the given timestamp.
915
+
916
+ :type min_read_timestamp: :class:`datetime.datetime`
917
+ :param min_read_timestamp: Execute all reads at a
918
+ timestamp >= ``min_read_timestamp``.
919
+
920
+ :type max_staleness: :class:`datetime.timedelta`
921
+ :param max_staleness: Read data at a
922
+ timestamp >= NOW - ``max_staleness`` seconds.
923
+
924
+ :type exact_staleness: :class:`datetime.timedelta`
925
+ :param exact_staleness: Execute all reads at a timestamp that is
926
+ ``exact_staleness`` old.
927
+
928
+ :type multi_use: :class:`bool`
929
+ :param multi_use: If true, multiple :meth:`read` / :meth:`execute_sql`
930
+ calls can be performed with the snapshot in the
931
+ context of a read-only transaction, used to ensure
932
+ isolation / consistency. Incompatible with
933
+ ``max_staleness`` and ``min_read_timestamp``.
934
+ """
935
+
936
+ def __init__(
937
+ self,
938
+ session,
939
+ read_timestamp=None,
940
+ min_read_timestamp=None,
941
+ max_staleness=None,
942
+ exact_staleness=None,
943
+ multi_use=False,
944
+ transaction_id=None,
945
+ ):
946
+ super(Snapshot, self).__init__(session)
947
+ opts = [read_timestamp, min_read_timestamp, max_staleness, exact_staleness]
948
+ flagged = [opt for opt in opts if opt is not None]
949
+
950
+ if len(flagged) > 1:
951
+ raise ValueError("Supply zero or one options.")
952
+
953
+ if multi_use:
954
+ if min_read_timestamp is not None or max_staleness is not None:
955
+ raise ValueError(
956
+ "'multi_use' is incompatible with "
957
+ "'min_read_timestamp' / 'max_staleness'"
958
+ )
959
+
960
+ self._transaction_read_timestamp = None
961
+ self._strong = len(flagged) == 0
962
+ self._read_timestamp = read_timestamp
963
+ self._min_read_timestamp = min_read_timestamp
964
+ self._max_staleness = max_staleness
965
+ self._exact_staleness = exact_staleness
966
+ self._multi_use = multi_use
967
+ self._transaction_id = transaction_id
968
+
969
+ def _make_txn_selector(self):
970
+ """Helper for :meth:`read`."""
971
+ if self._transaction_id is not None:
972
+ return TransactionSelector(id=self._transaction_id)
973
+
974
+ if self._read_timestamp:
975
+ key = "read_timestamp"
976
+ value = self._read_timestamp
977
+ elif self._min_read_timestamp:
978
+ key = "min_read_timestamp"
979
+ value = self._min_read_timestamp
980
+ elif self._max_staleness:
981
+ key = "max_staleness"
982
+ value = self._max_staleness
983
+ elif self._exact_staleness:
984
+ key = "exact_staleness"
985
+ value = self._exact_staleness
986
+ else:
987
+ key = "strong"
988
+ value = True
989
+
990
+ options = TransactionOptions(
991
+ read_only=TransactionOptions.ReadOnly(
992
+ **{key: value, "return_read_timestamp": True}
993
+ )
994
+ )
995
+
996
+ if self._multi_use:
997
+ return TransactionSelector(begin=options)
998
+ else:
999
+ return TransactionSelector(single_use=options)
1000
+
1001
+ def begin(self):
1002
+ """Begin a read-only transaction on the database.
1003
+
1004
+ :rtype: bytes
1005
+ :returns: the ID for the newly-begun transaction.
1006
+
1007
+ :raises ValueError:
1008
+ if the transaction is already begun, committed, or rolled back.
1009
+ """
1010
+ if not self._multi_use:
1011
+ raise ValueError("Cannot call 'begin' on single-use snapshots")
1012
+
1013
+ if self._transaction_id is not None:
1014
+ raise ValueError("Read-only transaction already begun")
1015
+
1016
+ if self._read_request_count > 0:
1017
+ raise ValueError("Read-only transaction already pending")
1018
+
1019
+ database = self._session._database
1020
+ api = database.spanner_api
1021
+ metadata = _metadata_with_prefix(database.name)
1022
+ if not self._read_only and database._route_to_leader_enabled:
1023
+ metadata.append(
1024
+ (_metadata_with_leader_aware_routing(database._route_to_leader_enabled))
1025
+ )
1026
+ txn_selector = self._make_txn_selector()
1027
+ with trace_call(
1028
+ f"CloudSpanner.{type(self).__name__}.begin",
1029
+ self._session,
1030
+ observability_options=getattr(database, "observability_options", None),
1031
+ metadata=metadata,
1032
+ ) as span, MetricsCapture():
1033
+ nth_request = getattr(database, "_next_nth_request", 0)
1034
+ attempt = AtomicCounter()
1035
+
1036
+ def attempt_tracking_method():
1037
+ all_metadata = database.metadata_with_request_id(
1038
+ nth_request,
1039
+ attempt.increment(),
1040
+ metadata,
1041
+ span,
1042
+ )
1043
+ method = functools.partial(
1044
+ api.begin_transaction,
1045
+ session=self._session.name,
1046
+ options=txn_selector.begin,
1047
+ metadata=all_metadata,
1048
+ )
1049
+ return method()
1050
+
1051
+ response = _retry(
1052
+ attempt_tracking_method,
1053
+ allowed_exceptions={InternalServerError: _check_rst_stream_error},
1054
+ )
1055
+ self._transaction_id = response.id
1056
+ self._transaction_read_timestamp = response.read_timestamp
1057
+ return self._transaction_id