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,118 @@
|
|
|
1
|
+
# Copyright 2023 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
|
+
from collections import defaultdict
|
|
16
|
+
import threading
|
|
17
|
+
|
|
18
|
+
from grpc_interceptor import ClientInterceptor
|
|
19
|
+
from google.api_core.exceptions import Aborted
|
|
20
|
+
from google.cloud.spanner_v1.request_id_header import parse_request_id
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MethodCountInterceptor(ClientInterceptor):
|
|
24
|
+
"""Test interceptor that counts number of times a method is being called."""
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
self._counts = defaultdict(int)
|
|
28
|
+
|
|
29
|
+
def intercept(self, method, request_or_iterator, call_details):
|
|
30
|
+
"""Count number of times a method is being called."""
|
|
31
|
+
self._counts[call_details.method] += 1
|
|
32
|
+
return method(request_or_iterator, call_details)
|
|
33
|
+
|
|
34
|
+
def reset(self):
|
|
35
|
+
self._counts = defaultdict(int)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class MethodAbortInterceptor(ClientInterceptor):
|
|
39
|
+
"""Test interceptor that throws Aborted exception for a specific method."""
|
|
40
|
+
|
|
41
|
+
def __init__(self):
|
|
42
|
+
self._method_to_abort = None
|
|
43
|
+
self._count = 0
|
|
44
|
+
self._max_raise_count = 1
|
|
45
|
+
self._connection = None
|
|
46
|
+
|
|
47
|
+
def intercept(self, method, request_or_iterator, call_details):
|
|
48
|
+
if (
|
|
49
|
+
self._count < self._max_raise_count
|
|
50
|
+
and call_details.method == self._method_to_abort
|
|
51
|
+
):
|
|
52
|
+
self._count += 1
|
|
53
|
+
if self._connection is not None:
|
|
54
|
+
self._connection._transaction.rollback()
|
|
55
|
+
raise Aborted("Thrown from ClientInterceptor for testing")
|
|
56
|
+
return method(request_or_iterator, call_details)
|
|
57
|
+
|
|
58
|
+
def set_method_to_abort(self, method_to_abort, connection=None, max_raise_count=1):
|
|
59
|
+
self._method_to_abort = method_to_abort
|
|
60
|
+
self._count = 0
|
|
61
|
+
self._max_raise_count = max_raise_count
|
|
62
|
+
self._connection = connection
|
|
63
|
+
|
|
64
|
+
def reset(self):
|
|
65
|
+
"""Reset the interceptor to the original state."""
|
|
66
|
+
self._method_to_abort = None
|
|
67
|
+
self._count = 0
|
|
68
|
+
self._connection = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
X_GOOG_REQUEST_ID = "x-goog-spanner-request-id"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class XGoogRequestIDHeaderInterceptor(ClientInterceptor):
|
|
75
|
+
def __init__(self):
|
|
76
|
+
self._unary_req_segments = []
|
|
77
|
+
self._stream_req_segments = []
|
|
78
|
+
self.__lock = threading.Lock()
|
|
79
|
+
|
|
80
|
+
def intercept(self, method, request_or_iterator, call_details):
|
|
81
|
+
metadata = call_details.metadata
|
|
82
|
+
x_goog_request_id = None
|
|
83
|
+
for key, value in metadata:
|
|
84
|
+
if key == X_GOOG_REQUEST_ID:
|
|
85
|
+
x_goog_request_id = value
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
if not x_goog_request_id:
|
|
89
|
+
raise Exception(
|
|
90
|
+
f"Missing {X_GOOG_REQUEST_ID} header in {call_details.method}"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
response_or_iterator = method(request_or_iterator, call_details)
|
|
94
|
+
streaming = getattr(response_or_iterator, "__iter__", None) is not None
|
|
95
|
+
|
|
96
|
+
with self.__lock:
|
|
97
|
+
if streaming:
|
|
98
|
+
self._stream_req_segments.append(
|
|
99
|
+
(call_details.method, parse_request_id(x_goog_request_id))
|
|
100
|
+
)
|
|
101
|
+
else:
|
|
102
|
+
self._unary_req_segments.append(
|
|
103
|
+
(call_details.method, parse_request_id(x_goog_request_id))
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return response_or_iterator
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def unary_request_ids(self):
|
|
110
|
+
return self._unary_req_segments
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def stream_request_ids(self):
|
|
114
|
+
return self._stream_req_segments
|
|
115
|
+
|
|
116
|
+
def reset(self):
|
|
117
|
+
self._stream_req_segments.clear()
|
|
118
|
+
self._unary_req_segments.clear()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Copyright 2024 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
|
+
from google.longrunning import operations_pb2 as operations_pb2
|
|
16
|
+
from google.protobuf import empty_pb2
|
|
17
|
+
import google.cloud.spanner_v1.testing.spanner_database_admin_pb2_grpc as database_admin_grpc
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# An in-memory mock DatabaseAdmin server that can be used for testing.
|
|
21
|
+
class DatabaseAdminServicer(database_admin_grpc.DatabaseAdminServicer):
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self._requests = []
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def requests(self):
|
|
27
|
+
return self._requests
|
|
28
|
+
|
|
29
|
+
def clear_requests(self):
|
|
30
|
+
self._requests = []
|
|
31
|
+
|
|
32
|
+
def UpdateDatabaseDdl(self, request, context):
|
|
33
|
+
self._requests.append(request)
|
|
34
|
+
operation = operations_pb2.Operation()
|
|
35
|
+
operation.done = True
|
|
36
|
+
operation.name = "projects/test-project/operations/test-operation"
|
|
37
|
+
operation.response.Pack(empty_pb2.Empty())
|
|
38
|
+
return operation
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Copyright 2024 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
|
+
import base64
|
|
15
|
+
import inspect
|
|
16
|
+
import grpc
|
|
17
|
+
from concurrent import futures
|
|
18
|
+
|
|
19
|
+
from google.protobuf import empty_pb2
|
|
20
|
+
from grpc_status.rpc_status import _Status
|
|
21
|
+
|
|
22
|
+
from google.cloud.spanner_v1 import (
|
|
23
|
+
TransactionOptions,
|
|
24
|
+
ResultSetMetadata,
|
|
25
|
+
)
|
|
26
|
+
from google.cloud.spanner_v1.testing.mock_database_admin import DatabaseAdminServicer
|
|
27
|
+
import google.cloud.spanner_v1.testing.spanner_database_admin_pb2_grpc as database_admin_grpc
|
|
28
|
+
import google.cloud.spanner_v1.testing.spanner_pb2_grpc as spanner_grpc
|
|
29
|
+
import google.cloud.spanner_v1.types.commit_response as commit
|
|
30
|
+
import google.cloud.spanner_v1.types.result_set as result_set
|
|
31
|
+
import google.cloud.spanner_v1.types.spanner as spanner
|
|
32
|
+
import google.cloud.spanner_v1.types.transaction as transaction
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class MockSpanner:
|
|
36
|
+
def __init__(self):
|
|
37
|
+
self.results = {}
|
|
38
|
+
self.errors = {}
|
|
39
|
+
|
|
40
|
+
def add_result(self, sql: str, result: result_set.ResultSet):
|
|
41
|
+
self.results[sql.lower().strip()] = result
|
|
42
|
+
|
|
43
|
+
def get_result(self, sql: str) -> result_set.ResultSet:
|
|
44
|
+
result = self.results.get(sql.lower().strip())
|
|
45
|
+
if result is None:
|
|
46
|
+
raise ValueError(f"No result found for {sql}")
|
|
47
|
+
return result
|
|
48
|
+
|
|
49
|
+
def add_error(self, method: str, error: _Status):
|
|
50
|
+
self.errors[method] = error
|
|
51
|
+
|
|
52
|
+
def pop_error(self, context):
|
|
53
|
+
name = inspect.currentframe().f_back.f_code.co_name
|
|
54
|
+
error: _Status | None = self.errors.pop(name, None)
|
|
55
|
+
if error:
|
|
56
|
+
context.abort_with_status(error)
|
|
57
|
+
|
|
58
|
+
def get_result_as_partial_result_sets(
|
|
59
|
+
self, sql: str, started_transaction: transaction.Transaction
|
|
60
|
+
) -> [result_set.PartialResultSet]:
|
|
61
|
+
result: result_set.ResultSet = self.get_result(sql)
|
|
62
|
+
partials = []
|
|
63
|
+
first = True
|
|
64
|
+
if len(result.rows) == 0:
|
|
65
|
+
partial = result_set.PartialResultSet()
|
|
66
|
+
partial.metadata = ResultSetMetadata(result.metadata)
|
|
67
|
+
partials.append(partial)
|
|
68
|
+
else:
|
|
69
|
+
for row in result.rows:
|
|
70
|
+
partial = result_set.PartialResultSet()
|
|
71
|
+
if first:
|
|
72
|
+
partial.metadata = ResultSetMetadata(result.metadata)
|
|
73
|
+
partial.values.extend(row)
|
|
74
|
+
partials.append(partial)
|
|
75
|
+
partials[len(partials) - 1].stats = result.stats
|
|
76
|
+
if started_transaction:
|
|
77
|
+
partials[0].metadata.transaction = started_transaction
|
|
78
|
+
return partials
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# An in-memory mock Spanner server that can be used for testing.
|
|
82
|
+
class SpannerServicer(spanner_grpc.SpannerServicer):
|
|
83
|
+
def __init__(self):
|
|
84
|
+
self._requests = []
|
|
85
|
+
self.session_counter = 0
|
|
86
|
+
self.sessions = {}
|
|
87
|
+
self.transaction_counter = 0
|
|
88
|
+
self.transactions = {}
|
|
89
|
+
self._mock_spanner = MockSpanner()
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def mock_spanner(self):
|
|
93
|
+
return self._mock_spanner
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def requests(self):
|
|
97
|
+
return self._requests
|
|
98
|
+
|
|
99
|
+
def clear_requests(self):
|
|
100
|
+
self._requests = []
|
|
101
|
+
|
|
102
|
+
def CreateSession(self, request, context):
|
|
103
|
+
self._requests.append(request)
|
|
104
|
+
return self.__create_session(request.database, request.session)
|
|
105
|
+
|
|
106
|
+
def BatchCreateSessions(self, request, context):
|
|
107
|
+
self._requests.append(request)
|
|
108
|
+
self.mock_spanner.pop_error(context)
|
|
109
|
+
sessions = []
|
|
110
|
+
for i in range(request.session_count):
|
|
111
|
+
sessions.append(
|
|
112
|
+
self.__create_session(request.database, request.session_template)
|
|
113
|
+
)
|
|
114
|
+
return spanner.BatchCreateSessionsResponse(dict(session=sessions))
|
|
115
|
+
|
|
116
|
+
def __create_session(self, database: str, session_template: spanner.Session):
|
|
117
|
+
self.session_counter += 1
|
|
118
|
+
session = spanner.Session()
|
|
119
|
+
session.name = database + "/sessions/" + str(self.session_counter)
|
|
120
|
+
session.multiplexed = session_template.multiplexed
|
|
121
|
+
session.labels.MergeFrom(session_template.labels)
|
|
122
|
+
session.creator_role = session_template.creator_role
|
|
123
|
+
self.sessions[session.name] = session
|
|
124
|
+
return session
|
|
125
|
+
|
|
126
|
+
def GetSession(self, request, context):
|
|
127
|
+
self._requests.append(request)
|
|
128
|
+
return spanner.Session()
|
|
129
|
+
|
|
130
|
+
def ListSessions(self, request, context):
|
|
131
|
+
self._requests.append(request)
|
|
132
|
+
return [spanner.Session()]
|
|
133
|
+
|
|
134
|
+
def DeleteSession(self, request, context):
|
|
135
|
+
self._requests.append(request)
|
|
136
|
+
return empty_pb2.Empty()
|
|
137
|
+
|
|
138
|
+
def ExecuteSql(self, request, context):
|
|
139
|
+
self._requests.append(request)
|
|
140
|
+
self.mock_spanner.pop_error(context)
|
|
141
|
+
started_transaction = self.__maybe_create_transaction(request)
|
|
142
|
+
result: result_set.ResultSet = self.mock_spanner.get_result(request.sql)
|
|
143
|
+
if started_transaction:
|
|
144
|
+
result.metadata = ResultSetMetadata(result.metadata)
|
|
145
|
+
result.metadata.transaction = started_transaction
|
|
146
|
+
return result
|
|
147
|
+
|
|
148
|
+
def ExecuteStreamingSql(self, request, context):
|
|
149
|
+
self._requests.append(request)
|
|
150
|
+
self.mock_spanner.pop_error(context)
|
|
151
|
+
started_transaction = self.__maybe_create_transaction(request)
|
|
152
|
+
partials = self.mock_spanner.get_result_as_partial_result_sets(
|
|
153
|
+
request.sql, started_transaction
|
|
154
|
+
)
|
|
155
|
+
for result in partials:
|
|
156
|
+
yield result
|
|
157
|
+
|
|
158
|
+
def ExecuteBatchDml(self, request, context):
|
|
159
|
+
self._requests.append(request)
|
|
160
|
+
self.mock_spanner.pop_error(context)
|
|
161
|
+
response = spanner.ExecuteBatchDmlResponse()
|
|
162
|
+
started_transaction = self.__maybe_create_transaction(request)
|
|
163
|
+
first = True
|
|
164
|
+
for statement in request.statements:
|
|
165
|
+
result = self.mock_spanner.get_result(statement.sql)
|
|
166
|
+
if first and started_transaction is not None:
|
|
167
|
+
result = result_set.ResultSet(
|
|
168
|
+
self.mock_spanner.get_result(statement.sql)
|
|
169
|
+
)
|
|
170
|
+
result.metadata = result_set.ResultSetMetadata(result.metadata)
|
|
171
|
+
result.metadata.transaction = started_transaction
|
|
172
|
+
response.result_sets.append(result)
|
|
173
|
+
return response
|
|
174
|
+
|
|
175
|
+
def Read(self, request, context):
|
|
176
|
+
self._requests.append(request)
|
|
177
|
+
return result_set.ResultSet()
|
|
178
|
+
|
|
179
|
+
def StreamingRead(self, request, context):
|
|
180
|
+
self._requests.append(request)
|
|
181
|
+
for result in [result_set.PartialResultSet(), result_set.PartialResultSet()]:
|
|
182
|
+
yield result
|
|
183
|
+
|
|
184
|
+
def BeginTransaction(self, request, context):
|
|
185
|
+
self._requests.append(request)
|
|
186
|
+
return self.__create_transaction(request.session, request.options)
|
|
187
|
+
|
|
188
|
+
def __maybe_create_transaction(self, request):
|
|
189
|
+
started_transaction = None
|
|
190
|
+
if not request.transaction.begin == TransactionOptions():
|
|
191
|
+
started_transaction = self.__create_transaction(
|
|
192
|
+
request.session, request.transaction.begin
|
|
193
|
+
)
|
|
194
|
+
return started_transaction
|
|
195
|
+
|
|
196
|
+
def __create_transaction(
|
|
197
|
+
self, session: str, options: transaction.TransactionOptions
|
|
198
|
+
) -> transaction.Transaction:
|
|
199
|
+
session = self.sessions[session]
|
|
200
|
+
if session is None:
|
|
201
|
+
raise ValueError(f"Session not found: {session}")
|
|
202
|
+
self.transaction_counter += 1
|
|
203
|
+
id_bytes = bytes(
|
|
204
|
+
f"{session.name}/transactions/{self.transaction_counter}", "UTF-8"
|
|
205
|
+
)
|
|
206
|
+
transaction_id = base64.urlsafe_b64encode(id_bytes)
|
|
207
|
+
self.transactions[transaction_id] = options
|
|
208
|
+
return transaction.Transaction(dict(id=transaction_id))
|
|
209
|
+
|
|
210
|
+
def Commit(self, request, context):
|
|
211
|
+
self._requests.append(request)
|
|
212
|
+
self.mock_spanner.pop_error(context)
|
|
213
|
+
if not request.transaction_id == b"":
|
|
214
|
+
tx = self.transactions[request.transaction_id]
|
|
215
|
+
if tx is None:
|
|
216
|
+
raise ValueError(f"Transaction not found: {request.transaction_id}")
|
|
217
|
+
tx_id = request.transaction_id
|
|
218
|
+
elif not request.single_use_transaction == TransactionOptions():
|
|
219
|
+
tx = self.__create_transaction(
|
|
220
|
+
request.session, request.single_use_transaction
|
|
221
|
+
)
|
|
222
|
+
tx_id = tx.id
|
|
223
|
+
else:
|
|
224
|
+
raise ValueError("Unsupported transaction type")
|
|
225
|
+
del self.transactions[tx_id]
|
|
226
|
+
return commit.CommitResponse()
|
|
227
|
+
|
|
228
|
+
def Rollback(self, request, context):
|
|
229
|
+
self._requests.append(request)
|
|
230
|
+
return empty_pb2.Empty()
|
|
231
|
+
|
|
232
|
+
def PartitionQuery(self, request, context):
|
|
233
|
+
self._requests.append(request)
|
|
234
|
+
return spanner.PartitionResponse()
|
|
235
|
+
|
|
236
|
+
def PartitionRead(self, request, context):
|
|
237
|
+
self._requests.append(request)
|
|
238
|
+
return spanner.PartitionResponse()
|
|
239
|
+
|
|
240
|
+
def BatchWrite(self, request, context):
|
|
241
|
+
self._requests.append(request)
|
|
242
|
+
for result in [spanner.BatchWriteResponse(), spanner.BatchWriteResponse()]:
|
|
243
|
+
yield result
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def start_mock_server() -> (grpc.Server, SpannerServicer, DatabaseAdminServicer, int):
|
|
247
|
+
# Create a gRPC server.
|
|
248
|
+
spanner_server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
|
|
249
|
+
|
|
250
|
+
# Add the Spanner services to the gRPC server.
|
|
251
|
+
spanner_servicer = SpannerServicer()
|
|
252
|
+
spanner_grpc.add_SpannerServicer_to_server(spanner_servicer, spanner_server)
|
|
253
|
+
database_admin_servicer = DatabaseAdminServicer()
|
|
254
|
+
database_admin_grpc.add_DatabaseAdminServicer_to_server(
|
|
255
|
+
database_admin_servicer, spanner_server
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Start the server on a random port.
|
|
259
|
+
port = spanner_server.add_insecure_port("[::]:0")
|
|
260
|
+
spanner_server.start()
|
|
261
|
+
return spanner_server, spanner_servicer, database_admin_servicer, port
|