google-cloud-bigtable 2.30.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/bigtable/__init__.py +25 -0
- google/cloud/bigtable/app_profile.py +377 -0
- google/cloud/bigtable/backup.py +490 -0
- google/cloud/bigtable/batcher.py +414 -0
- google/cloud/bigtable/client.py +475 -0
- google/cloud/bigtable/cluster.py +541 -0
- google/cloud/bigtable/column_family.py +362 -0
- google/cloud/bigtable/data/__init__.py +103 -0
- google/cloud/bigtable/data/_async/__init__.py +25 -0
- google/cloud/bigtable/data/_async/_mutate_rows.py +226 -0
- google/cloud/bigtable/data/_async/_read_rows.py +363 -0
- google/cloud/bigtable/data/_async/client.py +1522 -0
- google/cloud/bigtable/data/_async/mutations_batcher.py +534 -0
- google/cloud/bigtable/data/_cross_sync/__init__.py +20 -0
- google/cloud/bigtable/data/_cross_sync/_decorators.py +441 -0
- google/cloud/bigtable/data/_cross_sync/_mapping_meta.py +64 -0
- google/cloud/bigtable/data/_cross_sync/cross_sync.py +334 -0
- google/cloud/bigtable/data/_helpers.py +249 -0
- google/cloud/bigtable/data/_sync_autogen/_mutate_rows.py +182 -0
- google/cloud/bigtable/data/_sync_autogen/_read_rows.py +304 -0
- google/cloud/bigtable/data/_sync_autogen/client.py +1293 -0
- google/cloud/bigtable/data/_sync_autogen/mutations_batcher.py +449 -0
- google/cloud/bigtable/data/exceptions.py +340 -0
- google/cloud/bigtable/data/execute_query/__init__.py +43 -0
- google/cloud/bigtable/data/execute_query/_async/__init__.py +13 -0
- google/cloud/bigtable/data/execute_query/_async/execute_query_iterator.py +291 -0
- google/cloud/bigtable/data/execute_query/_byte_cursor.py +123 -0
- google/cloud/bigtable/data/execute_query/_checksum.py +43 -0
- google/cloud/bigtable/data/execute_query/_parameters_formatting.py +155 -0
- google/cloud/bigtable/data/execute_query/_query_result_parsing_utils.py +134 -0
- google/cloud/bigtable/data/execute_query/_reader.py +122 -0
- google/cloud/bigtable/data/execute_query/_sync_autogen/execute_query_iterator.py +239 -0
- google/cloud/bigtable/data/execute_query/metadata.py +399 -0
- google/cloud/bigtable/data/execute_query/values.py +123 -0
- google/cloud/bigtable/data/mutations.py +380 -0
- google/cloud/bigtable/data/read_modify_write_rules.py +112 -0
- google/cloud/bigtable/data/read_rows_query.py +536 -0
- google/cloud/bigtable/data/row.py +535 -0
- google/cloud/bigtable/data/row_filters.py +968 -0
- google/cloud/bigtable/encryption_info.py +64 -0
- google/cloud/bigtable/enums.py +223 -0
- google/cloud/bigtable/error.py +64 -0
- google/cloud/bigtable/gapic_version.py +16 -0
- google/cloud/bigtable/helpers.py +31 -0
- google/cloud/bigtable/instance.py +789 -0
- google/cloud/bigtable/policy.py +255 -0
- google/cloud/bigtable/row.py +1267 -0
- google/cloud/bigtable/row_data.py +380 -0
- google/cloud/bigtable/row_filters.py +838 -0
- google/cloud/bigtable/row_merger.py +250 -0
- google/cloud/bigtable/row_set.py +213 -0
- google/cloud/bigtable/table.py +1409 -0
- google/cloud/bigtable_admin/__init__.py +410 -0
- google/cloud/bigtable_admin/gapic_version.py +16 -0
- google/cloud/bigtable_admin/py.typed +2 -0
- google/cloud/bigtable_admin_v2/__init__.py +250 -0
- google/cloud/bigtable_admin_v2/gapic_metadata.json +962 -0
- google/cloud/bigtable_admin_v2/gapic_version.py +16 -0
- google/cloud/bigtable_admin_v2/py.typed +2 -0
- google/cloud/bigtable_admin_v2/services/__init__.py +15 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/__init__.py +22 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/async_client.py +3465 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/client.py +3944 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/pagers.py +681 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/__init__.py +40 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/base.py +751 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc.py +1249 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc_asyncio.py +1579 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/rest.py +6820 -0
- google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/rest_base.py +1746 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/__init__.py +22 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/async_client.py +3472 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/client.py +3949 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/pagers.py +669 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/__init__.py +40 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/base.py +708 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/grpc.py +1257 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/grpc_asyncio.py +1549 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/rest.py +6609 -0
- google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/rest_base.py +1714 -0
- google/cloud/bigtable_admin_v2/types/__init__.py +248 -0
- google/cloud/bigtable_admin_v2/types/bigtable_instance_admin.py +1364 -0
- google/cloud/bigtable_admin_v2/types/bigtable_table_admin.py +1715 -0
- google/cloud/bigtable_admin_v2/types/common.py +81 -0
- google/cloud/bigtable_admin_v2/types/instance.py +819 -0
- google/cloud/bigtable_admin_v2/types/table.py +1028 -0
- google/cloud/bigtable_admin_v2/types/types.py +776 -0
- google/cloud/bigtable_v2/__init__.py +136 -0
- google/cloud/bigtable_v2/gapic_metadata.json +193 -0
- google/cloud/bigtable_v2/gapic_version.py +16 -0
- google/cloud/bigtable_v2/py.typed +2 -0
- google/cloud/bigtable_v2/services/__init__.py +15 -0
- google/cloud/bigtable_v2/services/bigtable/__init__.py +22 -0
- google/cloud/bigtable_v2/services/bigtable/async_client.py +1741 -0
- google/cloud/bigtable_v2/services/bigtable/client.py +2194 -0
- google/cloud/bigtable_v2/services/bigtable/transports/__init__.py +38 -0
- google/cloud/bigtable_v2/services/bigtable/transports/base.py +343 -0
- google/cloud/bigtable_v2/services/bigtable/transports/grpc.py +660 -0
- google/cloud/bigtable_v2/services/bigtable/transports/grpc_asyncio.py +762 -0
- google/cloud/bigtable_v2/services/bigtable/transports/rest.py +2489 -0
- google/cloud/bigtable_v2/services/bigtable/transports/rest_base.py +711 -0
- google/cloud/bigtable_v2/types/__init__.py +138 -0
- google/cloud/bigtable_v2/types/bigtable.py +1531 -0
- google/cloud/bigtable_v2/types/data.py +1612 -0
- google/cloud/bigtable_v2/types/feature_flags.py +119 -0
- google/cloud/bigtable_v2/types/request_stats.py +171 -0
- google/cloud/bigtable_v2/types/response_params.py +64 -0
- google/cloud/bigtable_v2/types/types.py +579 -0
- google_cloud_bigtable-2.30.0.data/scripts/fixup_bigtable_admin_v2_keywords.py +233 -0
- google_cloud_bigtable-2.30.0.data/scripts/fixup_bigtable_v2_keywords.py +186 -0
- google_cloud_bigtable-2.30.0.dist-info/LICENSE +202 -0
- google_cloud_bigtable-2.30.0.dist-info/METADATA +154 -0
- google_cloud_bigtable-2.30.0.dist-info/RECORD +115 -0
- google_cloud_bigtable-2.30.0.dist-info/WHEEL +5 -0
- google_cloud_bigtable-2.30.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Copyright 2024 Google LLC
|
|
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
|
+
|
|
16
|
+
# This file is automatically generated by CrossSync. Do not edit manually.
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
from typing import Any, Dict, Optional, Sequence, Tuple, TYPE_CHECKING
|
|
20
|
+
from google.api_core import retry as retries
|
|
21
|
+
from google.cloud.bigtable.data.execute_query._byte_cursor import _ByteCursor
|
|
22
|
+
from google.cloud.bigtable.data._helpers import (
|
|
23
|
+
_attempt_timeout_generator,
|
|
24
|
+
_retry_exception_factory,
|
|
25
|
+
)
|
|
26
|
+
from google.cloud.bigtable.data.exceptions import (
|
|
27
|
+
EarlyMetadataCallError,
|
|
28
|
+
InvalidExecuteQueryResponse,
|
|
29
|
+
)
|
|
30
|
+
from google.cloud.bigtable.data.execute_query.values import QueryResultRow
|
|
31
|
+
from google.cloud.bigtable.data.execute_query.metadata import Metadata
|
|
32
|
+
from google.cloud.bigtable.data.execute_query._reader import (
|
|
33
|
+
_QueryResultRowReader,
|
|
34
|
+
_Reader,
|
|
35
|
+
)
|
|
36
|
+
from google.cloud.bigtable_v2.types.bigtable import (
|
|
37
|
+
ExecuteQueryRequest as ExecuteQueryRequestPB,
|
|
38
|
+
ExecuteQueryResponse,
|
|
39
|
+
)
|
|
40
|
+
from google.cloud.bigtable.data._cross_sync import CrossSync
|
|
41
|
+
|
|
42
|
+
if TYPE_CHECKING:
|
|
43
|
+
from google.cloud.bigtable.data import BigtableDataClient as DataClientType
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _has_resume_token(response: ExecuteQueryResponse) -> bool:
|
|
47
|
+
response_pb = response._pb
|
|
48
|
+
if response_pb.HasField("results"):
|
|
49
|
+
results = response_pb.results
|
|
50
|
+
return len(results.resume_token) > 0
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ExecuteQueryIterator:
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
client: DataClientType,
|
|
58
|
+
instance_id: str,
|
|
59
|
+
app_profile_id: Optional[str],
|
|
60
|
+
request_body: Dict[str, Any],
|
|
61
|
+
prepare_metadata: Metadata,
|
|
62
|
+
attempt_timeout: float | None,
|
|
63
|
+
operation_timeout: float,
|
|
64
|
+
req_metadata: Sequence[Tuple[str, str]] = (),
|
|
65
|
+
retryable_excs: Sequence[type[Exception]] = (),
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Collects responses from ExecuteQuery requests and parses them into QueryResultRows.
|
|
68
|
+
|
|
69
|
+
**Please Note** this is not meant to be constructed directly by applications. It should always
|
|
70
|
+
be created via the client. The constructor is subject to change.
|
|
71
|
+
|
|
72
|
+
It is **not thread-safe**. It should not be used by multiple threads.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
client: bigtable client
|
|
76
|
+
instance_id: id of the instance on which the query is executed
|
|
77
|
+
request_body: dict representing the body of the ExecuteQueryRequest
|
|
78
|
+
attempt_timeout: the time budget for an individual network request, in seconds.
|
|
79
|
+
If it takes longer than this time to complete, the request will be cancelled with
|
|
80
|
+
a DeadlineExceeded exception, and a retry will be attempted.
|
|
81
|
+
operation_timeout: the time budget for the entire operation, in seconds.
|
|
82
|
+
Failed requests will be retried within the budget
|
|
83
|
+
req_metadata: metadata used while sending the gRPC request
|
|
84
|
+
retryable_excs: a list of errors that will be retried if encountered.
|
|
85
|
+
Raises:
|
|
86
|
+
None
|
|
87
|
+
:class:`ValueError <exceptions.ValueError>` as a safeguard if data is processed in an unexpected state
|
|
88
|
+
"""
|
|
89
|
+
self._table_name = None
|
|
90
|
+
self._app_profile_id = app_profile_id
|
|
91
|
+
self._client = client
|
|
92
|
+
self._instance_id = instance_id
|
|
93
|
+
self._prepare_metadata = prepare_metadata
|
|
94
|
+
self._final_metadata = None
|
|
95
|
+
self._byte_cursor = _ByteCursor()
|
|
96
|
+
self._reader: _Reader[QueryResultRow] = _QueryResultRowReader()
|
|
97
|
+
self.has_received_token = False
|
|
98
|
+
self._result_generator = self._next_impl()
|
|
99
|
+
self._register_instance_task = None
|
|
100
|
+
self._is_closed = False
|
|
101
|
+
self._request_body = request_body
|
|
102
|
+
self._attempt_timeout_gen = _attempt_timeout_generator(
|
|
103
|
+
attempt_timeout, operation_timeout
|
|
104
|
+
)
|
|
105
|
+
self._stream = CrossSync._Sync_Impl.retry_target_stream(
|
|
106
|
+
self._make_request_with_resume_token,
|
|
107
|
+
retries.if_exception_type(*retryable_excs),
|
|
108
|
+
retries.exponential_sleep_generator(0.01, 60, multiplier=2),
|
|
109
|
+
operation_timeout,
|
|
110
|
+
exception_factory=_retry_exception_factory,
|
|
111
|
+
)
|
|
112
|
+
self._req_metadata = req_metadata
|
|
113
|
+
try:
|
|
114
|
+
self._register_instance_task = CrossSync._Sync_Impl.create_task(
|
|
115
|
+
self._client._register_instance,
|
|
116
|
+
self._instance_id,
|
|
117
|
+
self,
|
|
118
|
+
sync_executor=self._client._executor,
|
|
119
|
+
)
|
|
120
|
+
except RuntimeError as e:
|
|
121
|
+
raise RuntimeError(
|
|
122
|
+
f"{self.__class__.__name__} must be created within an async event loop context."
|
|
123
|
+
) from e
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def is_closed(self) -> bool:
|
|
127
|
+
"""Returns True if the iterator is closed, False otherwise."""
|
|
128
|
+
return self._is_closed
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def app_profile_id(self) -> Optional[str]:
|
|
132
|
+
"""Returns the app_profile_id of the iterator."""
|
|
133
|
+
return self._app_profile_id
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def table_name(self) -> Optional[str]:
|
|
137
|
+
"""Returns the table_name of the iterator."""
|
|
138
|
+
return self._table_name
|
|
139
|
+
|
|
140
|
+
def _make_request_with_resume_token(self):
|
|
141
|
+
"""perfoms the rpc call using the correct resume token."""
|
|
142
|
+
resume_token = self._byte_cursor.prepare_for_new_request()
|
|
143
|
+
request = ExecuteQueryRequestPB(
|
|
144
|
+
{**self._request_body, "resume_token": resume_token}
|
|
145
|
+
)
|
|
146
|
+
return self._client._gapic_client.execute_query(
|
|
147
|
+
request,
|
|
148
|
+
timeout=next(self._attempt_timeout_gen),
|
|
149
|
+
metadata=self._req_metadata,
|
|
150
|
+
retry=None,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def _next_impl(self) -> CrossSync._Sync_Impl.Iterator[QueryResultRow]:
|
|
154
|
+
"""Generator wrapping the response stream which parses the stream results
|
|
155
|
+
and returns full `QueryResultRow`s."""
|
|
156
|
+
for response in self._stream:
|
|
157
|
+
try:
|
|
158
|
+
if self._final_metadata is None and _has_resume_token(response):
|
|
159
|
+
self._finalize_metadata()
|
|
160
|
+
batches_to_parse = self._byte_cursor.consume(response)
|
|
161
|
+
if not batches_to_parse:
|
|
162
|
+
continue
|
|
163
|
+
if not self.metadata:
|
|
164
|
+
raise ValueError(
|
|
165
|
+
"Error parsing response before finalizing metadata"
|
|
166
|
+
)
|
|
167
|
+
results = self._reader.consume(batches_to_parse, self.metadata)
|
|
168
|
+
if results is None:
|
|
169
|
+
continue
|
|
170
|
+
except ValueError as e:
|
|
171
|
+
raise InvalidExecuteQueryResponse(
|
|
172
|
+
"Invalid ExecuteQuery response received"
|
|
173
|
+
) from e
|
|
174
|
+
for result in results:
|
|
175
|
+
yield result
|
|
176
|
+
if self._final_metadata is None:
|
|
177
|
+
self._finalize_metadata()
|
|
178
|
+
self.close()
|
|
179
|
+
|
|
180
|
+
def __next__(self) -> QueryResultRow:
|
|
181
|
+
"""Yields QueryResultRows representing the results of the query.
|
|
182
|
+
|
|
183
|
+
:raises: :class:`ValueError <exceptions.ValueError>` as a safeguard if data is processed in an unexpected state
|
|
184
|
+
"""
|
|
185
|
+
if self._is_closed:
|
|
186
|
+
raise CrossSync._Sync_Impl.StopIteration
|
|
187
|
+
return self._result_generator.__next__()
|
|
188
|
+
|
|
189
|
+
def __iter__(self):
|
|
190
|
+
return self
|
|
191
|
+
|
|
192
|
+
def _finalize_metadata(self) -> None:
|
|
193
|
+
"""Sets _final_metadata to the metadata of the latest prepare_response.
|
|
194
|
+
The iterator should call this after either the first resume token is received or the
|
|
195
|
+
stream completes succesfully with no responses.
|
|
196
|
+
|
|
197
|
+
This can't be set on init because the metadata will be able to change due to plan refresh.
|
|
198
|
+
Plan refresh isn't implemented yet, but we want functionality to stay the same when it is.
|
|
199
|
+
|
|
200
|
+
For example the following scenario for query "SELECT * FROM table":
|
|
201
|
+
- Make a request, table has one column family 'cf'
|
|
202
|
+
- Return an incomplete batch
|
|
203
|
+
- request fails with transient error
|
|
204
|
+
- Meanwhile the table has had a second column family added 'cf2'
|
|
205
|
+
- Retry the request, get an error indicating the `prepared_query` has expired
|
|
206
|
+
- Refresh the prepared_query and retry the request, the new prepared_query
|
|
207
|
+
contains both 'cf' & 'cf2'
|
|
208
|
+
- It sends a new incomplete batch and resets the old outdated batch
|
|
209
|
+
- It send the next chunk with a checksum and resume_token, closing the batch.
|
|
210
|
+
In this we need to use the updated schema from the refreshed prepare request."""
|
|
211
|
+
self._final_metadata = self._prepare_metadata
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def metadata(self) -> Metadata:
|
|
215
|
+
"""Returns query metadata from the server or None if the iterator has been closed
|
|
216
|
+
or if metadata has not been set yet.
|
|
217
|
+
|
|
218
|
+
Metadata will not be set until the first row has been yielded or response with no rows
|
|
219
|
+
completes.
|
|
220
|
+
|
|
221
|
+
raises: :class:`EarlyMetadataCallError` when called before the first row has been returned
|
|
222
|
+
or the iterator has completed with no rows in the response."""
|
|
223
|
+
if not self._final_metadata:
|
|
224
|
+
raise EarlyMetadataCallError()
|
|
225
|
+
return self._final_metadata
|
|
226
|
+
|
|
227
|
+
def close(self) -> None:
|
|
228
|
+
"""Cancel all background tasks. Should be called all rows were processed.
|
|
229
|
+
|
|
230
|
+
:raises: :class:`ValueError <exceptions.ValueError>` if called in an invalid state
|
|
231
|
+
"""
|
|
232
|
+
if self._is_closed:
|
|
233
|
+
return
|
|
234
|
+
if not self._byte_cursor.empty():
|
|
235
|
+
raise ValueError("Unexpected buffered data at end of executeQuery reqest")
|
|
236
|
+
self._is_closed = True
|
|
237
|
+
if self._register_instance_task is not None:
|
|
238
|
+
self._register_instance_task.cancel()
|
|
239
|
+
self._client._remove_instance_registration(self._instance_id, self)
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
# Copyright 2024 Google LLC
|
|
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
|
+
"""
|
|
16
|
+
This module provides the SqlType class used for specifying types in
|
|
17
|
+
ExecuteQuery and some utilities.
|
|
18
|
+
|
|
19
|
+
The SqlTypes are used in Metadata returned by the ExecuteQuery operation as well
|
|
20
|
+
as for specifying query parameter types explicitly.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from collections import defaultdict
|
|
24
|
+
import datetime
|
|
25
|
+
from typing import Any, Dict, List, Optional, Set, Tuple, Type, Union
|
|
26
|
+
|
|
27
|
+
from google.api_core.datetime_helpers import DatetimeWithNanoseconds
|
|
28
|
+
from google.protobuf import timestamp_pb2 # type: ignore
|
|
29
|
+
from google.type import date_pb2 # type: ignore
|
|
30
|
+
|
|
31
|
+
from google.cloud.bigtable.data.execute_query.values import _NamedList
|
|
32
|
+
from google.cloud.bigtable_v2 import ResultSetMetadata
|
|
33
|
+
from google.cloud.bigtable_v2 import Type as PBType
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SqlType:
|
|
37
|
+
"""
|
|
38
|
+
Classes denoting types of values returned by Bigtable's ExecuteQuery operation.
|
|
39
|
+
|
|
40
|
+
Used in :class:`.Metadata`.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
class Type:
|
|
44
|
+
expected_type: Optional[type] = None
|
|
45
|
+
value_pb_dict_field_name: Optional[str] = None
|
|
46
|
+
type_field_name: Optional[str] = None
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def from_pb_type(cls, pb_type: Optional[PBType] = None):
|
|
50
|
+
return cls()
|
|
51
|
+
|
|
52
|
+
def _to_type_pb_dict(self) -> Dict[str, Any]:
|
|
53
|
+
if not self.type_field_name:
|
|
54
|
+
raise NotImplementedError(
|
|
55
|
+
"Fill in expected_type and value_pb_dict_field_name"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return {self.type_field_name: {}}
|
|
59
|
+
|
|
60
|
+
def _to_value_pb_dict(self, value: Any) -> Dict[str, Any]:
|
|
61
|
+
if self.expected_type is None or self.value_pb_dict_field_name is None:
|
|
62
|
+
raise NotImplementedError(
|
|
63
|
+
"Fill in expected_type and value_pb_dict_field_name"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if value is None:
|
|
67
|
+
return {}
|
|
68
|
+
|
|
69
|
+
if not isinstance(value, self.expected_type):
|
|
70
|
+
raise ValueError(
|
|
71
|
+
f"Expected query parameter of type {self.expected_type.__name__}, got {type(value).__name__}"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return {self.value_pb_dict_field_name: value}
|
|
75
|
+
|
|
76
|
+
def __eq__(self, other):
|
|
77
|
+
return isinstance(other, type(self))
|
|
78
|
+
|
|
79
|
+
def __str__(self) -> str:
|
|
80
|
+
return self.__class__.__name__
|
|
81
|
+
|
|
82
|
+
def __repr__(self) -> str:
|
|
83
|
+
return self.__str__()
|
|
84
|
+
|
|
85
|
+
class Struct(_NamedList[Type], Type):
|
|
86
|
+
"""Struct SQL type."""
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def from_pb_type(cls, type_pb: Optional[PBType] = None) -> "SqlType.Struct":
|
|
90
|
+
if type_pb is None:
|
|
91
|
+
raise ValueError("missing required argument type_pb")
|
|
92
|
+
fields: List[Tuple[Optional[str], SqlType.Type]] = []
|
|
93
|
+
for field in type_pb.struct_type.fields:
|
|
94
|
+
fields.append((field.field_name, _pb_type_to_metadata_type(field.type)))
|
|
95
|
+
return cls(fields)
|
|
96
|
+
|
|
97
|
+
def _to_value_pb_dict(self, value: Any):
|
|
98
|
+
raise NotImplementedError("Struct is not supported as a query parameter")
|
|
99
|
+
|
|
100
|
+
def _to_type_pb_dict(self) -> Dict[str, Any]:
|
|
101
|
+
raise NotImplementedError("Struct is not supported as a query parameter")
|
|
102
|
+
|
|
103
|
+
def __eq__(self, other: object):
|
|
104
|
+
# Cannot use super() here - we'd either have to:
|
|
105
|
+
# - call super() in these base classes, which would in turn call Object.__eq__
|
|
106
|
+
# to compare objects by identity and return a False, or
|
|
107
|
+
# - do not call super() in these base classes, which would result in calling only
|
|
108
|
+
# one of the __eq__ methods (a super() in the base class would be required to call the other one), or
|
|
109
|
+
# - call super() in only one of the base classes, but that would be error prone and changing
|
|
110
|
+
# the order of base classes would introduce unexpected behaviour.
|
|
111
|
+
# we also have to disable mypy because it doesn't see that SqlType.Struct == _NamedList[Type]
|
|
112
|
+
return SqlType.Type.__eq__(self, other) and _NamedList.__eq__(self, other) # type: ignore
|
|
113
|
+
|
|
114
|
+
def __str__(self):
|
|
115
|
+
return super(_NamedList, self).__str__()
|
|
116
|
+
|
|
117
|
+
class Array(Type):
|
|
118
|
+
"""Array SQL type."""
|
|
119
|
+
|
|
120
|
+
def __init__(self, element_type: "SqlType.Type"):
|
|
121
|
+
if isinstance(element_type, SqlType.Array):
|
|
122
|
+
raise ValueError("Arrays of arrays are not supported.")
|
|
123
|
+
if isinstance(element_type, SqlType.Map):
|
|
124
|
+
raise ValueError("Arrays of Maps are not supported.")
|
|
125
|
+
self._element_type = element_type
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def element_type(self):
|
|
129
|
+
return self._element_type
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def from_pb_type(cls, type_pb: Optional[PBType] = None) -> "SqlType.Array":
|
|
133
|
+
if type_pb is None:
|
|
134
|
+
raise ValueError("missing required argument type_pb")
|
|
135
|
+
return cls(_pb_type_to_metadata_type(type_pb.array_type.element_type))
|
|
136
|
+
|
|
137
|
+
def _to_value_pb_dict(self, value: Any):
|
|
138
|
+
if value is None:
|
|
139
|
+
return {}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
"array_value": {
|
|
143
|
+
"values": [
|
|
144
|
+
self.element_type._to_value_pb_dict(entry) for entry in value
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
def _to_type_pb_dict(self) -> Dict[str, Any]:
|
|
150
|
+
return {
|
|
151
|
+
"array_type": {"element_type": self.element_type._to_type_pb_dict()}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
def __eq__(self, other):
|
|
155
|
+
return super().__eq__(other) and self.element_type == other.element_type
|
|
156
|
+
|
|
157
|
+
def __str__(self) -> str:
|
|
158
|
+
return f"{self.__class__.__name__}<{str(self.element_type)}>"
|
|
159
|
+
|
|
160
|
+
class Map(Type):
|
|
161
|
+
"""Map SQL type."""
|
|
162
|
+
|
|
163
|
+
def __init__(self, key_type: "SqlType.Type", value_type: "SqlType.Type"):
|
|
164
|
+
self._key_type = key_type
|
|
165
|
+
self._value_type = value_type
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def key_type(self):
|
|
169
|
+
return self._key_type
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def value_type(self):
|
|
173
|
+
return self._value_type
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def from_pb_type(cls, type_pb: Optional[PBType] = None) -> "SqlType.Map":
|
|
177
|
+
if type_pb is None:
|
|
178
|
+
raise ValueError("missing required argument type_pb")
|
|
179
|
+
return cls(
|
|
180
|
+
_pb_type_to_metadata_type(type_pb.map_type.key_type),
|
|
181
|
+
_pb_type_to_metadata_type(type_pb.map_type.value_type),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def _to_type_pb_dict(self) -> Dict[str, Any]:
|
|
185
|
+
raise NotImplementedError("Map is not supported as a query parameter")
|
|
186
|
+
|
|
187
|
+
def _to_value_pb_dict(self, value: Any):
|
|
188
|
+
raise NotImplementedError("Map is not supported as a query parameter")
|
|
189
|
+
|
|
190
|
+
def __eq__(self, other):
|
|
191
|
+
return (
|
|
192
|
+
super().__eq__(other)
|
|
193
|
+
and self.key_type == other.key_type
|
|
194
|
+
and self.value_type == other.value_type
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def __str__(self) -> str:
|
|
198
|
+
return (
|
|
199
|
+
f"{self.__class__.__name__}<"
|
|
200
|
+
f"{str(self._key_type)},{str(self._value_type)}>"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
class Bytes(Type):
|
|
204
|
+
"""Bytes SQL type."""
|
|
205
|
+
|
|
206
|
+
expected_type = bytes
|
|
207
|
+
value_pb_dict_field_name = "bytes_value"
|
|
208
|
+
type_field_name = "bytes_type"
|
|
209
|
+
|
|
210
|
+
class String(Type):
|
|
211
|
+
"""String SQL type."""
|
|
212
|
+
|
|
213
|
+
expected_type = str
|
|
214
|
+
value_pb_dict_field_name = "string_value"
|
|
215
|
+
type_field_name = "string_type"
|
|
216
|
+
|
|
217
|
+
class Int64(Type):
|
|
218
|
+
"""Int64 SQL type."""
|
|
219
|
+
|
|
220
|
+
expected_type = int
|
|
221
|
+
value_pb_dict_field_name = "int_value"
|
|
222
|
+
type_field_name = "int64_type"
|
|
223
|
+
|
|
224
|
+
class Float64(Type):
|
|
225
|
+
"""Float64 SQL type."""
|
|
226
|
+
|
|
227
|
+
expected_type = float
|
|
228
|
+
value_pb_dict_field_name = "float_value"
|
|
229
|
+
type_field_name = "float64_type"
|
|
230
|
+
|
|
231
|
+
class Float32(Type):
|
|
232
|
+
"""Float32 SQL type."""
|
|
233
|
+
|
|
234
|
+
expected_type = float
|
|
235
|
+
value_pb_dict_field_name = "float_value"
|
|
236
|
+
type_field_name = "float32_type"
|
|
237
|
+
|
|
238
|
+
class Bool(Type):
|
|
239
|
+
"""Bool SQL type."""
|
|
240
|
+
|
|
241
|
+
expected_type = bool
|
|
242
|
+
value_pb_dict_field_name = "bool_value"
|
|
243
|
+
type_field_name = "bool_type"
|
|
244
|
+
|
|
245
|
+
class Timestamp(Type):
|
|
246
|
+
"""
|
|
247
|
+
Timestamp SQL type.
|
|
248
|
+
|
|
249
|
+
Timestamp supports :class:`DatetimeWithNanoseconds` but Bigtable SQL does
|
|
250
|
+
not currently support nanoseconds precision. We support this for potential
|
|
251
|
+
compatibility in the future. Nanoseconds are currently ignored.
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
type_field_name = "timestamp_type"
|
|
255
|
+
expected_types = (
|
|
256
|
+
datetime.datetime,
|
|
257
|
+
DatetimeWithNanoseconds,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def _to_value_pb_dict(self, value: Any) -> Dict[str, Any]:
|
|
261
|
+
if value is None:
|
|
262
|
+
return {}
|
|
263
|
+
|
|
264
|
+
if not isinstance(value, self.expected_types):
|
|
265
|
+
raise ValueError(
|
|
266
|
+
f"Expected one of {', '.join((_type.__name__ for _type in self.expected_types))}"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
if isinstance(value, DatetimeWithNanoseconds):
|
|
270
|
+
return {"timestamp_value": value.timestamp_pb()}
|
|
271
|
+
else: # value must be an instance of datetime.datetime
|
|
272
|
+
ts = timestamp_pb2.Timestamp()
|
|
273
|
+
ts.FromDatetime(value)
|
|
274
|
+
return {"timestamp_value": ts}
|
|
275
|
+
|
|
276
|
+
class Date(Type):
|
|
277
|
+
"""Date SQL type."""
|
|
278
|
+
|
|
279
|
+
type_field_name = "date_type"
|
|
280
|
+
expected_type = datetime.date
|
|
281
|
+
|
|
282
|
+
def _to_value_pb_dict(self, value: Any) -> Dict[str, Any]:
|
|
283
|
+
if value is None:
|
|
284
|
+
return {}
|
|
285
|
+
|
|
286
|
+
if not isinstance(value, self.expected_type):
|
|
287
|
+
raise ValueError(
|
|
288
|
+
f"Expected query parameter of type {self.expected_type.__name__}, got {type(value).__name__}"
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
"date_value": date_pb2.Date(
|
|
293
|
+
year=value.year,
|
|
294
|
+
month=value.month,
|
|
295
|
+
day=value.day,
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class Metadata:
|
|
301
|
+
"""
|
|
302
|
+
Metadata class for the ExecuteQuery operation.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
columns (List[Tuple[Optional[str], SqlType.Type]]): List of column
|
|
306
|
+
metadata tuples. Each tuple contains the column name and the column
|
|
307
|
+
type.
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
class Column:
|
|
311
|
+
def __init__(self, column_name: Optional[str], column_type: SqlType.Type):
|
|
312
|
+
self._column_name = column_name
|
|
313
|
+
self._column_type = column_type
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def column_name(self) -> Optional[str]:
|
|
317
|
+
return self._column_name
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def column_type(self) -> SqlType.Type:
|
|
321
|
+
return self._column_type
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def columns(self) -> List[Column]:
|
|
325
|
+
return self._columns
|
|
326
|
+
|
|
327
|
+
def __init__(
|
|
328
|
+
self, columns: Optional[List[Tuple[Optional[str], SqlType.Type]]] = None
|
|
329
|
+
):
|
|
330
|
+
self._columns: List[Metadata.Column] = []
|
|
331
|
+
self._column_indexes: Dict[str, List[int]] = defaultdict(list)
|
|
332
|
+
self._duplicate_names: Set[str] = set()
|
|
333
|
+
|
|
334
|
+
if columns:
|
|
335
|
+
for column_name, column_type in columns:
|
|
336
|
+
if column_name is not None:
|
|
337
|
+
if column_name in self._column_indexes:
|
|
338
|
+
self._duplicate_names.add(column_name)
|
|
339
|
+
self._column_indexes[column_name].append(len(self._columns))
|
|
340
|
+
self._columns.append(Metadata.Column(column_name, column_type))
|
|
341
|
+
|
|
342
|
+
def __getitem__(self, index_or_name: Union[str, int]) -> Column:
|
|
343
|
+
if isinstance(index_or_name, str):
|
|
344
|
+
if index_or_name in self._duplicate_names:
|
|
345
|
+
raise KeyError(
|
|
346
|
+
f"Ambigious column name: '{index_or_name}', use index instead."
|
|
347
|
+
f" Field present on indexes {', '.join(map(str, self._column_indexes[index_or_name]))}."
|
|
348
|
+
)
|
|
349
|
+
if index_or_name not in self._column_indexes:
|
|
350
|
+
raise KeyError(f"No such column: {index_or_name}")
|
|
351
|
+
index = self._column_indexes[index_or_name][0]
|
|
352
|
+
else:
|
|
353
|
+
index = index_or_name
|
|
354
|
+
return self._columns[index]
|
|
355
|
+
|
|
356
|
+
def __len__(self):
|
|
357
|
+
return len(self._columns)
|
|
358
|
+
|
|
359
|
+
def __str__(self) -> str:
|
|
360
|
+
columns_str = ", ".join([str(column) for column in self._columns])
|
|
361
|
+
return f"{self.__class__.__name__}([{columns_str}])"
|
|
362
|
+
|
|
363
|
+
def __repr__(self) -> str:
|
|
364
|
+
return self.__str__()
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _pb_metadata_to_metadata_types(
|
|
368
|
+
metadata_pb: ResultSetMetadata,
|
|
369
|
+
) -> Metadata:
|
|
370
|
+
if "proto_schema" in metadata_pb:
|
|
371
|
+
fields: List[Tuple[Optional[str], SqlType.Type]] = []
|
|
372
|
+
for column_metadata in metadata_pb.proto_schema.columns:
|
|
373
|
+
fields.append(
|
|
374
|
+
(column_metadata.name, _pb_type_to_metadata_type(column_metadata.type))
|
|
375
|
+
)
|
|
376
|
+
return Metadata(fields)
|
|
377
|
+
raise ValueError("Invalid ResultSetMetadata object received.")
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
_PROTO_TYPE_TO_METADATA_TYPE_FACTORY: Dict[str, Type[SqlType.Type]] = {
|
|
381
|
+
"bytes_type": SqlType.Bytes,
|
|
382
|
+
"string_type": SqlType.String,
|
|
383
|
+
"int64_type": SqlType.Int64,
|
|
384
|
+
"float32_type": SqlType.Float32,
|
|
385
|
+
"float64_type": SqlType.Float64,
|
|
386
|
+
"bool_type": SqlType.Bool,
|
|
387
|
+
"timestamp_type": SqlType.Timestamp,
|
|
388
|
+
"date_type": SqlType.Date,
|
|
389
|
+
"struct_type": SqlType.Struct,
|
|
390
|
+
"array_type": SqlType.Array,
|
|
391
|
+
"map_type": SqlType.Map,
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _pb_type_to_metadata_type(type_pb: PBType) -> SqlType.Type:
|
|
396
|
+
kind = PBType.pb(type_pb).WhichOneof("kind")
|
|
397
|
+
if kind in _PROTO_TYPE_TO_METADATA_TYPE_FACTORY:
|
|
398
|
+
return _PROTO_TYPE_TO_METADATA_TYPE_FACTORY[kind].from_pb_type(type_pb)
|
|
399
|
+
raise ValueError(f"Unrecognized response data type: {type_pb}")
|