google-cloud-spanner 3.55.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. google/cloud/spanner.py +47 -0
  2. google/cloud/spanner_admin_database_v1/__init__.py +146 -0
  3. google/cloud/spanner_admin_database_v1/gapic_metadata.json +418 -0
  4. google/cloud/spanner_admin_database_v1/gapic_version.py +16 -0
  5. google/cloud/spanner_admin_database_v1/py.typed +2 -0
  6. google/cloud/spanner_admin_database_v1/services/__init__.py +15 -0
  7. google/cloud/spanner_admin_database_v1/services/database_admin/__init__.py +22 -0
  8. google/cloud/spanner_admin_database_v1/services/database_admin/async_client.py +4097 -0
  9. google/cloud/spanner_admin_database_v1/services/database_admin/client.py +4602 -0
  10. google/cloud/spanner_admin_database_v1/services/database_admin/pagers.py +989 -0
  11. google/cloud/spanner_admin_database_v1/services/database_admin/transports/__init__.py +38 -0
  12. google/cloud/spanner_admin_database_v1/services/database_admin/transports/base.py +820 -0
  13. google/cloud/spanner_admin_database_v1/services/database_admin/transports/grpc.py +1303 -0
  14. google/cloud/spanner_admin_database_v1/services/database_admin/transports/grpc_asyncio.py +1688 -0
  15. google/cloud/spanner_admin_database_v1/services/database_admin/transports/rest.py +6512 -0
  16. google/cloud/spanner_admin_database_v1/services/database_admin/transports/rest_base.py +1650 -0
  17. google/cloud/spanner_admin_database_v1/types/__init__.py +144 -0
  18. google/cloud/spanner_admin_database_v1/types/backup.py +1106 -0
  19. google/cloud/spanner_admin_database_v1/types/backup_schedule.py +369 -0
  20. google/cloud/spanner_admin_database_v1/types/common.py +180 -0
  21. google/cloud/spanner_admin_database_v1/types/spanner_database_admin.py +1303 -0
  22. google/cloud/spanner_admin_instance_v1/__init__.py +110 -0
  23. google/cloud/spanner_admin_instance_v1/gapic_metadata.json +343 -0
  24. google/cloud/spanner_admin_instance_v1/gapic_version.py +16 -0
  25. google/cloud/spanner_admin_instance_v1/py.typed +2 -0
  26. google/cloud/spanner_admin_instance_v1/services/__init__.py +15 -0
  27. google/cloud/spanner_admin_instance_v1/services/instance_admin/__init__.py +22 -0
  28. google/cloud/spanner_admin_instance_v1/services/instance_admin/async_client.py +3466 -0
  29. google/cloud/spanner_admin_instance_v1/services/instance_admin/client.py +3881 -0
  30. google/cloud/spanner_admin_instance_v1/services/instance_admin/pagers.py +856 -0
  31. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/__init__.py +38 -0
  32. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/base.py +545 -0
  33. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/grpc.py +1347 -0
  34. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/grpc_asyncio.py +1539 -0
  35. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/rest.py +4834 -0
  36. google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/rest_base.py +1198 -0
  37. google/cloud/spanner_admin_instance_v1/types/__init__.py +104 -0
  38. google/cloud/spanner_admin_instance_v1/types/common.py +99 -0
  39. google/cloud/spanner_admin_instance_v1/types/spanner_instance_admin.py +2375 -0
  40. google/cloud/spanner_dbapi/__init__.py +93 -0
  41. google/cloud/spanner_dbapi/_helpers.py +113 -0
  42. google/cloud/spanner_dbapi/batch_dml_executor.py +135 -0
  43. google/cloud/spanner_dbapi/checksum.py +80 -0
  44. google/cloud/spanner_dbapi/client_side_statement_executor.py +140 -0
  45. google/cloud/spanner_dbapi/client_side_statement_parser.py +106 -0
  46. google/cloud/spanner_dbapi/connection.py +818 -0
  47. google/cloud/spanner_dbapi/cursor.py +609 -0
  48. google/cloud/spanner_dbapi/exceptions.py +172 -0
  49. google/cloud/spanner_dbapi/parse_utils.py +392 -0
  50. google/cloud/spanner_dbapi/parsed_statement.py +63 -0
  51. google/cloud/spanner_dbapi/parser.py +258 -0
  52. google/cloud/spanner_dbapi/partition_helper.py +41 -0
  53. google/cloud/spanner_dbapi/transaction_helper.py +294 -0
  54. google/cloud/spanner_dbapi/types.py +106 -0
  55. google/cloud/spanner_dbapi/utils.py +147 -0
  56. google/cloud/spanner_dbapi/version.py +20 -0
  57. google/cloud/spanner_v1/__init__.py +154 -0
  58. google/cloud/spanner_v1/_helpers.py +751 -0
  59. google/cloud/spanner_v1/_opentelemetry_tracing.py +165 -0
  60. google/cloud/spanner_v1/backup.py +397 -0
  61. google/cloud/spanner_v1/batch.py +433 -0
  62. google/cloud/spanner_v1/client.py +538 -0
  63. google/cloud/spanner_v1/data_types.py +350 -0
  64. google/cloud/spanner_v1/database.py +1968 -0
  65. google/cloud/spanner_v1/database_sessions_manager.py +249 -0
  66. google/cloud/spanner_v1/gapic_metadata.json +268 -0
  67. google/cloud/spanner_v1/gapic_version.py +16 -0
  68. google/cloud/spanner_v1/instance.py +735 -0
  69. google/cloud/spanner_v1/keyset.py +193 -0
  70. google/cloud/spanner_v1/merged_result_set.py +146 -0
  71. google/cloud/spanner_v1/metrics/constants.py +71 -0
  72. google/cloud/spanner_v1/metrics/metrics_capture.py +75 -0
  73. google/cloud/spanner_v1/metrics/metrics_exporter.py +384 -0
  74. google/cloud/spanner_v1/metrics/metrics_interceptor.py +156 -0
  75. google/cloud/spanner_v1/metrics/metrics_tracer.py +588 -0
  76. google/cloud/spanner_v1/metrics/metrics_tracer_factory.py +328 -0
  77. google/cloud/spanner_v1/metrics/spanner_metrics_tracer_factory.py +172 -0
  78. google/cloud/spanner_v1/param_types.py +110 -0
  79. google/cloud/spanner_v1/pool.py +813 -0
  80. google/cloud/spanner_v1/py.typed +2 -0
  81. google/cloud/spanner_v1/request_id_header.py +64 -0
  82. google/cloud/spanner_v1/services/__init__.py +15 -0
  83. google/cloud/spanner_v1/services/spanner/__init__.py +22 -0
  84. google/cloud/spanner_v1/services/spanner/async_client.py +2205 -0
  85. google/cloud/spanner_v1/services/spanner/client.py +2624 -0
  86. google/cloud/spanner_v1/services/spanner/pagers.py +196 -0
  87. google/cloud/spanner_v1/services/spanner/transports/__init__.py +38 -0
  88. google/cloud/spanner_v1/services/spanner/transports/base.py +520 -0
  89. google/cloud/spanner_v1/services/spanner/transports/grpc.py +911 -0
  90. google/cloud/spanner_v1/services/spanner/transports/grpc_asyncio.py +1144 -0
  91. google/cloud/spanner_v1/services/spanner/transports/rest.py +3468 -0
  92. google/cloud/spanner_v1/services/spanner/transports/rest_base.py +981 -0
  93. google/cloud/spanner_v1/session.py +631 -0
  94. google/cloud/spanner_v1/session_options.py +133 -0
  95. google/cloud/spanner_v1/snapshot.py +1057 -0
  96. google/cloud/spanner_v1/streamed.py +402 -0
  97. google/cloud/spanner_v1/table.py +181 -0
  98. google/cloud/spanner_v1/testing/__init__.py +0 -0
  99. google/cloud/spanner_v1/testing/database_test.py +121 -0
  100. google/cloud/spanner_v1/testing/interceptors.py +118 -0
  101. google/cloud/spanner_v1/testing/mock_database_admin.py +38 -0
  102. google/cloud/spanner_v1/testing/mock_spanner.py +261 -0
  103. google/cloud/spanner_v1/testing/spanner_database_admin_pb2_grpc.py +1267 -0
  104. google/cloud/spanner_v1/testing/spanner_pb2_grpc.py +882 -0
  105. google/cloud/spanner_v1/transaction.py +747 -0
  106. google/cloud/spanner_v1/types/__init__.py +118 -0
  107. google/cloud/spanner_v1/types/commit_response.py +94 -0
  108. google/cloud/spanner_v1/types/keys.py +248 -0
  109. google/cloud/spanner_v1/types/mutation.py +201 -0
  110. google/cloud/spanner_v1/types/query_plan.py +220 -0
  111. google/cloud/spanner_v1/types/result_set.py +379 -0
  112. google/cloud/spanner_v1/types/spanner.py +1815 -0
  113. google/cloud/spanner_v1/types/transaction.py +818 -0
  114. google/cloud/spanner_v1/types/type.py +288 -0
  115. google_cloud_spanner-3.55.0.dist-info/LICENSE +202 -0
  116. google_cloud_spanner-3.55.0.dist-info/METADATA +318 -0
  117. google_cloud_spanner-3.55.0.dist-info/RECORD +119 -0
  118. google_cloud_spanner-3.55.0.dist-info/WHEEL +5 -0
  119. google_cloud_spanner-3.55.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,93 @@
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
+ """Connection-based DB API for Cloud Spanner."""
16
+
17
+ from google.cloud.spanner_dbapi.connection import Connection
18
+ from google.cloud.spanner_dbapi.connection import connect
19
+
20
+ from google.cloud.spanner_dbapi.cursor import Cursor
21
+
22
+ from google.cloud.spanner_dbapi.exceptions import DatabaseError
23
+ from google.cloud.spanner_dbapi.exceptions import DataError
24
+ from google.cloud.spanner_dbapi.exceptions import Error
25
+ from google.cloud.spanner_dbapi.exceptions import IntegrityError
26
+ from google.cloud.spanner_dbapi.exceptions import InterfaceError
27
+ from google.cloud.spanner_dbapi.exceptions import InternalError
28
+ from google.cloud.spanner_dbapi.exceptions import NotSupportedError
29
+ from google.cloud.spanner_dbapi.exceptions import OperationalError
30
+ from google.cloud.spanner_dbapi.exceptions import ProgrammingError
31
+ from google.cloud.spanner_dbapi.exceptions import Warning
32
+
33
+ from google.cloud.spanner_dbapi.parse_utils import get_param_types
34
+
35
+ from google.cloud.spanner_dbapi.types import BINARY
36
+ from google.cloud.spanner_dbapi.types import DATETIME
37
+ from google.cloud.spanner_dbapi.types import NUMBER
38
+ from google.cloud.spanner_dbapi.types import ROWID
39
+ from google.cloud.spanner_dbapi.types import STRING
40
+ from google.cloud.spanner_dbapi.types import Binary
41
+ from google.cloud.spanner_dbapi.types import Date
42
+ from google.cloud.spanner_dbapi.types import DateFromTicks
43
+ from google.cloud.spanner_dbapi.types import Time
44
+ from google.cloud.spanner_dbapi.types import TimeFromTicks
45
+ from google.cloud.spanner_dbapi.types import Timestamp
46
+ from google.cloud.spanner_dbapi.types import TimestampStr
47
+ from google.cloud.spanner_dbapi.types import TimestampFromTicks
48
+
49
+ from google.cloud.spanner_dbapi.version import DEFAULT_USER_AGENT
50
+
51
+ apilevel = "2.0" # supports DP-API 2.0 level.
52
+ paramstyle = "format" # ANSI C printf format codes, e.g. ...WHERE name=%s.
53
+
54
+ # Threads may share the module, but not connections. This is a paranoid threadsafety
55
+ # level, but it is necessary for starters to use when debugging failures.
56
+ # Eventually once transactions are working properly, we'll update the
57
+ # threadsafety level.
58
+ threadsafety = 1
59
+
60
+
61
+ __all__ = [
62
+ "Connection",
63
+ "connect",
64
+ "Cursor",
65
+ "DatabaseError",
66
+ "DataError",
67
+ "Error",
68
+ "IntegrityError",
69
+ "InterfaceError",
70
+ "InternalError",
71
+ "NotSupportedError",
72
+ "OperationalError",
73
+ "ProgrammingError",
74
+ "Warning",
75
+ "DEFAULT_USER_AGENT",
76
+ "apilevel",
77
+ "paramstyle",
78
+ "threadsafety",
79
+ "get_param_types",
80
+ "Binary",
81
+ "Date",
82
+ "DateFromTicks",
83
+ "Time",
84
+ "TimeFromTicks",
85
+ "Timestamp",
86
+ "TimestampFromTicks",
87
+ "BINARY",
88
+ "STRING",
89
+ "NUMBER",
90
+ "DATETIME",
91
+ "ROWID",
92
+ "TimestampStr",
93
+ ]
@@ -0,0 +1,113 @@
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
+ from google.cloud.spanner_v1 import param_types
16
+
17
+
18
+ SQL_LIST_TABLES = """
19
+ SELECT table_name
20
+ FROM information_schema.tables
21
+ WHERE table_catalog = ''
22
+ AND table_schema = @table_schema
23
+ AND table_type = 'BASE TABLE'
24
+ """
25
+
26
+ SQL_LIST_TABLES_AND_VIEWS = """
27
+ SELECT table_name
28
+ FROM information_schema.tables
29
+ WHERE table_catalog = '' AND table_schema = @table_schema
30
+ """
31
+
32
+ SQL_GET_TABLE_COLUMN_SCHEMA = """
33
+ SELECT COLUMN_NAME, IS_NULLABLE, SPANNER_TYPE
34
+ FROM INFORMATION_SCHEMA.COLUMNS
35
+ WHERE TABLE_SCHEMA = @schema_name AND TABLE_NAME = @table_name
36
+ """
37
+
38
+ # This table maps spanner_types to Spanner's data type sizes as per
39
+ # https://cloud.google.com/spanner/docs/data-types#allowable-types
40
+ # It is used to map `display_size` to a known type for Cursor.description
41
+ # after a row fetch.
42
+ # Since ResultMetadata
43
+ # https://cloud.google.com/spanner/docs/reference/rest/v1/ResultSetMetadata
44
+ # does not send back the actual size, we have to lookup the respective size.
45
+ # Some fields' sizes are dependent upon the dynamic data hence aren't sent back
46
+ # by Cloud Spanner.
47
+ CODE_TO_DISPLAY_SIZE = {
48
+ param_types.BOOL.code: 1,
49
+ param_types.DATE.code: 4,
50
+ param_types.FLOAT64.code: 8,
51
+ param_types.FLOAT32.code: 4,
52
+ param_types.INT64.code: 8,
53
+ param_types.TIMESTAMP.code: 12,
54
+ }
55
+
56
+
57
+ class ColumnInfo:
58
+ """Row column description object."""
59
+
60
+ def __init__(
61
+ self,
62
+ name,
63
+ type_code,
64
+ display_size=None,
65
+ internal_size=None,
66
+ precision=None,
67
+ scale=None,
68
+ null_ok=False,
69
+ ):
70
+ self.name = name
71
+ self.type_code = type_code
72
+ self.display_size = display_size
73
+ self.internal_size = internal_size
74
+ self.precision = precision
75
+ self.scale = scale
76
+ self.null_ok = null_ok
77
+
78
+ self.fields = (
79
+ self.name,
80
+ self.type_code,
81
+ self.display_size,
82
+ self.internal_size,
83
+ self.precision,
84
+ self.scale,
85
+ self.null_ok,
86
+ )
87
+
88
+ def __repr__(self):
89
+ return self.__str__()
90
+
91
+ def __getitem__(self, index):
92
+ return self.fields[index]
93
+
94
+ def __str__(self):
95
+ str_repr = ", ".join(
96
+ filter(
97
+ lambda part: part is not None,
98
+ [
99
+ "name='%s'" % self.name,
100
+ "type_code=%d" % self.type_code,
101
+ "display_size=%d" % self.display_size
102
+ if self.display_size
103
+ else None,
104
+ "internal_size=%d" % self.internal_size
105
+ if self.internal_size
106
+ else None,
107
+ "precision='%s'" % self.precision if self.precision else None,
108
+ "scale='%s'" % self.scale if self.scale else None,
109
+ "null_ok='%s'" % self.null_ok if self.null_ok else None,
110
+ ],
111
+ )
112
+ )
113
+ return "ColumnInfo(%s)" % str_repr
@@ -0,0 +1,135 @@
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 __future__ import annotations
16
+
17
+ from enum import Enum
18
+ from typing import TYPE_CHECKING, List
19
+ from google.cloud.spanner_dbapi.parsed_statement import (
20
+ ParsedStatement,
21
+ StatementType,
22
+ Statement,
23
+ )
24
+ from google.rpc.code_pb2 import ABORTED, OK
25
+ from google.api_core.exceptions import Aborted
26
+
27
+ from google.cloud.spanner_dbapi.utils import StreamedManyResultSets
28
+
29
+ if TYPE_CHECKING:
30
+ from google.cloud.spanner_dbapi.cursor import Cursor
31
+
32
+
33
+ class BatchDmlExecutor:
34
+ """Executor that is used when a DML batch is started. These batches only
35
+ accept DML statements. All DML statements are buffered locally and sent to
36
+ Spanner when runBatch() is called.
37
+
38
+ :type "Cursor": :class:`~google.cloud.spanner_dbapi.cursor.Cursor`
39
+ :param cursor:
40
+ """
41
+
42
+ def __init__(self, cursor: "Cursor"):
43
+ self._cursor = cursor
44
+ self._connection = cursor.connection
45
+ self._statements: List[Statement] = []
46
+
47
+ def execute_statement(self, parsed_statement: ParsedStatement):
48
+ """Executes the statement when dml batch is active by buffering the
49
+ statement in-memory.
50
+
51
+ :type parsed_statement: ParsedStatement
52
+ :param parsed_statement: parsed statement containing sql query and query
53
+ params
54
+ """
55
+ from google.cloud.spanner_dbapi import ProgrammingError
56
+
57
+ # Note: Let the server handle it if the client-side parser did not
58
+ # recognize the type of statement.
59
+ if (
60
+ parsed_statement.statement_type != StatementType.UPDATE
61
+ and parsed_statement.statement_type != StatementType.INSERT
62
+ and parsed_statement.statement_type != StatementType.UNKNOWN
63
+ ):
64
+ raise ProgrammingError("Only DML statements are allowed in batch DML mode.")
65
+ self._statements.append(parsed_statement.statement)
66
+
67
+ def run_batch_dml(self):
68
+ """Executes all the buffered statements on the active dml batch by
69
+ making a call to Spanner.
70
+ """
71
+ return run_batch_dml(self._cursor, self._statements)
72
+
73
+
74
+ def run_batch_dml(cursor: "Cursor", statements: List[Statement]):
75
+ """Executes all the dml statements by making a batch call to Spanner.
76
+
77
+ :type cursor: Cursor
78
+ :param cursor: Database Cursor object
79
+
80
+ :type statements: List[Statement]
81
+ :param statements: list of statements to execute in batch
82
+ """
83
+ from google.cloud.spanner_dbapi import OperationalError
84
+
85
+ many_result_set = StreamedManyResultSets()
86
+ if not statements:
87
+ return many_result_set
88
+ connection = cursor.connection
89
+ statements_tuple = []
90
+ for statement in statements:
91
+ statements_tuple.append(statement.get_tuple())
92
+ if not connection._client_transaction_started:
93
+ res = connection.database.run_in_transaction(
94
+ _do_batch_update_autocommit, statements_tuple
95
+ )
96
+ many_result_set.add_iter(res)
97
+ cursor._row_count = sum([max(val, 0) for val in res])
98
+ else:
99
+ while True:
100
+ try:
101
+ transaction = connection.transaction_checkout()
102
+ status, res = transaction.batch_update(statements_tuple)
103
+ if status.code == ABORTED:
104
+ connection._transaction = None
105
+ raise Aborted(status.message)
106
+ elif status.code != OK:
107
+ raise OperationalError(status.message)
108
+
109
+ cursor._batch_dml_rows_count = res
110
+ many_result_set.add_iter(res)
111
+ cursor._row_count = sum([max(val, 0) for val in res])
112
+ return many_result_set
113
+ except Aborted:
114
+ # We are raising it so it could be handled in transaction_helper.py and is retried
115
+ if cursor._in_retry_mode:
116
+ raise
117
+ else:
118
+ connection._transaction_helper.retry_transaction()
119
+
120
+
121
+ def _do_batch_update_autocommit(transaction, statements):
122
+ from google.cloud.spanner_dbapi import OperationalError
123
+
124
+ status, res = transaction.batch_update(statements, last_statement=True)
125
+ if status.code == ABORTED:
126
+ raise Aborted(status.message)
127
+ elif status.code != OK:
128
+ raise OperationalError(status.message)
129
+ return res
130
+
131
+
132
+ class BatchMode(Enum):
133
+ DML = 1
134
+ DDL = 2
135
+ NONE = 3
@@ -0,0 +1,80 @@
1
+ # Copyright 2020 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
+ """API to calculate checksums of SQL statements results."""
16
+
17
+ import hashlib
18
+ import pickle
19
+
20
+ from google.cloud.spanner_dbapi.exceptions import RetryAborted
21
+
22
+
23
+ class ResultsChecksum:
24
+ """Cumulative checksum.
25
+
26
+ Used to calculate a total checksum of all the results
27
+ returned by operations executed within transaction.
28
+ Includes methods for checksums comparison.
29
+ These checksums are used while retrying an aborted
30
+ transaction to check if the results of a retried transaction
31
+ are equal to the results of the original transaction.
32
+ """
33
+
34
+ def __init__(self):
35
+ self.checksum = hashlib.sha256()
36
+ self.count = 0 # counter of consumed results
37
+
38
+ def __len__(self):
39
+ """Return the number of consumed results.
40
+
41
+ :rtype: :class:`int`
42
+ :returns: The number of results.
43
+ """
44
+ return self.count
45
+
46
+ def __eq__(self, other):
47
+ """Check if checksums are equal.
48
+
49
+ :type other: :class:`google.cloud.spanner_dbapi.checksum.ResultsChecksum`
50
+ :param other: Another checksum to compare with this one.
51
+ """
52
+ return self.checksum.digest() == other.checksum.digest()
53
+
54
+ def consume_result(self, result):
55
+ """Add the given result into the checksum.
56
+
57
+ :type result: Union[int, list]
58
+ :param result: Streamed row or row count from an UPDATE operation.
59
+ """
60
+ self.checksum.update(pickle.dumps(result))
61
+ self.count += 1
62
+
63
+
64
+ def _compare_checksums(original, retried):
65
+ from google.cloud.spanner_dbapi.transaction_helper import RETRY_ABORTED_ERROR
66
+
67
+ """Compare the given checksums.
68
+
69
+ Raise an error if the given checksums are not equal.
70
+
71
+ :type original: :class:`~google.cloud.spanner_dbapi.checksum.ResultsChecksum`
72
+ :param original: results checksum of the original transaction.
73
+
74
+ :type retried: :class:`~google.cloud.spanner_dbapi.checksum.ResultsChecksum`
75
+ :param retried: results checksum of the retried transaction.
76
+
77
+ :raises: :exc:`google.cloud.spanner_dbapi.exceptions.RetryAborted` in case if checksums are not equal.
78
+ """
79
+ if retried != original:
80
+ raise RetryAborted(RETRY_ABORTED_ERROR)
@@ -0,0 +1,140 @@
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
+ from typing import TYPE_CHECKING, Union
15
+ from google.cloud.spanner_v1 import TransactionOptions
16
+
17
+ if TYPE_CHECKING:
18
+ from google.cloud.spanner_dbapi.cursor import Cursor
19
+ from google.cloud.spanner_dbapi import ProgrammingError
20
+
21
+ from google.cloud.spanner_dbapi.parsed_statement import (
22
+ ParsedStatement,
23
+ ClientSideStatementType,
24
+ )
25
+ from google.cloud.spanner_v1 import (
26
+ Type,
27
+ StructType,
28
+ TypeCode,
29
+ ResultSetMetadata,
30
+ PartialResultSet,
31
+ )
32
+
33
+ from google.cloud.spanner_v1._helpers import _make_value_pb
34
+ from google.cloud.spanner_v1.streamed import StreamedResultSet
35
+
36
+ CONNECTION_CLOSED_ERROR = "This connection is closed"
37
+ TRANSACTION_NOT_STARTED_WARNING = (
38
+ "This method is non-operational as a transaction has not been started."
39
+ )
40
+
41
+
42
+ def execute(cursor: "Cursor", parsed_statement: ParsedStatement):
43
+ """Executes the client side statements by calling the relevant method.
44
+
45
+ It is an internal method that can make backwards-incompatible changes.
46
+
47
+ :type cursor: Cursor
48
+ :param cursor: Cursor object of the dbApi
49
+
50
+ :type parsed_statement: ParsedStatement
51
+ :param parsed_statement: parsed_statement based on the sql query
52
+ """
53
+ connection = cursor.connection
54
+ column_values = []
55
+ if connection.is_closed:
56
+ raise ProgrammingError(CONNECTION_CLOSED_ERROR)
57
+ statement_type = parsed_statement.client_side_statement_type
58
+ if statement_type == ClientSideStatementType.COMMIT:
59
+ connection.commit()
60
+ return None
61
+ if statement_type == ClientSideStatementType.BEGIN:
62
+ connection.begin(isolation_level=_get_isolation_level(parsed_statement))
63
+ return None
64
+ if statement_type == ClientSideStatementType.ROLLBACK:
65
+ connection.rollback()
66
+ return None
67
+ if statement_type == ClientSideStatementType.SHOW_COMMIT_TIMESTAMP:
68
+ if (
69
+ connection._transaction is not None
70
+ and connection._transaction.committed is not None
71
+ ):
72
+ column_values.append(connection._transaction.committed)
73
+ return _get_streamed_result_set(
74
+ ClientSideStatementType.SHOW_COMMIT_TIMESTAMP.name,
75
+ TypeCode.TIMESTAMP,
76
+ column_values,
77
+ )
78
+ if statement_type == ClientSideStatementType.SHOW_READ_TIMESTAMP:
79
+ if (
80
+ connection._snapshot is not None
81
+ and connection._snapshot._transaction_read_timestamp is not None
82
+ ):
83
+ column_values.append(connection._snapshot._transaction_read_timestamp)
84
+ return _get_streamed_result_set(
85
+ ClientSideStatementType.SHOW_READ_TIMESTAMP.name,
86
+ TypeCode.TIMESTAMP,
87
+ column_values,
88
+ )
89
+ if statement_type == ClientSideStatementType.START_BATCH_DML:
90
+ connection.start_batch_dml(cursor)
91
+ return None
92
+ if statement_type == ClientSideStatementType.RUN_BATCH:
93
+ return connection.run_batch()
94
+ if statement_type == ClientSideStatementType.ABORT_BATCH:
95
+ return connection.abort_batch()
96
+ if statement_type == ClientSideStatementType.PARTITION_QUERY:
97
+ partition_ids = connection.partition_query(parsed_statement)
98
+ return _get_streamed_result_set(
99
+ "PARTITION",
100
+ TypeCode.STRING,
101
+ partition_ids,
102
+ )
103
+ if statement_type == ClientSideStatementType.RUN_PARTITION:
104
+ return connection.run_partition(
105
+ parsed_statement.client_side_statement_params[0]
106
+ )
107
+ if statement_type == ClientSideStatementType.RUN_PARTITIONED_QUERY:
108
+ return connection.run_partitioned_query(parsed_statement)
109
+ if statement_type == ClientSideStatementType.SET_AUTOCOMMIT_DML_MODE:
110
+ return connection._set_autocommit_dml_mode(parsed_statement)
111
+
112
+
113
+ def _get_streamed_result_set(column_name, type_code, column_values):
114
+ struct_type_pb = StructType(
115
+ fields=[StructType.Field(name=column_name, type_=Type(code=type_code))]
116
+ )
117
+
118
+ result_set = PartialResultSet(metadata=ResultSetMetadata(row_type=struct_type_pb))
119
+ if len(column_values) > 0:
120
+ column_values_pb = []
121
+ for column_value in column_values:
122
+ column_values_pb.append(_make_value_pb(column_value))
123
+ result_set.values.extend(column_values_pb)
124
+ return StreamedResultSet(iter([result_set]))
125
+
126
+
127
+ def _get_isolation_level(
128
+ statement: ParsedStatement,
129
+ ) -> Union[TransactionOptions.IsolationLevel, None]:
130
+ if (
131
+ statement.client_side_statement_params is None
132
+ or len(statement.client_side_statement_params) == 0
133
+ ):
134
+ return None
135
+ level = statement.client_side_statement_params[0]
136
+ if not isinstance(level, str) or level == "":
137
+ return None
138
+ # Replace (duplicate) whitespaces in the string with an underscore.
139
+ level = "_".join(level.split()).upper()
140
+ return TransactionOptions.IsolationLevel[level]
@@ -0,0 +1,106 @@
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
+ import re
16
+
17
+ from google.cloud.spanner_dbapi.parsed_statement import (
18
+ ParsedStatement,
19
+ StatementType,
20
+ ClientSideStatementType,
21
+ Statement,
22
+ )
23
+
24
+ RE_BEGIN = re.compile(
25
+ r"^\s*(?:BEGIN|START)(?:\s+TRANSACTION)?(?:\s+ISOLATION\s+LEVEL\s+(REPEATABLE\s+READ|SERIALIZABLE))?\s*$",
26
+ re.IGNORECASE,
27
+ )
28
+ RE_COMMIT = re.compile(r"^\s*(COMMIT)(\s+TRANSACTION)?\s*$", re.IGNORECASE)
29
+ RE_ROLLBACK = re.compile(r"^\s*(ROLLBACK)(\s+TRANSACTION)?\s*$", re.IGNORECASE)
30
+ RE_SHOW_COMMIT_TIMESTAMP = re.compile(
31
+ r"^\s*(SHOW)\s+(VARIABLE)\s+(COMMIT_TIMESTAMP)\s*$", re.IGNORECASE
32
+ )
33
+ RE_SHOW_READ_TIMESTAMP = re.compile(
34
+ r"^\s*(SHOW)\s+(VARIABLE)\s+(READ_TIMESTAMP)\s*$", re.IGNORECASE
35
+ )
36
+ RE_START_BATCH_DML = re.compile(r"^\s*(START)\s+(BATCH)\s+(DML)\s*$", re.IGNORECASE)
37
+ RE_RUN_BATCH = re.compile(r"^\s*(RUN)\s+(BATCH)\s*$", re.IGNORECASE)
38
+ RE_ABORT_BATCH = re.compile(r"^\s*(ABORT)\s+(BATCH)\s*$", re.IGNORECASE)
39
+ RE_PARTITION_QUERY = re.compile(r"^\s*(PARTITION)\s+(.+)", re.IGNORECASE)
40
+ RE_RUN_PARTITION = re.compile(r"^\s*(RUN)\s+(PARTITION)\s+(.+)", re.IGNORECASE)
41
+ RE_RUN_PARTITIONED_QUERY = re.compile(
42
+ r"^\s*(RUN)\s+(PARTITIONED)\s+(QUERY)\s+(.+)", re.IGNORECASE
43
+ )
44
+ RE_SET_AUTOCOMMIT_DML_MODE = re.compile(
45
+ r"^\s*(SET)\s+(AUTOCOMMIT_DML_MODE)\s+(=)\s+(.+)", re.IGNORECASE
46
+ )
47
+
48
+
49
+ def parse_stmt(query):
50
+ """Parses the sql query to check if it matches with any of the client side
51
+ statement regex.
52
+
53
+ It is an internal method that can make backwards-incompatible changes.
54
+
55
+ :type query: str
56
+ :param query: sql query
57
+
58
+ :rtype: ParsedStatement
59
+ :returns: ParsedStatement object.
60
+ """
61
+ client_side_statement_type = None
62
+ client_side_statement_params = []
63
+ if RE_COMMIT.match(query):
64
+ client_side_statement_type = ClientSideStatementType.COMMIT
65
+ elif RE_ROLLBACK.match(query):
66
+ client_side_statement_type = ClientSideStatementType.ROLLBACK
67
+ elif RE_SHOW_COMMIT_TIMESTAMP.match(query):
68
+ client_side_statement_type = ClientSideStatementType.SHOW_COMMIT_TIMESTAMP
69
+ elif RE_SHOW_READ_TIMESTAMP.match(query):
70
+ client_side_statement_type = ClientSideStatementType.SHOW_READ_TIMESTAMP
71
+ elif RE_START_BATCH_DML.match(query):
72
+ client_side_statement_type = ClientSideStatementType.START_BATCH_DML
73
+ elif RE_BEGIN.match(query):
74
+ match = re.search(RE_BEGIN, query)
75
+ isolation_level = match.group(1)
76
+ if isolation_level is not None:
77
+ client_side_statement_params.append(isolation_level)
78
+ client_side_statement_type = ClientSideStatementType.BEGIN
79
+ elif RE_RUN_BATCH.match(query):
80
+ client_side_statement_type = ClientSideStatementType.RUN_BATCH
81
+ elif RE_ABORT_BATCH.match(query):
82
+ client_side_statement_type = ClientSideStatementType.ABORT_BATCH
83
+ elif RE_RUN_PARTITIONED_QUERY.match(query):
84
+ match = re.search(RE_RUN_PARTITIONED_QUERY, query)
85
+ client_side_statement_params.append(match.group(4))
86
+ client_side_statement_type = ClientSideStatementType.RUN_PARTITIONED_QUERY
87
+ elif RE_PARTITION_QUERY.match(query):
88
+ match = re.search(RE_PARTITION_QUERY, query)
89
+ client_side_statement_params.append(match.group(2))
90
+ client_side_statement_type = ClientSideStatementType.PARTITION_QUERY
91
+ elif RE_RUN_PARTITION.match(query):
92
+ match = re.search(RE_RUN_PARTITION, query)
93
+ client_side_statement_params.append(match.group(3))
94
+ client_side_statement_type = ClientSideStatementType.RUN_PARTITION
95
+ elif RE_SET_AUTOCOMMIT_DML_MODE.match(query):
96
+ match = re.search(RE_SET_AUTOCOMMIT_DML_MODE, query)
97
+ client_side_statement_params.append(match.group(4))
98
+ client_side_statement_type = ClientSideStatementType.SET_AUTOCOMMIT_DML_MODE
99
+ if client_side_statement_type is not None:
100
+ return ParsedStatement(
101
+ StatementType.CLIENT_SIDE,
102
+ Statement(query),
103
+ client_side_statement_type,
104
+ client_side_statement_params,
105
+ )
106
+ return None