google-cloud-spanner 3.53.0__tar.gz → 3.54.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.
- {google_cloud_spanner-3.53.0/google_cloud_spanner.egg-info → google_cloud_spanner-3.54.0}/PKG-INFO +1 -1
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/gapic_version.py +1 -1
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/gapic_version.py +1 -1
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/batch_dml_executor.py +3 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/client_side_statement_executor.py +19 -2
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/client_side_statement_parser.py +15 -8
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/connection.py +39 -11
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/cursor.py +3 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/parse_utils.py +16 -6
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/parsed_statement.py +1 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/__init__.py +2 -1
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/_helpers.py +12 -1
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/data_types.py +148 -1
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/gapic_version.py +1 -1
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/param_types.py +1 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/streamed.py +1 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0/google_cloud_spanner.egg-info}/PKG-INFO +1 -1
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google_cloud_spanner.egg-info/SOURCES.txt +2 -0
- google_cloud_spanner-3.54.0/tests/mockserver_tests/test_dbapi_isolation_level.py +150 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/_helpers.py +12 -1
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/conftest.py +10 -3
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/test_session_api.py +207 -0
- google_cloud_spanner-3.54.0/tests/unit/spanner_dbapi/test_client_side_statement_executor.py +54 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_parse_utils.py +95 -1
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test__helpers.py +481 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_metrics.py +0 -1
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/LICENSE +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/MANIFEST.in +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/README.rst +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/gapic_metadata.json +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/py.typed +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/async_client.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/client.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/pagers.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/transports/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/transports/base.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/transports/grpc.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/transports/grpc_asyncio.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/transports/rest.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/services/database_admin/transports/rest_base.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/types/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/types/backup.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/types/backup_schedule.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/types/common.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_database_v1/types/spanner_database_admin.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/gapic_metadata.json +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/py.typed +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/async_client.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/client.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/pagers.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/base.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/grpc.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/grpc_asyncio.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/rest.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/services/instance_admin/transports/rest_base.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/types/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/types/common.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_admin_instance_v1/types/spanner_instance_admin.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/_helpers.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/checksum.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/exceptions.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/parser.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/partition_helper.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/transaction_helper.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/types.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/utils.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/version.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/_opentelemetry_tracing.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/backup.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/batch.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/client.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/database.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/gapic_metadata.json +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/instance.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/keyset.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/merged_result_set.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/metrics/constants.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/metrics/metrics_capture.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/metrics/metrics_exporter.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/metrics/metrics_interceptor.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/metrics/metrics_tracer.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/metrics/metrics_tracer_factory.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/metrics/spanner_metrics_tracer_factory.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/pool.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/py.typed +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/request_id_header.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/async_client.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/client.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/pagers.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/transports/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/transports/base.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/transports/grpc.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/transports/grpc_asyncio.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/transports/rest.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/services/spanner/transports/rest_base.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/session.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/snapshot.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/table.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/testing/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/testing/database_test.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/testing/interceptors.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/testing/mock_database_admin.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/testing/mock_spanner.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/testing/spanner_database_admin_pb2_grpc.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/testing/spanner_pb2_grpc.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/transaction.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/commit_response.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/keys.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/mutation.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/query_plan.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/result_set.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/spanner.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/transaction.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/types/type.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google_cloud_spanner.egg-info/dependency_links.txt +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google_cloud_spanner.egg-info/not-zip-safe +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google_cloud_spanner.egg-info/requires.txt +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google_cloud_spanner.egg-info/top_level.txt +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/setup.cfg +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/setup.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/_fixtures.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/_helpers.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/mockserver_tests/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/mockserver_tests/mock_server_test_base.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/mockserver_tests/test_aborted_transaction.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/mockserver_tests/test_basics.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/mockserver_tests/test_dbapi_autocommit.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/mockserver_tests/test_tags.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/_sample_data.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/test_backup_api.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/test_database_api.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/test_dbapi.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/test_instance_api.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/test_observability_options.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/test_streaming_chunking.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/test_table_api.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/testdata/descriptors.pb +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/testdata/singer.proto +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/testdata/singer_pb2.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/utils/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/utils/clear_streaming.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/utils/populate_streaming.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/utils/scrub_instances.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/system/utils/streaming_utils.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/gapic/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/gapic/spanner_admin_database_v1/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/gapic/spanner_admin_database_v1/test_database_admin.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/gapic/spanner_admin_instance_v1/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/gapic/spanner_admin_instance_v1/test_instance_admin.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/gapic/spanner_v1/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/gapic/spanner_v1/test_spanner.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/__init__.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test__helpers.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_batch_dml_executor.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_checksum.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_connect.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_connection.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_cursor.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_globals.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_parser.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_transaction_helper.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_types.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/spanner_dbapi/test_utils.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/streaming-read-acceptance-test.json +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test__opentelemetry_tracing.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_atomic_counter.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_backup.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_batch.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_client.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_database.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_datatypes.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_instance.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_keyset.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_metrics_capture.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_metrics_exporter.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_metrics_interceptor.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_metrics_tracer.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_metrics_tracer_factory.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_packaging.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_param_types.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_pool.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_session.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_snapshot.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_spanner.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_spanner_metrics_tracer_factory.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_streamed.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_table.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/test_transaction.py +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/testdata/singer.proto +0 -0
- {google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/tests/unit/testdata/singer_pb2.py +0 -0
|
@@ -54,9 +54,12 @@ class BatchDmlExecutor:
|
|
|
54
54
|
"""
|
|
55
55
|
from google.cloud.spanner_dbapi import ProgrammingError
|
|
56
56
|
|
|
57
|
+
# Note: Let the server handle it if the client-side parser did not
|
|
58
|
+
# recognize the type of statement.
|
|
57
59
|
if (
|
|
58
60
|
parsed_statement.statement_type != StatementType.UPDATE
|
|
59
61
|
and parsed_statement.statement_type != StatementType.INSERT
|
|
62
|
+
and parsed_statement.statement_type != StatementType.UNKNOWN
|
|
60
63
|
):
|
|
61
64
|
raise ProgrammingError("Only DML statements are allowed in batch DML mode.")
|
|
62
65
|
self._statements.append(parsed_statement.statement)
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
from typing import TYPE_CHECKING
|
|
14
|
+
from typing import TYPE_CHECKING, Union
|
|
15
|
+
from google.cloud.spanner_v1 import TransactionOptions
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
17
18
|
from google.cloud.spanner_dbapi.cursor import Cursor
|
|
@@ -58,7 +59,7 @@ def execute(cursor: "Cursor", parsed_statement: ParsedStatement):
|
|
|
58
59
|
connection.commit()
|
|
59
60
|
return None
|
|
60
61
|
if statement_type == ClientSideStatementType.BEGIN:
|
|
61
|
-
connection.begin()
|
|
62
|
+
connection.begin(isolation_level=_get_isolation_level(parsed_statement))
|
|
62
63
|
return None
|
|
63
64
|
if statement_type == ClientSideStatementType.ROLLBACK:
|
|
64
65
|
connection.rollback()
|
|
@@ -121,3 +122,19 @@ def _get_streamed_result_set(column_name, type_code, column_values):
|
|
|
121
122
|
column_values_pb.append(_make_value_pb(column_value))
|
|
122
123
|
result_set.values.extend(column_values_pb)
|
|
123
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]
|
|
@@ -21,18 +21,21 @@ from google.cloud.spanner_dbapi.parsed_statement import (
|
|
|
21
21
|
Statement,
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
-
RE_BEGIN = re.compile(
|
|
25
|
-
|
|
26
|
-
|
|
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)
|
|
27
30
|
RE_SHOW_COMMIT_TIMESTAMP = re.compile(
|
|
28
|
-
r"^\s*(SHOW)\s+(VARIABLE)\s+(COMMIT_TIMESTAMP)", re.IGNORECASE
|
|
31
|
+
r"^\s*(SHOW)\s+(VARIABLE)\s+(COMMIT_TIMESTAMP)\s*$", re.IGNORECASE
|
|
29
32
|
)
|
|
30
33
|
RE_SHOW_READ_TIMESTAMP = re.compile(
|
|
31
|
-
r"^\s*(SHOW)\s+(VARIABLE)\s+(READ_TIMESTAMP)", re.IGNORECASE
|
|
34
|
+
r"^\s*(SHOW)\s+(VARIABLE)\s+(READ_TIMESTAMP)\s*$", re.IGNORECASE
|
|
32
35
|
)
|
|
33
|
-
RE_START_BATCH_DML = re.compile(r"^\s*(START)\s+(BATCH)\s+(DML)", re.IGNORECASE)
|
|
34
|
-
RE_RUN_BATCH = re.compile(r"^\s*(RUN)\s+(BATCH)", re.IGNORECASE)
|
|
35
|
-
RE_ABORT_BATCH = re.compile(r"^\s*(ABORT)\s+(BATCH)", re.IGNORECASE)
|
|
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)
|
|
36
39
|
RE_PARTITION_QUERY = re.compile(r"^\s*(PARTITION)\s+(.+)", re.IGNORECASE)
|
|
37
40
|
RE_RUN_PARTITION = re.compile(r"^\s*(RUN)\s+(PARTITION)\s+(.+)", re.IGNORECASE)
|
|
38
41
|
RE_RUN_PARTITIONED_QUERY = re.compile(
|
|
@@ -68,6 +71,10 @@ def parse_stmt(query):
|
|
|
68
71
|
elif RE_START_BATCH_DML.match(query):
|
|
69
72
|
client_side_statement_type = ClientSideStatementType.START_BATCH_DML
|
|
70
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)
|
|
71
78
|
client_side_statement_type = ClientSideStatementType.BEGIN
|
|
72
79
|
elif RE_RUN_BATCH.match(query):
|
|
73
80
|
client_side_statement_type = ClientSideStatementType.RUN_BATCH
|
{google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/connection.py
RENAMED
|
@@ -20,16 +20,12 @@ from google.api_core.gapic_v1.client_info import ClientInfo
|
|
|
20
20
|
from google.cloud import spanner_v1 as spanner
|
|
21
21
|
from google.cloud.spanner_dbapi import partition_helper
|
|
22
22
|
from google.cloud.spanner_dbapi.batch_dml_executor import BatchMode, BatchDmlExecutor
|
|
23
|
-
from google.cloud.spanner_dbapi.
|
|
24
|
-
from google.cloud.spanner_dbapi.parsed_statement import (
|
|
25
|
-
StatementType,
|
|
26
|
-
AutocommitDmlMode,
|
|
27
|
-
)
|
|
23
|
+
from google.cloud.spanner_dbapi.parsed_statement import AutocommitDmlMode
|
|
28
24
|
from google.cloud.spanner_dbapi.partition_helper import PartitionId
|
|
29
25
|
from google.cloud.spanner_dbapi.parsed_statement import ParsedStatement, Statement
|
|
30
26
|
from google.cloud.spanner_dbapi.transaction_helper import TransactionRetryHelper
|
|
31
27
|
from google.cloud.spanner_dbapi.cursor import Cursor
|
|
32
|
-
from google.cloud.spanner_v1 import RequestOptions
|
|
28
|
+
from google.cloud.spanner_v1 import RequestOptions, TransactionOptions
|
|
33
29
|
from google.cloud.spanner_v1.snapshot import Snapshot
|
|
34
30
|
|
|
35
31
|
from google.cloud.spanner_dbapi.exceptions import (
|
|
@@ -112,6 +108,7 @@ class Connection:
|
|
|
112
108
|
self._staleness = None
|
|
113
109
|
self.request_priority = None
|
|
114
110
|
self._transaction_begin_marked = False
|
|
111
|
+
self._transaction_isolation_level = None
|
|
115
112
|
# whether transaction started at Spanner. This means that we had
|
|
116
113
|
# made at least one call to Spanner.
|
|
117
114
|
self._spanner_transaction_started = False
|
|
@@ -283,6 +280,33 @@ class Connection:
|
|
|
283
280
|
"""
|
|
284
281
|
self._connection_variables["transaction_tag"] = value
|
|
285
282
|
|
|
283
|
+
@property
|
|
284
|
+
def isolation_level(self):
|
|
285
|
+
"""The default isolation level that is used for all read/write
|
|
286
|
+
transactions on this `Connection`.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
google.cloud.spanner_v1.types.TransactionOptions.IsolationLevel:
|
|
290
|
+
The isolation level that is used for read/write transactions on
|
|
291
|
+
this `Connection`.
|
|
292
|
+
"""
|
|
293
|
+
return self._connection_variables.get(
|
|
294
|
+
"isolation_level",
|
|
295
|
+
TransactionOptions.IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
@isolation_level.setter
|
|
299
|
+
def isolation_level(self, value: TransactionOptions.IsolationLevel):
|
|
300
|
+
"""Sets the isolation level that is used for all read/write
|
|
301
|
+
transactions on this `Connection`.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
value (google.cloud.spanner_v1.types.TransactionOptions.IsolationLevel):
|
|
305
|
+
The isolation level for all read/write transactions on this
|
|
306
|
+
`Connection`.
|
|
307
|
+
"""
|
|
308
|
+
self._connection_variables["isolation_level"] = value
|
|
309
|
+
|
|
286
310
|
@property
|
|
287
311
|
def staleness(self):
|
|
288
312
|
"""Current read staleness option value of this `Connection`.
|
|
@@ -363,6 +387,12 @@ class Connection:
|
|
|
363
387
|
if not self._spanner_transaction_started:
|
|
364
388
|
self._transaction = self._session_checkout().transaction()
|
|
365
389
|
self._transaction.transaction_tag = self.transaction_tag
|
|
390
|
+
if self._transaction_isolation_level:
|
|
391
|
+
self._transaction.isolation_level = (
|
|
392
|
+
self._transaction_isolation_level
|
|
393
|
+
)
|
|
394
|
+
else:
|
|
395
|
+
self._transaction.isolation_level = self.isolation_level
|
|
366
396
|
self.transaction_tag = None
|
|
367
397
|
self._snapshot = None
|
|
368
398
|
self._spanner_transaction_started = True
|
|
@@ -405,7 +435,7 @@ class Connection:
|
|
|
405
435
|
self.is_closed = True
|
|
406
436
|
|
|
407
437
|
@check_not_closed
|
|
408
|
-
def begin(self):
|
|
438
|
+
def begin(self, isolation_level=None):
|
|
409
439
|
"""
|
|
410
440
|
Marks the transaction as started.
|
|
411
441
|
|
|
@@ -421,6 +451,7 @@ class Connection:
|
|
|
421
451
|
"is already running"
|
|
422
452
|
)
|
|
423
453
|
self._transaction_begin_marked = True
|
|
454
|
+
self._transaction_isolation_level = isolation_level
|
|
424
455
|
|
|
425
456
|
def commit(self):
|
|
426
457
|
"""Commits any pending transaction to the database.
|
|
@@ -465,6 +496,7 @@ class Connection:
|
|
|
465
496
|
self._release_session()
|
|
466
497
|
self._transaction_helper.reset()
|
|
467
498
|
self._transaction_begin_marked = False
|
|
499
|
+
self._transaction_isolation_level = None
|
|
468
500
|
self._spanner_transaction_started = False
|
|
469
501
|
|
|
470
502
|
@check_not_closed
|
|
@@ -666,10 +698,6 @@ class Connection:
|
|
|
666
698
|
self._autocommit_dml_mode = autocommit_dml_mode
|
|
667
699
|
|
|
668
700
|
def _partitioned_query_validation(self, partitioned_query, statement):
|
|
669
|
-
if _get_statement_type(Statement(partitioned_query)) is not StatementType.QUERY:
|
|
670
|
-
raise ProgrammingError(
|
|
671
|
-
"Only queries can be partitioned. Invalid statement: " + statement.sql
|
|
672
|
-
)
|
|
673
701
|
if self.read_only is not True and self._client_transaction_started is True:
|
|
674
702
|
raise ProgrammingError(
|
|
675
703
|
"Partitioned query is not supported, because the connection is in a read/write transaction."
|
{google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_dbapi/cursor.py
RENAMED
|
@@ -404,9 +404,12 @@ class Cursor(object):
|
|
|
404
404
|
# For every operation, we've got to ensure that any prior DDL
|
|
405
405
|
# statements were run.
|
|
406
406
|
self.connection.run_prior_DDL_statements()
|
|
407
|
+
# Treat UNKNOWN statements as if they are DML and let the server
|
|
408
|
+
# determine what is wrong with it.
|
|
407
409
|
if self._parsed_statement.statement_type in (
|
|
408
410
|
StatementType.INSERT,
|
|
409
411
|
StatementType.UPDATE,
|
|
412
|
+
StatementType.UNKNOWN,
|
|
410
413
|
):
|
|
411
414
|
statements = []
|
|
412
415
|
for params in seq_of_params:
|
|
@@ -155,6 +155,7 @@ STMT_UPDATING = "UPDATING"
|
|
|
155
155
|
STMT_INSERT = "INSERT"
|
|
156
156
|
|
|
157
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.
|
|
158
159
|
RE_NON_UPDATE = re.compile(r"^\W*(SELECT|GRAPH|FROM)", re.IGNORECASE)
|
|
159
160
|
|
|
160
161
|
RE_WITH = re.compile(r"^\s*(WITH)", re.IGNORECASE)
|
|
@@ -162,18 +163,22 @@ RE_WITH = re.compile(r"^\s*(WITH)", re.IGNORECASE)
|
|
|
162
163
|
# DDL statements follow
|
|
163
164
|
# https://cloud.google.com/spanner/docs/data-definition-language
|
|
164
165
|
RE_DDL = re.compile(
|
|
165
|
-
r"^\s*(CREATE|ALTER|DROP|GRANT|REVOKE|RENAME)", re.IGNORECASE | re.DOTALL
|
|
166
|
+
r"^\s*(CREATE|ALTER|DROP|GRANT|REVOKE|RENAME|ANALYZE)", re.IGNORECASE | re.DOTALL
|
|
166
167
|
)
|
|
167
168
|
|
|
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)
|
|
169
173
|
|
|
170
174
|
RE_INSERT = re.compile(
|
|
171
175
|
# Only match the `INSERT INTO <table_name> (columns...)
|
|
172
176
|
# otherwise the rest of the statement could be a complex
|
|
173
177
|
# operation.
|
|
174
|
-
r"^\s*INSERT
|
|
178
|
+
r"^\s*INSERT(?:\s+INTO)?\s+(?P<table_name>[^\s\(\)]+)\s*\((?P<columns>[^\(\)]+)\)",
|
|
175
179
|
re.IGNORECASE | re.DOTALL,
|
|
176
180
|
)
|
|
181
|
+
"""Deprecated: Use the RE_IS_INSERT, RE_IS_UPDATE, and RE_IS_DELETE regexes"""
|
|
177
182
|
|
|
178
183
|
RE_VALUES_TILL_END = re.compile(r"VALUES\s*\(.+$", re.IGNORECASE | re.DOTALL)
|
|
179
184
|
|
|
@@ -259,8 +264,13 @@ def _get_statement_type(statement):
|
|
|
259
264
|
# statements and doesn't yet support WITH for DML statements.
|
|
260
265
|
return StatementType.QUERY
|
|
261
266
|
|
|
262
|
-
|
|
263
|
-
|
|
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
|
|
264
274
|
|
|
265
275
|
|
|
266
276
|
def sql_pyformat_args_to_spanner(sql, params):
|
|
@@ -355,7 +365,7 @@ def get_param_types(params):
|
|
|
355
365
|
def ensure_where_clause(sql):
|
|
356
366
|
"""
|
|
357
367
|
Cloud Spanner requires a WHERE clause on UPDATE and DELETE statements.
|
|
358
|
-
Add a dummy WHERE clause if
|
|
368
|
+
Add a dummy WHERE clause if not detected.
|
|
359
369
|
|
|
360
370
|
:type sql: str
|
|
361
371
|
:param sql: SQL code to check.
|
{google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/__init__.py
RENAMED
|
@@ -63,7 +63,7 @@ from .types.type import StructType
|
|
|
63
63
|
from .types.type import Type
|
|
64
64
|
from .types.type import TypeAnnotationCode
|
|
65
65
|
from .types.type import TypeCode
|
|
66
|
-
from .data_types import JsonObject
|
|
66
|
+
from .data_types import JsonObject, Interval
|
|
67
67
|
from .transaction import BatchTransactionId, DefaultTransactionOptions
|
|
68
68
|
|
|
69
69
|
from google.cloud.spanner_v1 import param_types
|
|
@@ -145,6 +145,7 @@ __all__ = (
|
|
|
145
145
|
"TypeCode",
|
|
146
146
|
# Custom spanner related data types
|
|
147
147
|
"JsonObject",
|
|
148
|
+
"Interval",
|
|
148
149
|
# google.cloud.spanner_v1.services
|
|
149
150
|
"SpannerClient",
|
|
150
151
|
"SpannerAsyncClient",
|
{google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/_helpers.py
RENAMED
|
@@ -31,7 +31,7 @@ from google.api_core.exceptions import Aborted
|
|
|
31
31
|
from google.cloud._helpers import _date_from_iso8601_date
|
|
32
32
|
from google.cloud.spanner_v1 import TypeCode
|
|
33
33
|
from google.cloud.spanner_v1 import ExecuteSqlRequest
|
|
34
|
-
from google.cloud.spanner_v1 import JsonObject
|
|
34
|
+
from google.cloud.spanner_v1 import JsonObject, Interval
|
|
35
35
|
from google.cloud.spanner_v1 import TransactionOptions
|
|
36
36
|
from google.cloud.spanner_v1.request_id_header import with_request_id
|
|
37
37
|
from google.rpc.error_details_pb2 import RetryInfo
|
|
@@ -251,6 +251,8 @@ def _make_value_pb(value):
|
|
|
251
251
|
return Value(null_value="NULL_VALUE")
|
|
252
252
|
else:
|
|
253
253
|
return Value(string_value=base64.b64encode(value))
|
|
254
|
+
if isinstance(value, Interval):
|
|
255
|
+
return Value(string_value=str(value))
|
|
254
256
|
|
|
255
257
|
raise ValueError("Unknown type: %s" % (value,))
|
|
256
258
|
|
|
@@ -367,6 +369,8 @@ def _get_type_decoder(field_type, field_name, column_info=None):
|
|
|
367
369
|
for item_field in field_type.struct_type.fields
|
|
368
370
|
]
|
|
369
371
|
return lambda value_pb: _parse_struct(value_pb, element_decoders)
|
|
372
|
+
elif type_code == TypeCode.INTERVAL:
|
|
373
|
+
return _parse_interval
|
|
370
374
|
else:
|
|
371
375
|
raise ValueError("Unknown type: %s" % (field_type,))
|
|
372
376
|
|
|
@@ -473,6 +477,13 @@ def _parse_nullable(value_pb, decoder):
|
|
|
473
477
|
return decoder(value_pb)
|
|
474
478
|
|
|
475
479
|
|
|
480
|
+
def _parse_interval(value_pb):
|
|
481
|
+
"""Parse a Value protobuf containing an interval."""
|
|
482
|
+
if hasattr(value_pb, "string_value"):
|
|
483
|
+
return Interval.from_str(value_pb.string_value)
|
|
484
|
+
return Interval.from_str(value_pb)
|
|
485
|
+
|
|
486
|
+
|
|
476
487
|
class _SessionWrapper(object):
|
|
477
488
|
"""Base class for objects wrapping a session.
|
|
478
489
|
|
{google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/data_types.py
RENAMED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
|
|
17
17
|
import json
|
|
18
18
|
import types
|
|
19
|
-
|
|
19
|
+
import re
|
|
20
|
+
from dataclasses import dataclass
|
|
20
21
|
from google.protobuf.message import Message
|
|
21
22
|
from google.protobuf.internal.enum_type_wrapper import EnumTypeWrapper
|
|
22
23
|
|
|
@@ -97,6 +98,152 @@ class JsonObject(dict):
|
|
|
97
98
|
return json.dumps(self, sort_keys=True, separators=(",", ":"))
|
|
98
99
|
|
|
99
100
|
|
|
101
|
+
@dataclass
|
|
102
|
+
class Interval:
|
|
103
|
+
"""Represents a Spanner INTERVAL type.
|
|
104
|
+
|
|
105
|
+
An interval is a combination of months, days and nanoseconds.
|
|
106
|
+
Internally, Spanner supports Interval value with the following range of individual fields:
|
|
107
|
+
months: [-120000, 120000]
|
|
108
|
+
days: [-3660000, 3660000]
|
|
109
|
+
nanoseconds: [-316224000000000000000, 316224000000000000000]
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
months: int = 0
|
|
113
|
+
days: int = 0
|
|
114
|
+
nanos: int = 0
|
|
115
|
+
|
|
116
|
+
def __str__(self) -> str:
|
|
117
|
+
"""Returns the ISO8601 duration format string representation."""
|
|
118
|
+
result = ["P"]
|
|
119
|
+
|
|
120
|
+
# Handle years and months
|
|
121
|
+
if self.months:
|
|
122
|
+
is_negative = self.months < 0
|
|
123
|
+
abs_months = abs(self.months)
|
|
124
|
+
years, months = divmod(abs_months, 12)
|
|
125
|
+
if years:
|
|
126
|
+
result.append(f"{'-' if is_negative else ''}{years}Y")
|
|
127
|
+
if months:
|
|
128
|
+
result.append(f"{'-' if is_negative else ''}{months}M")
|
|
129
|
+
|
|
130
|
+
# Handle days
|
|
131
|
+
if self.days:
|
|
132
|
+
result.append(f"{self.days}D")
|
|
133
|
+
|
|
134
|
+
# Handle time components
|
|
135
|
+
if self.nanos:
|
|
136
|
+
result.append("T")
|
|
137
|
+
nanos = abs(self.nanos)
|
|
138
|
+
is_negative = self.nanos < 0
|
|
139
|
+
|
|
140
|
+
# Convert to hours, minutes, seconds
|
|
141
|
+
nanos_per_hour = 3600000000000
|
|
142
|
+
hours, nanos = divmod(nanos, nanos_per_hour)
|
|
143
|
+
if hours:
|
|
144
|
+
if is_negative:
|
|
145
|
+
result.append("-")
|
|
146
|
+
result.append(f"{hours}H")
|
|
147
|
+
|
|
148
|
+
nanos_per_minute = 60000000000
|
|
149
|
+
minutes, nanos = divmod(nanos, nanos_per_minute)
|
|
150
|
+
if minutes:
|
|
151
|
+
if is_negative:
|
|
152
|
+
result.append("-")
|
|
153
|
+
result.append(f"{minutes}M")
|
|
154
|
+
|
|
155
|
+
nanos_per_second = 1000000000
|
|
156
|
+
seconds, nanos_fraction = divmod(nanos, nanos_per_second)
|
|
157
|
+
|
|
158
|
+
if seconds or nanos_fraction:
|
|
159
|
+
if is_negative:
|
|
160
|
+
result.append("-")
|
|
161
|
+
if seconds:
|
|
162
|
+
result.append(str(seconds))
|
|
163
|
+
elif nanos_fraction:
|
|
164
|
+
result.append("0")
|
|
165
|
+
|
|
166
|
+
if nanos_fraction:
|
|
167
|
+
nano_str = f"{nanos_fraction:09d}"
|
|
168
|
+
trimmed = nano_str.rstrip("0")
|
|
169
|
+
if len(trimmed) <= 3:
|
|
170
|
+
while len(trimmed) < 3:
|
|
171
|
+
trimmed += "0"
|
|
172
|
+
elif len(trimmed) <= 6:
|
|
173
|
+
while len(trimmed) < 6:
|
|
174
|
+
trimmed += "0"
|
|
175
|
+
else:
|
|
176
|
+
while len(trimmed) < 9:
|
|
177
|
+
trimmed += "0"
|
|
178
|
+
result.append(f".{trimmed}")
|
|
179
|
+
result.append("S")
|
|
180
|
+
|
|
181
|
+
if len(result) == 1:
|
|
182
|
+
result.append("0Y") # Special case for zero interval
|
|
183
|
+
|
|
184
|
+
return "".join(result)
|
|
185
|
+
|
|
186
|
+
@classmethod
|
|
187
|
+
def from_str(cls, s: str) -> "Interval":
|
|
188
|
+
"""Parse an ISO8601 duration format string into an Interval."""
|
|
189
|
+
pattern = r"^P(-?\d+Y)?(-?\d+M)?(-?\d+D)?(T(-?\d+H)?(-?\d+M)?(-?((\d+([.,]\d{1,9})?)|([.,]\d{1,9}))S)?)?$"
|
|
190
|
+
match = re.match(pattern, s)
|
|
191
|
+
if not match or len(s) == 1:
|
|
192
|
+
raise ValueError(f"Invalid interval format: {s}")
|
|
193
|
+
|
|
194
|
+
parts = match.groups()
|
|
195
|
+
if not any(parts[:3]) and not parts[3]:
|
|
196
|
+
raise ValueError(
|
|
197
|
+
f"Invalid interval format: at least one component (Y/M/D/H/M/S) is required: {s}"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if parts[3] == "T" and not any(parts[4:7]):
|
|
201
|
+
raise ValueError(
|
|
202
|
+
f"Invalid interval format: time designator 'T' present but no time components specified: {s}"
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def parse_num(s: str, suffix: str) -> int:
|
|
206
|
+
if not s:
|
|
207
|
+
return 0
|
|
208
|
+
return int(s.rstrip(suffix))
|
|
209
|
+
|
|
210
|
+
years = parse_num(parts[0], "Y")
|
|
211
|
+
months = parse_num(parts[1], "M")
|
|
212
|
+
total_months = years * 12 + months
|
|
213
|
+
|
|
214
|
+
days = parse_num(parts[2], "D")
|
|
215
|
+
|
|
216
|
+
nanos = 0
|
|
217
|
+
if parts[3]: # Has time component
|
|
218
|
+
# Convert hours to nanoseconds
|
|
219
|
+
hours = parse_num(parts[4], "H")
|
|
220
|
+
nanos += hours * 3600000000000
|
|
221
|
+
|
|
222
|
+
# Convert minutes to nanoseconds
|
|
223
|
+
minutes = parse_num(parts[5], "M")
|
|
224
|
+
nanos += minutes * 60000000000
|
|
225
|
+
|
|
226
|
+
# Handle seconds and fractional seconds
|
|
227
|
+
if parts[6]:
|
|
228
|
+
seconds = parts[6].rstrip("S")
|
|
229
|
+
if "," in seconds:
|
|
230
|
+
seconds = seconds.replace(",", ".")
|
|
231
|
+
|
|
232
|
+
if "." in seconds:
|
|
233
|
+
sec_parts = seconds.split(".")
|
|
234
|
+
whole_seconds = sec_parts[0] if sec_parts[0] else "0"
|
|
235
|
+
nanos += int(whole_seconds) * 1000000000
|
|
236
|
+
frac = sec_parts[1][:9].ljust(9, "0")
|
|
237
|
+
frac_nanos = int(frac)
|
|
238
|
+
if seconds.startswith("-"):
|
|
239
|
+
frac_nanos = -frac_nanos
|
|
240
|
+
nanos += frac_nanos
|
|
241
|
+
else:
|
|
242
|
+
nanos += int(seconds) * 1000000000
|
|
243
|
+
|
|
244
|
+
return cls(months=total_months, days=days, nanos=nanos)
|
|
245
|
+
|
|
246
|
+
|
|
100
247
|
def _proto_message(bytes_val, proto_message_object):
|
|
101
248
|
"""Helper for :func:`get_proto_message`.
|
|
102
249
|
parses serialized protocol buffer bytes data into proto message.
|
{google_cloud_spanner-3.53.0 → google_cloud_spanner-3.54.0}/google/cloud/spanner_v1/param_types.py
RENAMED
|
@@ -36,6 +36,7 @@ JSON = Type(code=TypeCode.JSON)
|
|
|
36
36
|
PG_NUMERIC = Type(code=TypeCode.NUMERIC, type_annotation=TypeAnnotationCode.PG_NUMERIC)
|
|
37
37
|
PG_JSONB = Type(code=TypeCode.JSON, type_annotation=TypeAnnotationCode.PG_JSONB)
|
|
38
38
|
PG_OID = Type(code=TypeCode.INT64, type_annotation=TypeAnnotationCode.PG_OID)
|
|
39
|
+
INTERVAL = Type(code=TypeCode.INTERVAL)
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
def Array(element_type):
|
|
@@ -129,6 +129,7 @@ tests/mockserver_tests/mock_server_test_base.py
|
|
|
129
129
|
tests/mockserver_tests/test_aborted_transaction.py
|
|
130
130
|
tests/mockserver_tests/test_basics.py
|
|
131
131
|
tests/mockserver_tests/test_dbapi_autocommit.py
|
|
132
|
+
tests/mockserver_tests/test_dbapi_isolation_level.py
|
|
132
133
|
tests/mockserver_tests/test_tags.py
|
|
133
134
|
tests/system/__init__.py
|
|
134
135
|
tests/system/_helpers.py
|
|
@@ -189,6 +190,7 @@ tests/unit/spanner_dbapi/__init__.py
|
|
|
189
190
|
tests/unit/spanner_dbapi/test__helpers.py
|
|
190
191
|
tests/unit/spanner_dbapi/test_batch_dml_executor.py
|
|
191
192
|
tests/unit/spanner_dbapi/test_checksum.py
|
|
193
|
+
tests/unit/spanner_dbapi/test_client_side_statement_executor.py
|
|
192
194
|
tests/unit/spanner_dbapi/test_connect.py
|
|
193
195
|
tests/unit/spanner_dbapi/test_connection.py
|
|
194
196
|
tests/unit/spanner_dbapi/test_cursor.py
|