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,609 @@
1
+ # Copyright 2020 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
+ """Database cursor for Google Cloud Spanner DB API."""
16
+ from collections import namedtuple
17
+
18
+ import sqlparse
19
+
20
+ from google.api_core.exceptions import Aborted
21
+ from google.api_core.exceptions import AlreadyExists
22
+ from google.api_core.exceptions import FailedPrecondition
23
+ from google.api_core.exceptions import InternalServerError
24
+ from google.api_core.exceptions import InvalidArgument
25
+ from google.api_core.exceptions import OutOfRange
26
+
27
+ from google.cloud import spanner_v1 as spanner
28
+ from google.cloud.spanner_dbapi.batch_dml_executor import BatchMode
29
+ from google.cloud.spanner_dbapi.exceptions import IntegrityError
30
+ from google.cloud.spanner_dbapi.exceptions import InterfaceError
31
+ from google.cloud.spanner_dbapi.exceptions import OperationalError
32
+ from google.cloud.spanner_dbapi.exceptions import ProgrammingError
33
+
34
+ from google.cloud.spanner_dbapi import (
35
+ _helpers,
36
+ client_side_statement_executor,
37
+ batch_dml_executor,
38
+ )
39
+ from google.cloud.spanner_dbapi._helpers import ColumnInfo
40
+ from google.cloud.spanner_dbapi._helpers import CODE_TO_DISPLAY_SIZE
41
+
42
+ from google.cloud.spanner_dbapi import parse_utils
43
+ from google.cloud.spanner_dbapi.parse_utils import get_param_types
44
+ from google.cloud.spanner_dbapi.parsed_statement import (
45
+ StatementType,
46
+ Statement,
47
+ ParsedStatement,
48
+ AutocommitDmlMode,
49
+ )
50
+ from google.cloud.spanner_dbapi.transaction_helper import CursorStatementType
51
+ from google.cloud.spanner_dbapi.utils import PeekIterator
52
+ from google.cloud.spanner_dbapi.utils import StreamedManyResultSets
53
+ from google.cloud.spanner_v1 import RequestOptions
54
+ from google.cloud.spanner_v1.merged_result_set import MergedResultSet
55
+
56
+ ColumnDetails = namedtuple("column_details", ["null_ok", "spanner_type"])
57
+
58
+
59
+ def check_not_closed(function):
60
+ """`Cursor` class methods decorator.
61
+
62
+ Raise an exception if the cursor is closed, or not bound to a
63
+ connection, or the parent connection is closed.
64
+
65
+ :raises: :class:`InterfaceError` if this cursor is closed.
66
+ :raises: :class:`ProgrammingError` if this cursor is not bound to a connection.
67
+ """
68
+
69
+ def wrapper(cursor, *args, **kwargs):
70
+ if not cursor.connection:
71
+ raise ProgrammingError("Cursor is not connected to the database")
72
+
73
+ if cursor.is_closed:
74
+ raise InterfaceError("Cursor and/or connection is already closed.")
75
+
76
+ return function(cursor, *args, **kwargs)
77
+
78
+ return wrapper
79
+
80
+
81
+ class Cursor(object):
82
+ """Database cursor to manage the context of a fetch operation.
83
+
84
+ :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection`
85
+ :param connection: A DB-API connection to Google Cloud Spanner.
86
+ """
87
+
88
+ def __init__(self, connection):
89
+ self._itr = None
90
+ self._result_set = None
91
+ self._row_count = None
92
+ self.lastrowid = None
93
+ self.connection = connection
94
+ self.transaction_helper = self.connection._transaction_helper
95
+ self._is_closed = False
96
+ # the number of rows to fetch at a time with fetchmany()
97
+ self.arraysize = 1
98
+ self._parsed_statement: ParsedStatement = None
99
+ self._in_retry_mode = False
100
+ self._batch_dml_rows_count = None
101
+ self._request_tag = None
102
+
103
+ @property
104
+ def request_tag(self):
105
+ """The request tag that will be applied to the next statement on this
106
+ cursor. This property is automatically cleared when a statement is
107
+ executed.
108
+
109
+ Returns:
110
+ str: The request tag that will be applied to the next statement on
111
+ this cursor.
112
+ """
113
+ return self._request_tag
114
+
115
+ @request_tag.setter
116
+ def request_tag(self, value):
117
+ """Sets the request tag for the next statement on this cursor. This
118
+ property is automatically cleared when a statement is executed.
119
+
120
+ Args:
121
+ value (str): The request tag for the statement.
122
+ """
123
+ self._request_tag = value
124
+
125
+ @property
126
+ def request_options(self):
127
+ options = self.connection.request_options
128
+ if self._request_tag:
129
+ if not options:
130
+ options = RequestOptions()
131
+ options.request_tag = self._request_tag
132
+ self._request_tag = None
133
+ return options
134
+
135
+ @property
136
+ def is_closed(self):
137
+ """The cursor close indicator.
138
+
139
+ :rtype: bool
140
+ :returns: True if the cursor or the parent connection is closed,
141
+ otherwise False.
142
+ """
143
+ return self._is_closed or self.connection.is_closed
144
+
145
+ @property
146
+ def description(self):
147
+ """
148
+ Read-only attribute containing the result columns description
149
+ of a form:
150
+
151
+ - ``name``
152
+ - ``type_code``
153
+ - ``display_size``
154
+ - ``internal_size``
155
+ - ``precision``
156
+ - ``scale``
157
+ - ``null_ok``
158
+
159
+ :rtype: tuple
160
+ :returns: The result columns' description.
161
+ """
162
+ if (
163
+ self._result_set is None
164
+ or not getattr(self._result_set, "metadata", None)
165
+ or self._result_set.metadata.row_type is None
166
+ or self._result_set.metadata.row_type.fields is None
167
+ or len(self._result_set.metadata.row_type.fields) == 0
168
+ ):
169
+ return
170
+
171
+ columns = []
172
+ for field in self._result_set.metadata.row_type.fields:
173
+ columns.append(
174
+ ColumnInfo(
175
+ name=field.name,
176
+ type_code=field.type_.code,
177
+ # Size of the SQL type of the column.
178
+ display_size=CODE_TO_DISPLAY_SIZE.get(field.type_.code),
179
+ # Client perceived size of the column.
180
+ internal_size=field._pb.ByteSize(),
181
+ )
182
+ )
183
+ return tuple(columns)
184
+
185
+ @property
186
+ def rowcount(self):
187
+ """The number of rows updated by the last INSERT, UPDATE, DELETE request's `execute()` call.
188
+ For SELECT requests the rowcount returns -1.
189
+
190
+ :rtype: int
191
+ :returns: The number of rows updated by the last INSERT, UPDATE, DELETE request's .execute*() call.
192
+ """
193
+
194
+ if self._row_count is not None or self._result_set is None:
195
+ return self._row_count
196
+
197
+ stats = getattr(self._result_set, "stats", None)
198
+ if stats is not None and "row_count_exact" in stats:
199
+ return stats.row_count_exact
200
+
201
+ return -1
202
+
203
+ @check_not_closed
204
+ def callproc(self, procname, args=None):
205
+ """A no-op, raising an error if the cursor or connection is closed."""
206
+ pass
207
+
208
+ @check_not_closed
209
+ def nextset(self):
210
+ """A no-op, raising an error if the cursor or connection is closed."""
211
+ pass
212
+
213
+ @check_not_closed
214
+ def setinputsizes(self, sizes):
215
+ """A no-op, raising an error if the cursor or connection is closed."""
216
+ pass
217
+
218
+ @check_not_closed
219
+ def setoutputsize(self, size, column=None):
220
+ """A no-op, raising an error if the cursor or connection is closed."""
221
+ pass
222
+
223
+ def close(self):
224
+ """Closes this cursor."""
225
+ self._is_closed = True
226
+
227
+ def _do_execute_update_in_autocommit(self, transaction, sql, params):
228
+ """This function should only be used in autocommit mode."""
229
+ self.connection._transaction = transaction
230
+ self.connection._snapshot = None
231
+ self._result_set = transaction.execute_sql(
232
+ sql,
233
+ params=params,
234
+ param_types=get_param_types(params),
235
+ last_statement=True,
236
+ )
237
+ self._itr = PeekIterator(self._result_set)
238
+ self._row_count = None
239
+
240
+ def _batch_DDLs(self, sql):
241
+ """
242
+ Check that the given operation contains only DDL
243
+ statements and batch them into an internal list.
244
+
245
+ :type sql: str
246
+ :param sql: A SQL query statement.
247
+
248
+ :raises: :class:`ValueError` in case not a DDL statement
249
+ present in the operation.
250
+ """
251
+ statements = []
252
+ for ddl in sqlparse.split(sql):
253
+ if ddl:
254
+ ddl = ddl.rstrip(";")
255
+ if (
256
+ parse_utils.classify_statement(ddl).statement_type
257
+ != StatementType.DDL
258
+ ):
259
+ raise ValueError("Only DDL statements may be batched.")
260
+
261
+ statements.append(ddl)
262
+
263
+ # Only queue DDL statements if they are all correctly classified.
264
+ self.connection._ddl_statements.extend(statements)
265
+
266
+ def _reset(self):
267
+ if self.connection.database is None:
268
+ raise ValueError("Database needs to be passed for this operation")
269
+ self._itr = None
270
+ self._result_set = None
271
+ self._row_count = None
272
+ self._batch_dml_rows_count = None
273
+
274
+ @check_not_closed
275
+ def execute(self, sql, args=None):
276
+ self._execute(sql, args, False)
277
+
278
+ def _execute(self, sql, args=None, call_from_execute_many=False):
279
+ """Prepares and executes a Spanner database operation.
280
+
281
+ :type sql: str
282
+ :param sql: A SQL query statement.
283
+
284
+ :type args: list
285
+ :param args: Additional parameters to supplement the SQL query.
286
+ """
287
+ self._reset()
288
+ exception = None
289
+ try:
290
+ self._parsed_statement = parse_utils.classify_statement(sql, args)
291
+ if self._parsed_statement is None:
292
+ raise ProgrammingError("Invalid Statement.")
293
+
294
+ if self._parsed_statement.statement_type == StatementType.CLIENT_SIDE:
295
+ self._result_set = client_side_statement_executor.execute(
296
+ self, self._parsed_statement
297
+ )
298
+ if self._result_set is not None:
299
+ if isinstance(
300
+ self._result_set, StreamedManyResultSets
301
+ ) or isinstance(self._result_set, MergedResultSet):
302
+ self._itr = self._result_set
303
+ else:
304
+ self._itr = PeekIterator(self._result_set)
305
+ elif self.connection._batch_mode == BatchMode.DML:
306
+ self.connection.execute_batch_dml_statement(self._parsed_statement)
307
+ elif self.connection.read_only or (
308
+ not self.connection._client_transaction_started
309
+ and self._parsed_statement.statement_type == StatementType.QUERY
310
+ ):
311
+ self._handle_DQL(sql, args or None)
312
+ elif self._parsed_statement.statement_type == StatementType.DDL:
313
+ self._batch_DDLs(sql)
314
+ if not self.connection._client_transaction_started:
315
+ self.connection.run_prior_DDL_statements()
316
+ elif (
317
+ self.connection.autocommit_dml_mode
318
+ is AutocommitDmlMode.PARTITIONED_NON_ATOMIC
319
+ ):
320
+ self._row_count = self.connection.database.execute_partitioned_dml(
321
+ sql,
322
+ params=args,
323
+ param_types=self._parsed_statement.statement.param_types,
324
+ request_options=self.request_options,
325
+ )
326
+ self._result_set = None
327
+ else:
328
+ self._execute_in_rw_transaction()
329
+
330
+ except (AlreadyExists, FailedPrecondition, OutOfRange) as e:
331
+ exception = e
332
+ raise IntegrityError(getattr(e, "details", e)) from e
333
+ except InvalidArgument as e:
334
+ exception = e
335
+ raise ProgrammingError(getattr(e, "details", e)) from e
336
+ except InternalServerError as e:
337
+ exception = e
338
+ raise OperationalError(getattr(e, "details", e)) from e
339
+ except Exception as e:
340
+ exception = e
341
+ raise
342
+ finally:
343
+ if not self._in_retry_mode and not call_from_execute_many:
344
+ self.transaction_helper.add_execute_statement_for_retry(
345
+ self, sql, args, exception, False
346
+ )
347
+ if self.connection._client_transaction_started is False:
348
+ self.connection._spanner_transaction_started = False
349
+
350
+ def _execute_in_rw_transaction(self):
351
+ # For every other operation, we've got to ensure that
352
+ # any prior DDL statements were run.
353
+ self.connection.run_prior_DDL_statements()
354
+ statement = self._parsed_statement.statement
355
+ if self.connection._client_transaction_started:
356
+ while True:
357
+ try:
358
+ self._result_set = self.connection.run_statement(
359
+ statement, self.request_options
360
+ )
361
+ self._itr = PeekIterator(self._result_set)
362
+ return
363
+ except Aborted:
364
+ # We are raising it so it could be handled in transaction_helper.py and is retried
365
+ if self._in_retry_mode:
366
+ raise
367
+ else:
368
+ self.transaction_helper.retry_transaction()
369
+ else:
370
+ self.connection.database.run_in_transaction(
371
+ self._do_execute_update_in_autocommit,
372
+ statement.sql,
373
+ statement.params or None,
374
+ )
375
+
376
+ @check_not_closed
377
+ def executemany(self, operation, seq_of_params):
378
+ """Execute the given SQL with every parameters set
379
+ from the given sequence of parameters.
380
+
381
+ :type operation: str
382
+ :param operation: SQL code to execute.
383
+
384
+ :type seq_of_params: list
385
+ :param seq_of_params: Sequence of additional parameters to run
386
+ the query with.
387
+ """
388
+ self._reset()
389
+ exception = None
390
+ try:
391
+ self._parsed_statement = parse_utils.classify_statement(operation)
392
+ if self._parsed_statement.statement_type == StatementType.DDL:
393
+ raise ProgrammingError(
394
+ "Executing DDL statements with executemany() method is not allowed."
395
+ )
396
+
397
+ if self._parsed_statement.statement_type == StatementType.CLIENT_SIDE:
398
+ raise ProgrammingError(
399
+ "Executing the following operation: "
400
+ + operation
401
+ + ", with executemany() method is not allowed."
402
+ )
403
+
404
+ # For every operation, we've got to ensure that any prior DDL
405
+ # statements were run.
406
+ self.connection.run_prior_DDL_statements()
407
+ # Treat UNKNOWN statements as if they are DML and let the server
408
+ # determine what is wrong with it.
409
+ if self._parsed_statement.statement_type in (
410
+ StatementType.INSERT,
411
+ StatementType.UPDATE,
412
+ StatementType.UNKNOWN,
413
+ ):
414
+ statements = []
415
+ for params in seq_of_params:
416
+ sql, params = parse_utils.sql_pyformat_args_to_spanner(
417
+ operation, params
418
+ )
419
+ statements.append(Statement(sql, params, get_param_types(params)))
420
+ many_result_set = batch_dml_executor.run_batch_dml(self, statements)
421
+ else:
422
+ many_result_set = StreamedManyResultSets()
423
+ for params in seq_of_params:
424
+ self._execute(operation, params, True)
425
+ many_result_set.add_iter(self._itr)
426
+
427
+ self._result_set = many_result_set
428
+ self._itr = many_result_set
429
+ except Exception as e:
430
+ exception = e
431
+ raise
432
+ finally:
433
+ if not self._in_retry_mode:
434
+ self.transaction_helper.add_execute_statement_for_retry(
435
+ self,
436
+ operation,
437
+ seq_of_params,
438
+ exception,
439
+ True,
440
+ )
441
+ if self.connection._client_transaction_started is False:
442
+ self.connection._spanner_transaction_started = False
443
+
444
+ @check_not_closed
445
+ def fetchone(self):
446
+ """Fetch the next row of a query result set, returning a single
447
+ sequence, or None when no more data is available."""
448
+ rows = self._fetch(CursorStatementType.FETCH_ONE)
449
+ if not rows:
450
+ return
451
+ return rows[0]
452
+
453
+ @check_not_closed
454
+ def fetchall(self):
455
+ """Fetch all (remaining) rows of a query result, returning them as
456
+ a sequence of sequences.
457
+ """
458
+ return self._fetch(CursorStatementType.FETCH_ALL)
459
+
460
+ @check_not_closed
461
+ def fetchmany(self, size=None):
462
+ """Fetch the next set of rows of a query result, returning a sequence
463
+ of sequences. An empty sequence is returned when no more rows are available.
464
+
465
+ :type size: int
466
+ :param size: (Optional) The maximum number of results to fetch.
467
+
468
+ :raises InterfaceError:
469
+ if the previous call to .execute*() did not produce any result set
470
+ or if no call was issued yet.
471
+ """
472
+ if size is None:
473
+ size = self.arraysize
474
+ return self._fetch(CursorStatementType.FETCH_MANY, size)
475
+
476
+ def _fetch(self, cursor_statement_type, size=None):
477
+ exception = None
478
+ rows = []
479
+ is_fetch_all = False
480
+ try:
481
+ while True:
482
+ rows = []
483
+ try:
484
+ if cursor_statement_type == CursorStatementType.FETCH_ALL:
485
+ is_fetch_all = True
486
+ for row in self:
487
+ rows.append(row)
488
+ elif cursor_statement_type == CursorStatementType.FETCH_MANY:
489
+ for _ in range(size):
490
+ try:
491
+ row = next(self)
492
+ rows.append(row)
493
+ except StopIteration:
494
+ break
495
+ elif cursor_statement_type == CursorStatementType.FETCH_ONE:
496
+ try:
497
+ row = next(self)
498
+ rows.append(row)
499
+ except StopIteration:
500
+ return
501
+ break
502
+ except Aborted:
503
+ if not self.connection.read_only:
504
+ if self._in_retry_mode:
505
+ raise
506
+ else:
507
+ self.transaction_helper.retry_transaction()
508
+ except Exception as e:
509
+ exception = e
510
+ raise
511
+ finally:
512
+ if not self._in_retry_mode:
513
+ self.transaction_helper.add_fetch_statement_for_retry(
514
+ self, rows, exception, is_fetch_all
515
+ )
516
+ return rows
517
+
518
+ def _handle_DQL_with_snapshot(self, snapshot, sql, params):
519
+ self._result_set = snapshot.execute_sql(
520
+ sql,
521
+ params,
522
+ get_param_types(params),
523
+ request_options=self.request_options,
524
+ )
525
+ # Read the first element so that the StreamedResultSet can
526
+ # return the metadata after a DQL statement.
527
+ self._itr = PeekIterator(self._result_set)
528
+ # Unfortunately, Spanner doesn't seem to send back
529
+ # information about the number of rows available.
530
+ self._row_count = None
531
+ if self._result_set.metadata.transaction.read_timestamp is not None:
532
+ snapshot._transaction_read_timestamp = (
533
+ self._result_set.metadata.transaction.read_timestamp
534
+ )
535
+
536
+ def _handle_DQL(self, sql, params):
537
+ if self.connection.database is None:
538
+ raise ValueError("Database needs to be passed for this operation")
539
+ sql, params = parse_utils.sql_pyformat_args_to_spanner(sql, params)
540
+ if self.connection.read_only and self.connection._client_transaction_started:
541
+ # initiate or use the existing multi-use snapshot
542
+ self._handle_DQL_with_snapshot(
543
+ self.connection.snapshot_checkout(), sql, params
544
+ )
545
+ else:
546
+ # execute with single-use snapshot
547
+ with self.connection.database.snapshot(
548
+ **self.connection.staleness
549
+ ) as snapshot:
550
+ self.connection._snapshot = snapshot
551
+ self.connection._transaction = None
552
+ self._handle_DQL_with_snapshot(snapshot, sql, params)
553
+
554
+ def __enter__(self):
555
+ return self
556
+
557
+ def __exit__(self, etype, value, traceback):
558
+ self.close()
559
+
560
+ def __next__(self):
561
+ if self._itr is None:
562
+ raise ProgrammingError("no results to return")
563
+ return next(self._itr)
564
+
565
+ def __iter__(self):
566
+ if self._itr is None:
567
+ raise ProgrammingError("no results to return")
568
+ return self._itr
569
+
570
+ def list_tables(self, schema_name="", include_views=True):
571
+ """List the tables of the linked Database.
572
+
573
+ :rtype: list
574
+ :returns: The list of tables within the Database.
575
+ """
576
+ return self.run_sql_in_snapshot(
577
+ sql=_helpers.SQL_LIST_TABLES_AND_VIEWS
578
+ if include_views
579
+ else _helpers.SQL_LIST_TABLES,
580
+ params={"table_schema": schema_name},
581
+ param_types={"table_schema": spanner.param_types.STRING},
582
+ )
583
+
584
+ def run_sql_in_snapshot(self, sql, params=None, param_types=None):
585
+ # Some SQL e.g. for INFORMATION_SCHEMA cannot be run in read-write transactions
586
+ # hence this method exists to circumvent that limit.
587
+ if self.connection.database is None:
588
+ raise ValueError("Database needs to be passed for this operation")
589
+ self.connection.run_prior_DDL_statements()
590
+
591
+ with self.connection.database.snapshot() as snapshot:
592
+ return list(snapshot.execute_sql(sql, params, param_types))
593
+
594
+ def get_table_column_schema(self, table_name, schema_name=""):
595
+ rows = self.run_sql_in_snapshot(
596
+ sql=_helpers.SQL_GET_TABLE_COLUMN_SCHEMA,
597
+ params={"schema_name": schema_name, "table_name": table_name},
598
+ param_types={
599
+ "schema_name": spanner.param_types.STRING,
600
+ "table_name": spanner.param_types.STRING,
601
+ },
602
+ )
603
+
604
+ column_details = {}
605
+ for column_name, is_nullable, spanner_type in rows:
606
+ column_details[column_name] = ColumnDetails(
607
+ null_ok=is_nullable == "YES", spanner_type=spanner_type
608
+ )
609
+ return column_details