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,172 @@
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
+ """Spanner DB API exceptions."""
16
+
17
+ from google.api_core.exceptions import GoogleAPICallError
18
+
19
+
20
+ class Warning(Exception):
21
+ """Important DB API warning."""
22
+
23
+ pass
24
+
25
+
26
+ class Error(Exception):
27
+ """The base class for all the DB API exceptions.
28
+
29
+ Does not include :class:`Warning`.
30
+ """
31
+
32
+ def _is_error_cause_instance_of_google_api_exception(self):
33
+ return isinstance(self.__cause__, GoogleAPICallError)
34
+
35
+ @property
36
+ def reason(self):
37
+ """The reason of the error.
38
+ Reference:
39
+ https://cloud.google.com/apis/design/errors#error_info
40
+ Returns:
41
+ Union[str, None]: An optional string containing reason of the error.
42
+ """
43
+ return (
44
+ self.__cause__.reason
45
+ if self._is_error_cause_instance_of_google_api_exception()
46
+ else None
47
+ )
48
+
49
+ @property
50
+ def domain(self):
51
+ """The logical grouping to which the "reason" belongs.
52
+ Reference:
53
+ https://cloud.google.com/apis/design/errors#error_info
54
+ Returns:
55
+ Union[str, None]: An optional string containing a logical grouping to which the "reason" belongs.
56
+ """
57
+ return (
58
+ self.__cause__.domain
59
+ if self._is_error_cause_instance_of_google_api_exception()
60
+ else None
61
+ )
62
+
63
+ @property
64
+ def metadata(self):
65
+ """Additional structured details about this error.
66
+ Reference:
67
+ https://cloud.google.com/apis/design/errors#error_info
68
+ Returns:
69
+ Union[Dict[str, str], None]: An optional object containing structured details about the error.
70
+ """
71
+ return (
72
+ self.__cause__.metadata
73
+ if self._is_error_cause_instance_of_google_api_exception()
74
+ else None
75
+ )
76
+
77
+ @property
78
+ def details(self):
79
+ """Information contained in google.rpc.status.details.
80
+ Reference:
81
+ https://cloud.google.com/apis/design/errors#error_model
82
+ https://cloud.google.com/apis/design/errors#error_details
83
+ Returns:
84
+ Sequence[Any]: A list of structured objects from error_details.proto
85
+ """
86
+ return (
87
+ self.__cause__.details
88
+ if self._is_error_cause_instance_of_google_api_exception()
89
+ else None
90
+ )
91
+
92
+
93
+ class InterfaceError(Error):
94
+ """
95
+ Error related to the database interface
96
+ rather than the database itself.
97
+ """
98
+
99
+ pass
100
+
101
+
102
+ class DatabaseError(Error):
103
+ """Error related to the database."""
104
+
105
+ pass
106
+
107
+
108
+ class DataError(DatabaseError):
109
+ """
110
+ Error due to problems with the processed data like
111
+ division by zero, numeric value out of range, etc.
112
+ """
113
+
114
+ pass
115
+
116
+
117
+ class OperationalError(DatabaseError):
118
+ """
119
+ Error related to the database's operation, e.g. an
120
+ unexpected disconnect, the data source name is not
121
+ found, a transaction could not be processed, a
122
+ memory allocation error, etc.
123
+ """
124
+
125
+ pass
126
+
127
+
128
+ class IntegrityError(DatabaseError):
129
+ """
130
+ Error for cases of relational integrity of the database
131
+ is affected, e.g. a foreign key check fails.
132
+ """
133
+
134
+ pass
135
+
136
+
137
+ class InternalError(DatabaseError):
138
+ """
139
+ Internal database error, e.g. the cursor is not valid
140
+ anymore, the transaction is out of sync, etc.
141
+ """
142
+
143
+ pass
144
+
145
+
146
+ class ProgrammingError(DatabaseError):
147
+ """
148
+ Programming error, e.g. table not found or already
149
+ exists, syntax error in the SQL statement, wrong
150
+ number of parameters specified, etc.
151
+ """
152
+
153
+ pass
154
+
155
+
156
+ class NotSupportedError(DatabaseError):
157
+ """
158
+ Error for case of a method or database API not
159
+ supported by the database was used.
160
+ """
161
+
162
+ pass
163
+
164
+
165
+ class RetryAborted(OperationalError):
166
+ """
167
+ Error for case of no aborted transaction retry
168
+ is available, because of underlying data being
169
+ changed during a retry.
170
+ """
171
+
172
+ pass
@@ -0,0 +1,392 @@
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
+ "SQL parsing and classification utils."
16
+
17
+ import datetime
18
+ import decimal
19
+ import re
20
+ import warnings
21
+
22
+ import sqlparse
23
+ from google.cloud import spanner_v1 as spanner
24
+ from google.cloud.spanner_v1 import JsonObject
25
+ from . import client_side_statement_parser
26
+
27
+ from .exceptions import Error
28
+ from .parsed_statement import ParsedStatement, StatementType, Statement
29
+ from .types import DateStr, TimestampStr
30
+ from .utils import sanitize_literals_for_upload
31
+
32
+ # Note: This mapping deliberately does not contain a value for float.
33
+ # The reason for that is that it is better to just let Spanner determine
34
+ # the parameter type instead of specifying one explicitly. The reason for
35
+ # this is that if the client specifies FLOAT64, and the actual column that
36
+ # the parameter is used for is of type FLOAT32, then Spanner will return an
37
+ # error. If however the client does not specify a type, then Spanner will
38
+ # automatically choose the appropriate type based on the column where the
39
+ # value will be inserted/updated or that it will be compared with.
40
+ TYPES_MAP = {
41
+ bool: spanner.param_types.BOOL,
42
+ bytes: spanner.param_types.BYTES,
43
+ str: spanner.param_types.STRING,
44
+ int: spanner.param_types.INT64,
45
+ datetime.datetime: spanner.param_types.TIMESTAMP,
46
+ datetime.date: spanner.param_types.DATE,
47
+ DateStr: spanner.param_types.DATE,
48
+ TimestampStr: spanner.param_types.TIMESTAMP,
49
+ decimal.Decimal: spanner.param_types.NUMERIC,
50
+ JsonObject: spanner.param_types.JSON,
51
+ }
52
+
53
+ SPANNER_RESERVED_KEYWORDS = {
54
+ "ALL",
55
+ "AND",
56
+ "ANY",
57
+ "ARRAY",
58
+ "AS",
59
+ "ASC",
60
+ "ASSERT_ROWS_MODIFIED",
61
+ "AT",
62
+ "BETWEEN",
63
+ "BY",
64
+ "CASE",
65
+ "CAST",
66
+ "COLLATE",
67
+ "CONTAINS",
68
+ "CREATE",
69
+ "CROSS",
70
+ "CUBE",
71
+ "CURRENT",
72
+ "DEFAULT",
73
+ "DEFINE",
74
+ "DESC",
75
+ "DISTINCT",
76
+ "DROP",
77
+ "ELSE",
78
+ "END",
79
+ "ENUM",
80
+ "ESCAPE",
81
+ "EXCEPT",
82
+ "EXCLUDE",
83
+ "EXISTS",
84
+ "EXTRACT",
85
+ "FALSE",
86
+ "FETCH",
87
+ "FOLLOWING",
88
+ "FOR",
89
+ "FROM",
90
+ "FULL",
91
+ "GROUP",
92
+ "GROUPING",
93
+ "GROUPS",
94
+ "HASH",
95
+ "HAVING",
96
+ "IF",
97
+ "IGNORE",
98
+ "IN",
99
+ "INNER",
100
+ "INTERSECT",
101
+ "INTERVAL",
102
+ "INTO",
103
+ "IS",
104
+ "JOIN",
105
+ "LATERAL",
106
+ "LEFT",
107
+ "LIKE",
108
+ "LIMIT",
109
+ "LOOKUP",
110
+ "MERGE",
111
+ "NATURAL",
112
+ "NEW",
113
+ "NO",
114
+ "NOT",
115
+ "NULL",
116
+ "NULLS",
117
+ "OF",
118
+ "ON",
119
+ "OR",
120
+ "ORDER",
121
+ "OUTER",
122
+ "OVER",
123
+ "PARTITION",
124
+ "PRECEDING",
125
+ "PROTO",
126
+ "RANGE",
127
+ "RECURSIVE",
128
+ "RESPECT",
129
+ "RIGHT",
130
+ "ROLLUP",
131
+ "ROWS",
132
+ "SELECT",
133
+ "SET",
134
+ "SOME",
135
+ "STRUCT",
136
+ "TABLESAMPLE",
137
+ "THEN",
138
+ "TO",
139
+ "TREAT",
140
+ "TRUE",
141
+ "UNBOUNDED",
142
+ "UNION",
143
+ "UNNEST",
144
+ "USING",
145
+ "WHEN",
146
+ "WHERE",
147
+ "WINDOW",
148
+ "WITH",
149
+ "WITHIN",
150
+ }
151
+
152
+ STMT_DDL = "DDL"
153
+ STMT_NON_UPDATING = "NON_UPDATING"
154
+ STMT_UPDATING = "UPDATING"
155
+ STMT_INSERT = "INSERT"
156
+
157
+ # Heuristic for identifying statements that don't need to be run as updates.
158
+ # TODO: This and the other regexes do not match statements that start with a hint.
159
+ RE_NON_UPDATE = re.compile(r"^\W*(SELECT|GRAPH|FROM)", re.IGNORECASE)
160
+
161
+ RE_WITH = re.compile(r"^\s*(WITH)", re.IGNORECASE)
162
+
163
+ # DDL statements follow
164
+ # https://cloud.google.com/spanner/docs/data-definition-language
165
+ RE_DDL = re.compile(
166
+ r"^\s*(CREATE|ALTER|DROP|GRANT|REVOKE|RENAME|ANALYZE)", re.IGNORECASE | re.DOTALL
167
+ )
168
+
169
+ # TODO: These do not match statements that start with a hint.
170
+ RE_IS_INSERT = re.compile(r"^\s*(INSERT\s+)", re.IGNORECASE | re.DOTALL)
171
+ RE_IS_UPDATE = re.compile(r"^\s*(UPDATE\s+)", re.IGNORECASE | re.DOTALL)
172
+ RE_IS_DELETE = re.compile(r"^\s*(DELETE\s+)", re.IGNORECASE | re.DOTALL)
173
+
174
+ RE_INSERT = re.compile(
175
+ # Only match the `INSERT INTO <table_name> (columns...)
176
+ # otherwise the rest of the statement could be a complex
177
+ # operation.
178
+ r"^\s*INSERT(?:\s+INTO)?\s+(?P<table_name>[^\s\(\)]+)\s*\((?P<columns>[^\(\)]+)\)",
179
+ re.IGNORECASE | re.DOTALL,
180
+ )
181
+ """Deprecated: Use the RE_IS_INSERT, RE_IS_UPDATE, and RE_IS_DELETE regexes"""
182
+
183
+ RE_VALUES_TILL_END = re.compile(r"VALUES\s*\(.+$", re.IGNORECASE | re.DOTALL)
184
+
185
+ RE_VALUES_PYFORMAT = re.compile(
186
+ # To match: (%s, %s,....%s)
187
+ r"(\(\s*%s[^\(\)]+\))",
188
+ re.DOTALL,
189
+ )
190
+
191
+ RE_PYFORMAT = re.compile(r"(%s|%\([^\(\)]+\)s)+", re.DOTALL)
192
+
193
+
194
+ def classify_stmt(query):
195
+ """Determine SQL query type.
196
+ :type query: str
197
+ :param query: A SQL query.
198
+ :rtype: str
199
+ :returns: The query type name.
200
+ """
201
+ warnings.warn(
202
+ "This method is deprecated. Use _classify_stmt method", DeprecationWarning
203
+ )
204
+
205
+ # sqlparse will strip Cloud Spanner comments,
206
+ # still, special commenting styles, like
207
+ # PostgreSQL dollar quoted comments are not
208
+ # supported and will not be stripped.
209
+ query = sqlparse.format(query, strip_comments=True).strip()
210
+
211
+ if RE_DDL.match(query):
212
+ return STMT_DDL
213
+
214
+ if RE_IS_INSERT.match(query):
215
+ return STMT_INSERT
216
+
217
+ if RE_NON_UPDATE.match(query) or RE_WITH.match(query):
218
+ # As of 13-March-2020, Cloud Spanner only supports WITH for DQL
219
+ # statements and doesn't yet support WITH for DML statements.
220
+ return STMT_NON_UPDATING
221
+
222
+ return STMT_UPDATING
223
+
224
+
225
+ def classify_statement(query, args=None):
226
+ """Determine SQL query type.
227
+
228
+ It is an internal method that can make backwards-incompatible changes.
229
+
230
+ :type query: str
231
+ :param query: A SQL query.
232
+
233
+ :rtype: ParsedStatement
234
+ :returns: parsed statement attributes.
235
+ """
236
+ # sqlparse will strip Cloud Spanner comments,
237
+ # still, special commenting styles, like
238
+ # PostgreSQL dollar quoted comments are not
239
+ # supported and will not be stripped.
240
+ query = sqlparse.format(query, strip_comments=True).strip()
241
+ if query == "":
242
+ return None
243
+ parsed_statement: ParsedStatement = client_side_statement_parser.parse_stmt(query)
244
+ if parsed_statement is not None:
245
+ return parsed_statement
246
+ query, args = sql_pyformat_args_to_spanner(query, args or None)
247
+ statement = Statement(
248
+ query,
249
+ args,
250
+ get_param_types(args or None),
251
+ )
252
+ statement_type = _get_statement_type(statement)
253
+ return ParsedStatement(statement_type, statement)
254
+
255
+
256
+ def _get_statement_type(statement):
257
+ query = statement.sql
258
+ if RE_DDL.match(query):
259
+ return StatementType.DDL
260
+ if RE_IS_INSERT.match(query):
261
+ return StatementType.INSERT
262
+ if RE_NON_UPDATE.match(query) or RE_WITH.match(query):
263
+ # As of 13-March-2020, Cloud Spanner only supports WITH for DQL
264
+ # statements and doesn't yet support WITH for DML statements.
265
+ return StatementType.QUERY
266
+
267
+ if RE_IS_UPDATE.match(query) or RE_IS_DELETE.match(query):
268
+ # TODO: Remove this? It makes more sense to have this in SQLAlchemy and
269
+ # Django than here.
270
+ statement.sql = ensure_where_clause(query)
271
+ return StatementType.UPDATE
272
+
273
+ return StatementType.UNKNOWN
274
+
275
+
276
+ def sql_pyformat_args_to_spanner(sql, params):
277
+ """
278
+ Transform pyformat set SQL to named arguments for Cloud Spanner.
279
+ It will also unescape previously escaped format specifiers
280
+ like %%s to %s.
281
+ For example:
282
+ SQL: 'SELECT * from t where f1=%s, f2=%s, f3=%s'
283
+ Params: ('a', 23, '888***')
284
+ becomes:
285
+ SQL: 'SELECT * from t where f1=@a0, f2=@a1, f3=@a2'
286
+ Params: {'a0': 'a', 'a1': 23, 'a2': '888***'}
287
+
288
+ OR
289
+ SQL: 'SELECT * from t where f1=%(f1)s, f2=%(f2)s, f3=%(f3)s'
290
+ Params: {'f1': 'a', 'f2': 23, 'f3': '888***', 'extra': 'aye')
291
+ becomes:
292
+ SQL: 'SELECT * from t where f1=@a0, f2=@a1, f3=@a2'
293
+ Params: {'a0': 'a', 'a1': 23, 'a2': '888***'}
294
+
295
+ :type sql: str
296
+ :param sql: A SQL request.
297
+
298
+ :type params: list
299
+ :param params: A list of parameters.
300
+
301
+ :rtype: tuple(str, dict)
302
+ :returns: A tuple of the sanitized SQL and a dictionary of the named
303
+ arguments.
304
+ """
305
+ if not params:
306
+ return sanitize_literals_for_upload(sql), None
307
+
308
+ found_pyformat_placeholders = RE_PYFORMAT.findall(sql)
309
+ params_is_dict = isinstance(params, dict)
310
+
311
+ if params_is_dict:
312
+ if not found_pyformat_placeholders:
313
+ return sanitize_literals_for_upload(sql), params
314
+ else:
315
+ n_params = len(params) if params else 0
316
+ n_matches = len(found_pyformat_placeholders)
317
+ if n_matches != n_params:
318
+ raise Error(
319
+ "pyformat_args mismatch\ngot %d args from %s\n"
320
+ "want %d args in %s"
321
+ % (n_matches, found_pyformat_placeholders, n_params, params)
322
+ )
323
+
324
+ named_args = {}
325
+ # We've now got for example:
326
+ # Case a) Params is a non-dict
327
+ # SQL: 'SELECT * from t where f1=%s, f2=%s, f3=%s'
328
+ # Params: ('a', 23, '888***')
329
+ # Case b) Params is a dict and the matches are %(value)s'
330
+ for i, pyfmt in enumerate(found_pyformat_placeholders):
331
+ key = "a%d" % i
332
+ sql = sql.replace(pyfmt, "@" + key, 1)
333
+ if params_is_dict:
334
+ # The '%(key)s' case, so interpolate it.
335
+ resolved_value = pyfmt % params
336
+ named_args[key] = resolved_value
337
+ else:
338
+ named_args[key] = params[i]
339
+
340
+ return sanitize_literals_for_upload(sql), named_args
341
+
342
+
343
+ def get_param_types(params):
344
+ """Determine Cloud Spanner types for the given parameters.
345
+
346
+ :type params: dict
347
+ :param params: Parameters requiring to find Cloud Spanner types.
348
+
349
+ :rtype: dict
350
+ :returns: The types index for the given parameters.
351
+ """
352
+ if params is None:
353
+ return
354
+
355
+ param_types = {}
356
+
357
+ for key, value in params.items():
358
+ type_ = type(value)
359
+ if type_ in TYPES_MAP:
360
+ param_types[key] = TYPES_MAP[type_]
361
+
362
+ return param_types
363
+
364
+
365
+ def ensure_where_clause(sql):
366
+ """
367
+ Cloud Spanner requires a WHERE clause on UPDATE and DELETE statements.
368
+ Add a dummy WHERE clause if not detected.
369
+
370
+ :type sql: str
371
+ :param sql: SQL code to check.
372
+ """
373
+ if any(isinstance(token, sqlparse.sql.Where) for token in sqlparse.parse(sql)[0]):
374
+ return sql
375
+
376
+ return sql + " WHERE 1=1"
377
+
378
+
379
+ def escape_name(name):
380
+ """
381
+ Apply backticks to the name that either contain '-' or
382
+ ' ', or is a Cloud Spanner's reserved keyword.
383
+
384
+ :type name: str
385
+ :param name: Name to escape.
386
+
387
+ :rtype: str
388
+ :returns: Name escaped if it has to be escaped.
389
+ """
390
+ if "-" in name or " " in name or name.upper() in SPANNER_RESERVED_KEYWORDS:
391
+ return "`" + name + "`"
392
+ return name
@@ -0,0 +1,63 @@
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 dataclasses import dataclass
15
+ from enum import Enum
16
+ from typing import Any, List
17
+
18
+
19
+ class StatementType(Enum):
20
+ UNKNOWN = 0
21
+ CLIENT_SIDE = 1
22
+ DDL = 2
23
+ QUERY = 3
24
+ UPDATE = 4
25
+ INSERT = 5
26
+
27
+
28
+ class ClientSideStatementType(Enum):
29
+ COMMIT = 1
30
+ BEGIN = 2
31
+ ROLLBACK = 3
32
+ SHOW_COMMIT_TIMESTAMP = 4
33
+ SHOW_READ_TIMESTAMP = 5
34
+ START_BATCH_DML = 6
35
+ RUN_BATCH = 7
36
+ ABORT_BATCH = 8
37
+ PARTITION_QUERY = 9
38
+ RUN_PARTITION = 10
39
+ RUN_PARTITIONED_QUERY = 11
40
+ SET_AUTOCOMMIT_DML_MODE = 12
41
+
42
+
43
+ class AutocommitDmlMode(Enum):
44
+ TRANSACTIONAL = 1
45
+ PARTITIONED_NON_ATOMIC = 2
46
+
47
+
48
+ @dataclass
49
+ class Statement:
50
+ sql: str
51
+ params: Any = None
52
+ param_types: Any = None
53
+
54
+ def get_tuple(self):
55
+ return self.sql, self.params, self.param_types
56
+
57
+
58
+ @dataclass
59
+ class ParsedStatement:
60
+ statement_type: StatementType
61
+ statement: Statement
62
+ client_side_statement_type: ClientSideStatementType = None
63
+ client_side_statement_params: List[Any] = None