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