sqlalchemy-spanner 1.6.2__tar.gz → 1.7.0__tar.gz
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.
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/PKG-INFO +8 -2
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/google/cloud/sqlalchemy_spanner/_opentelemetry_tracing.py +13 -0
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/google/cloud/sqlalchemy_spanner/requirements.py +1 -1
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py +139 -19
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/sqlalchemy_spanner.egg-info/PKG-INFO +8 -2
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/test/test_suite_13.py +126 -1
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/test/test_suite_14.py +131 -1
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/test/test_suite_20.py +195 -12
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/LICENSE +0 -0
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/README.rst +0 -0
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/google/__init__.py +0 -0
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/google/cloud/__init__.py +0 -0
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/google/cloud/sqlalchemy_spanner/__init__.py +0 -0
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/google/cloud/sqlalchemy_spanner/provision.py +0 -0
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/setup.cfg +0 -0
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/setup.py +0 -0
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/sqlalchemy_spanner.egg-info/SOURCES.txt +0 -0
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/sqlalchemy_spanner.egg-info/dependency_links.txt +0 -0
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/sqlalchemy_spanner.egg-info/entry_points.txt +0 -0
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/sqlalchemy_spanner.egg-info/namespace_packages.txt +0 -0
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/sqlalchemy_spanner.egg-info/not-zip-safe +0 -0
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/sqlalchemy_spanner.egg-info/requires.txt +0 -0
- {sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/sqlalchemy_spanner.egg-info/top_level.txt +0 -0
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sqlalchemy-spanner
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: SQLAlchemy dialect integrated into Cloud Spanner database
|
|
5
5
|
Home-page: https://github.com/cloudspannerecosystem/python-spanner-sqlalchemy
|
|
6
6
|
Author: Google LLC
|
|
7
7
|
Author-email: cloud-spanner-developers@googlegroups.com
|
|
8
8
|
Classifier: Intended Audience :: Developers
|
|
9
|
-
Provides-Extra: tracing
|
|
10
9
|
License-File: LICENSE
|
|
10
|
+
Requires-Dist: sqlalchemy>=1.1.13
|
|
11
|
+
Requires-Dist: google-cloud-spanner>=3.12.0
|
|
12
|
+
Requires-Dist: alembic
|
|
13
|
+
Provides-Extra: tracing
|
|
14
|
+
Requires-Dist: opentelemetry-api>=1.1.0; extra == "tracing"
|
|
15
|
+
Requires-Dist: opentelemetry-sdk>=1.1.0; extra == "tracing"
|
|
16
|
+
Requires-Dist: opentelemetry-instrumentation>=0.20b0; extra == "tracing"
|
|
11
17
|
|
|
12
18
|
Spanner dialect for SQLAlchemy
|
|
13
19
|
==============================
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
|
|
15
15
|
"""Manages OpenTelemetry trace creation and handling"""
|
|
16
16
|
|
|
17
|
+
import collections
|
|
18
|
+
import os
|
|
19
|
+
|
|
17
20
|
from contextlib import contextmanager
|
|
18
21
|
|
|
19
22
|
from google.api_core.exceptions import GoogleAPICallError
|
|
@@ -46,6 +49,16 @@ def trace_call(name, extra_attributes=None):
|
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
if extra_attributes:
|
|
52
|
+
if os.environ.get("SQLALCHEMY_SPANNER_TRACE_HIDE_QUERY_PARAMETERS"):
|
|
53
|
+
extra_attributes.pop("db.params", None)
|
|
54
|
+
|
|
55
|
+
# Stringify "db.params" sequence values before sending to OpenTelemetry,
|
|
56
|
+
# otherwise OpenTelemetry may log a Warning if types differ.
|
|
57
|
+
if isinstance(extra_attributes, dict):
|
|
58
|
+
for k, v in extra_attributes.items():
|
|
59
|
+
if k == "db.params" and isinstance(v, collections.abc.Sequence):
|
|
60
|
+
extra_attributes[k] = [str(e) for e in v]
|
|
61
|
+
|
|
49
62
|
attributes.update(extra_attributes)
|
|
50
63
|
|
|
51
64
|
with tracer.start_as_current_span(
|
|
@@ -23,6 +23,7 @@ from alembic.ddl.base import (
|
|
|
23
23
|
format_type,
|
|
24
24
|
)
|
|
25
25
|
from sqlalchemy.exc import NoSuchTableError
|
|
26
|
+
from sqlalchemy.sql import elements
|
|
26
27
|
from sqlalchemy import ForeignKeyConstraint, types
|
|
27
28
|
from sqlalchemy.engine.base import Engine
|
|
28
29
|
from sqlalchemy.engine.default import DefaultDialect, DefaultExecutionContext
|
|
@@ -40,6 +41,7 @@ from sqlalchemy.sql.compiler import (
|
|
|
40
41
|
)
|
|
41
42
|
from sqlalchemy.sql.default_comparator import operator_lookup
|
|
42
43
|
from sqlalchemy.sql.operators import json_getitem_op
|
|
44
|
+
from sqlalchemy.sql import expression
|
|
43
45
|
|
|
44
46
|
from google.cloud.spanner_v1.data_types import JsonObject
|
|
45
47
|
from google.cloud import spanner_dbapi
|
|
@@ -173,6 +175,16 @@ class SpannerExecutionContext(DefaultExecutionContext):
|
|
|
173
175
|
if priority is not None:
|
|
174
176
|
self._dbapi_connection.connection.request_priority = priority
|
|
175
177
|
|
|
178
|
+
def fire_sequence(self, seq, type_):
|
|
179
|
+
"""Builds a statement for fetching next value of the sequence."""
|
|
180
|
+
return self._execute_scalar(
|
|
181
|
+
(
|
|
182
|
+
"SELECT GET_NEXT_SEQUENCE_VALUE(SEQUENCE %s)"
|
|
183
|
+
% self.identifier_preparer.format_sequence(seq)
|
|
184
|
+
),
|
|
185
|
+
type_,
|
|
186
|
+
)
|
|
187
|
+
|
|
176
188
|
|
|
177
189
|
class SpannerIdentifierPreparer(IdentifierPreparer):
|
|
178
190
|
"""Identifiers compiler.
|
|
@@ -303,8 +315,14 @@ class SpannerSQLCompiler(SQLCompiler):
|
|
|
303
315
|
in string. Override the method to add additional escape before using it to
|
|
304
316
|
generate a SQL statement.
|
|
305
317
|
"""
|
|
318
|
+
if value is None and not type_.should_evaluate_none:
|
|
319
|
+
# issue #10535 - handle NULL in the compiler without placing
|
|
320
|
+
# this onto each type, except for "evaluate None" types
|
|
321
|
+
# (e.g. JSON)
|
|
322
|
+
return self.process(elements.Null._instance())
|
|
323
|
+
|
|
306
324
|
raw = ["\\", "'", '"', "\n", "\t", "\r"]
|
|
307
|
-
if
|
|
325
|
+
if isinstance(value, str) and any(single in value for single in raw):
|
|
308
326
|
value = 'r"""{}"""'.format(value)
|
|
309
327
|
return value
|
|
310
328
|
else:
|
|
@@ -343,6 +361,20 @@ class SpannerSQLCompiler(SQLCompiler):
|
|
|
343
361
|
text += " OFFSET " + self.process(select._offset_clause, **kw)
|
|
344
362
|
return text
|
|
345
363
|
|
|
364
|
+
def returning_clause(self, stmt, returning_cols, **kw):
|
|
365
|
+
columns = [
|
|
366
|
+
self._label_select_column(None, c, True, False, {})
|
|
367
|
+
for c in expression._select_iterables(returning_cols)
|
|
368
|
+
]
|
|
369
|
+
|
|
370
|
+
return "THEN RETURN " + ", ".join(columns)
|
|
371
|
+
|
|
372
|
+
def visit_sequence(self, seq, **kw):
|
|
373
|
+
"""Builds a statement for fetching next value of the sequence."""
|
|
374
|
+
return " GET_NEXT_SEQUENCE_VALUE(SEQUENCE %s)" % self.preparer.format_sequence(
|
|
375
|
+
seq
|
|
376
|
+
)
|
|
377
|
+
|
|
346
378
|
|
|
347
379
|
class SpannerDDLCompiler(DDLCompiler):
|
|
348
380
|
"""Spanner DDL statements compiler."""
|
|
@@ -406,7 +438,7 @@ class SpannerDDLCompiler(DDLCompiler):
|
|
|
406
438
|
for index in drop_table.element.indexes:
|
|
407
439
|
indexes += "DROP INDEX {};".format(self.preparer.quote(index.name))
|
|
408
440
|
|
|
409
|
-
return indexes + constrs +
|
|
441
|
+
return indexes + constrs + super().visit_drop_table(drop_table)
|
|
410
442
|
|
|
411
443
|
def visit_primary_key_constraint(self, constraint, **kw):
|
|
412
444
|
"""Build primary key definition.
|
|
@@ -457,6 +489,24 @@ class SpannerDDLCompiler(DDLCompiler):
|
|
|
457
489
|
|
|
458
490
|
return post_cmds
|
|
459
491
|
|
|
492
|
+
def get_identity_options(self, identity_options):
|
|
493
|
+
text = ["sequence_kind = 'bit_reversed_positive'"]
|
|
494
|
+
if identity_options.start is not None:
|
|
495
|
+
text.append("start_with_counter = %d" % identity_options.start)
|
|
496
|
+
return ", ".join(text)
|
|
497
|
+
|
|
498
|
+
def visit_create_sequence(self, create, prefix=None, **kw):
|
|
499
|
+
"""Builds a ``CREATE SEQUENCE`` statement for the sequence."""
|
|
500
|
+
text = "CREATE SEQUENCE %s" % self.preparer.format_sequence(create.element)
|
|
501
|
+
options = self.get_identity_options(create.element)
|
|
502
|
+
if options:
|
|
503
|
+
text += " OPTIONS (" + options + ")"
|
|
504
|
+
return text
|
|
505
|
+
|
|
506
|
+
def visit_drop_sequence(self, drop, **kw):
|
|
507
|
+
"""Builds a ``DROP SEQUENCE`` statement for the sequence."""
|
|
508
|
+
return "DROP SEQUENCE %s" % self.preparer.format_sequence(drop.element)
|
|
509
|
+
|
|
460
510
|
|
|
461
511
|
class SpannerTypeCompiler(GenericTypeCompiler):
|
|
462
512
|
"""Spanner types compiler.
|
|
@@ -531,7 +581,8 @@ class SpannerDialect(DefaultDialect):
|
|
|
531
581
|
supports_sane_rowcount = False
|
|
532
582
|
supports_sane_multi_rowcount = False
|
|
533
583
|
supports_default_values = False
|
|
534
|
-
supports_sequences =
|
|
584
|
+
supports_sequences = True
|
|
585
|
+
sequences_optional = False
|
|
535
586
|
supports_native_enum = True
|
|
536
587
|
supports_native_boolean = True
|
|
537
588
|
supports_native_decimal = True
|
|
@@ -694,6 +745,36 @@ class SpannerDialect(DefaultDialect):
|
|
|
694
745
|
|
|
695
746
|
return all_views
|
|
696
747
|
|
|
748
|
+
@engine_to_connection
|
|
749
|
+
def get_sequence_names(self, connection, schema=None, **kw):
|
|
750
|
+
"""
|
|
751
|
+
Return a list of all sequence names available in the database.
|
|
752
|
+
|
|
753
|
+
The method is used by SQLAlchemy introspection systems.
|
|
754
|
+
|
|
755
|
+
Args:
|
|
756
|
+
connection (sqlalchemy.engine.base.Connection):
|
|
757
|
+
SQLAlchemy connection or engine object.
|
|
758
|
+
schema (str): Optional. Schema name
|
|
759
|
+
|
|
760
|
+
Returns:
|
|
761
|
+
list: List of sequence names.
|
|
762
|
+
"""
|
|
763
|
+
sql = """
|
|
764
|
+
SELECT name
|
|
765
|
+
FROM information_schema.sequences
|
|
766
|
+
WHERE SCHEMA='{}'
|
|
767
|
+
""".format(
|
|
768
|
+
schema or ""
|
|
769
|
+
)
|
|
770
|
+
all_sequences = []
|
|
771
|
+
with connection.connection.database.snapshot() as snap:
|
|
772
|
+
rows = list(snap.execute_sql(sql))
|
|
773
|
+
for seq in rows:
|
|
774
|
+
all_sequences.append(seq[0])
|
|
775
|
+
|
|
776
|
+
return all_sequences
|
|
777
|
+
|
|
697
778
|
@engine_to_connection
|
|
698
779
|
def get_view_definition(self, connection, view_name, schema=None, **kw):
|
|
699
780
|
"""
|
|
@@ -765,7 +846,7 @@ class SpannerDialect(DefaultDialect):
|
|
|
765
846
|
col.spanner_type, col.is_nullable, col.generation_expression
|
|
766
847
|
FROM information_schema.columns as col
|
|
767
848
|
JOIN information_schema.tables AS t
|
|
768
|
-
|
|
849
|
+
USING (TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME)
|
|
769
850
|
WHERE
|
|
770
851
|
{table_filter_query}
|
|
771
852
|
{table_type_query}
|
|
@@ -898,9 +979,14 @@ class SpannerDialect(DefaultDialect):
|
|
|
898
979
|
ARRAY_AGG(ic.column_ordering)
|
|
899
980
|
FROM information_schema.indexes as i
|
|
900
981
|
JOIN information_schema.index_columns AS ic
|
|
901
|
-
ON
|
|
982
|
+
ON ic.index_name = i.index_name
|
|
983
|
+
AND ic.table_catalog = i.table_catalog
|
|
984
|
+
AND ic.table_schema = i.table_schema
|
|
985
|
+
AND ic.table_name = i.table_name
|
|
902
986
|
JOIN information_schema.tables AS t
|
|
903
|
-
ON
|
|
987
|
+
ON i.table_catalog = t.table_catalog
|
|
988
|
+
AND i.table_schema = t.table_schema
|
|
989
|
+
AND i.table_name = t.table_name
|
|
904
990
|
WHERE
|
|
905
991
|
{table_filter_query}
|
|
906
992
|
{table_type_query}
|
|
@@ -995,9 +1081,11 @@ class SpannerDialect(DefaultDialect):
|
|
|
995
1081
|
SELECT tc.table_schema, tc.table_name, ccu.COLUMN_NAME
|
|
996
1082
|
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc
|
|
997
1083
|
JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu
|
|
998
|
-
|
|
1084
|
+
USING (TABLE_CATALOG, TABLE_SCHEMA, CONSTRAINT_NAME)
|
|
999
1085
|
JOIN information_schema.tables AS t
|
|
1000
|
-
ON
|
|
1086
|
+
ON tc.TABLE_CATALOG = t.TABLE_CATALOG
|
|
1087
|
+
AND tc.TABLE_SCHEMA = t.TABLE_SCHEMA
|
|
1088
|
+
AND tc.TABLE_NAME = t.TABLE_NAME
|
|
1001
1089
|
WHERE {table_filter_query} {table_type_query}
|
|
1002
1090
|
{schema_filter_query} tc.CONSTRAINT_TYPE = "PRIMARY KEY"
|
|
1003
1091
|
""".format(
|
|
@@ -1115,13 +1203,19 @@ class SpannerDialect(DefaultDialect):
|
|
|
1115
1203
|
)
|
|
1116
1204
|
FROM information_schema.table_constraints AS tc
|
|
1117
1205
|
JOIN information_schema.constraint_column_usage AS ccu
|
|
1118
|
-
|
|
1206
|
+
USING (table_catalog, table_schema, constraint_name)
|
|
1119
1207
|
JOIN information_schema.constraint_table_usage AS ctu
|
|
1120
|
-
ON ctu.
|
|
1208
|
+
ON ctu.table_catalog = tc.table_catalog
|
|
1209
|
+
and ctu.table_schema = tc.table_schema
|
|
1210
|
+
and ctu.constraint_name = tc.constraint_name
|
|
1121
1211
|
JOIN information_schema.key_column_usage AS kcu
|
|
1122
|
-
ON kcu.
|
|
1212
|
+
ON kcu.table_catalog = tc.table_catalog
|
|
1213
|
+
and kcu.table_schema = tc.table_schema
|
|
1214
|
+
and kcu.constraint_name = tc.constraint_name
|
|
1123
1215
|
JOIN information_schema.tables AS t
|
|
1124
|
-
ON
|
|
1216
|
+
ON t.table_catalog = tc.table_catalog
|
|
1217
|
+
and t.table_schema = tc.table_schema
|
|
1218
|
+
and t.table_name = tc.table_name
|
|
1125
1219
|
WHERE
|
|
1126
1220
|
{table_filter_query}
|
|
1127
1221
|
{table_type_query}
|
|
@@ -1242,15 +1336,14 @@ WHERE table_type = 'BASE TABLE' AND table_schema = '{schema}'
|
|
|
1242
1336
|
SELECT ccu.CONSTRAINT_NAME, ccu.COLUMN_NAME
|
|
1243
1337
|
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc
|
|
1244
1338
|
JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu
|
|
1245
|
-
|
|
1246
|
-
LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc
|
|
1247
|
-
on tc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
|
|
1339
|
+
USING (TABLE_CATALOG, TABLE_SCHEMA, CONSTRAINT_NAME)
|
|
1248
1340
|
WHERE
|
|
1249
1341
|
tc.TABLE_NAME="{table_name}"
|
|
1342
|
+
AND tc.TABLE_SCHEMA="{table_schema}"
|
|
1250
1343
|
AND tc.CONSTRAINT_TYPE = "UNIQUE"
|
|
1251
|
-
AND
|
|
1344
|
+
AND tc.CONSTRAINT_NAME IS NOT NULL
|
|
1252
1345
|
""".format(
|
|
1253
|
-
table_name=table_name
|
|
1346
|
+
table_schema=schema or "", table_name=table_name
|
|
1254
1347
|
)
|
|
1255
1348
|
|
|
1256
1349
|
cols = []
|
|
@@ -1282,10 +1375,37 @@ WHERE
|
|
|
1282
1375
|
"""
|
|
1283
1376
|
SELECT true
|
|
1284
1377
|
FROM INFORMATION_SCHEMA.TABLES
|
|
1285
|
-
WHERE TABLE_NAME="{table_name}"
|
|
1378
|
+
WHERE TABLE_SCHEMA="{table_schema}" AND TABLE_NAME="{table_name}"
|
|
1286
1379
|
LIMIT 1
|
|
1287
1380
|
""".format(
|
|
1288
|
-
table_name=table_name
|
|
1381
|
+
table_schema=schema or "", table_name=table_name
|
|
1382
|
+
)
|
|
1383
|
+
)
|
|
1384
|
+
|
|
1385
|
+
for _ in rows:
|
|
1386
|
+
return True
|
|
1387
|
+
|
|
1388
|
+
return False
|
|
1389
|
+
|
|
1390
|
+
@engine_to_connection
|
|
1391
|
+
def has_sequence(self, connection, sequence_name, schema=None, **kw):
|
|
1392
|
+
"""Check the existence of a particular sequence in the database.
|
|
1393
|
+
|
|
1394
|
+
Given a :class:`_engine.Connection` object and a string
|
|
1395
|
+
`sequence_name`, return True if the given sequence exists in
|
|
1396
|
+
the database, False otherwise.
|
|
1397
|
+
"""
|
|
1398
|
+
|
|
1399
|
+
with connection.connection.database.snapshot() as snap:
|
|
1400
|
+
rows = snap.execute_sql(
|
|
1401
|
+
"""
|
|
1402
|
+
SELECT true
|
|
1403
|
+
FROM INFORMATION_SCHEMA.SEQUENCES
|
|
1404
|
+
WHERE NAME="{sequence_name}"
|
|
1405
|
+
AND SCHEMA="{schema}"
|
|
1406
|
+
LIMIT 1
|
|
1407
|
+
""".format(
|
|
1408
|
+
sequence_name=sequence_name, schema=schema or ""
|
|
1289
1409
|
)
|
|
1290
1410
|
)
|
|
1291
1411
|
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sqlalchemy-spanner
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: SQLAlchemy dialect integrated into Cloud Spanner database
|
|
5
5
|
Home-page: https://github.com/cloudspannerecosystem/python-spanner-sqlalchemy
|
|
6
6
|
Author: Google LLC
|
|
7
7
|
Author-email: cloud-spanner-developers@googlegroups.com
|
|
8
8
|
Classifier: Intended Audience :: Developers
|
|
9
|
-
Provides-Extra: tracing
|
|
10
9
|
License-File: LICENSE
|
|
10
|
+
Requires-Dist: sqlalchemy>=1.1.13
|
|
11
|
+
Requires-Dist: google-cloud-spanner>=3.12.0
|
|
12
|
+
Requires-Dist: alembic
|
|
13
|
+
Provides-Extra: tracing
|
|
14
|
+
Requires-Dist: opentelemetry-api>=1.1.0; extra == "tracing"
|
|
15
|
+
Requires-Dist: opentelemetry-sdk>=1.1.0; extra == "tracing"
|
|
16
|
+
Requires-Dist: opentelemetry-instrumentation>=0.20b0; extra == "tracing"
|
|
11
17
|
|
|
12
18
|
Spanner dialect for SQLAlchemy
|
|
13
19
|
==============================
|
|
@@ -37,6 +37,7 @@ from sqlalchemy.schema import Computed
|
|
|
37
37
|
from sqlalchemy.testing import config
|
|
38
38
|
from sqlalchemy.testing import engines
|
|
39
39
|
from sqlalchemy.testing import eq_
|
|
40
|
+
from sqlalchemy.testing import is_instance_of
|
|
40
41
|
from sqlalchemy.testing import provide_metadata, emits_warning
|
|
41
42
|
from sqlalchemy.testing import fixtures
|
|
42
43
|
from sqlalchemy.testing import is_true
|
|
@@ -73,7 +74,10 @@ from sqlalchemy.testing.suite.test_insert import * # noqa: F401, F403
|
|
|
73
74
|
from sqlalchemy.testing.suite.test_reflection import * # noqa: F401, F403
|
|
74
75
|
from sqlalchemy.testing.suite.test_results import * # noqa: F401, F403
|
|
75
76
|
from sqlalchemy.testing.suite.test_select import * # noqa: F401, F403
|
|
76
|
-
from sqlalchemy.testing.suite.test_sequence import
|
|
77
|
+
from sqlalchemy.testing.suite.test_sequence import (
|
|
78
|
+
SequenceTest as _SequenceTest,
|
|
79
|
+
HasSequenceTest as _HasSequenceTest,
|
|
80
|
+
) # noqa: F401, F403
|
|
77
81
|
from sqlalchemy.testing.suite.test_update_delete import * # noqa: F401, F403
|
|
78
82
|
|
|
79
83
|
from sqlalchemy.testing.suite.test_cte import CTETest as _CTETest
|
|
@@ -2059,3 +2063,124 @@ class CreateEngineWithoutDatabaseTest(fixtures.TestBase):
|
|
|
2059
2063
|
engine = create_engine(get_db_url().split("/database")[0])
|
|
2060
2064
|
with engine.connect() as connection:
|
|
2061
2065
|
assert connection.connection.database is None
|
|
2066
|
+
|
|
2067
|
+
|
|
2068
|
+
@pytest.mark.skipif(
|
|
2069
|
+
bool(os.environ.get("SPANNER_EMULATOR_HOST")), reason="Skipped on emulator"
|
|
2070
|
+
)
|
|
2071
|
+
class SequenceTest(_SequenceTest):
|
|
2072
|
+
@classmethod
|
|
2073
|
+
def define_tables(cls, metadata):
|
|
2074
|
+
Table(
|
|
2075
|
+
"seq_pk",
|
|
2076
|
+
metadata,
|
|
2077
|
+
Column(
|
|
2078
|
+
"id",
|
|
2079
|
+
Integer,
|
|
2080
|
+
sqlalchemy.Sequence("tab_id_seq"),
|
|
2081
|
+
primary_key=True,
|
|
2082
|
+
),
|
|
2083
|
+
Column("data", String(50)),
|
|
2084
|
+
)
|
|
2085
|
+
|
|
2086
|
+
Table(
|
|
2087
|
+
"seq_opt_pk",
|
|
2088
|
+
metadata,
|
|
2089
|
+
Column(
|
|
2090
|
+
"id",
|
|
2091
|
+
Integer,
|
|
2092
|
+
sqlalchemy.Sequence("tab_id_seq_opt", data_type=Integer, optional=True),
|
|
2093
|
+
primary_key=True,
|
|
2094
|
+
),
|
|
2095
|
+
Column("data", String(50)),
|
|
2096
|
+
)
|
|
2097
|
+
|
|
2098
|
+
Table(
|
|
2099
|
+
"seq_no_returning",
|
|
2100
|
+
metadata,
|
|
2101
|
+
Column(
|
|
2102
|
+
"id",
|
|
2103
|
+
Integer,
|
|
2104
|
+
sqlalchemy.Sequence("noret_id_seq"),
|
|
2105
|
+
primary_key=True,
|
|
2106
|
+
),
|
|
2107
|
+
Column("data", String(50)),
|
|
2108
|
+
implicit_returning=False,
|
|
2109
|
+
)
|
|
2110
|
+
|
|
2111
|
+
def test_insert_lastrowid(self, connection):
|
|
2112
|
+
r = connection.execute(self.tables.seq_pk.insert(), dict(data="some data"))
|
|
2113
|
+
assert len(r.inserted_primary_key) == 1
|
|
2114
|
+
is_instance_of(r.inserted_primary_key[0], int)
|
|
2115
|
+
|
|
2116
|
+
def test_nextval_direct(self, connection):
|
|
2117
|
+
r = connection.execute(self.tables.seq_pk.c.id.default)
|
|
2118
|
+
is_instance_of(r, int)
|
|
2119
|
+
|
|
2120
|
+
def _assert_round_trip(self, table, conn):
|
|
2121
|
+
row = conn.execute(table.select()).first()
|
|
2122
|
+
id, name = row
|
|
2123
|
+
is_instance_of(id, int)
|
|
2124
|
+
eq_(name, "some data")
|
|
2125
|
+
|
|
2126
|
+
@testing.combinations((True,), (False,), argnames="implicit_returning")
|
|
2127
|
+
@testing.requires.schemas
|
|
2128
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
2129
|
+
def test_insert_roundtrip_translate(self, connection, implicit_returning):
|
|
2130
|
+
pass
|
|
2131
|
+
|
|
2132
|
+
@testing.requires.schemas
|
|
2133
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
2134
|
+
def test_nextval_direct_schema_translate(self, connection):
|
|
2135
|
+
pass
|
|
2136
|
+
|
|
2137
|
+
|
|
2138
|
+
@pytest.mark.skipif(
|
|
2139
|
+
bool(os.environ.get("SPANNER_EMULATOR_HOST")), reason="Skipped on emulator"
|
|
2140
|
+
)
|
|
2141
|
+
class HasSequenceTest(_HasSequenceTest):
|
|
2142
|
+
@classmethod
|
|
2143
|
+
def define_tables(cls, metadata):
|
|
2144
|
+
sqlalchemy.Sequence("user_id_seq", metadata=metadata)
|
|
2145
|
+
sqlalchemy.Sequence(
|
|
2146
|
+
"other_seq", metadata=metadata, nomaxvalue=True, nominvalue=True
|
|
2147
|
+
)
|
|
2148
|
+
Table(
|
|
2149
|
+
"user_id_table",
|
|
2150
|
+
metadata,
|
|
2151
|
+
Column("id", Integer, primary_key=True),
|
|
2152
|
+
)
|
|
2153
|
+
|
|
2154
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
2155
|
+
def test_has_sequence_cache(self, connection, metadata):
|
|
2156
|
+
pass
|
|
2157
|
+
|
|
2158
|
+
@testing.requires.schemas
|
|
2159
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
2160
|
+
def test_has_sequence_schema(self, connection):
|
|
2161
|
+
pass
|
|
2162
|
+
|
|
2163
|
+
@testing.requires.schemas
|
|
2164
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
2165
|
+
def test_has_sequence_schemas_neg(self, connection):
|
|
2166
|
+
pass
|
|
2167
|
+
|
|
2168
|
+
@testing.requires.schemas
|
|
2169
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
2170
|
+
def test_has_sequence_default_not_in_remote(self, connection):
|
|
2171
|
+
pass
|
|
2172
|
+
|
|
2173
|
+
@testing.requires.schemas
|
|
2174
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
2175
|
+
def test_has_sequence_remote_not_in_default(self, connection):
|
|
2176
|
+
pass
|
|
2177
|
+
|
|
2178
|
+
@testing.requires.schemas
|
|
2179
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
2180
|
+
def test_get_sequence_names_no_sequence_schema(self, connection):
|
|
2181
|
+
pass
|
|
2182
|
+
|
|
2183
|
+
@testing.requires.schemas
|
|
2184
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
2185
|
+
def test_get_sequence_names_sequences_schema(self, connection):
|
|
2186
|
+
pass
|
|
@@ -37,6 +37,7 @@ from sqlalchemy.schema import Computed
|
|
|
37
37
|
from sqlalchemy.testing import config
|
|
38
38
|
from sqlalchemy.testing import engines
|
|
39
39
|
from sqlalchemy.testing import eq_
|
|
40
|
+
from sqlalchemy.testing import is_instance_of
|
|
40
41
|
from sqlalchemy.testing import provide_metadata, emits_warning
|
|
41
42
|
from sqlalchemy.testing import fixtures
|
|
42
43
|
from sqlalchemy.testing.provision import temp_table_keyword_args
|
|
@@ -76,7 +77,11 @@ from sqlalchemy.testing.suite.test_insert import * # noqa: F401, F403
|
|
|
76
77
|
from sqlalchemy.testing.suite.test_reflection import * # noqa: F401, F403
|
|
77
78
|
from sqlalchemy.testing.suite.test_results import * # noqa: F401, F403
|
|
78
79
|
from sqlalchemy.testing.suite.test_select import * # noqa: F401, F403
|
|
79
|
-
from sqlalchemy.testing.suite.test_sequence import
|
|
80
|
+
from sqlalchemy.testing.suite.test_sequence import (
|
|
81
|
+
SequenceTest as _SequenceTest,
|
|
82
|
+
HasSequenceTest as _HasSequenceTest,
|
|
83
|
+
HasSequenceTestEmpty as _HasSequenceTestEmpty,
|
|
84
|
+
) # noqa: F401, F403
|
|
80
85
|
from sqlalchemy.testing.suite.test_update_delete import * # noqa: F401, F403
|
|
81
86
|
from sqlalchemy.testing.suite.test_cte import CTETest as _CTETest
|
|
82
87
|
from sqlalchemy.testing.suite.test_ddl import TableDDLTest as _TableDDLTest
|
|
@@ -2392,3 +2397,128 @@ class CreateEngineWithoutDatabaseTest(fixtures.TestBase):
|
|
|
2392
2397
|
engine = create_engine(get_db_url().split("/database")[0])
|
|
2393
2398
|
with engine.connect() as connection:
|
|
2394
2399
|
assert connection.connection.database is None
|
|
2400
|
+
|
|
2401
|
+
|
|
2402
|
+
@pytest.mark.skipif(
|
|
2403
|
+
bool(os.environ.get("SPANNER_EMULATOR_HOST")), reason="Skipped on emulator"
|
|
2404
|
+
)
|
|
2405
|
+
class SequenceTest(_SequenceTest):
|
|
2406
|
+
@classmethod
|
|
2407
|
+
def define_tables(cls, metadata):
|
|
2408
|
+
Table(
|
|
2409
|
+
"seq_pk",
|
|
2410
|
+
metadata,
|
|
2411
|
+
Column(
|
|
2412
|
+
"id",
|
|
2413
|
+
Integer,
|
|
2414
|
+
sqlalchemy.Sequence("tab_id_seq"),
|
|
2415
|
+
primary_key=True,
|
|
2416
|
+
),
|
|
2417
|
+
Column("data", String(50)),
|
|
2418
|
+
)
|
|
2419
|
+
|
|
2420
|
+
Table(
|
|
2421
|
+
"seq_opt_pk",
|
|
2422
|
+
metadata,
|
|
2423
|
+
Column(
|
|
2424
|
+
"id",
|
|
2425
|
+
Integer,
|
|
2426
|
+
sqlalchemy.Sequence("tab_id_seq_opt", data_type=Integer, optional=True),
|
|
2427
|
+
primary_key=True,
|
|
2428
|
+
),
|
|
2429
|
+
Column("data", String(50)),
|
|
2430
|
+
)
|
|
2431
|
+
|
|
2432
|
+
Table(
|
|
2433
|
+
"seq_no_returning",
|
|
2434
|
+
metadata,
|
|
2435
|
+
Column(
|
|
2436
|
+
"id",
|
|
2437
|
+
Integer,
|
|
2438
|
+
sqlalchemy.Sequence("noret_id_seq"),
|
|
2439
|
+
primary_key=True,
|
|
2440
|
+
),
|
|
2441
|
+
Column("data", String(50)),
|
|
2442
|
+
implicit_returning=False,
|
|
2443
|
+
)
|
|
2444
|
+
|
|
2445
|
+
def test_insert_lastrowid(self, connection):
|
|
2446
|
+
r = connection.execute(self.tables.seq_pk.insert(), dict(data="some data"))
|
|
2447
|
+
assert len(r.inserted_primary_key) == 1
|
|
2448
|
+
is_instance_of(r.inserted_primary_key[0], int)
|
|
2449
|
+
|
|
2450
|
+
def test_nextval_direct(self, connection):
|
|
2451
|
+
r = connection.execute(self.tables.seq_pk.c.id.default)
|
|
2452
|
+
is_instance_of(r, int)
|
|
2453
|
+
|
|
2454
|
+
def _assert_round_trip(self, table, conn):
|
|
2455
|
+
row = conn.execute(table.select()).first()
|
|
2456
|
+
id, name = row
|
|
2457
|
+
is_instance_of(id, int)
|
|
2458
|
+
eq_(name, "some data")
|
|
2459
|
+
|
|
2460
|
+
@testing.combinations((True,), (False,), argnames="implicit_returning")
|
|
2461
|
+
@testing.requires.schemas
|
|
2462
|
+
@pytest.mark.skip("Spanner doesn't support user defined schemas")
|
|
2463
|
+
def test_insert_roundtrip_translate(self, connection, implicit_returning):
|
|
2464
|
+
pass
|
|
2465
|
+
|
|
2466
|
+
@testing.requires.schemas
|
|
2467
|
+
@pytest.mark.skip("Spanner doesn't support user defined schemas")
|
|
2468
|
+
def test_nextval_direct_schema_translate(self, connection):
|
|
2469
|
+
pass
|
|
2470
|
+
|
|
2471
|
+
|
|
2472
|
+
@pytest.mark.skipif(
|
|
2473
|
+
bool(os.environ.get("SPANNER_EMULATOR_HOST")), reason="Skipped on emulator"
|
|
2474
|
+
)
|
|
2475
|
+
class HasSequenceTest(_HasSequenceTest):
|
|
2476
|
+
@classmethod
|
|
2477
|
+
def define_tables(cls, metadata):
|
|
2478
|
+
sqlalchemy.Sequence("user_id_seq", metadata=metadata)
|
|
2479
|
+
sqlalchemy.Sequence(
|
|
2480
|
+
"other_seq", metadata=metadata, nomaxvalue=True, nominvalue=True
|
|
2481
|
+
)
|
|
2482
|
+
Table(
|
|
2483
|
+
"user_id_table",
|
|
2484
|
+
metadata,
|
|
2485
|
+
Column("id", Integer, primary_key=True),
|
|
2486
|
+
)
|
|
2487
|
+
|
|
2488
|
+
@testing.requires.schemas
|
|
2489
|
+
@pytest.mark.skip("Spanner doesn't support user defined schemas")
|
|
2490
|
+
def test_has_sequence_schema(self, connection):
|
|
2491
|
+
pass
|
|
2492
|
+
|
|
2493
|
+
@testing.requires.schemas
|
|
2494
|
+
@pytest.mark.skip("Spanner doesn't support user defined schemas")
|
|
2495
|
+
def test_has_sequence_schemas_neg(self, connection):
|
|
2496
|
+
pass
|
|
2497
|
+
|
|
2498
|
+
@testing.requires.schemas
|
|
2499
|
+
@pytest.mark.skip("Spanner doesn't support user defined schemas")
|
|
2500
|
+
def test_has_sequence_default_not_in_remote(self, connection):
|
|
2501
|
+
pass
|
|
2502
|
+
|
|
2503
|
+
@testing.requires.schemas
|
|
2504
|
+
@pytest.mark.skip("Spanner doesn't support user defined schemas")
|
|
2505
|
+
def test_has_sequence_remote_not_in_default(self, connection):
|
|
2506
|
+
pass
|
|
2507
|
+
|
|
2508
|
+
@testing.requires.schemas
|
|
2509
|
+
@pytest.mark.skip("Spanner doesn't support user defined schemas")
|
|
2510
|
+
def test_get_sequence_names_no_sequence_schema(self, connection):
|
|
2511
|
+
pass
|
|
2512
|
+
|
|
2513
|
+
@testing.requires.schemas
|
|
2514
|
+
@pytest.mark.skip("Spanner doesn't support user defined schemas")
|
|
2515
|
+
def test_get_sequence_names_sequences_schema(self, connection):
|
|
2516
|
+
pass
|
|
2517
|
+
|
|
2518
|
+
|
|
2519
|
+
@pytest.mark.skipif(
|
|
2520
|
+
bool(os.environ.get("SPANNER_EMULATOR_HOST")), reason="Skipped on emulator"
|
|
2521
|
+
)
|
|
2522
|
+
class HasSequenceTestEmpty(_HasSequenceTestEmpty):
|
|
2523
|
+
def test_get_sequence_names_no_sequence(self, connection):
|
|
2524
|
+
super().test_get_sequence_names_no_sequence(connection)
|
|
@@ -39,6 +39,7 @@ from sqlalchemy.schema import Computed
|
|
|
39
39
|
from sqlalchemy.testing import config
|
|
40
40
|
from sqlalchemy.testing import engines
|
|
41
41
|
from sqlalchemy.testing import eq_
|
|
42
|
+
from sqlalchemy.testing import is_instance_of
|
|
42
43
|
from sqlalchemy.testing import provide_metadata, emits_warning
|
|
43
44
|
from sqlalchemy.testing import fixtures
|
|
44
45
|
from sqlalchemy.testing.provision import temp_table_keyword_args
|
|
@@ -80,7 +81,11 @@ from sqlalchemy.testing.suite.test_reflection import * # noqa: F401, F403
|
|
|
80
81
|
from sqlalchemy.testing.suite.test_deprecations import * # noqa: F401, F403
|
|
81
82
|
from sqlalchemy.testing.suite.test_results import * # noqa: F401, F403
|
|
82
83
|
from sqlalchemy.testing.suite.test_select import * # noqa: F401, F403
|
|
83
|
-
from sqlalchemy.testing.suite.test_sequence import
|
|
84
|
+
from sqlalchemy.testing.suite.test_sequence import (
|
|
85
|
+
SequenceTest as _SequenceTest,
|
|
86
|
+
HasSequenceTest as _HasSequenceTest,
|
|
87
|
+
HasSequenceTestEmpty as _HasSequenceTestEmpty,
|
|
88
|
+
) # noqa: F401, F403
|
|
84
89
|
from sqlalchemy.testing.suite.test_unicode_ddl import * # noqa: F401, F403
|
|
85
90
|
from sqlalchemy.testing.suite.test_update_delete import * # noqa: F401, F403
|
|
86
91
|
from sqlalchemy.testing.suite.test_cte import CTETest as _CTETest
|
|
@@ -144,7 +149,10 @@ from sqlalchemy.testing.suite.test_types import ( # noqa: F401, F403
|
|
|
144
149
|
UnicodeTextTest as _UnicodeTextTest,
|
|
145
150
|
_UnicodeFixture as __UnicodeFixture,
|
|
146
151
|
) # noqa: F401, F403
|
|
147
|
-
from test._helpers import
|
|
152
|
+
from test._helpers import (
|
|
153
|
+
get_db_url,
|
|
154
|
+
get_project,
|
|
155
|
+
)
|
|
148
156
|
|
|
149
157
|
config.test_schema = ""
|
|
150
158
|
|
|
@@ -157,7 +165,7 @@ class BooleanTest(_BooleanTest):
|
|
|
157
165
|
def test_render_literal_bool(self):
|
|
158
166
|
pass
|
|
159
167
|
|
|
160
|
-
def test_render_literal_bool_true(self,
|
|
168
|
+
def test_render_literal_bool_true(self, literal_round_trip_spanner):
|
|
161
169
|
"""
|
|
162
170
|
SPANNER OVERRIDE:
|
|
163
171
|
|
|
@@ -166,9 +174,9 @@ class BooleanTest(_BooleanTest):
|
|
|
166
174
|
following insertions will fail with `Row [] already exists".
|
|
167
175
|
Overriding the test to avoid the same failure.
|
|
168
176
|
"""
|
|
169
|
-
|
|
177
|
+
literal_round_trip_spanner(Boolean(), [True], [True])
|
|
170
178
|
|
|
171
|
-
def test_render_literal_bool_false(self,
|
|
179
|
+
def test_render_literal_bool_false(self, literal_round_trip_spanner):
|
|
172
180
|
"""
|
|
173
181
|
SPANNER OVERRIDE:
|
|
174
182
|
|
|
@@ -177,7 +185,7 @@ class BooleanTest(_BooleanTest):
|
|
|
177
185
|
following insertions will fail with `Row [] already exists".
|
|
178
186
|
Overriding the test to avoid the same failure.
|
|
179
187
|
"""
|
|
180
|
-
|
|
188
|
+
literal_round_trip_spanner(Boolean(), [False], [False])
|
|
181
189
|
|
|
182
190
|
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
183
191
|
def test_whereclause(self):
|
|
@@ -1998,6 +2006,9 @@ class IntegerTest(_IntegerTest):
|
|
|
1998
2006
|
intvalue,
|
|
1999
2007
|
)
|
|
2000
2008
|
|
|
2009
|
+
def test_literal(self, literal_round_trip_spanner):
|
|
2010
|
+
literal_round_trip_spanner(Integer, [5], [5])
|
|
2011
|
+
|
|
2001
2012
|
|
|
2002
2013
|
class _UnicodeFixture(__UnicodeFixture):
|
|
2003
2014
|
@classmethod
|
|
@@ -2111,6 +2122,10 @@ class InsertBehaviorTest(_InsertBehaviorTest):
|
|
|
2111
2122
|
def test_insert_from_select_autoinc(self):
|
|
2112
2123
|
pass
|
|
2113
2124
|
|
|
2125
|
+
@pytest.mark.skip("Spanner does not support auto increment")
|
|
2126
|
+
def test_no_results_for_non_returning_insert(self, connection, style, executemany):
|
|
2127
|
+
pass
|
|
2128
|
+
|
|
2114
2129
|
def test_autoclose_on_insert(self):
|
|
2115
2130
|
"""
|
|
2116
2131
|
SPANNER OVERRIDE:
|
|
@@ -2180,6 +2195,19 @@ class StringTest(_StringTest):
|
|
|
2180
2195
|
args[1],
|
|
2181
2196
|
)
|
|
2182
2197
|
|
|
2198
|
+
def test_literal(self, literal_round_trip_spanner):
|
|
2199
|
+
# note that in Python 3, this invokes the Unicode
|
|
2200
|
+
# datatype for the literal part because all strings are unicode
|
|
2201
|
+
literal_round_trip_spanner(String(40), ["some text"], ["some text"])
|
|
2202
|
+
|
|
2203
|
+
def test_literal_quoting(self, literal_round_trip_spanner):
|
|
2204
|
+
data = """some 'text' hey "hi there" that's text"""
|
|
2205
|
+
literal_round_trip_spanner(String(40), [data], [data])
|
|
2206
|
+
|
|
2207
|
+
def test_literal_backslashes(self, literal_round_trip_spanner):
|
|
2208
|
+
data = r"backslash one \ backslash two \\ end"
|
|
2209
|
+
literal_round_trip_spanner(String(40), [data], [data])
|
|
2210
|
+
|
|
2183
2211
|
|
|
2184
2212
|
class TextTest(_TextTest):
|
|
2185
2213
|
@classmethod
|
|
@@ -2215,6 +2243,21 @@ class TextTest(_TextTest):
|
|
|
2215
2243
|
def test_text_null_strings(self, connection):
|
|
2216
2244
|
pass
|
|
2217
2245
|
|
|
2246
|
+
def test_literal(self, literal_round_trip_spanner):
|
|
2247
|
+
literal_round_trip_spanner(Text, ["some text"], ["some text"])
|
|
2248
|
+
|
|
2249
|
+
def test_literal_quoting(self, literal_round_trip_spanner):
|
|
2250
|
+
data = """some 'text' hey "hi there" that's text"""
|
|
2251
|
+
literal_round_trip_spanner(Text, [data], [data])
|
|
2252
|
+
|
|
2253
|
+
def test_literal_backslashes(self, literal_round_trip_spanner):
|
|
2254
|
+
data = r"backslash one \ backslash two \\ end"
|
|
2255
|
+
literal_round_trip_spanner(Text, [data], [data])
|
|
2256
|
+
|
|
2257
|
+
def test_literal_percentsigns(self, literal_round_trip_spanner):
|
|
2258
|
+
data = r"percent % signs %% percent"
|
|
2259
|
+
literal_round_trip_spanner(Text, [data], [data])
|
|
2260
|
+
|
|
2218
2261
|
|
|
2219
2262
|
class NumericTest(_NumericTest):
|
|
2220
2263
|
@testing.fixture
|
|
@@ -2245,7 +2288,7 @@ class NumericTest(_NumericTest):
|
|
|
2245
2288
|
return run
|
|
2246
2289
|
|
|
2247
2290
|
@emits_warning(r".*does \*not\* support Decimal objects natively")
|
|
2248
|
-
def test_render_literal_numeric(self,
|
|
2291
|
+
def test_render_literal_numeric(self, literal_round_trip_spanner):
|
|
2249
2292
|
"""
|
|
2250
2293
|
SPANNER OVERRIDE:
|
|
2251
2294
|
|
|
@@ -2254,14 +2297,14 @@ class NumericTest(_NumericTest):
|
|
|
2254
2297
|
following insertions will fail with `Row [] already exists".
|
|
2255
2298
|
Overriding the test to avoid the same failure.
|
|
2256
2299
|
"""
|
|
2257
|
-
|
|
2300
|
+
literal_round_trip_spanner(
|
|
2258
2301
|
Numeric(precision=8, scale=4),
|
|
2259
2302
|
[decimal.Decimal("15.7563")],
|
|
2260
2303
|
[decimal.Decimal("15.7563")],
|
|
2261
2304
|
)
|
|
2262
2305
|
|
|
2263
2306
|
@emits_warning(r".*does \*not\* support Decimal objects natively")
|
|
2264
|
-
def test_render_literal_numeric_asfloat(self,
|
|
2307
|
+
def test_render_literal_numeric_asfloat(self, literal_round_trip_spanner):
|
|
2265
2308
|
"""
|
|
2266
2309
|
SPANNER OVERRIDE:
|
|
2267
2310
|
|
|
@@ -2270,13 +2313,13 @@ class NumericTest(_NumericTest):
|
|
|
2270
2313
|
following insertions will fail with `Row [] already exists".
|
|
2271
2314
|
Overriding the test to avoid the same failure.
|
|
2272
2315
|
"""
|
|
2273
|
-
|
|
2316
|
+
literal_round_trip_spanner(
|
|
2274
2317
|
Numeric(precision=8, scale=4, asdecimal=False),
|
|
2275
2318
|
[decimal.Decimal("15.7563")],
|
|
2276
2319
|
[15.7563],
|
|
2277
2320
|
)
|
|
2278
2321
|
|
|
2279
|
-
def test_render_literal_float(self,
|
|
2322
|
+
def test_render_literal_float(self, literal_round_trip_spanner):
|
|
2280
2323
|
"""
|
|
2281
2324
|
SPANNER OVERRIDE:
|
|
2282
2325
|
|
|
@@ -2285,7 +2328,7 @@ class NumericTest(_NumericTest):
|
|
|
2285
2328
|
following insertions will fail with `Row [] already exists".
|
|
2286
2329
|
Overriding the test to avoid the same failure.
|
|
2287
2330
|
"""
|
|
2288
|
-
|
|
2331
|
+
literal_round_trip_spanner(
|
|
2289
2332
|
Float(4),
|
|
2290
2333
|
[decimal.Decimal("15.7563")],
|
|
2291
2334
|
[15.7563],
|
|
@@ -2495,6 +2538,17 @@ class IsOrIsNotDistinctFromTest(_IsOrIsNotDistinctFromTest):
|
|
|
2495
2538
|
pass
|
|
2496
2539
|
|
|
2497
2540
|
|
|
2541
|
+
@pytest.mark.skip("Spanner doesn't bizarre characters in foreign key names")
|
|
2542
|
+
class BizarroCharacterFKResolutionTest(fixtures.TestBase):
|
|
2543
|
+
pass
|
|
2544
|
+
|
|
2545
|
+
|
|
2546
|
+
class IsolationLevelTest(fixtures.TestBase):
|
|
2547
|
+
@pytest.mark.skip("Cloud Spanner does not support different isolation levels")
|
|
2548
|
+
def test_dialect_user_setting_is_restored(self, testing_engine):
|
|
2549
|
+
pass
|
|
2550
|
+
|
|
2551
|
+
|
|
2498
2552
|
class OrderByLabelTest(_OrderByLabelTest):
|
|
2499
2553
|
@pytest.mark.skip(
|
|
2500
2554
|
"Spanner requires an alias for the GROUP BY list when specifying derived "
|
|
@@ -3041,3 +3095,132 @@ class CreateEngineWithoutDatabaseTest(fixtures.TestBase):
|
|
|
3041
3095
|
engine = create_engine(get_db_url().split("/database")[0])
|
|
3042
3096
|
with engine.connect() as connection:
|
|
3043
3097
|
assert connection.connection.database is None
|
|
3098
|
+
|
|
3099
|
+
|
|
3100
|
+
@pytest.mark.skipif(
|
|
3101
|
+
bool(os.environ.get("SPANNER_EMULATOR_HOST")), reason="Skipped on emulator"
|
|
3102
|
+
)
|
|
3103
|
+
class SequenceTest(_SequenceTest):
|
|
3104
|
+
@classmethod
|
|
3105
|
+
def define_tables(cls, metadata):
|
|
3106
|
+
Table(
|
|
3107
|
+
"seq_pk",
|
|
3108
|
+
metadata,
|
|
3109
|
+
Column(
|
|
3110
|
+
"id",
|
|
3111
|
+
Integer,
|
|
3112
|
+
sqlalchemy.Sequence("tab_id_seq"),
|
|
3113
|
+
primary_key=True,
|
|
3114
|
+
),
|
|
3115
|
+
Column("data", String(50)),
|
|
3116
|
+
)
|
|
3117
|
+
|
|
3118
|
+
Table(
|
|
3119
|
+
"seq_opt_pk",
|
|
3120
|
+
metadata,
|
|
3121
|
+
Column(
|
|
3122
|
+
"id",
|
|
3123
|
+
Integer,
|
|
3124
|
+
sqlalchemy.Sequence("tab_id_seq_opt", data_type=Integer, optional=True),
|
|
3125
|
+
primary_key=True,
|
|
3126
|
+
),
|
|
3127
|
+
Column("data", String(50)),
|
|
3128
|
+
)
|
|
3129
|
+
|
|
3130
|
+
Table(
|
|
3131
|
+
"seq_no_returning",
|
|
3132
|
+
metadata,
|
|
3133
|
+
Column(
|
|
3134
|
+
"id",
|
|
3135
|
+
Integer,
|
|
3136
|
+
sqlalchemy.Sequence("noret_id_seq"),
|
|
3137
|
+
primary_key=True,
|
|
3138
|
+
),
|
|
3139
|
+
Column("data", String(50)),
|
|
3140
|
+
implicit_returning=False,
|
|
3141
|
+
)
|
|
3142
|
+
|
|
3143
|
+
def test_insert_lastrowid(self, connection):
|
|
3144
|
+
r = connection.execute(self.tables.seq_pk.insert(), dict(data="some data"))
|
|
3145
|
+
assert len(r.inserted_primary_key) == 1
|
|
3146
|
+
is_instance_of(r.inserted_primary_key[0], int)
|
|
3147
|
+
|
|
3148
|
+
def test_nextval_direct(self, connection):
|
|
3149
|
+
r = connection.execute(self.tables.seq_pk.c.id.default)
|
|
3150
|
+
is_instance_of(r, int)
|
|
3151
|
+
|
|
3152
|
+
def _assert_round_trip(self, table, conn):
|
|
3153
|
+
row = conn.execute(table.select()).first()
|
|
3154
|
+
id, name = row
|
|
3155
|
+
is_instance_of(id, int)
|
|
3156
|
+
eq_(name, "some data")
|
|
3157
|
+
|
|
3158
|
+
@testing.combinations((True,), (False,), argnames="implicit_returning")
|
|
3159
|
+
@testing.requires.schemas
|
|
3160
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
3161
|
+
def test_insert_roundtrip_translate(self, connection, implicit_returning):
|
|
3162
|
+
pass
|
|
3163
|
+
|
|
3164
|
+
@testing.requires.schemas
|
|
3165
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
3166
|
+
def test_nextval_direct_schema_translate(self, connection):
|
|
3167
|
+
pass
|
|
3168
|
+
|
|
3169
|
+
|
|
3170
|
+
@pytest.mark.skipif(
|
|
3171
|
+
bool(os.environ.get("SPANNER_EMULATOR_HOST")), reason="Skipped on emulator"
|
|
3172
|
+
)
|
|
3173
|
+
class HasSequenceTest(_HasSequenceTest):
|
|
3174
|
+
@classmethod
|
|
3175
|
+
def define_tables(cls, metadata):
|
|
3176
|
+
sqlalchemy.Sequence("user_id_seq", metadata=metadata)
|
|
3177
|
+
sqlalchemy.Sequence(
|
|
3178
|
+
"other_seq", metadata=metadata, nomaxvalue=True, nominvalue=True
|
|
3179
|
+
)
|
|
3180
|
+
Table(
|
|
3181
|
+
"user_id_table",
|
|
3182
|
+
metadata,
|
|
3183
|
+
Column("id", Integer, primary_key=True),
|
|
3184
|
+
)
|
|
3185
|
+
|
|
3186
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
3187
|
+
def test_has_sequence_cache(self, connection, metadata):
|
|
3188
|
+
pass
|
|
3189
|
+
|
|
3190
|
+
@testing.requires.schemas
|
|
3191
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
3192
|
+
def test_has_sequence_schema(self, connection):
|
|
3193
|
+
pass
|
|
3194
|
+
|
|
3195
|
+
@testing.requires.schemas
|
|
3196
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
3197
|
+
def test_has_sequence_schemas_neg(self, connection):
|
|
3198
|
+
pass
|
|
3199
|
+
|
|
3200
|
+
@testing.requires.schemas
|
|
3201
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
3202
|
+
def test_has_sequence_default_not_in_remote(self, connection):
|
|
3203
|
+
pass
|
|
3204
|
+
|
|
3205
|
+
@testing.requires.schemas
|
|
3206
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
3207
|
+
def test_has_sequence_remote_not_in_default(self, connection):
|
|
3208
|
+
pass
|
|
3209
|
+
|
|
3210
|
+
@testing.requires.schemas
|
|
3211
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
3212
|
+
def test_get_sequence_names_no_sequence_schema(self, connection):
|
|
3213
|
+
pass
|
|
3214
|
+
|
|
3215
|
+
@testing.requires.schemas
|
|
3216
|
+
@pytest.mark.skip("Not supported by Cloud Spanner")
|
|
3217
|
+
def test_get_sequence_names_sequences_schema(self, connection):
|
|
3218
|
+
pass
|
|
3219
|
+
|
|
3220
|
+
|
|
3221
|
+
@pytest.mark.skipif(
|
|
3222
|
+
bool(os.environ.get("SPANNER_EMULATOR_HOST")), reason="Skipped on emulator"
|
|
3223
|
+
)
|
|
3224
|
+
class HasSequenceTestEmpty(_HasSequenceTestEmpty):
|
|
3225
|
+
def test_get_sequence_names_no_sequence(self, connection):
|
|
3226
|
+
super().test_get_sequence_names_no_sequence(connection)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/google/cloud/sqlalchemy_spanner/__init__.py
RENAMED
|
File without changes
|
{sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/google/cloud/sqlalchemy_spanner/provision.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/sqlalchemy_spanner.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/sqlalchemy_spanner.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/sqlalchemy_spanner.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/sqlalchemy_spanner.egg-info/requires.txt
RENAMED
|
File without changes
|
{sqlalchemy-spanner-1.6.2 → sqlalchemy_spanner-1.7.0}/sqlalchemy_spanner.egg-info/top_level.txt
RENAMED
|
File without changes
|