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,1968 @@
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
+ """User-friendly container for Cloud Spanner Database."""
16
+
17
+ import copy
18
+ import functools
19
+
20
+ import grpc
21
+ import logging
22
+ import re
23
+ import threading
24
+
25
+ import google.auth.credentials
26
+ from google.api_core.retry import Retry
27
+ from google.api_core.retry import if_exception_type
28
+ from google.cloud.exceptions import NotFound
29
+ from google.api_core.exceptions import Aborted
30
+ from google.api_core import gapic_v1
31
+ from google.iam.v1 import iam_policy_pb2
32
+ from google.iam.v1 import options_pb2
33
+ from google.protobuf.field_mask_pb2 import FieldMask
34
+
35
+ from google.cloud.spanner_admin_database_v1 import CreateDatabaseRequest
36
+ from google.cloud.spanner_admin_database_v1 import Database as DatabasePB
37
+ from google.cloud.spanner_admin_database_v1 import ListDatabaseRolesRequest
38
+ from google.cloud.spanner_admin_database_v1 import EncryptionConfig
39
+ from google.cloud.spanner_admin_database_v1 import RestoreDatabaseEncryptionConfig
40
+ from google.cloud.spanner_admin_database_v1 import RestoreDatabaseRequest
41
+ from google.cloud.spanner_admin_database_v1 import UpdateDatabaseDdlRequest
42
+ from google.cloud.spanner_admin_database_v1.types import DatabaseDialect
43
+ from google.cloud.spanner_v1.transaction import BatchTransactionId
44
+ from google.cloud.spanner_v1 import ExecuteSqlRequest
45
+ from google.cloud.spanner_v1 import Type
46
+ from google.cloud.spanner_v1 import TypeCode
47
+ from google.cloud.spanner_v1 import TransactionSelector
48
+ from google.cloud.spanner_v1 import TransactionOptions
49
+ from google.cloud.spanner_v1 import DefaultTransactionOptions
50
+ from google.cloud.spanner_v1 import RequestOptions
51
+ from google.cloud.spanner_v1 import SpannerClient
52
+ from google.cloud.spanner_v1._helpers import _merge_query_options
53
+ from google.cloud.spanner_v1._helpers import (
54
+ _metadata_with_prefix,
55
+ _metadata_with_leader_aware_routing,
56
+ _metadata_with_request_id,
57
+ )
58
+ from google.cloud.spanner_v1.batch import Batch
59
+ from google.cloud.spanner_v1.batch import MutationGroups
60
+ from google.cloud.spanner_v1.keyset import KeySet
61
+ from google.cloud.spanner_v1.merged_result_set import MergedResultSet
62
+ from google.cloud.spanner_v1.pool import BurstyPool
63
+ from google.cloud.spanner_v1.pool import SessionCheckout
64
+ from google.cloud.spanner_v1.session import Session
65
+ from google.cloud.spanner_v1.session_options import SessionOptions
66
+ from google.cloud.spanner_v1.database_sessions_manager import DatabaseSessionsManager
67
+ from google.cloud.spanner_v1.snapshot import _restart_on_unavailable
68
+ from google.cloud.spanner_v1.snapshot import Snapshot
69
+ from google.cloud.spanner_v1.streamed import StreamedResultSet
70
+ from google.cloud.spanner_v1.services.spanner.transports.grpc import (
71
+ SpannerGrpcTransport,
72
+ )
73
+ from google.cloud.spanner_v1.table import Table
74
+ from google.cloud.spanner_v1._opentelemetry_tracing import (
75
+ add_span_event,
76
+ get_current_span,
77
+ trace_call,
78
+ )
79
+ from google.cloud.spanner_v1.metrics.metrics_capture import MetricsCapture
80
+
81
+
82
+ SPANNER_DATA_SCOPE = "https://www.googleapis.com/auth/spanner.data"
83
+
84
+
85
+ _DATABASE_NAME_RE = re.compile(
86
+ r"^projects/(?P<project>[^/]+)/"
87
+ r"instances/(?P<instance_id>[a-z][-a-z0-9]*)/"
88
+ r"databases/(?P<database_id>[a-z][a-z0-9_\-]*[a-z0-9])$"
89
+ )
90
+
91
+ _DATABASE_METADATA_FILTER = "name:{0}/operations/"
92
+
93
+ _LIST_TABLES_QUERY = """SELECT TABLE_NAME
94
+ FROM INFORMATION_SCHEMA.TABLES
95
+ {}
96
+ """
97
+
98
+ DEFAULT_RETRY_BACKOFF = Retry(initial=0.02, maximum=32, multiplier=1.3)
99
+
100
+
101
+ class Database(object):
102
+ """Representation of a Cloud Spanner Database.
103
+
104
+ We can use a :class:`Database` to:
105
+
106
+ * :meth:`create` the database
107
+ * :meth:`reload` the database
108
+ * :meth:`update` the database
109
+ * :meth:`drop` the database
110
+
111
+ :type database_id: str
112
+ :param database_id: The ID of the database.
113
+
114
+ :type instance: :class:`~google.cloud.spanner_v1.instance.Instance`
115
+ :param instance: The instance that owns the database.
116
+
117
+ :type ddl_statements: list of string
118
+ :param ddl_statements: (Optional) DDL statements, excluding the
119
+ CREATE DATABASE statement.
120
+
121
+ :type pool: concrete subclass of
122
+ :class:`~google.cloud.spanner_v1.pool.AbstractSessionPool`.
123
+ :param pool: (Optional) session pool to be used by database. If not
124
+ passed, the database will construct an instance of
125
+ :class:`~google.cloud.spanner_v1.pool.BurstyPool`.
126
+
127
+ :type logger: :class:`logging.Logger`
128
+ :param logger: (Optional) a custom logger that is used if `log_commit_stats`
129
+ is `True` to log commit statistics. If not passed, a logger
130
+ will be created when needed that will log the commit statistics
131
+ to stdout.
132
+ :type encryption_config:
133
+ :class:`~google.cloud.spanner_admin_database_v1.types.EncryptionConfig`
134
+ or :class:`~google.cloud.spanner_admin_database_v1.types.RestoreDatabaseEncryptionConfig`
135
+ or :class:`dict`
136
+ :param encryption_config:
137
+ (Optional) Encryption configuration for the database.
138
+ If a dict is provided, it must be of the same form as either of the protobuf
139
+ messages :class:`~google.cloud.spanner_admin_database_v1.types.EncryptionConfig`
140
+ or :class:`~google.cloud.spanner_admin_database_v1.types.RestoreDatabaseEncryptionConfig`
141
+ :type database_dialect:
142
+ :class:`~google.cloud.spanner_admin_database_v1.types.DatabaseDialect`
143
+ :param database_dialect:
144
+ (Optional) database dialect for the database
145
+ :type database_role: str or None
146
+ :param database_role: (Optional) user-assigned database_role for the session.
147
+ :type enable_drop_protection: boolean
148
+ :param enable_drop_protection: (Optional) Represents whether the database
149
+ has drop protection enabled or not.
150
+ :type proto_descriptors: bytes
151
+ :param proto_descriptors: (Optional) Proto descriptors used by CREATE/ALTER PROTO BUNDLE
152
+ statements in 'ddl_statements' above.
153
+ """
154
+
155
+ _spanner_api: SpannerClient = None
156
+
157
+ __transport_lock = threading.Lock()
158
+ __transports_to_channel_id = dict()
159
+
160
+ def __init__(
161
+ self,
162
+ database_id,
163
+ instance,
164
+ ddl_statements=(),
165
+ pool=None,
166
+ logger=None,
167
+ encryption_config=None,
168
+ database_dialect=DatabaseDialect.DATABASE_DIALECT_UNSPECIFIED,
169
+ database_role=None,
170
+ enable_drop_protection=False,
171
+ proto_descriptors=None,
172
+ ):
173
+ self.database_id = database_id
174
+ self._instance = instance
175
+ self._ddl_statements = _check_ddl_statements(ddl_statements)
176
+ self._local = threading.local()
177
+ self._state = None
178
+ self._create_time = None
179
+ self._restore_info = None
180
+ self._version_retention_period = None
181
+ self._earliest_version_time = None
182
+ self._encryption_info = None
183
+ self._default_leader = None
184
+ self.log_commit_stats = False
185
+ self._logger = logger
186
+ self._encryption_config = encryption_config
187
+ self._database_dialect = database_dialect
188
+ self._database_role = database_role
189
+ self._route_to_leader_enabled = self._instance._client.route_to_leader_enabled
190
+ self._enable_drop_protection = enable_drop_protection
191
+ self._reconciling = False
192
+ self._directed_read_options = self._instance._client.directed_read_options
193
+ self.default_transaction_options: DefaultTransactionOptions = (
194
+ self._instance._client.default_transaction_options
195
+ )
196
+ self._proto_descriptors = proto_descriptors
197
+ self._channel_id = 0 # It'll be created when _spanner_api is created.
198
+
199
+ if pool is None:
200
+ pool = BurstyPool(database_role=database_role)
201
+
202
+ self._pool = pool
203
+ pool.bind(self)
204
+
205
+ self.session_options = SessionOptions()
206
+ self._sessions_manager = DatabaseSessionsManager(self, pool)
207
+
208
+ @classmethod
209
+ def from_pb(cls, database_pb, instance, pool=None):
210
+ """Creates an instance of this class from a protobuf.
211
+
212
+ :type database_pb:
213
+ :class:`~google.cloud.spanner_admin_instance_v1.types.Instance`
214
+ :param database_pb: A instance protobuf object.
215
+
216
+ :type instance: :class:`~google.cloud.spanner_v1.instance.Instance`
217
+ :param instance: The instance that owns the database.
218
+
219
+ :type pool: concrete subclass of
220
+ :class:`~google.cloud.spanner_v1.pool.AbstractSessionPool`.
221
+ :param pool: (Optional) session pool to be used by database.
222
+
223
+ :rtype: :class:`Database`
224
+ :returns: The database parsed from the protobuf response.
225
+ :raises ValueError:
226
+ if the instance name does not match the expected format
227
+ or if the parsed project ID does not match the project ID
228
+ on the instance's client, or if the parsed instance ID does
229
+ not match the instance's ID.
230
+ """
231
+ match = _DATABASE_NAME_RE.match(database_pb.name)
232
+ if match is None:
233
+ raise ValueError(
234
+ "Database protobuf name was not in the " "expected format.",
235
+ database_pb.name,
236
+ )
237
+ if match.group("project") != instance._client.project:
238
+ raise ValueError(
239
+ "Project ID on database does not match the "
240
+ "project ID on the instance's client"
241
+ )
242
+ instance_id = match.group("instance_id")
243
+ if instance_id != instance.instance_id:
244
+ raise ValueError(
245
+ "Instance ID on database does not match the "
246
+ "Instance ID on the instance"
247
+ )
248
+ database_id = match.group("database_id")
249
+
250
+ return cls(database_id, instance, pool=pool)
251
+
252
+ @property
253
+ def name(self):
254
+ """Database name used in requests.
255
+
256
+ .. note::
257
+
258
+ This property will not change if ``database_id`` does not, but the
259
+ return value is not cached.
260
+
261
+ The database name is of the form
262
+
263
+ ``"projects/../instances/../databases/{database_id}"``
264
+
265
+ :rtype: str
266
+ :returns: The database name.
267
+ """
268
+ return self._instance.name + "/databases/" + self.database_id
269
+
270
+ @property
271
+ def state(self):
272
+ """State of this database.
273
+
274
+ :rtype: :class:`~google.cloud.spanner_admin_database_v1.types.Database.State`
275
+ :returns: an enum describing the state of the database
276
+ """
277
+ return self._state
278
+
279
+ @property
280
+ def create_time(self):
281
+ """Create time of this database.
282
+
283
+ :rtype: :class:`datetime.datetime`
284
+ :returns: a datetime object representing the create time of
285
+ this database
286
+ """
287
+ return self._create_time
288
+
289
+ @property
290
+ def restore_info(self):
291
+ """Restore info for this database.
292
+
293
+ :rtype: :class:`~google.cloud.spanner_v1.types.RestoreInfo`
294
+ :returns: an object representing the restore info for this database
295
+ """
296
+ return self._restore_info
297
+
298
+ @property
299
+ def version_retention_period(self):
300
+ """The period in which Cloud Spanner retains all versions of data
301
+ for the database.
302
+
303
+ :rtype: str
304
+ :returns: a string representing the duration of the version retention period
305
+ """
306
+ return self._version_retention_period
307
+
308
+ @property
309
+ def earliest_version_time(self):
310
+ """The earliest time at which older versions of the data can be read.
311
+
312
+ :rtype: :class:`datetime.datetime`
313
+ :returns: a datetime object representing the earliest version time
314
+ """
315
+ return self._earliest_version_time
316
+
317
+ @property
318
+ def encryption_config(self):
319
+ """Encryption config for this database.
320
+ :rtype: :class:`~google.cloud.spanner_admin_instance_v1.types.EncryptionConfig`
321
+ :returns: an object representing the encryption config for this database
322
+ """
323
+ return self._encryption_config
324
+
325
+ @property
326
+ def encryption_info(self):
327
+ """Encryption info for this database.
328
+ :rtype: a list of :class:`~google.cloud.spanner_admin_instance_v1.types.EncryptionInfo`
329
+ :returns: a list of objects representing encryption info for this database
330
+ """
331
+ return self._encryption_info
332
+
333
+ @property
334
+ def default_leader(self):
335
+ """The read-write region which contains the database's leader replicas.
336
+
337
+ :rtype: str
338
+ :returns: a string representing the read-write region
339
+ """
340
+ return self._default_leader
341
+
342
+ @property
343
+ def ddl_statements(self):
344
+ """DDL Statements used to define database schema.
345
+
346
+ See
347
+ cloud.google.com/spanner/docs/data-definition-language
348
+
349
+ :rtype: sequence of string
350
+ :returns: the statements
351
+ """
352
+ return self._ddl_statements
353
+
354
+ @property
355
+ def database_dialect(self):
356
+ """DDL Statements used to define database schema.
357
+
358
+ See
359
+ cloud.google.com/spanner/docs/data-definition-language
360
+
361
+ :rtype: :class:`google.cloud.spanner_admin_database_v1.types.DatabaseDialect`
362
+ :returns: the dialect of the database
363
+ """
364
+ if self._database_dialect == DatabaseDialect.DATABASE_DIALECT_UNSPECIFIED:
365
+ self.reload()
366
+ return self._database_dialect
367
+
368
+ @property
369
+ def default_schema_name(self):
370
+ """Default schema name for this database.
371
+
372
+ :rtype: str
373
+ :returns: "" for GoogleSQL and "public" for PostgreSQL
374
+ """
375
+ if self.database_dialect == DatabaseDialect.POSTGRESQL:
376
+ return "public"
377
+ return ""
378
+
379
+ @property
380
+ def database_role(self):
381
+ """User-assigned database_role for sessions created by the pool.
382
+ :rtype: str
383
+ :returns: a str with the name of the database role.
384
+ """
385
+ return self._database_role
386
+
387
+ @property
388
+ def reconciling(self):
389
+ """Whether the database is currently reconciling.
390
+
391
+ :rtype: boolean
392
+ :returns: a boolean representing whether the database is reconciling
393
+ """
394
+ return self._reconciling
395
+
396
+ @property
397
+ def enable_drop_protection(self):
398
+ """Whether the database has drop protection enabled.
399
+
400
+ :rtype: boolean
401
+ :returns: a boolean representing whether the database has drop
402
+ protection enabled
403
+ """
404
+ return self._enable_drop_protection
405
+
406
+ @enable_drop_protection.setter
407
+ def enable_drop_protection(self, value):
408
+ self._enable_drop_protection = value
409
+
410
+ @property
411
+ def proto_descriptors(self):
412
+ """Proto Descriptors for this database.
413
+ :rtype: bytes
414
+ :returns: bytes representing the proto descriptors for this database
415
+ """
416
+ return self._proto_descriptors
417
+
418
+ @property
419
+ def logger(self):
420
+ """Logger used by the database.
421
+
422
+ The default logger will log commit stats at the log level INFO using
423
+ `sys.stderr`.
424
+
425
+ :rtype: :class:`logging.Logger` or `None`
426
+ :returns: the logger
427
+ """
428
+ if self._logger is None:
429
+ self._logger = logging.getLogger(self.name)
430
+ self._logger.setLevel(logging.INFO)
431
+
432
+ ch = logging.StreamHandler()
433
+ ch.setLevel(logging.INFO)
434
+ self._logger.addHandler(ch)
435
+ return self._logger
436
+
437
+ @property
438
+ def spanner_api(self):
439
+ """Helper for session-related API calls."""
440
+ if self._spanner_api is None:
441
+ client_info = self._instance._client._client_info
442
+ client_options = self._instance._client._client_options
443
+ if self._instance.emulator_host is not None:
444
+ transport = SpannerGrpcTransport(
445
+ channel=grpc.insecure_channel(self._instance.emulator_host)
446
+ )
447
+ self._spanner_api = SpannerClient(
448
+ client_info=client_info, transport=transport
449
+ )
450
+ return self._spanner_api
451
+ credentials = self._instance._client.credentials
452
+ if isinstance(credentials, google.auth.credentials.Scoped):
453
+ credentials = credentials.with_scopes((SPANNER_DATA_SCOPE,))
454
+ self._spanner_api = SpannerClient(
455
+ credentials=credentials,
456
+ client_info=client_info,
457
+ client_options=client_options,
458
+ )
459
+
460
+ with self.__transport_lock:
461
+ transport = self._spanner_api._transport
462
+ channel_id = self.__transports_to_channel_id.get(transport, None)
463
+ if channel_id is None:
464
+ channel_id = len(self.__transports_to_channel_id) + 1
465
+ self.__transports_to_channel_id[transport] = channel_id
466
+ self._channel_id = channel_id
467
+
468
+ return self._spanner_api
469
+
470
+ def metadata_with_request_id(
471
+ self, nth_request, nth_attempt, prior_metadata=[], span=None
472
+ ):
473
+ if span is None:
474
+ span = get_current_span()
475
+
476
+ return _metadata_with_request_id(
477
+ self._nth_client_id,
478
+ self._channel_id,
479
+ nth_request,
480
+ nth_attempt,
481
+ prior_metadata,
482
+ span,
483
+ )
484
+
485
+ def __eq__(self, other):
486
+ if not isinstance(other, self.__class__):
487
+ return NotImplemented
488
+ return (
489
+ other.database_id == self.database_id and other._instance == self._instance
490
+ )
491
+
492
+ def __ne__(self, other):
493
+ return not self == other
494
+
495
+ def create(self):
496
+ """Create this database within its instance
497
+
498
+ Includes any configured schema assigned to :attr:`ddl_statements`.
499
+
500
+ See
501
+ https://cloud.google.com/spanner/reference/rpc/google.spanner.admin.database.v1#google.spanner.admin.database.v1.DatabaseAdmin.CreateDatabase
502
+
503
+ :rtype: :class:`~google.api_core.operation.Operation`
504
+ :returns: a future used to poll the status of the create request
505
+ :raises Conflict: if the database already exists
506
+ :raises NotFound: if the instance owning the database does not exist
507
+ """
508
+ api = self._instance._client.database_admin_api
509
+ metadata = _metadata_with_prefix(self.name)
510
+ db_name = self.database_id
511
+ if "-" in db_name:
512
+ if self._database_dialect == DatabaseDialect.POSTGRESQL:
513
+ db_name = f'"{db_name}"'
514
+ else:
515
+ db_name = f"`{db_name}`"
516
+ if type(self._encryption_config) is dict:
517
+ self._encryption_config = EncryptionConfig(**self._encryption_config)
518
+
519
+ request = CreateDatabaseRequest(
520
+ parent=self._instance.name,
521
+ create_statement="CREATE DATABASE %s" % (db_name,),
522
+ extra_statements=list(self._ddl_statements),
523
+ encryption_config=self._encryption_config,
524
+ database_dialect=self._database_dialect,
525
+ proto_descriptors=self._proto_descriptors,
526
+ )
527
+ future = api.create_database(
528
+ request=request,
529
+ metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
530
+ )
531
+ return future
532
+
533
+ def exists(self):
534
+ """Test whether this database exists.
535
+
536
+ See
537
+ https://cloud.google.com/spanner/reference/rpc/google.spanner.admin.database.v1#google.spanner.admin.database.v1.DatabaseAdmin.GetDatabaseDDL
538
+
539
+ :rtype: bool
540
+ :returns: True if the database exists, else false.
541
+ """
542
+ api = self._instance._client.database_admin_api
543
+ metadata = _metadata_with_prefix(self.name)
544
+
545
+ try:
546
+ api.get_database_ddl(
547
+ database=self.name,
548
+ metadata=self.metadata_with_request_id(
549
+ self._next_nth_request, 1, metadata
550
+ ),
551
+ )
552
+ except NotFound:
553
+ return False
554
+ return True
555
+
556
+ def reload(self):
557
+ """Reload this database.
558
+
559
+ Refresh any configured schema into :attr:`ddl_statements`.
560
+
561
+ See
562
+ https://cloud.google.com/spanner/reference/rpc/google.spanner.admin.database.v1#google.spanner.admin.database.v1.DatabaseAdmin.GetDatabaseDDL
563
+
564
+ :raises NotFound: if the database does not exist
565
+ """
566
+ api = self._instance._client.database_admin_api
567
+ metadata = _metadata_with_prefix(self.name)
568
+ response = api.get_database_ddl(
569
+ database=self.name,
570
+ metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
571
+ )
572
+ self._ddl_statements = tuple(response.statements)
573
+ self._proto_descriptors = response.proto_descriptors
574
+ response = api.get_database(
575
+ name=self.name,
576
+ metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
577
+ )
578
+ self._state = DatabasePB.State(response.state)
579
+ self._create_time = response.create_time
580
+ self._restore_info = response.restore_info
581
+ self._version_retention_period = response.version_retention_period
582
+ self._earliest_version_time = response.earliest_version_time
583
+ self._encryption_config = response.encryption_config
584
+ self._encryption_info = response.encryption_info
585
+ self._default_leader = response.default_leader
586
+ # Only update if the data is specific to avoid losing specificity.
587
+ if response.database_dialect != DatabaseDialect.DATABASE_DIALECT_UNSPECIFIED:
588
+ self._database_dialect = response.database_dialect
589
+ self._enable_drop_protection = response.enable_drop_protection
590
+ self._reconciling = response.reconciling
591
+
592
+ def update_ddl(self, ddl_statements, operation_id="", proto_descriptors=None):
593
+ """Update DDL for this database.
594
+
595
+ Apply any configured schema from :attr:`ddl_statements`.
596
+
597
+ See
598
+ https://cloud.google.com/spanner/reference/rpc/google.spanner.admin.database.v1#google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabaseDdl
599
+
600
+ :type ddl_statements: Sequence[str]
601
+ :param ddl_statements: a list of DDL statements to use on this database
602
+ :type operation_id: str
603
+ :param operation_id: (optional) a string ID for the long-running operation
604
+ :type proto_descriptors: bytes
605
+ :param proto_descriptors: (optional) Proto descriptors used by CREATE/ALTER PROTO BUNDLE statements
606
+
607
+ :rtype: :class:`google.api_core.operation.Operation`
608
+ :returns: an operation instance
609
+ :raises NotFound: if the database does not exist
610
+ """
611
+ client = self._instance._client
612
+ api = client.database_admin_api
613
+ metadata = _metadata_with_prefix(self.name)
614
+
615
+ request = UpdateDatabaseDdlRequest(
616
+ database=self.name,
617
+ statements=ddl_statements,
618
+ operation_id=operation_id,
619
+ proto_descriptors=proto_descriptors,
620
+ )
621
+
622
+ future = api.update_database_ddl(
623
+ request=request,
624
+ metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
625
+ )
626
+ return future
627
+
628
+ def update(self, fields):
629
+ """Update this database.
630
+
631
+ See
632
+ https://cloud.google.com/spanner/reference/rpc/google.spanner.admin.database.v1#google.spanner.admin.database.v1.DatabaseAdmin.UpdateDatabase
633
+
634
+ .. note::
635
+
636
+ Updates the specified fields of a Cloud Spanner database. Currently,
637
+ only the `enable_drop_protection` field supports updates. To change
638
+ this value before updating, set it via
639
+
640
+ .. code:: python
641
+
642
+ database.enable_drop_protection = True
643
+
644
+ before calling :meth:`update`.
645
+
646
+ :type fields: Sequence[str]
647
+ :param fields: a list of fields to update
648
+
649
+ :rtype: :class:`google.api_core.operation.Operation`
650
+ :returns: an operation instance
651
+ :raises NotFound: if the database does not exist
652
+ """
653
+ api = self._instance._client.database_admin_api
654
+ database_pb = DatabasePB(
655
+ name=self.name, enable_drop_protection=self._enable_drop_protection
656
+ )
657
+
658
+ # Only support updating drop protection for now.
659
+ field_mask = FieldMask(paths=fields)
660
+ metadata = _metadata_with_prefix(self.name)
661
+
662
+ future = api.update_database(
663
+ database=database_pb,
664
+ update_mask=field_mask,
665
+ metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
666
+ )
667
+
668
+ return future
669
+
670
+ def drop(self):
671
+ """Drop this database.
672
+
673
+ See
674
+ https://cloud.google.com/spanner/reference/rpc/google.spanner.admin.database.v1#google.spanner.admin.database.v1.DatabaseAdmin.DropDatabase
675
+ """
676
+ api = self._instance._client.database_admin_api
677
+ metadata = _metadata_with_prefix(self.name)
678
+ api.drop_database(
679
+ database=self.name,
680
+ metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
681
+ )
682
+
683
+ def execute_partitioned_dml(
684
+ self,
685
+ dml,
686
+ params=None,
687
+ param_types=None,
688
+ query_options=None,
689
+ request_options=None,
690
+ exclude_txn_from_change_streams=False,
691
+ ):
692
+ """Execute a partitionable DML statement.
693
+
694
+ :type dml: str
695
+ :param dml: DML statement
696
+
697
+ :type params: dict, {str -> column value}
698
+ :param params: values for parameter replacement. Keys must match
699
+ the names used in ``dml``.
700
+
701
+ :type param_types: dict[str -> Union[dict, .types.Type]]
702
+ :param param_types:
703
+ (Optional) maps explicit types for one or more param values;
704
+ required if parameters are passed.
705
+
706
+ :type query_options:
707
+ :class:`~google.cloud.spanner_v1.types.ExecuteSqlRequest.QueryOptions`
708
+ or :class:`dict`
709
+ :param query_options:
710
+ (Optional) Query optimizer configuration to use for the given query.
711
+ If a dict is provided, it must be of the same form as the protobuf
712
+ message :class:`~google.cloud.spanner_v1.types.QueryOptions`
713
+
714
+ :type request_options:
715
+ :class:`google.cloud.spanner_v1.types.RequestOptions`
716
+ :param request_options:
717
+ (Optional) Common options for this request.
718
+ If a dict is provided, it must be of the same form as the protobuf
719
+ message :class:`~google.cloud.spanner_v1.types.RequestOptions`.
720
+ Please note, the `transactionTag` setting will be ignored as it is
721
+ not supported for partitioned DML.
722
+
723
+ :type exclude_txn_from_change_streams: bool
724
+ :param exclude_txn_from_change_streams:
725
+ (Optional) If true, instructs the transaction to be excluded from being recorded in change streams
726
+ with the DDL option `allow_txn_exclusion=true`. This does not exclude the transaction from
727
+ being recorded in the change streams with the DDL option `allow_txn_exclusion` being false or
728
+ unset.
729
+
730
+ :rtype: int
731
+ :returns: Count of rows affected by the DML statement.
732
+ """
733
+ query_options = _merge_query_options(
734
+ self._instance._client._query_options, query_options
735
+ )
736
+ if request_options is None:
737
+ request_options = RequestOptions()
738
+ elif type(request_options) is dict:
739
+ request_options = RequestOptions(request_options)
740
+ request_options.transaction_tag = None
741
+
742
+ if params is not None:
743
+ from google.cloud.spanner_v1.transaction import Transaction
744
+
745
+ params_pb = Transaction._make_params_pb(params, param_types)
746
+ else:
747
+ params_pb = {}
748
+
749
+ api = self.spanner_api
750
+
751
+ txn_options = TransactionOptions(
752
+ partitioned_dml=TransactionOptions.PartitionedDml(),
753
+ exclude_txn_from_change_streams=exclude_txn_from_change_streams,
754
+ )
755
+
756
+ metadata = _metadata_with_prefix(self.name)
757
+ if self._route_to_leader_enabled:
758
+ metadata.append(
759
+ _metadata_with_leader_aware_routing(self._route_to_leader_enabled)
760
+ )
761
+
762
+ def execute_pdml():
763
+ with trace_call(
764
+ "CloudSpanner.Database.execute_partitioned_pdml",
765
+ observability_options=self.observability_options,
766
+ ) as span, MetricsCapture():
767
+ from google.cloud.spanner_v1.session_options import TransactionType
768
+
769
+ session = self._sessions_manager.get_session(
770
+ TransactionType.PARTITIONED
771
+ )
772
+ try:
773
+ add_span_event(span, "Starting BeginTransaction")
774
+ txn = api.begin_transaction(
775
+ session=session.name,
776
+ options=txn_options,
777
+ metadata=self.metadata_with_request_id(
778
+ self._next_nth_request,
779
+ 1,
780
+ metadata,
781
+ span,
782
+ ),
783
+ )
784
+
785
+ txn_selector = TransactionSelector(id=txn.id)
786
+
787
+ request = ExecuteSqlRequest(
788
+ session=session.name,
789
+ sql=dml,
790
+ params=params_pb,
791
+ param_types=param_types,
792
+ query_options=query_options,
793
+ request_options=request_options,
794
+ )
795
+
796
+ method = functools.partial(
797
+ api.execute_streaming_sql,
798
+ metadata=metadata,
799
+ )
800
+
801
+ iterator = _restart_on_unavailable(
802
+ method=method,
803
+ trace_name="CloudSpanner.ExecuteStreamingSql",
804
+ request=request,
805
+ metadata=metadata,
806
+ transaction_selector=txn_selector,
807
+ observability_options=self.observability_options,
808
+ request_id_manager=self,
809
+ )
810
+
811
+ result_set = StreamedResultSet(iterator)
812
+ list(result_set) # consume all partials
813
+
814
+ return result_set.stats.row_count_lower_bound
815
+ finally:
816
+ self._sessions_manager.put_session(session)
817
+
818
+ return _retry_on_aborted(execute_pdml, DEFAULT_RETRY_BACKOFF)()
819
+
820
+ @property
821
+ def _next_nth_request(self):
822
+ if self._instance and self._instance._client:
823
+ return self._instance._client._next_nth_request
824
+ return 1
825
+
826
+ @property
827
+ def _nth_client_id(self):
828
+ if self._instance and self._instance._client:
829
+ return self._instance._client._nth_client_id
830
+ return 0
831
+
832
+ def session(self, labels=None, database_role=None):
833
+ """Factory to create a session for this database.
834
+
835
+ :type labels: dict (str -> str) or None
836
+ :param labels: (Optional) user-assigned labels for the session.
837
+
838
+ :type database_role: str
839
+ :param database_role: (Optional) user-assigned database_role for the session.
840
+
841
+ :rtype: :class:`~google.cloud.spanner_v1.session.Session`
842
+ :returns: a session bound to this database.
843
+ """
844
+ # If role is specified in param, then that role is used
845
+ # instead.
846
+ role = database_role or self._database_role
847
+ return Session(self, labels=labels, database_role=role)
848
+
849
+ def snapshot(self, **kw):
850
+ """Return an object which wraps a snapshot.
851
+
852
+ The wrapper *must* be used as a context manager, with the snapshot
853
+ as the value returned by the wrapper.
854
+
855
+ See
856
+ https://cloud.google.com/spanner/reference/rpc/google.spanner.v1#google.spanner.v1.TransactionOptions.ReadOnly
857
+
858
+ :type kw: dict
859
+ :param kw:
860
+ Passed through to
861
+ :class:`~google.cloud.spanner_v1.snapshot.Snapshot` constructor.
862
+
863
+ :rtype: :class:`~google.cloud.spanner_v1.database.SnapshotCheckout`
864
+ :returns: new wrapper
865
+ """
866
+ return SnapshotCheckout(self, **kw)
867
+
868
+ def batch(
869
+ self,
870
+ request_options=None,
871
+ max_commit_delay=None,
872
+ exclude_txn_from_change_streams=False,
873
+ isolation_level=TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED,
874
+ **kw,
875
+ ):
876
+ """Return an object which wraps a batch.
877
+
878
+ The wrapper *must* be used as a context manager, with the batch
879
+ as the value returned by the wrapper.
880
+
881
+ :type request_options:
882
+ :class:`google.cloud.spanner_v1.types.RequestOptions`
883
+ :param request_options:
884
+ (Optional) Common options for the commit request.
885
+ If a dict is provided, it must be of the same form as the protobuf
886
+ message :class:`~google.cloud.spanner_v1.types.RequestOptions`.
887
+
888
+ :type max_commit_delay: :class:`datetime.timedelta`
889
+ :param max_commit_delay:
890
+ (Optional) The amount of latency this request is willing to incur
891
+ in order to improve throughput. Value must be between 0ms and
892
+ 500ms.
893
+
894
+ :type exclude_txn_from_change_streams: bool
895
+ :param exclude_txn_from_change_streams:
896
+ (Optional) If true, instructs the transaction to be excluded from being recorded in change streams
897
+ with the DDL option `allow_txn_exclusion=true`. This does not exclude the transaction from
898
+ being recorded in the change streams with the DDL option `allow_txn_exclusion` being false or
899
+ unset.
900
+
901
+ :type isolation_level:
902
+ :class:`google.cloud.spanner_v1.types.TransactionOptions.IsolationLevel`
903
+ :param isolation_level:
904
+ (Optional) Sets the isolation level for this transaction. This overrides any default isolation level set for the client.
905
+
906
+ :rtype: :class:`~google.cloud.spanner_v1.database.BatchCheckout`
907
+ :returns: new wrapper
908
+ """
909
+
910
+ return BatchCheckout(
911
+ self,
912
+ request_options,
913
+ max_commit_delay,
914
+ exclude_txn_from_change_streams,
915
+ isolation_level,
916
+ **kw,
917
+ )
918
+
919
+ def mutation_groups(self):
920
+ """Return an object which wraps a mutation_group.
921
+
922
+ The wrapper *must* be used as a context manager, with the mutation group
923
+ as the value returned by the wrapper.
924
+
925
+ :rtype: :class:`~google.cloud.spanner_v1.database.MutationGroupsCheckout`
926
+ :returns: new wrapper
927
+ """
928
+ return MutationGroupsCheckout(self)
929
+
930
+ def batch_snapshot(
931
+ self,
932
+ read_timestamp=None,
933
+ exact_staleness=None,
934
+ session_id=None,
935
+ transaction_id=None,
936
+ ):
937
+ """Return an object which wraps a batch read / query.
938
+
939
+ :type read_timestamp: :class:`datetime.datetime`
940
+ :param read_timestamp: Execute all reads at the given timestamp.
941
+
942
+ :type exact_staleness: :class:`datetime.timedelta`
943
+ :param exact_staleness: Execute all reads at a timestamp that is
944
+ ``exact_staleness`` old.
945
+
946
+ :type session_id: str
947
+ :param session_id: id of the session used in transaction
948
+
949
+ :type transaction_id: str
950
+ :param transaction_id: id of the transaction
951
+
952
+ :rtype: :class:`~google.cloud.spanner_v1.database.BatchSnapshot`
953
+ :returns: new wrapper
954
+ """
955
+ return BatchSnapshot(
956
+ self,
957
+ read_timestamp=read_timestamp,
958
+ exact_staleness=exact_staleness,
959
+ session_id=session_id,
960
+ transaction_id=transaction_id,
961
+ )
962
+
963
+ def run_in_transaction(self, func, *args, **kw):
964
+ """Perform a unit of work in a transaction, retrying on abort.
965
+
966
+ :type func: callable
967
+ :param func: takes a required positional argument, the transaction,
968
+ and additional positional / keyword arguments as supplied
969
+ by the caller.
970
+
971
+ :type args: tuple
972
+ :param args: additional positional arguments to be passed to ``func``.
973
+
974
+ :type kw: dict
975
+ :param kw: (Optional) keyword arguments to be passed to ``func``.
976
+ If passed,
977
+ "timeout_secs" will be removed and used to
978
+ override the default retry timeout which defines maximum timestamp
979
+ to continue retrying the transaction.
980
+ "max_commit_delay" will be removed and used to set the
981
+ max_commit_delay for the request. Value must be between
982
+ 0ms and 500ms.
983
+ "exclude_txn_from_change_streams" if true, instructs the transaction to be excluded
984
+ from being recorded in change streams with the DDL option `allow_txn_exclusion=true`.
985
+ This does not exclude the transaction from being recorded in the change streams with
986
+ the DDL option `allow_txn_exclusion` being false or unset.
987
+ "isolation_level" sets the isolation level for the transaction.
988
+
989
+ :rtype: Any
990
+ :returns: The return value of ``func``.
991
+
992
+ :raises Exception:
993
+ reraises any non-ABORT exceptions raised by ``func``.
994
+ """
995
+ observability_options = getattr(self, "observability_options", None)
996
+ with trace_call(
997
+ "CloudSpanner.Database.run_in_transaction",
998
+ observability_options=observability_options,
999
+ ), MetricsCapture():
1000
+ # Sanity check: Is there a transaction already running?
1001
+ # If there is, then raise a red flag. Otherwise, mark that this one
1002
+ # is running.
1003
+ if getattr(self._local, "transaction_running", False):
1004
+ raise RuntimeError("Spanner does not support nested transactions.")
1005
+ self._local.transaction_running = True
1006
+
1007
+ # Check out a session and run the function in a transaction; once
1008
+ # done, flip the sanity check bit back.
1009
+ try:
1010
+ with SessionCheckout(self._pool) as session:
1011
+ return session.run_in_transaction(func, *args, **kw)
1012
+ finally:
1013
+ self._local.transaction_running = False
1014
+
1015
+ def restore(self, source):
1016
+ """Restore from a backup to this database.
1017
+
1018
+ :type source: :class:`~google.cloud.spanner_v1.backup.Backup`
1019
+ :param source: the path of the source being restored from.
1020
+
1021
+ :rtype: :class:`~google.api_core.operation.Operation`
1022
+ :returns: a future used to poll the status of the create request
1023
+ :raises Conflict: if the database already exists
1024
+ :raises NotFound:
1025
+ if the instance owning the database does not exist, or
1026
+ if the backup being restored from does not exist
1027
+ :raises ValueError: if backup is not set
1028
+ """
1029
+ if source is None:
1030
+ raise ValueError("Restore source not specified")
1031
+ if type(self._encryption_config) is dict:
1032
+ self._encryption_config = RestoreDatabaseEncryptionConfig(
1033
+ **self._encryption_config
1034
+ )
1035
+ if (
1036
+ self.encryption_config
1037
+ and self.encryption_config.kms_key_name
1038
+ and self.encryption_config.encryption_type
1039
+ != RestoreDatabaseEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION
1040
+ ):
1041
+ raise ValueError("kms_key_name only used with CUSTOMER_MANAGED_ENCRYPTION")
1042
+ api = self._instance._client.database_admin_api
1043
+ metadata = _metadata_with_prefix(self.name)
1044
+ request = RestoreDatabaseRequest(
1045
+ parent=self._instance.name,
1046
+ database_id=self.database_id,
1047
+ backup=source.name,
1048
+ encryption_config=self._encryption_config or None,
1049
+ )
1050
+ future = api.restore_database(
1051
+ request=request,
1052
+ metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
1053
+ )
1054
+ return future
1055
+
1056
+ def is_ready(self):
1057
+ """Test whether this database is ready for use.
1058
+
1059
+ :rtype: bool
1060
+ :returns: True if the database state is READY_OPTIMIZING or READY, else False.
1061
+ """
1062
+ return (
1063
+ self.state == DatabasePB.State.READY_OPTIMIZING
1064
+ or self.state == DatabasePB.State.READY
1065
+ )
1066
+
1067
+ def is_optimized(self):
1068
+ """Test whether this database has finished optimizing.
1069
+
1070
+ :rtype: bool
1071
+ :returns: True if the database state is READY, else False.
1072
+ """
1073
+ return self.state == DatabasePB.State.READY
1074
+
1075
+ def list_database_operations(self, filter_="", page_size=None):
1076
+ """List database operations for the database.
1077
+
1078
+ :type filter_: str
1079
+ :param filter_:
1080
+ Optional. A string specifying a filter for which database operations to list.
1081
+
1082
+ :type page_size: int
1083
+ :param page_size:
1084
+ Optional. The maximum number of operations in each page of results from this
1085
+ request. Non-positive values are ignored. Defaults to a sensible value set
1086
+ by the API.
1087
+
1088
+ :type: :class:`~google.api_core.page_iterator.Iterator`
1089
+ :returns:
1090
+ Iterator of :class:`~google.api_core.operation.Operation`
1091
+ resources within the current instance.
1092
+ """
1093
+ database_filter = _DATABASE_METADATA_FILTER.format(self.name)
1094
+ if filter_:
1095
+ database_filter = "({0}) AND ({1})".format(filter_, database_filter)
1096
+ return self._instance.list_database_operations(
1097
+ filter_=database_filter, page_size=page_size
1098
+ )
1099
+
1100
+ def list_database_roles(self, page_size=None):
1101
+ """Lists Cloud Spanner database roles.
1102
+
1103
+ :type page_size: int
1104
+ :param page_size:
1105
+ Optional. The maximum number of database roles in each page of results
1106
+ from this request. Non-positive values are ignored. Defaults to a
1107
+ sensible value set by the API.
1108
+
1109
+ :type: Iterable
1110
+ :returns:
1111
+ Iterable of :class:`~google.cloud.spanner_admin_database_v1.types.spanner_database_admin.DatabaseRole`
1112
+ resources within the current database.
1113
+ """
1114
+ api = self._instance._client.database_admin_api
1115
+ metadata = _metadata_with_prefix(self.name)
1116
+
1117
+ request = ListDatabaseRolesRequest(
1118
+ parent=self.name,
1119
+ page_size=page_size,
1120
+ )
1121
+ return api.list_database_roles(
1122
+ request=request,
1123
+ metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
1124
+ )
1125
+
1126
+ def table(self, table_id):
1127
+ """Factory to create a table object within this database.
1128
+
1129
+ Note: This method does not create a table in Cloud Spanner, but it can
1130
+ be used to check if a table exists.
1131
+
1132
+ .. code-block:: python
1133
+
1134
+ my_table = database.table("my_table")
1135
+ if my_table.exists():
1136
+ print("Table with ID 'my_table' exists.")
1137
+ else:
1138
+ print("Table with ID 'my_table' does not exist.")
1139
+
1140
+ :type table_id: str
1141
+ :param table_id: The ID of the table.
1142
+
1143
+ :rtype: :class:`~google.cloud.spanner_v1.table.Table`
1144
+ :returns: a table owned by this database.
1145
+ """
1146
+ return Table(table_id, self)
1147
+
1148
+ def list_tables(self, schema="_default"):
1149
+ """List tables within the database.
1150
+
1151
+ :type schema: str
1152
+ :param schema: The schema to search for tables, or None for all schemas. Use the special string "_default" to
1153
+ search for tables in the default schema of the database.
1154
+
1155
+ :type: Iterable
1156
+ :returns:
1157
+ Iterable of :class:`~google.cloud.spanner_v1.table.Table`
1158
+ resources within the current database.
1159
+ """
1160
+ if "_default" == schema:
1161
+ schema = self.default_schema_name
1162
+
1163
+ with self.snapshot() as snapshot:
1164
+ if schema is None:
1165
+ results = snapshot.execute_sql(
1166
+ sql=_LIST_TABLES_QUERY.format(""),
1167
+ )
1168
+ else:
1169
+ if self._database_dialect == DatabaseDialect.POSTGRESQL:
1170
+ where_clause = "WHERE TABLE_SCHEMA = $1"
1171
+ param_name = "p1"
1172
+ else:
1173
+ where_clause = (
1174
+ "WHERE TABLE_SCHEMA = @schema AND SPANNER_STATE = 'COMMITTED'"
1175
+ )
1176
+ param_name = "schema"
1177
+ results = snapshot.execute_sql(
1178
+ sql=_LIST_TABLES_QUERY.format(where_clause),
1179
+ params={param_name: schema},
1180
+ param_types={param_name: Type(code=TypeCode.STRING)},
1181
+ )
1182
+ for row in results:
1183
+ yield self.table(row[0])
1184
+
1185
+ def get_iam_policy(self, policy_version=None):
1186
+ """Gets the access control policy for a database resource.
1187
+
1188
+ :type policy_version: int
1189
+ :param policy_version:
1190
+ (Optional) the maximum policy version that will be
1191
+ used to format the policy. Valid values are 0, 1 ,3.
1192
+
1193
+ :rtype: :class:`~google.iam.v1.policy_pb2.Policy`
1194
+ :returns:
1195
+ returns an Identity and Access Management (IAM) policy. It is used to
1196
+ specify access control policies for Cloud Platform
1197
+ resources.
1198
+ """
1199
+ api = self._instance._client.database_admin_api
1200
+ metadata = _metadata_with_prefix(self.name)
1201
+
1202
+ request = iam_policy_pb2.GetIamPolicyRequest(
1203
+ resource=self.name,
1204
+ options=options_pb2.GetPolicyOptions(
1205
+ requested_policy_version=policy_version
1206
+ ),
1207
+ )
1208
+ response = api.get_iam_policy(
1209
+ request=request,
1210
+ metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
1211
+ )
1212
+ return response
1213
+
1214
+ def set_iam_policy(self, policy):
1215
+ """Sets the access control policy on a database resource.
1216
+ Replaces any existing policy.
1217
+
1218
+ :type policy: :class:`~google.iam.v1.policy_pb2.Policy`
1219
+ :param policy_version:
1220
+ the complete policy to be applied to the resource.
1221
+
1222
+ :rtype: :class:`~google.iam.v1.policy_pb2.Policy`
1223
+ :returns:
1224
+ returns the new Identity and Access Management (IAM) policy.
1225
+ """
1226
+ api = self._instance._client.database_admin_api
1227
+ metadata = _metadata_with_prefix(self.name)
1228
+
1229
+ request = iam_policy_pb2.SetIamPolicyRequest(
1230
+ resource=self.name,
1231
+ policy=policy,
1232
+ )
1233
+ response = api.set_iam_policy(
1234
+ request=request,
1235
+ metadata=self.metadata_with_request_id(self._next_nth_request, 1, metadata),
1236
+ )
1237
+ return response
1238
+
1239
+ @property
1240
+ def observability_options(self):
1241
+ """
1242
+ Returns the observability options that you set when creating
1243
+ the SpannerClient.
1244
+ """
1245
+ if not (self._instance and self._instance._client):
1246
+ return None
1247
+
1248
+ opts = getattr(self._instance._client, "observability_options", None)
1249
+ if not opts:
1250
+ opts = dict()
1251
+
1252
+ opts["db_name"] = self.name
1253
+ return opts
1254
+
1255
+ @property
1256
+ def sessions_manager(self):
1257
+ """Returns the database sessions manager.
1258
+
1259
+ :rtype: :class:`~google.cloud.spanner_v1.database_sessions_manager.DatabaseSessionsManager`
1260
+ :returns: The sessions manager for this database.
1261
+ """
1262
+ return self._sessions_manager
1263
+
1264
+
1265
+ class BatchCheckout(object):
1266
+ """Context manager for using a batch from a database.
1267
+
1268
+ Inside the context manager, checks out a session from the database,
1269
+ creates a batch from it, making the batch available.
1270
+
1271
+ Caller must *not* use the batch to perform API requests outside the scope
1272
+ of the context manager.
1273
+
1274
+ :type database: :class:`~google.cloud.spanner_v1.database.Database`
1275
+ :param database: database to use
1276
+
1277
+ :type request_options:
1278
+ :class:`google.cloud.spanner_v1.types.RequestOptions`
1279
+ :param request_options:
1280
+ (Optional) Common options for the commit request.
1281
+ If a dict is provided, it must be of the same form as the protobuf
1282
+ message :class:`~google.cloud.spanner_v1.types.RequestOptions`.
1283
+
1284
+ :type max_commit_delay: :class:`datetime.timedelta`
1285
+ :param max_commit_delay:
1286
+ (Optional) The amount of latency this request is willing to incur
1287
+ in order to improve throughput.
1288
+ """
1289
+
1290
+ def __init__(
1291
+ self,
1292
+ database,
1293
+ request_options=None,
1294
+ max_commit_delay=None,
1295
+ exclude_txn_from_change_streams=False,
1296
+ isolation_level=TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED,
1297
+ **kw,
1298
+ ):
1299
+ self._database = database
1300
+ self._session = self._batch = None
1301
+ if request_options is None:
1302
+ self._request_options = RequestOptions()
1303
+ elif type(request_options) is dict:
1304
+ self._request_options = RequestOptions(request_options)
1305
+ else:
1306
+ self._request_options = request_options
1307
+ self._max_commit_delay = max_commit_delay
1308
+ self._exclude_txn_from_change_streams = exclude_txn_from_change_streams
1309
+ self._isolation_level = isolation_level
1310
+ self._kw = kw
1311
+
1312
+ def __enter__(self):
1313
+ """Begin ``with`` block."""
1314
+ from google.cloud.spanner_v1.session_options import TransactionType
1315
+
1316
+ current_span = get_current_span()
1317
+ session = self._session = self._database.sessions_manager.get_session(
1318
+ TransactionType.READ_WRITE
1319
+ )
1320
+ add_span_event(current_span, "Using session", {"id": session.session_id})
1321
+ batch = self._batch = Batch(session)
1322
+ if self._request_options.transaction_tag:
1323
+ batch.transaction_tag = self._request_options.transaction_tag
1324
+ return batch
1325
+
1326
+ def __exit__(self, exc_type, exc_val, exc_tb):
1327
+ """End ``with`` block."""
1328
+ try:
1329
+ if exc_type is None:
1330
+ self._batch.commit(
1331
+ return_commit_stats=self._database.log_commit_stats,
1332
+ request_options=self._request_options,
1333
+ max_commit_delay=self._max_commit_delay,
1334
+ exclude_txn_from_change_streams=self._exclude_txn_from_change_streams,
1335
+ isolation_level=self._isolation_level,
1336
+ **self._kw,
1337
+ )
1338
+ finally:
1339
+ if self._database.log_commit_stats and self._batch.commit_stats:
1340
+ self._database.logger.info(
1341
+ "CommitStats: {}".format(self._batch.commit_stats),
1342
+ extra={"commit_stats": self._batch.commit_stats},
1343
+ )
1344
+ self._database.sessions_manager.put_session(self._session)
1345
+ current_span = get_current_span()
1346
+ add_span_event(
1347
+ current_span,
1348
+ "Returned session to pool",
1349
+ {"id": self._session.session_id},
1350
+ )
1351
+
1352
+
1353
+ class MutationGroupsCheckout(object):
1354
+ """Context manager for using mutation groups from a database.
1355
+
1356
+ Inside the context manager, checks out a session from the database,
1357
+ creates mutation groups from it, making the groups available.
1358
+
1359
+ Caller must *not* use the object to perform API requests outside the scope
1360
+ of the context manager.
1361
+
1362
+ :type database: :class:`~google.cloud.spanner_v1.database.Database`
1363
+ :param database: database to use
1364
+ """
1365
+
1366
+ def __init__(self, database):
1367
+ self._database = database
1368
+ self._session = None
1369
+
1370
+ def __enter__(self):
1371
+ """Begin ``with`` block."""
1372
+ from google.cloud.spanner_v1.session_options import TransactionType
1373
+
1374
+ session = self._session = self._database.sessions_manager.get_session(
1375
+ TransactionType.READ_WRITE
1376
+ )
1377
+ return MutationGroups(session)
1378
+
1379
+ def __exit__(self, exc_type, exc_val, exc_tb):
1380
+ """End ``with`` block."""
1381
+ if isinstance(exc_val, NotFound):
1382
+ # If NotFound exception occurs inside the with block
1383
+ # then we validate if the session still exists.
1384
+ if not self._session.exists():
1385
+ self._session = self._database._pool._new_session()
1386
+ self._session.create()
1387
+ self._database.sessions_manager.put_session(self._session)
1388
+
1389
+
1390
+ class SnapshotCheckout(object):
1391
+ """Context manager for using a snapshot from a database.
1392
+
1393
+ Inside the context manager, checks out a session from the database,
1394
+ creates a snapshot from it, making the snapshot available.
1395
+
1396
+ Caller must *not* use the snapshot to perform API requests outside the
1397
+ scope of the context manager.
1398
+
1399
+ :type database: :class:`~google.cloud.spanner_v1.database.Database`
1400
+ :param database: database to use
1401
+
1402
+ :type kw: dict
1403
+ :param kw:
1404
+ Passed through to
1405
+ :class:`~google.cloud.spanner_v1.snapshot.Snapshot` constructor.
1406
+ """
1407
+
1408
+ def __init__(self, database, **kw):
1409
+ self._database = database
1410
+ self._session = None
1411
+ self._kw = kw
1412
+
1413
+ def __enter__(self):
1414
+ """Begin ``with`` block."""
1415
+ from google.cloud.spanner_v1.session_options import TransactionType
1416
+
1417
+ session = self._session = self._database.sessions_manager.get_session(
1418
+ TransactionType.READ_ONLY
1419
+ )
1420
+ return Snapshot(session, **self._kw)
1421
+
1422
+ def __exit__(self, exc_type, exc_val, exc_tb):
1423
+ """End ``with`` block."""
1424
+ if isinstance(exc_val, NotFound):
1425
+ # If NotFound exception occurs inside the with block
1426
+ # then we validate if the session still exists.
1427
+ if not self._session.exists():
1428
+ self._session = self._database._pool._new_session()
1429
+ self._session.create()
1430
+ self._database.sessions_manager.put_session(self._session)
1431
+
1432
+
1433
+ class BatchSnapshot(object):
1434
+ """Wrapper for generating and processing read / query batches.
1435
+
1436
+ :type database: :class:`~google.cloud.spanner_v1.database.Database`
1437
+ :param database: database to use
1438
+
1439
+ :type read_timestamp: :class:`datetime.datetime`
1440
+ :param read_timestamp: Execute all reads at the given timestamp.
1441
+
1442
+ :type exact_staleness: :class:`datetime.timedelta`
1443
+ :param exact_staleness: Execute all reads at a timestamp that is
1444
+ ``exact_staleness`` old.
1445
+ """
1446
+
1447
+ def __init__(
1448
+ self,
1449
+ database,
1450
+ read_timestamp=None,
1451
+ exact_staleness=None,
1452
+ session_id=None,
1453
+ transaction_id=None,
1454
+ ):
1455
+ self._database = database
1456
+ self._session_id = session_id
1457
+ self._session = None
1458
+ self._snapshot = None
1459
+ self._transaction_id = transaction_id
1460
+ self._read_timestamp = read_timestamp
1461
+ self._exact_staleness = exact_staleness
1462
+
1463
+ @classmethod
1464
+ def from_dict(cls, database, mapping):
1465
+ """Reconstruct an instance from a mapping.
1466
+
1467
+ :type database: :class:`~google.cloud.spanner_v1.database.Database`
1468
+ :param database: database to use
1469
+
1470
+ :type mapping: mapping
1471
+ :param mapping: serialized state of the instance
1472
+
1473
+ :rtype: :class:`BatchSnapshot`
1474
+ """
1475
+ instance = cls(database)
1476
+ session = instance._session = database.session()
1477
+ session._session_id = mapping["session_id"]
1478
+ snapshot = instance._snapshot = session.snapshot()
1479
+ snapshot._transaction_id = mapping["transaction_id"]
1480
+ return instance
1481
+
1482
+ def to_dict(self):
1483
+ """Return state as a dictionary.
1484
+
1485
+ Result can be used to serialize the instance and reconstitute
1486
+ it later using :meth:`from_dict`.
1487
+
1488
+ :rtype: dict
1489
+ """
1490
+ session = self._get_session()
1491
+ snapshot = self._get_snapshot()
1492
+ return {
1493
+ "session_id": session._session_id,
1494
+ "transaction_id": snapshot._transaction_id,
1495
+ }
1496
+
1497
+ @property
1498
+ def observability_options(self):
1499
+ return getattr(self._database, "observability_options", {})
1500
+
1501
+ def _get_session(self):
1502
+ """Create session as needed.
1503
+
1504
+ .. note::
1505
+
1506
+ Caller is responsible for cleaning up the session after
1507
+ all partitions have been processed.
1508
+ """
1509
+ if self._session is None:
1510
+ from google.cloud.spanner_v1.session_options import TransactionType
1511
+
1512
+ # Use sessions manager for partition operations
1513
+ session = self._session = self._database.sessions_manager.get_session(
1514
+ TransactionType.PARTITIONED
1515
+ )
1516
+ if self._session_id is not None:
1517
+ session._session_id = self._session_id
1518
+ return self._session
1519
+
1520
+ def _get_snapshot(self):
1521
+ """Create snapshot if needed."""
1522
+ if self._snapshot is None:
1523
+ self._snapshot = self._get_session().snapshot(
1524
+ read_timestamp=self._read_timestamp,
1525
+ exact_staleness=self._exact_staleness,
1526
+ multi_use=True,
1527
+ transaction_id=self._transaction_id,
1528
+ )
1529
+ if self._transaction_id is None:
1530
+ self._snapshot.begin()
1531
+ return self._snapshot
1532
+
1533
+ def get_batch_transaction_id(self):
1534
+ snapshot = self._snapshot
1535
+ if snapshot is None:
1536
+ raise ValueError("Read-only transaction not begun")
1537
+ return BatchTransactionId(
1538
+ snapshot._transaction_id,
1539
+ snapshot._session.session_id,
1540
+ snapshot._read_timestamp,
1541
+ )
1542
+
1543
+ def read(self, *args, **kw):
1544
+ """Convenience method: perform read operation via snapshot.
1545
+
1546
+ See :meth:`~google.cloud.spanner_v1.snapshot.Snapshot.read`.
1547
+ """
1548
+ return self._get_snapshot().read(*args, **kw)
1549
+
1550
+ def execute_sql(self, *args, **kw):
1551
+ """Convenience method: perform query operation via snapshot.
1552
+
1553
+ See :meth:`~google.cloud.spanner_v1.snapshot.Snapshot.execute_sql`.
1554
+ """
1555
+ return self._get_snapshot().execute_sql(*args, **kw)
1556
+
1557
+ def generate_read_batches(
1558
+ self,
1559
+ table,
1560
+ columns,
1561
+ keyset,
1562
+ index="",
1563
+ partition_size_bytes=None,
1564
+ max_partitions=None,
1565
+ data_boost_enabled=False,
1566
+ directed_read_options=None,
1567
+ *,
1568
+ retry=gapic_v1.method.DEFAULT,
1569
+ timeout=gapic_v1.method.DEFAULT,
1570
+ ):
1571
+ """Start a partitioned batch read operation.
1572
+
1573
+ Uses the ``PartitionRead`` API request to initiate the partitioned
1574
+ read. Returns a list of batch information needed to perform the
1575
+ actual reads.
1576
+
1577
+ :type table: str
1578
+ :param table: name of the table from which to fetch data
1579
+
1580
+ :type columns: list of str
1581
+ :param columns: names of columns to be retrieved
1582
+
1583
+ :type keyset: :class:`~google.cloud.spanner_v1.keyset.KeySet`
1584
+ :param keyset: keys / ranges identifying rows to be retrieved
1585
+
1586
+ :type index: str
1587
+ :param index: (Optional) name of index to use, rather than the
1588
+ table's primary key
1589
+
1590
+ :type partition_size_bytes: int
1591
+ :param partition_size_bytes:
1592
+ (Optional) desired size for each partition generated. The service
1593
+ uses this as a hint, the actual partition size may differ.
1594
+
1595
+ :type max_partitions: int
1596
+ :param max_partitions:
1597
+ (Optional) desired maximum number of partitions generated. The
1598
+ service uses this as a hint, the actual number of partitions may
1599
+ differ.
1600
+
1601
+ :type data_boost_enabled:
1602
+ :param data_boost_enabled:
1603
+ (Optional) If this is for a partitioned read and this field is
1604
+ set ``true``, the request will be executed via offline access.
1605
+
1606
+ :type directed_read_options: :class:`~google.cloud.spanner_v1.DirectedReadOptions`
1607
+ or :class:`dict`
1608
+ :param directed_read_options: (Optional) Request level option used to set the directed_read_options
1609
+ for ReadRequests that indicates which replicas
1610
+ or regions should be used for non-transactional reads.
1611
+
1612
+ :type retry: :class:`~google.api_core.retry.Retry`
1613
+ :param retry: (Optional) The retry settings for this request.
1614
+
1615
+ :type timeout: float
1616
+ :param timeout: (Optional) The timeout for this request.
1617
+
1618
+ :rtype: iterable of dict
1619
+ :returns:
1620
+ mappings of information used perform actual partitioned reads via
1621
+ :meth:`process_read_batch`.
1622
+ """
1623
+ with trace_call(
1624
+ f"CloudSpanner.{type(self).__name__}.generate_read_batches",
1625
+ extra_attributes=dict(table=table, columns=columns),
1626
+ observability_options=self.observability_options,
1627
+ ), MetricsCapture():
1628
+ partitions = self._get_snapshot().partition_read(
1629
+ table=table,
1630
+ columns=columns,
1631
+ keyset=keyset,
1632
+ index=index,
1633
+ partition_size_bytes=partition_size_bytes,
1634
+ max_partitions=max_partitions,
1635
+ retry=retry,
1636
+ timeout=timeout,
1637
+ )
1638
+
1639
+ read_info = {
1640
+ "table": table,
1641
+ "columns": columns,
1642
+ "keyset": keyset._to_dict(),
1643
+ "index": index,
1644
+ "data_boost_enabled": data_boost_enabled,
1645
+ "directed_read_options": directed_read_options,
1646
+ }
1647
+ for partition in partitions:
1648
+ yield {"partition": partition, "read": read_info.copy()}
1649
+
1650
+ def process_read_batch(
1651
+ self,
1652
+ batch,
1653
+ *,
1654
+ retry=gapic_v1.method.DEFAULT,
1655
+ timeout=gapic_v1.method.DEFAULT,
1656
+ ):
1657
+ """Process a single, partitioned read.
1658
+
1659
+ :type batch: mapping
1660
+ :param batch:
1661
+ one of the mappings returned from an earlier call to
1662
+ :meth:`generate_read_batches`.
1663
+
1664
+ :type retry: :class:`~google.api_core.retry.Retry`
1665
+ :param retry: (Optional) The retry settings for this request.
1666
+
1667
+ :type timeout: float
1668
+ :param timeout: (Optional) The timeout for this request.
1669
+
1670
+
1671
+ :rtype: :class:`~google.cloud.spanner_v1.streamed.StreamedResultSet`
1672
+ :returns: a result set instance which can be used to consume rows.
1673
+ """
1674
+ observability_options = self.observability_options
1675
+ with trace_call(
1676
+ f"CloudSpanner.{type(self).__name__}.process_read_batch",
1677
+ observability_options=observability_options,
1678
+ ), MetricsCapture():
1679
+ kwargs = copy.deepcopy(batch["read"])
1680
+ keyset_dict = kwargs.pop("keyset")
1681
+ kwargs["keyset"] = KeySet._from_dict(keyset_dict)
1682
+ return self._get_snapshot().read(
1683
+ partition=batch["partition"], **kwargs, retry=retry, timeout=timeout
1684
+ )
1685
+
1686
+ def generate_query_batches(
1687
+ self,
1688
+ sql,
1689
+ params=None,
1690
+ param_types=None,
1691
+ partition_size_bytes=None,
1692
+ max_partitions=None,
1693
+ query_options=None,
1694
+ data_boost_enabled=False,
1695
+ directed_read_options=None,
1696
+ *,
1697
+ retry=gapic_v1.method.DEFAULT,
1698
+ timeout=gapic_v1.method.DEFAULT,
1699
+ ):
1700
+ """Start a partitioned query operation.
1701
+
1702
+ Uses the ``PartitionQuery`` API request to start a partitioned
1703
+ query operation. Returns a list of batch information needed to
1704
+ perform the actual queries.
1705
+
1706
+ :type sql: str
1707
+ :param sql: SQL query statement
1708
+
1709
+ :type params: dict, {str -> column value}
1710
+ :param params: values for parameter replacement. Keys must match
1711
+ the names used in ``sql``.
1712
+
1713
+ :type param_types: dict[str -> Union[dict, .types.Type]]
1714
+ :param param_types:
1715
+ (Optional) maps explicit types for one or more param values;
1716
+ required if parameters are passed.
1717
+
1718
+ :type partition_size_bytes: int
1719
+ :param partition_size_bytes:
1720
+ (Optional) desired size for each partition generated. The service
1721
+ uses this as a hint, the actual partition size may differ.
1722
+
1723
+ :type max_partitions: int
1724
+ :param max_partitions:
1725
+ (Optional) desired maximum number of partitions generated. The
1726
+ service uses this as a hint, the actual number of partitions may
1727
+ differ.
1728
+
1729
+ :type query_options:
1730
+ :class:`~google.cloud.spanner_v1.types.ExecuteSqlRequest.QueryOptions`
1731
+ or :class:`dict`
1732
+ :param query_options:
1733
+ (Optional) Query optimizer configuration to use for the given query.
1734
+ If a dict is provided, it must be of the same form as the protobuf
1735
+ message :class:`~google.cloud.spanner_v1.types.QueryOptions`
1736
+
1737
+ :type data_boost_enabled:
1738
+ :param data_boost_enabled:
1739
+ (Optional) If this is for a partitioned query and this field is
1740
+ set ``true``, the request will be executed via offline access.
1741
+
1742
+ :type directed_read_options: :class:`~google.cloud.spanner_v1.DirectedReadOptions`
1743
+ or :class:`dict`
1744
+ :param directed_read_options: (Optional) Request level option used to set the directed_read_options
1745
+ for ExecuteSqlRequests that indicates which replicas
1746
+ or regions should be used for non-transactional queries.
1747
+
1748
+ :type retry: :class:`~google.api_core.retry.Retry`
1749
+ :param retry: (Optional) The retry settings for this request.
1750
+
1751
+ :type timeout: float
1752
+ :param timeout: (Optional) The timeout for this request.
1753
+
1754
+ :rtype: iterable of dict
1755
+ :returns:
1756
+ mappings of information used perform actual partitioned reads via
1757
+ :meth:`process_read_batch`.
1758
+ """
1759
+ with trace_call(
1760
+ f"CloudSpanner.{type(self).__name__}.generate_query_batches",
1761
+ extra_attributes=dict(sql=sql),
1762
+ observability_options=self.observability_options,
1763
+ ), MetricsCapture():
1764
+ partitions = self._get_snapshot().partition_query(
1765
+ sql=sql,
1766
+ params=params,
1767
+ param_types=param_types,
1768
+ partition_size_bytes=partition_size_bytes,
1769
+ max_partitions=max_partitions,
1770
+ retry=retry,
1771
+ timeout=timeout,
1772
+ )
1773
+
1774
+ query_info = {
1775
+ "sql": sql,
1776
+ "data_boost_enabled": data_boost_enabled,
1777
+ "directed_read_options": directed_read_options,
1778
+ }
1779
+ if params:
1780
+ query_info["params"] = params
1781
+ query_info["param_types"] = param_types
1782
+
1783
+ # Query-level options have higher precedence than client-level and
1784
+ # environment-level options
1785
+ default_query_options = self._database._instance._client._query_options
1786
+ query_info["query_options"] = _merge_query_options(
1787
+ default_query_options, query_options
1788
+ )
1789
+
1790
+ for partition in partitions:
1791
+ yield {"partition": partition, "query": query_info}
1792
+
1793
+ def process_query_batch(
1794
+ self,
1795
+ batch,
1796
+ *,
1797
+ retry=gapic_v1.method.DEFAULT,
1798
+ timeout=gapic_v1.method.DEFAULT,
1799
+ ):
1800
+ """Process a single, partitioned query.
1801
+
1802
+ :type batch: mapping
1803
+ :param batch:
1804
+ one of the mappings returned from an earlier call to
1805
+ :meth:`generate_query_batches`.
1806
+
1807
+ :type retry: :class:`~google.api_core.retry.Retry`
1808
+ :param retry: (Optional) The retry settings for this request.
1809
+
1810
+ :type timeout: float
1811
+ :param timeout: (Optional) The timeout for this request.
1812
+
1813
+ :rtype: :class:`~google.cloud.spanner_v1.streamed.StreamedResultSet`
1814
+ :returns: a result set instance which can be used to consume rows.
1815
+ """
1816
+ with trace_call(
1817
+ f"CloudSpanner.{type(self).__name__}.process_query_batch",
1818
+ observability_options=self.observability_options,
1819
+ ), MetricsCapture():
1820
+ return self._get_snapshot().execute_sql(
1821
+ partition=batch["partition"],
1822
+ **batch["query"],
1823
+ retry=retry,
1824
+ timeout=timeout,
1825
+ )
1826
+
1827
+ def run_partitioned_query(
1828
+ self,
1829
+ sql,
1830
+ params=None,
1831
+ param_types=None,
1832
+ partition_size_bytes=None,
1833
+ max_partitions=None,
1834
+ query_options=None,
1835
+ data_boost_enabled=False,
1836
+ ):
1837
+ """Start a partitioned query operation to get list of partitions and
1838
+ then executes each partition on a separate thread
1839
+
1840
+ :type sql: str
1841
+ :param sql: SQL query statement
1842
+
1843
+ :type params: dict, {str -> column value}
1844
+ :param params: values for parameter replacement. Keys must match
1845
+ the names used in ``sql``.
1846
+
1847
+ :type param_types: dict[str -> Union[dict, .types.Type]]
1848
+ :param param_types:
1849
+ (Optional) maps explicit types for one or more param values;
1850
+ required if parameters are passed.
1851
+
1852
+ :type partition_size_bytes: int
1853
+ :param partition_size_bytes:
1854
+ (Optional) desired size for each partition generated. The service
1855
+ uses this as a hint, the actual partition size may differ.
1856
+
1857
+ :type max_partitions: int
1858
+ :param max_partitions:
1859
+ (Optional) desired maximum number of partitions generated. The
1860
+ service uses this as a hint, the actual number of partitions may
1861
+ differ.
1862
+
1863
+ :type query_options:
1864
+ :class:`~google.cloud.spanner_v1.types.ExecuteSqlRequest.QueryOptions`
1865
+ or :class:`dict`
1866
+ :param query_options:
1867
+ (Optional) Query optimizer configuration to use for the given query.
1868
+ If a dict is provided, it must be of the same form as the protobuf
1869
+ message :class:`~google.cloud.spanner_v1.types.QueryOptions`
1870
+
1871
+ :type data_boost_enabled:
1872
+ :param data_boost_enabled:
1873
+ (Optional) If this is for a partitioned query and this field is
1874
+ set ``true``, the request will be executed using data boost.
1875
+ Please see https://cloud.google.com/spanner/docs/databoost/databoost-overview
1876
+
1877
+ :rtype: :class:`~google.cloud.spanner_v1.merged_result_set.MergedResultSet`
1878
+ :returns: a result set instance which can be used to consume rows.
1879
+ """
1880
+ with trace_call(
1881
+ f"CloudSpanner.${type(self).__name__}.run_partitioned_query",
1882
+ extra_attributes=dict(sql=sql),
1883
+ observability_options=self.observability_options,
1884
+ ), MetricsCapture():
1885
+ partitions = list(
1886
+ self.generate_query_batches(
1887
+ sql,
1888
+ params,
1889
+ param_types,
1890
+ partition_size_bytes,
1891
+ max_partitions,
1892
+ query_options,
1893
+ data_boost_enabled,
1894
+ )
1895
+ )
1896
+ return MergedResultSet(self, partitions, 0)
1897
+
1898
+ def process(self, batch):
1899
+ """Process a single, partitioned query or read.
1900
+
1901
+ :type batch: mapping
1902
+ :param batch:
1903
+ one of the mappings returned from an earlier call to
1904
+ :meth:`generate_query_batches`.
1905
+
1906
+ :rtype: :class:`~google.cloud.spanner_v1.streamed.StreamedResultSet`
1907
+ :returns: a result set instance which can be used to consume rows.
1908
+ :raises ValueError: if batch does not contain either 'read' or 'query'
1909
+ """
1910
+ if "query" in batch:
1911
+ return self.process_query_batch(batch)
1912
+ if "read" in batch:
1913
+ return self.process_read_batch(batch)
1914
+ raise ValueError("Invalid batch")
1915
+
1916
+ def close(self):
1917
+ """Clean up underlying session.
1918
+
1919
+ .. note::
1920
+
1921
+ If the transaction has been shared across multiple machines,
1922
+ calling this on any machine would invalidate the transaction
1923
+ everywhere. Ideally this would be called when data has been read
1924
+ from all the partitions.
1925
+ """
1926
+ if self._session is not None:
1927
+ if not self._session.is_multiplexed:
1928
+ self._session.delete()
1929
+
1930
+
1931
+ def _check_ddl_statements(value):
1932
+ """Validate DDL Statements used to define database schema.
1933
+
1934
+ See
1935
+ https://cloud.google.com/spanner/docs/data-definition-language
1936
+
1937
+ :type value: list of string
1938
+ :param value: DDL statements, excluding the 'CREATE DATABASE' statement
1939
+
1940
+ :rtype: tuple
1941
+ :returns: tuple of validated DDL statement strings.
1942
+ :raises ValueError:
1943
+ if elements in ``value`` are not strings, or if ``value`` contains
1944
+ a ``CREATE DATABASE`` statement.
1945
+ """
1946
+ if not all(isinstance(line, str) for line in value):
1947
+ raise ValueError("Pass a list of strings")
1948
+
1949
+ if any("create database" in line.lower() for line in value):
1950
+ raise ValueError("Do not pass a 'CREATE DATABASE' statement")
1951
+
1952
+ return tuple(value)
1953
+
1954
+
1955
+ def _retry_on_aborted(func, retry_config):
1956
+ """Helper for :meth:`Database.execute_partitioned_dml`.
1957
+
1958
+ Wrap function in a Retry that will retry on Aborted exceptions
1959
+ with the retry config specified.
1960
+
1961
+ :type func: callable
1962
+ :param func: the function to be retried on Aborted exceptions
1963
+
1964
+ :type retry_config: Retry
1965
+ :param retry_config: retry object with the settings to be used
1966
+ """
1967
+ retry = retry_config.with_predicate(if_exception_type(Aborted))
1968
+ return retry(func)