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.
Files changed (115) hide show
  1. google/cloud/bigtable/__init__.py +25 -0
  2. google/cloud/bigtable/app_profile.py +377 -0
  3. google/cloud/bigtable/backup.py +490 -0
  4. google/cloud/bigtable/batcher.py +414 -0
  5. google/cloud/bigtable/client.py +475 -0
  6. google/cloud/bigtable/cluster.py +541 -0
  7. google/cloud/bigtable/column_family.py +362 -0
  8. google/cloud/bigtable/data/__init__.py +103 -0
  9. google/cloud/bigtable/data/_async/__init__.py +25 -0
  10. google/cloud/bigtable/data/_async/_mutate_rows.py +226 -0
  11. google/cloud/bigtable/data/_async/_read_rows.py +363 -0
  12. google/cloud/bigtable/data/_async/client.py +1522 -0
  13. google/cloud/bigtable/data/_async/mutations_batcher.py +534 -0
  14. google/cloud/bigtable/data/_cross_sync/__init__.py +20 -0
  15. google/cloud/bigtable/data/_cross_sync/_decorators.py +441 -0
  16. google/cloud/bigtable/data/_cross_sync/_mapping_meta.py +64 -0
  17. google/cloud/bigtable/data/_cross_sync/cross_sync.py +334 -0
  18. google/cloud/bigtable/data/_helpers.py +249 -0
  19. google/cloud/bigtable/data/_sync_autogen/_mutate_rows.py +182 -0
  20. google/cloud/bigtable/data/_sync_autogen/_read_rows.py +304 -0
  21. google/cloud/bigtable/data/_sync_autogen/client.py +1293 -0
  22. google/cloud/bigtable/data/_sync_autogen/mutations_batcher.py +449 -0
  23. google/cloud/bigtable/data/exceptions.py +340 -0
  24. google/cloud/bigtable/data/execute_query/__init__.py +43 -0
  25. google/cloud/bigtable/data/execute_query/_async/__init__.py +13 -0
  26. google/cloud/bigtable/data/execute_query/_async/execute_query_iterator.py +291 -0
  27. google/cloud/bigtable/data/execute_query/_byte_cursor.py +123 -0
  28. google/cloud/bigtable/data/execute_query/_checksum.py +43 -0
  29. google/cloud/bigtable/data/execute_query/_parameters_formatting.py +155 -0
  30. google/cloud/bigtable/data/execute_query/_query_result_parsing_utils.py +134 -0
  31. google/cloud/bigtable/data/execute_query/_reader.py +122 -0
  32. google/cloud/bigtable/data/execute_query/_sync_autogen/execute_query_iterator.py +239 -0
  33. google/cloud/bigtable/data/execute_query/metadata.py +399 -0
  34. google/cloud/bigtable/data/execute_query/values.py +123 -0
  35. google/cloud/bigtable/data/mutations.py +380 -0
  36. google/cloud/bigtable/data/read_modify_write_rules.py +112 -0
  37. google/cloud/bigtable/data/read_rows_query.py +536 -0
  38. google/cloud/bigtable/data/row.py +535 -0
  39. google/cloud/bigtable/data/row_filters.py +968 -0
  40. google/cloud/bigtable/encryption_info.py +64 -0
  41. google/cloud/bigtable/enums.py +223 -0
  42. google/cloud/bigtable/error.py +64 -0
  43. google/cloud/bigtable/gapic_version.py +16 -0
  44. google/cloud/bigtable/helpers.py +31 -0
  45. google/cloud/bigtable/instance.py +789 -0
  46. google/cloud/bigtable/policy.py +255 -0
  47. google/cloud/bigtable/row.py +1267 -0
  48. google/cloud/bigtable/row_data.py +380 -0
  49. google/cloud/bigtable/row_filters.py +838 -0
  50. google/cloud/bigtable/row_merger.py +250 -0
  51. google/cloud/bigtable/row_set.py +213 -0
  52. google/cloud/bigtable/table.py +1409 -0
  53. google/cloud/bigtable_admin/__init__.py +410 -0
  54. google/cloud/bigtable_admin/gapic_version.py +16 -0
  55. google/cloud/bigtable_admin/py.typed +2 -0
  56. google/cloud/bigtable_admin_v2/__init__.py +250 -0
  57. google/cloud/bigtable_admin_v2/gapic_metadata.json +962 -0
  58. google/cloud/bigtable_admin_v2/gapic_version.py +16 -0
  59. google/cloud/bigtable_admin_v2/py.typed +2 -0
  60. google/cloud/bigtable_admin_v2/services/__init__.py +15 -0
  61. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/__init__.py +22 -0
  62. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/async_client.py +3465 -0
  63. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/client.py +3944 -0
  64. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/pagers.py +681 -0
  65. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/__init__.py +40 -0
  66. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/base.py +751 -0
  67. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc.py +1249 -0
  68. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc_asyncio.py +1579 -0
  69. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/rest.py +6820 -0
  70. google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/rest_base.py +1746 -0
  71. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/__init__.py +22 -0
  72. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/async_client.py +3472 -0
  73. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/client.py +3949 -0
  74. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/pagers.py +669 -0
  75. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/__init__.py +40 -0
  76. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/base.py +708 -0
  77. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/grpc.py +1257 -0
  78. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/grpc_asyncio.py +1549 -0
  79. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/rest.py +6609 -0
  80. google/cloud/bigtable_admin_v2/services/bigtable_table_admin/transports/rest_base.py +1714 -0
  81. google/cloud/bigtable_admin_v2/types/__init__.py +248 -0
  82. google/cloud/bigtable_admin_v2/types/bigtable_instance_admin.py +1364 -0
  83. google/cloud/bigtable_admin_v2/types/bigtable_table_admin.py +1715 -0
  84. google/cloud/bigtable_admin_v2/types/common.py +81 -0
  85. google/cloud/bigtable_admin_v2/types/instance.py +819 -0
  86. google/cloud/bigtable_admin_v2/types/table.py +1028 -0
  87. google/cloud/bigtable_admin_v2/types/types.py +776 -0
  88. google/cloud/bigtable_v2/__init__.py +136 -0
  89. google/cloud/bigtable_v2/gapic_metadata.json +193 -0
  90. google/cloud/bigtable_v2/gapic_version.py +16 -0
  91. google/cloud/bigtable_v2/py.typed +2 -0
  92. google/cloud/bigtable_v2/services/__init__.py +15 -0
  93. google/cloud/bigtable_v2/services/bigtable/__init__.py +22 -0
  94. google/cloud/bigtable_v2/services/bigtable/async_client.py +1741 -0
  95. google/cloud/bigtable_v2/services/bigtable/client.py +2194 -0
  96. google/cloud/bigtable_v2/services/bigtable/transports/__init__.py +38 -0
  97. google/cloud/bigtable_v2/services/bigtable/transports/base.py +343 -0
  98. google/cloud/bigtable_v2/services/bigtable/transports/grpc.py +660 -0
  99. google/cloud/bigtable_v2/services/bigtable/transports/grpc_asyncio.py +762 -0
  100. google/cloud/bigtable_v2/services/bigtable/transports/rest.py +2489 -0
  101. google/cloud/bigtable_v2/services/bigtable/transports/rest_base.py +711 -0
  102. google/cloud/bigtable_v2/types/__init__.py +138 -0
  103. google/cloud/bigtable_v2/types/bigtable.py +1531 -0
  104. google/cloud/bigtable_v2/types/data.py +1612 -0
  105. google/cloud/bigtable_v2/types/feature_flags.py +119 -0
  106. google/cloud/bigtable_v2/types/request_stats.py +171 -0
  107. google/cloud/bigtable_v2/types/response_params.py +64 -0
  108. google/cloud/bigtable_v2/types/types.py +579 -0
  109. google_cloud_bigtable-2.30.0.data/scripts/fixup_bigtable_admin_v2_keywords.py +233 -0
  110. google_cloud_bigtable-2.30.0.data/scripts/fixup_bigtable_v2_keywords.py +186 -0
  111. google_cloud_bigtable-2.30.0.dist-info/LICENSE +202 -0
  112. google_cloud_bigtable-2.30.0.dist-info/METADATA +154 -0
  113. google_cloud_bigtable-2.30.0.dist-info/RECORD +115 -0
  114. google_cloud_bigtable-2.30.0.dist-info/WHEEL +5 -0
  115. 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}")