From f9695daedd27f19d8ab3e0f7d353ed626a8bb350 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 5 Mar 2019 14:28:44 -0500 Subject: [PATCH 1/6] Regen to pick up batch DML proto changes. --- .../cloud/spanner_v1/gapic/spanner_client.py | 109 +++- .../spanner_v1/gapic/spanner_client_config.py | 5 + .../transports/spanner_grpc_transport.py | 34 +- .../cloud/spanner_v1/proto/spanner.proto | 125 +++- .../cloud/spanner_v1/proto/spanner_pb2.py | 565 ++++++++++++++++-- .../spanner_v1/proto/spanner_pb2_grpc.py | 39 +- spanner/synth.metadata | 8 +- .../unit/gapic/v1/test_spanner_client_v1.py | 49 ++ 8 files changed, 873 insertions(+), 61 deletions(-) diff --git a/spanner/google/cloud/spanner_v1/gapic/spanner_client.py b/spanner/google/cloud/spanner_v1/gapic/spanner_client.py index 9f2abf20e5a6..1cc1da386d71 100644 --- a/spanner/google/cloud/spanner_v1/gapic/spanner_client.py +++ b/spanner/google/cloud/spanner_v1/gapic/spanner_client.py @@ -442,7 +442,9 @@ def delete_session( metadata=None, ): """ - Ends a session, releasing server resources associated with it. + Ends a session, releasing server resources associated with it. This will + asynchronously trigger cancellation of any operations that are running with + this session. Example: >>> from google.cloud import spanner_v1 @@ -790,6 +792,111 @@ def execute_streaming_sql( request, retry=retry, timeout=timeout, metadata=metadata ) + def execute_batch_dml( + self, + session, + transaction, + statements, + seqno, + retry=google.api_core.gapic_v1.method.DEFAULT, + timeout=google.api_core.gapic_v1.method.DEFAULT, + metadata=None, + ): + """ + Executes a batch of SQL DML statements. This method allows many + statements to be run with lower latency than submitting them + sequentially with ``ExecuteSql``. + + Statements are executed in order, sequentially. + ``ExecuteBatchDmlResponse`` will contain a ``ResultSet`` for each DML + statement that has successfully executed. If a statement fails, its + error status will be returned as part of the + ``ExecuteBatchDmlResponse``. Execution will stop at the first failed + statement; the remaining statements will not run. + + ExecuteBatchDml is expected to return an OK status with a response even + if there was an error while processing one of the DML statements. + Clients must inspect response.status to determine if there were any + errors while processing the request. + + See more details in ``ExecuteBatchDmlRequest`` and + ``ExecuteBatchDmlResponse``. + + Example: + >>> from google.cloud import spanner_v1 + >>> + >>> client = spanner_v1.SpannerClient() + >>> + >>> session = client.session_path('[PROJECT]', '[INSTANCE]', '[DATABASE]', '[SESSION]') + >>> + >>> # TODO: Initialize `transaction`: + >>> transaction = {} + >>> + >>> # TODO: Initialize `statements`: + >>> statements = [] + >>> + >>> # TODO: Initialize `seqno`: + >>> seqno = 0 + >>> + >>> response = client.execute_batch_dml(session, transaction, statements, seqno) + + Args: + session (str): Required. The session in which the DML statements should be performed. + transaction (Union[dict, ~google.cloud.spanner_v1.types.TransactionSelector]): The transaction to use. A ReadWrite transaction is required. Single-use + transactions are not supported (to avoid replay). The caller must either + supply an existing transaction ID or begin a new transaction. + + If a dict is provided, it must be of the same form as the protobuf + message :class:`~google.cloud.spanner_v1.types.TransactionSelector` + statements (list[Union[dict, ~google.cloud.spanner_v1.types.Statement]]): The list of statements to execute in this batch. Statements are executed + serially, such that the effects of statement i are visible to statement + i+1. Each statement must be a DML statement. Execution will stop at the + first failed statement; the remaining statements will not run. + + REQUIRES: statements\_size() > 0. + + If a dict is provided, it must be of the same form as the protobuf + message :class:`~google.cloud.spanner_v1.types.Statement` + seqno (long): A per-transaction sequence number used to identify this request. This is + used in the same space as the seqno in ``ExecuteSqlRequest``. See more + details in ``ExecuteSqlRequest``. + retry (Optional[google.api_core.retry.Retry]): A retry object used + to retry requests. If ``None`` is specified, requests will not + be retried. + timeout (Optional[float]): The amount of time, in seconds, to wait + for the request to complete. Note that if ``retry`` is + specified, the timeout applies to each individual attempt. + metadata (Optional[Sequence[Tuple[str, str]]]): Additional metadata + that is provided to the method. + + Returns: + A :class:`~google.cloud.spanner_v1.types.ExecuteBatchDmlResponse` instance. + + Raises: + google.api_core.exceptions.GoogleAPICallError: If the request + failed for any reason. + google.api_core.exceptions.RetryError: If the request failed due + to a retryable error and retry attempts failed. + ValueError: If the parameters are invalid. + """ + # Wrap the transport method to add retry and timeout logic. + if "execute_batch_dml" not in self._inner_api_calls: + self._inner_api_calls[ + "execute_batch_dml" + ] = google.api_core.gapic_v1.method.wrap_method( + self.transport.execute_batch_dml, + default_retry=self._method_configs["ExecuteBatchDml"].retry, + default_timeout=self._method_configs["ExecuteBatchDml"].timeout, + client_info=self._client_info, + ) + + request = spanner_pb2.ExecuteBatchDmlRequest( + session=session, transaction=transaction, statements=statements, seqno=seqno + ) + return self._inner_api_calls["execute_batch_dml"]( + request, retry=retry, timeout=timeout, metadata=metadata + ) + def read( self, session, diff --git a/spanner/google/cloud/spanner_v1/gapic/spanner_client_config.py b/spanner/google/cloud/spanner_v1/gapic/spanner_client_config.py index 90e885d61151..5d69ca0312b7 100644 --- a/spanner/google/cloud/spanner_v1/gapic/spanner_client_config.py +++ b/spanner/google/cloud/spanner_v1/gapic/spanner_client_config.py @@ -66,6 +66,11 @@ "retry_codes_name": "non_idempotent", "retry_params_name": "streaming", }, + "ExecuteBatchDml": { + "timeout_millis": 30000, + "retry_codes_name": "idempotent", + "retry_params_name": "default", + }, "Read": { "timeout_millis": 30000, "retry_codes_name": "idempotent", diff --git a/spanner/google/cloud/spanner_v1/gapic/transports/spanner_grpc_transport.py b/spanner/google/cloud/spanner_v1/gapic/transports/spanner_grpc_transport.py index 2f14657d7eda..85d8a4a9f247 100644 --- a/spanner/google/cloud/spanner_v1/gapic/transports/spanner_grpc_transport.py +++ b/spanner/google/cloud/spanner_v1/gapic/transports/spanner_grpc_transport.py @@ -166,7 +166,9 @@ def list_sessions(self): def delete_session(self): """Return the gRPC stub for :meth:`SpannerClient.delete_session`. - Ends a session, releasing server resources associated with it. + Ends a session, releasing server resources associated with it. This will + asynchronously trigger cancellation of any operations that are running with + this session. Returns: Callable: A callable which accepts the appropriate @@ -214,6 +216,36 @@ def execute_streaming_sql(self): """ return self._stubs["spanner_stub"].ExecuteStreamingSql + @property + def execute_batch_dml(self): + """Return the gRPC stub for :meth:`SpannerClient.execute_batch_dml`. + + Executes a batch of SQL DML statements. This method allows many + statements to be run with lower latency than submitting them + sequentially with ``ExecuteSql``. + + Statements are executed in order, sequentially. + ``ExecuteBatchDmlResponse`` will contain a ``ResultSet`` for each DML + statement that has successfully executed. If a statement fails, its + error status will be returned as part of the + ``ExecuteBatchDmlResponse``. Execution will stop at the first failed + statement; the remaining statements will not run. + + ExecuteBatchDml is expected to return an OK status with a response even + if there was an error while processing one of the DML statements. + Clients must inspect response.status to determine if there were any + errors while processing the request. + + See more details in ``ExecuteBatchDmlRequest`` and + ``ExecuteBatchDmlResponse``. + + Returns: + Callable: A callable which accepts the appropriate + deserialized request object and returns a + deserialized response object. + """ + return self._stubs["spanner_stub"].ExecuteBatchDml + @property def read(self): """Return the gRPC stub for :meth:`SpannerClient.read`. diff --git a/spanner/google/cloud/spanner_v1/proto/spanner.proto b/spanner/google/cloud/spanner_v1/proto/spanner.proto index 7a01fb5e9dc3..b2091c92f7e1 100644 --- a/spanner/google/cloud/spanner_v1/proto/spanner.proto +++ b/spanner/google/cloud/spanner_v1/proto/spanner.proto @@ -1,4 +1,4 @@ -// Copyright 2018 Google LLC +// Copyright 2018 Google LLC. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,6 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +// syntax = "proto3"; @@ -20,6 +21,7 @@ import "google/api/annotations.proto"; import "google/protobuf/empty.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; +import "google/rpc/status.proto"; import "google/spanner/v1/keys.proto"; import "google/spanner/v1/mutation.proto"; import "google/spanner/v1/result_set.proto"; @@ -80,7 +82,9 @@ service Spanner { }; } - // Ends a session, releasing server resources associated with it. + // Ends a session, releasing server resources associated with it. This will + // asynchronously trigger cancellation of any operations that are running with + // this session. rpc DeleteSession(DeleteSessionRequest) returns (google.protobuf.Empty) { option (google.api.http) = { delete: "/v1/{name=projects/*/instances/*/databases/*/sessions/*}" @@ -119,6 +123,32 @@ service Spanner { }; } + // Executes a batch of SQL DML statements. This method allows many statements + // to be run with lower latency than submitting them sequentially with + // [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql]. + // + // Statements are executed in order, sequentially. + // [ExecuteBatchDmlResponse][Spanner.ExecuteBatchDmlResponse] will contain a + // [ResultSet][google.spanner.v1.ResultSet] for each DML statement that has successfully executed. If a + // statement fails, its error status will be returned as part of the + // [ExecuteBatchDmlResponse][Spanner.ExecuteBatchDmlResponse]. Execution will + // stop at the first failed statement; the remaining statements will not run. + // + // ExecuteBatchDml is expected to return an OK status with a response even if + // there was an error while processing one of the DML statements. Clients must + // inspect response.status to determine if there were any errors while + // processing the request. + // + // See more details in + // [ExecuteBatchDmlRequest][Spanner.ExecuteBatchDmlRequest] and + // [ExecuteBatchDmlResponse][Spanner.ExecuteBatchDmlResponse]. + rpc ExecuteBatchDml(ExecuteBatchDmlRequest) returns (ExecuteBatchDmlResponse) { + option (google.api.http) = { + post: "/v1/{session=projects/*/instances/*/databases/*/sessions/*}:executeBatchDml" + body: "*" + }; + } + // Reads rows from the database using key lookups and scans, as a // simple key/value style alternative to // [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql]. This method cannot be @@ -421,6 +451,97 @@ message ExecuteSqlRequest { int64 seqno = 9; } +// The request for [ExecuteBatchDml][google.spanner.v1.Spanner.ExecuteBatchDml] +message ExecuteBatchDmlRequest { + // A single DML statement. + message Statement { + // Required. The DML string. + string sql = 1; + + // The DML string can contain parameter placeholders. A parameter + // placeholder consists of `'@'` followed by the parameter + // name. Parameter names consist of any combination of letters, + // numbers, and underscores. + // + // Parameters can appear anywhere that a literal value is expected. The + // same parameter name can be used more than once, for example: + // `"WHERE id > @msg_id AND id < @msg_id + 100"` + // + // It is an error to execute an SQL statement with unbound parameters. + // + // Parameter values are specified using `params`, which is a JSON + // object whose keys are parameter names, and whose values are the + // corresponding parameter values. + google.protobuf.Struct params = 2; + + // It is not always possible for Cloud Spanner to infer the right SQL type + // from a JSON value. For example, values of type `BYTES` and values + // of type `STRING` both appear in [params][google.spanner.v1.ExecuteBatchDmlRequest.Statement.params] as JSON strings. + // + // In these cases, `param_types` can be used to specify the exact + // SQL type for some or all of the SQL statement parameters. See the + // definition of [Type][google.spanner.v1.Type] for more information + // about SQL types. + map param_types = 3; + } + + // Required. The session in which the DML statements should be performed. + string session = 1; + + // The transaction to use. A ReadWrite transaction is required. Single-use + // transactions are not supported (to avoid replay). The caller must either + // supply an existing transaction ID or begin a new transaction. + TransactionSelector transaction = 2; + + // The list of statements to execute in this batch. Statements are executed + // serially, such that the effects of statement i are visible to statement + // i+1. Each statement must be a DML statement. Execution will stop at the + // first failed statement; the remaining statements will not run. + // + // REQUIRES: statements_size() > 0. + repeated Statement statements = 3; + + // A per-transaction sequence number used to identify this request. This is + // used in the same space as the seqno in + // [ExecuteSqlRequest][Spanner.ExecuteSqlRequest]. See more details + // in [ExecuteSqlRequest][Spanner.ExecuteSqlRequest]. + int64 seqno = 4; +} + +// The response for [ExecuteBatchDml][google.spanner.v1.Spanner.ExecuteBatchDml]. Contains a list +// of [ResultSet][google.spanner.v1.ResultSet], one for each DML statement that has successfully executed. +// If a statement fails, the error is returned as part of the response payload. +// Clients can determine whether all DML statements have run successfully, or if +// a statement failed, using one of the following approaches: +// +// 1. Check if 'status' field is OkStatus. +// 2. Check if result_sets_size() equals the number of statements in +// [ExecuteBatchDmlRequest][Spanner.ExecuteBatchDmlRequest]. +// +// Example 1: A request with 5 DML statements, all executed successfully. +// Result: A response with 5 ResultSets, one for each statement in the same +// order, and an OK status. +// +// Example 2: A request with 5 DML statements. The 3rd statement has a syntax +// error. +// Result: A response with 2 ResultSets, for the first 2 statements that +// run successfully, and a syntax error (INVALID_ARGUMENT) status. From +// result_set_size() client can determine that the 3rd statement has failed. +message ExecuteBatchDmlResponse { + // ResultSets, one for each statement in the request that ran successfully, in + // the same order as the statements in the request. Each [ResultSet][google.spanner.v1.ResultSet] will + // not contain any rows. The [ResultSetStats][google.spanner.v1.ResultSetStats] in each [ResultSet][google.spanner.v1.ResultSet] will + // contain the number of rows modified by the statement. + // + // Only the first ResultSet in the response contains a valid + // [ResultSetMetadata][google.spanner.v1.ResultSetMetadata]. + repeated ResultSet result_sets = 1; + + // If all DML statements are executed successfully, status will be OK. + // Otherwise, the error status of the first failed statement. + google.rpc.Status status = 2; +} + // Options for a PartitionQueryRequest and // PartitionReadRequest. message PartitionOptions { diff --git a/spanner/google/cloud/spanner_v1/proto/spanner_pb2.py b/spanner/google/cloud/spanner_v1/proto/spanner_pb2.py index f2a56827daed..e2e3b84020bb 100644 --- a/spanner/google/cloud/spanner_v1/proto/spanner_pb2.py +++ b/spanner/google/cloud/spanner_v1/proto/spanner_pb2.py @@ -18,6 +18,7 @@ from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 +from google.rpc import status_pb2 as google_dot_rpc_dot_status__pb2 from google.cloud.spanner_v1.proto import ( keys_pb2 as google_dot_cloud_dot_spanner__v1_dot_proto_dot_keys__pb2, ) @@ -43,13 +44,14 @@ "\n\025com.google.spanner.v1B\014SpannerProtoP\001Z8google.golang.org/genproto/googleapis/spanner/v1;spanner\252\002\027Google.Cloud.Spanner.V1\312\002\027Google\\Cloud\\Spanner\\V1" ), serialized_pb=_b( - '\n+google/cloud/spanner_v1/proto/spanner.proto\x12\x11google.spanner.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a(google/cloud/spanner_v1/proto/keys.proto\x1a,google/cloud/spanner_v1/proto/mutation.proto\x1a.google/cloud/spanner_v1/proto/result_set.proto\x1a/google/cloud/spanner_v1/proto/transaction.proto\x1a(google/cloud/spanner_v1/proto/type.proto"U\n\x14\x43reateSessionRequest\x12\x10\n\x08\x64\x61tabase\x18\x01 \x01(\t\x12+\n\x07session\x18\x02 \x01(\x0b\x32\x1a.google.spanner.v1.Session"\xee\x01\n\x07Session\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x36\n\x06labels\x18\x02 \x03(\x0b\x32&.google.spanner.v1.Session.LabelsEntry\x12/\n\x0b\x63reate_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12=\n\x19\x61pproximate_last_use_time\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01"!\n\x11GetSessionRequest\x12\x0c\n\x04name\x18\x01 \x01(\t"^\n\x13ListSessionsRequest\x12\x10\n\x08\x64\x61tabase\x18\x01 \x01(\t\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\x12\x0e\n\x06\x66ilter\x18\x04 \x01(\t"]\n\x14ListSessionsResponse\x12,\n\x08sessions\x18\x01 \x03(\x0b\x32\x1a.google.spanner.v1.Session\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t"$\n\x14\x44\x65leteSessionRequest\x12\x0c\n\x04name\x18\x01 \x01(\t"\xe0\x03\n\x11\x45xecuteSqlRequest\x12\x0f\n\x07session\x18\x01 \x01(\t\x12;\n\x0btransaction\x18\x02 \x01(\x0b\x32&.google.spanner.v1.TransactionSelector\x12\x0b\n\x03sql\x18\x03 \x01(\t\x12\'\n\x06params\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\x12I\n\x0bparam_types\x18\x05 \x03(\x0b\x32\x34.google.spanner.v1.ExecuteSqlRequest.ParamTypesEntry\x12\x14\n\x0cresume_token\x18\x06 \x01(\x0c\x12\x42\n\nquery_mode\x18\x07 \x01(\x0e\x32..google.spanner.v1.ExecuteSqlRequest.QueryMode\x12\x17\n\x0fpartition_token\x18\x08 \x01(\x0c\x12\r\n\x05seqno\x18\t \x01(\x03\x1aJ\n\x0fParamTypesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.google.spanner.v1.Type:\x02\x38\x01".\n\tQueryMode\x12\n\n\x06NORMAL\x10\x00\x12\x08\n\x04PLAN\x10\x01\x12\x0b\n\x07PROFILE\x10\x02"H\n\x10PartitionOptions\x12\x1c\n\x14partition_size_bytes\x18\x01 \x01(\x03\x12\x16\n\x0emax_partitions\x18\x02 \x01(\x03"\xf6\x02\n\x15PartitionQueryRequest\x12\x0f\n\x07session\x18\x01 \x01(\t\x12;\n\x0btransaction\x18\x02 \x01(\x0b\x32&.google.spanner.v1.TransactionSelector\x12\x0b\n\x03sql\x18\x03 \x01(\t\x12\'\n\x06params\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\x12M\n\x0bparam_types\x18\x05 \x03(\x0b\x32\x38.google.spanner.v1.PartitionQueryRequest.ParamTypesEntry\x12>\n\x11partition_options\x18\x06 \x01(\x0b\x32#.google.spanner.v1.PartitionOptions\x1aJ\n\x0fParamTypesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.google.spanner.v1.Type:\x02\x38\x01"\xff\x01\n\x14PartitionReadRequest\x12\x0f\n\x07session\x18\x01 \x01(\t\x12;\n\x0btransaction\x18\x02 \x01(\x0b\x32&.google.spanner.v1.TransactionSelector\x12\r\n\x05table\x18\x03 \x01(\t\x12\r\n\x05index\x18\x04 \x01(\t\x12\x0f\n\x07\x63olumns\x18\x05 \x03(\t\x12*\n\x07key_set\x18\x06 \x01(\x0b\x32\x19.google.spanner.v1.KeySet\x12>\n\x11partition_options\x18\t \x01(\x0b\x32#.google.spanner.v1.PartitionOptions"$\n\tPartition\x12\x17\n\x0fpartition_token\x18\x01 \x01(\x0c"z\n\x11PartitionResponse\x12\x30\n\npartitions\x18\x01 \x03(\x0b\x32\x1c.google.spanner.v1.Partition\x12\x33\n\x0btransaction\x18\x02 \x01(\x0b\x32\x1e.google.spanner.v1.Transaction"\xf4\x01\n\x0bReadRequest\x12\x0f\n\x07session\x18\x01 \x01(\t\x12;\n\x0btransaction\x18\x02 \x01(\x0b\x32&.google.spanner.v1.TransactionSelector\x12\r\n\x05table\x18\x03 \x01(\t\x12\r\n\x05index\x18\x04 \x01(\t\x12\x0f\n\x07\x63olumns\x18\x05 \x03(\t\x12*\n\x07key_set\x18\x06 \x01(\x0b\x32\x19.google.spanner.v1.KeySet\x12\r\n\x05limit\x18\x08 \x01(\x03\x12\x14\n\x0cresume_token\x18\t \x01(\x0c\x12\x17\n\x0fpartition_token\x18\n \x01(\x0c"b\n\x17\x42\x65ginTransactionRequest\x12\x0f\n\x07session\x18\x01 \x01(\t\x12\x36\n\x07options\x18\x02 \x01(\x0b\x32%.google.spanner.v1.TransactionOptions"\xc2\x01\n\rCommitRequest\x12\x0f\n\x07session\x18\x01 \x01(\t\x12\x18\n\x0etransaction_id\x18\x02 \x01(\x0cH\x00\x12G\n\x16single_use_transaction\x18\x03 \x01(\x0b\x32%.google.spanner.v1.TransactionOptionsH\x00\x12.\n\tmutations\x18\x04 \x03(\x0b\x32\x1b.google.spanner.v1.MutationB\r\n\x0btransaction"F\n\x0e\x43ommitResponse\x12\x34\n\x10\x63ommit_timestamp\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp":\n\x0fRollbackRequest\x12\x0f\n\x07session\x18\x01 \x01(\t\x12\x16\n\x0etransaction_id\x18\x02 \x01(\x0c\x32\x83\x11\n\x07Spanner\x12\x9b\x01\n\rCreateSession\x12\'.google.spanner.v1.CreateSessionRequest\x1a\x1a.google.spanner.v1.Session"E\x82\xd3\xe4\x93\x02?":/v1/{database=projects/*/instances/*/databases/*}/sessions:\x01*\x12\x90\x01\n\nGetSession\x12$.google.spanner.v1.GetSessionRequest\x1a\x1a.google.spanner.v1.Session"@\x82\xd3\xe4\x93\x02:\x12\x38/v1/{name=projects/*/instances/*/databases/*/sessions/*}\x12\xa3\x01\n\x0cListSessions\x12&.google.spanner.v1.ListSessionsRequest\x1a\'.google.spanner.v1.ListSessionsResponse"B\x82\xd3\xe4\x93\x02<\x12:/v1/{database=projects/*/instances/*/databases/*}/sessions\x12\x92\x01\n\rDeleteSession\x12\'.google.spanner.v1.DeleteSessionRequest\x1a\x16.google.protobuf.Empty"@\x82\xd3\xe4\x93\x02:*8/v1/{name=projects/*/instances/*/databases/*/sessions/*}\x12\xa3\x01\n\nExecuteSql\x12$.google.spanner.v1.ExecuteSqlRequest\x1a\x1c.google.spanner.v1.ResultSet"Q\x82\xd3\xe4\x93\x02K"F/v1/{session=projects/*/instances/*/databases/*/sessions/*}:executeSql:\x01*\x12\xbe\x01\n\x13\x45xecuteStreamingSql\x12$.google.spanner.v1.ExecuteSqlRequest\x1a#.google.spanner.v1.PartialResultSet"Z\x82\xd3\xe4\x93\x02T"O/v1/{session=projects/*/instances/*/databases/*/sessions/*}:executeStreamingSql:\x01*0\x01\x12\x91\x01\n\x04Read\x12\x1e.google.spanner.v1.ReadRequest\x1a\x1c.google.spanner.v1.ResultSet"K\x82\xd3\xe4\x93\x02\x45"@/v1/{session=projects/*/instances/*/databases/*/sessions/*}:read:\x01*\x12\xac\x01\n\rStreamingRead\x12\x1e.google.spanner.v1.ReadRequest\x1a#.google.spanner.v1.PartialResultSet"T\x82\xd3\xe4\x93\x02N"I/v1/{session=projects/*/instances/*/databases/*/sessions/*}:streamingRead:\x01*0\x01\x12\xb7\x01\n\x10\x42\x65ginTransaction\x12*.google.spanner.v1.BeginTransactionRequest\x1a\x1e.google.spanner.v1.Transaction"W\x82\xd3\xe4\x93\x02Q"L/v1/{session=projects/*/instances/*/databases/*/sessions/*}:beginTransaction:\x01*\x12\x9c\x01\n\x06\x43ommit\x12 .google.spanner.v1.CommitRequest\x1a!.google.spanner.v1.CommitResponse"M\x82\xd3\xe4\x93\x02G"B/v1/{session=projects/*/instances/*/databases/*/sessions/*}:commit:\x01*\x12\x97\x01\n\x08Rollback\x12".google.spanner.v1.RollbackRequest\x1a\x16.google.protobuf.Empty"O\x82\xd3\xe4\x93\x02I"D/v1/{session=projects/*/instances/*/databases/*/sessions/*}:rollback:\x01*\x12\xb7\x01\n\x0ePartitionQuery\x12(.google.spanner.v1.PartitionQueryRequest\x1a$.google.spanner.v1.PartitionResponse"U\x82\xd3\xe4\x93\x02O"J/v1/{session=projects/*/instances/*/databases/*/sessions/*}:partitionQuery:\x01*\x12\xb4\x01\n\rPartitionRead\x12\'.google.spanner.v1.PartitionReadRequest\x1a$.google.spanner.v1.PartitionResponse"T\x82\xd3\xe4\x93\x02N"I/v1/{session=projects/*/instances/*/databases/*/sessions/*}:partitionRead:\x01*B\x95\x01\n\x15\x63om.google.spanner.v1B\x0cSpannerProtoP\x01Z8google.golang.org/genproto/googleapis/spanner/v1;spanner\xaa\x02\x17Google.Cloud.Spanner.V1\xca\x02\x17Google\\Cloud\\Spanner\\V1b\x06proto3' + '\n+google/cloud/spanner_v1/proto/spanner.proto\x12\x11google.spanner.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x17google/rpc/status.proto\x1a(google/cloud/spanner_v1/proto/keys.proto\x1a,google/cloud/spanner_v1/proto/mutation.proto\x1a.google/cloud/spanner_v1/proto/result_set.proto\x1a/google/cloud/spanner_v1/proto/transaction.proto\x1a(google/cloud/spanner_v1/proto/type.proto"U\n\x14\x43reateSessionRequest\x12\x10\n\x08\x64\x61tabase\x18\x01 \x01(\t\x12+\n\x07session\x18\x02 \x01(\x0b\x32\x1a.google.spanner.v1.Session"\xee\x01\n\x07Session\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x36\n\x06labels\x18\x02 \x03(\x0b\x32&.google.spanner.v1.Session.LabelsEntry\x12/\n\x0b\x63reate_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12=\n\x19\x61pproximate_last_use_time\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01"!\n\x11GetSessionRequest\x12\x0c\n\x04name\x18\x01 \x01(\t"^\n\x13ListSessionsRequest\x12\x10\n\x08\x64\x61tabase\x18\x01 \x01(\t\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\x12\x0e\n\x06\x66ilter\x18\x04 \x01(\t"]\n\x14ListSessionsResponse\x12,\n\x08sessions\x18\x01 \x03(\x0b\x32\x1a.google.spanner.v1.Session\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t"$\n\x14\x44\x65leteSessionRequest\x12\x0c\n\x04name\x18\x01 \x01(\t"\xe0\x03\n\x11\x45xecuteSqlRequest\x12\x0f\n\x07session\x18\x01 \x01(\t\x12;\n\x0btransaction\x18\x02 \x01(\x0b\x32&.google.spanner.v1.TransactionSelector\x12\x0b\n\x03sql\x18\x03 \x01(\t\x12\'\n\x06params\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\x12I\n\x0bparam_types\x18\x05 \x03(\x0b\x32\x34.google.spanner.v1.ExecuteSqlRequest.ParamTypesEntry\x12\x14\n\x0cresume_token\x18\x06 \x01(\x0c\x12\x42\n\nquery_mode\x18\x07 \x01(\x0e\x32..google.spanner.v1.ExecuteSqlRequest.QueryMode\x12\x17\n\x0fpartition_token\x18\x08 \x01(\x0c\x12\r\n\x05seqno\x18\t \x01(\x03\x1aJ\n\x0fParamTypesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.google.spanner.v1.Type:\x02\x38\x01".\n\tQueryMode\x12\n\n\x06NORMAL\x10\x00\x12\x08\n\x04PLAN\x10\x01\x12\x0b\n\x07PROFILE\x10\x02"\xa8\x03\n\x16\x45xecuteBatchDmlRequest\x12\x0f\n\x07session\x18\x01 \x01(\t\x12;\n\x0btransaction\x18\x02 \x01(\x0b\x32&.google.spanner.v1.TransactionSelector\x12G\n\nstatements\x18\x03 \x03(\x0b\x32\x33.google.spanner.v1.ExecuteBatchDmlRequest.Statement\x12\r\n\x05seqno\x18\x04 \x01(\x03\x1a\xe7\x01\n\tStatement\x12\x0b\n\x03sql\x18\x01 \x01(\t\x12\'\n\x06params\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\x12X\n\x0bparam_types\x18\x03 \x03(\x0b\x32\x43.google.spanner.v1.ExecuteBatchDmlRequest.Statement.ParamTypesEntry\x1aJ\n\x0fParamTypesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.google.spanner.v1.Type:\x02\x38\x01"p\n\x17\x45xecuteBatchDmlResponse\x12\x31\n\x0bresult_sets\x18\x01 \x03(\x0b\x32\x1c.google.spanner.v1.ResultSet\x12"\n\x06status\x18\x02 \x01(\x0b\x32\x12.google.rpc.Status"H\n\x10PartitionOptions\x12\x1c\n\x14partition_size_bytes\x18\x01 \x01(\x03\x12\x16\n\x0emax_partitions\x18\x02 \x01(\x03"\xf6\x02\n\x15PartitionQueryRequest\x12\x0f\n\x07session\x18\x01 \x01(\t\x12;\n\x0btransaction\x18\x02 \x01(\x0b\x32&.google.spanner.v1.TransactionSelector\x12\x0b\n\x03sql\x18\x03 \x01(\t\x12\'\n\x06params\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\x12M\n\x0bparam_types\x18\x05 \x03(\x0b\x32\x38.google.spanner.v1.PartitionQueryRequest.ParamTypesEntry\x12>\n\x11partition_options\x18\x06 \x01(\x0b\x32#.google.spanner.v1.PartitionOptions\x1aJ\n\x0fParamTypesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05value\x18\x02 \x01(\x0b\x32\x17.google.spanner.v1.Type:\x02\x38\x01"\xff\x01\n\x14PartitionReadRequest\x12\x0f\n\x07session\x18\x01 \x01(\t\x12;\n\x0btransaction\x18\x02 \x01(\x0b\x32&.google.spanner.v1.TransactionSelector\x12\r\n\x05table\x18\x03 \x01(\t\x12\r\n\x05index\x18\x04 \x01(\t\x12\x0f\n\x07\x63olumns\x18\x05 \x03(\t\x12*\n\x07key_set\x18\x06 \x01(\x0b\x32\x19.google.spanner.v1.KeySet\x12>\n\x11partition_options\x18\t \x01(\x0b\x32#.google.spanner.v1.PartitionOptions"$\n\tPartition\x12\x17\n\x0fpartition_token\x18\x01 \x01(\x0c"z\n\x11PartitionResponse\x12\x30\n\npartitions\x18\x01 \x03(\x0b\x32\x1c.google.spanner.v1.Partition\x12\x33\n\x0btransaction\x18\x02 \x01(\x0b\x32\x1e.google.spanner.v1.Transaction"\xf4\x01\n\x0bReadRequest\x12\x0f\n\x07session\x18\x01 \x01(\t\x12;\n\x0btransaction\x18\x02 \x01(\x0b\x32&.google.spanner.v1.TransactionSelector\x12\r\n\x05table\x18\x03 \x01(\t\x12\r\n\x05index\x18\x04 \x01(\t\x12\x0f\n\x07\x63olumns\x18\x05 \x03(\t\x12*\n\x07key_set\x18\x06 \x01(\x0b\x32\x19.google.spanner.v1.KeySet\x12\r\n\x05limit\x18\x08 \x01(\x03\x12\x14\n\x0cresume_token\x18\t \x01(\x0c\x12\x17\n\x0fpartition_token\x18\n \x01(\x0c"b\n\x17\x42\x65ginTransactionRequest\x12\x0f\n\x07session\x18\x01 \x01(\t\x12\x36\n\x07options\x18\x02 \x01(\x0b\x32%.google.spanner.v1.TransactionOptions"\xc2\x01\n\rCommitRequest\x12\x0f\n\x07session\x18\x01 \x01(\t\x12\x18\n\x0etransaction_id\x18\x02 \x01(\x0cH\x00\x12G\n\x16single_use_transaction\x18\x03 \x01(\x0b\x32%.google.spanner.v1.TransactionOptionsH\x00\x12.\n\tmutations\x18\x04 \x03(\x0b\x32\x1b.google.spanner.v1.MutationB\r\n\x0btransaction"F\n\x0e\x43ommitResponse\x12\x34\n\x10\x63ommit_timestamp\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp":\n\x0fRollbackRequest\x12\x0f\n\x07session\x18\x01 \x01(\t\x12\x16\n\x0etransaction_id\x18\x02 \x01(\x0c\x32\xc6\x12\n\x07Spanner\x12\x9b\x01\n\rCreateSession\x12\'.google.spanner.v1.CreateSessionRequest\x1a\x1a.google.spanner.v1.Session"E\x82\xd3\xe4\x93\x02?":/v1/{database=projects/*/instances/*/databases/*}/sessions:\x01*\x12\x90\x01\n\nGetSession\x12$.google.spanner.v1.GetSessionRequest\x1a\x1a.google.spanner.v1.Session"@\x82\xd3\xe4\x93\x02:\x12\x38/v1/{name=projects/*/instances/*/databases/*/sessions/*}\x12\xa3\x01\n\x0cListSessions\x12&.google.spanner.v1.ListSessionsRequest\x1a\'.google.spanner.v1.ListSessionsResponse"B\x82\xd3\xe4\x93\x02<\x12:/v1/{database=projects/*/instances/*/databases/*}/sessions\x12\x92\x01\n\rDeleteSession\x12\'.google.spanner.v1.DeleteSessionRequest\x1a\x16.google.protobuf.Empty"@\x82\xd3\xe4\x93\x02:*8/v1/{name=projects/*/instances/*/databases/*/sessions/*}\x12\xa3\x01\n\nExecuteSql\x12$.google.spanner.v1.ExecuteSqlRequest\x1a\x1c.google.spanner.v1.ResultSet"Q\x82\xd3\xe4\x93\x02K"F/v1/{session=projects/*/instances/*/databases/*/sessions/*}:executeSql:\x01*\x12\xbe\x01\n\x13\x45xecuteStreamingSql\x12$.google.spanner.v1.ExecuteSqlRequest\x1a#.google.spanner.v1.PartialResultSet"Z\x82\xd3\xe4\x93\x02T"O/v1/{session=projects/*/instances/*/databases/*/sessions/*}:executeStreamingSql:\x01*0\x01\x12\xc0\x01\n\x0f\x45xecuteBatchDml\x12).google.spanner.v1.ExecuteBatchDmlRequest\x1a*.google.spanner.v1.ExecuteBatchDmlResponse"V\x82\xd3\xe4\x93\x02P"K/v1/{session=projects/*/instances/*/databases/*/sessions/*}:executeBatchDml:\x01*\x12\x91\x01\n\x04Read\x12\x1e.google.spanner.v1.ReadRequest\x1a\x1c.google.spanner.v1.ResultSet"K\x82\xd3\xe4\x93\x02\x45"@/v1/{session=projects/*/instances/*/databases/*/sessions/*}:read:\x01*\x12\xac\x01\n\rStreamingRead\x12\x1e.google.spanner.v1.ReadRequest\x1a#.google.spanner.v1.PartialResultSet"T\x82\xd3\xe4\x93\x02N"I/v1/{session=projects/*/instances/*/databases/*/sessions/*}:streamingRead:\x01*0\x01\x12\xb7\x01\n\x10\x42\x65ginTransaction\x12*.google.spanner.v1.BeginTransactionRequest\x1a\x1e.google.spanner.v1.Transaction"W\x82\xd3\xe4\x93\x02Q"L/v1/{session=projects/*/instances/*/databases/*/sessions/*}:beginTransaction:\x01*\x12\x9c\x01\n\x06\x43ommit\x12 .google.spanner.v1.CommitRequest\x1a!.google.spanner.v1.CommitResponse"M\x82\xd3\xe4\x93\x02G"B/v1/{session=projects/*/instances/*/databases/*/sessions/*}:commit:\x01*\x12\x97\x01\n\x08Rollback\x12".google.spanner.v1.RollbackRequest\x1a\x16.google.protobuf.Empty"O\x82\xd3\xe4\x93\x02I"D/v1/{session=projects/*/instances/*/databases/*/sessions/*}:rollback:\x01*\x12\xb7\x01\n\x0ePartitionQuery\x12(.google.spanner.v1.PartitionQueryRequest\x1a$.google.spanner.v1.PartitionResponse"U\x82\xd3\xe4\x93\x02O"J/v1/{session=projects/*/instances/*/databases/*/sessions/*}:partitionQuery:\x01*\x12\xb4\x01\n\rPartitionRead\x12\'.google.spanner.v1.PartitionReadRequest\x1a$.google.spanner.v1.PartitionResponse"T\x82\xd3\xe4\x93\x02N"I/v1/{session=projects/*/instances/*/databases/*/sessions/*}:partitionRead:\x01*B\x95\x01\n\x15\x63om.google.spanner.v1B\x0cSpannerProtoP\x01Z8google.golang.org/genproto/googleapis/spanner/v1;spanner\xaa\x02\x17Google.Cloud.Spanner.V1\xca\x02\x17Google\\Cloud\\Spanner\\V1b\x06proto3' ), dependencies=[ google_dot_api_dot_annotations__pb2.DESCRIPTOR, google_dot_protobuf_dot_empty__pb2.DESCRIPTOR, google_dot_protobuf_dot_struct__pb2.DESCRIPTOR, google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR, + google_dot_rpc_dot_status__pb2.DESCRIPTOR, google_dot_cloud_dot_spanner__v1_dot_proto_dot_keys__pb2.DESCRIPTOR, google_dot_cloud_dot_spanner__v1_dot_proto_dot_mutation__pb2.DESCRIPTOR, google_dot_cloud_dot_spanner__v1_dot_proto_dot_result__set__pb2.DESCRIPTOR, @@ -77,8 +79,8 @@ ], containing_type=None, serialized_options=None, - serialized_start=1442, - serialized_end=1488, + serialized_start=1467, + serialized_end=1513, ) _sym_db.RegisterEnumDescriptor(_EXECUTESQLREQUEST_QUERYMODE) @@ -135,8 +137,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=415, - serialized_end=500, + serialized_start=440, + serialized_end=525, ) @@ -192,8 +194,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=696, - serialized_end=741, + serialized_start=721, + serialized_end=766, ) _SESSION = _descriptor.Descriptor( @@ -284,8 +286,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=503, - serialized_end=741, + serialized_start=528, + serialized_end=766, ) @@ -323,8 +325,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=743, - serialized_end=776, + serialized_start=768, + serialized_end=801, ) @@ -416,8 +418,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=778, - serialized_end=872, + serialized_start=803, + serialized_end=897, ) @@ -473,8 +475,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=874, - serialized_end=967, + serialized_start=899, + serialized_end=992, ) @@ -512,8 +514,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=969, - serialized_end=1005, + serialized_start=994, + serialized_end=1030, ) @@ -569,8 +571,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1366, - serialized_end=1440, + serialized_start=1391, + serialized_end=1465, ) _EXECUTESQLREQUEST = _descriptor.Descriptor( @@ -751,8 +753,288 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1008, - serialized_end=1488, + serialized_start=1033, + serialized_end=1513, +) + + +_EXECUTEBATCHDMLREQUEST_STATEMENT_PARAMTYPESENTRY = _descriptor.Descriptor( + name="ParamTypesEntry", + full_name="google.spanner.v1.ExecuteBatchDmlRequest.Statement.ParamTypesEntry", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="key", + full_name="google.spanner.v1.ExecuteBatchDmlRequest.Statement.ParamTypesEntry.key", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="value", + full_name="google.spanner.v1.ExecuteBatchDmlRequest.Statement.ParamTypesEntry.value", + index=1, + number=2, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=_b("8\001"), + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1391, + serialized_end=1465, +) + +_EXECUTEBATCHDMLREQUEST_STATEMENT = _descriptor.Descriptor( + name="Statement", + full_name="google.spanner.v1.ExecuteBatchDmlRequest.Statement", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="sql", + full_name="google.spanner.v1.ExecuteBatchDmlRequest.Statement.sql", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="params", + full_name="google.spanner.v1.ExecuteBatchDmlRequest.Statement.params", + index=1, + number=2, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="param_types", + full_name="google.spanner.v1.ExecuteBatchDmlRequest.Statement.param_types", + index=2, + number=3, + type=11, + cpp_type=10, + label=3, + has_default_value=False, + default_value=[], + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[_EXECUTEBATCHDMLREQUEST_STATEMENT_PARAMTYPESENTRY], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1709, + serialized_end=1940, +) + +_EXECUTEBATCHDMLREQUEST = _descriptor.Descriptor( + name="ExecuteBatchDmlRequest", + full_name="google.spanner.v1.ExecuteBatchDmlRequest", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="session", + full_name="google.spanner.v1.ExecuteBatchDmlRequest.session", + index=0, + number=1, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=_b("").decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="transaction", + full_name="google.spanner.v1.ExecuteBatchDmlRequest.transaction", + index=1, + number=2, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="statements", + full_name="google.spanner.v1.ExecuteBatchDmlRequest.statements", + index=2, + number=3, + type=11, + cpp_type=10, + label=3, + has_default_value=False, + default_value=[], + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="seqno", + full_name="google.spanner.v1.ExecuteBatchDmlRequest.seqno", + index=3, + number=4, + type=3, + cpp_type=2, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[_EXECUTEBATCHDMLREQUEST_STATEMENT], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1516, + serialized_end=1940, +) + + +_EXECUTEBATCHDMLRESPONSE = _descriptor.Descriptor( + name="ExecuteBatchDmlResponse", + full_name="google.spanner.v1.ExecuteBatchDmlResponse", + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name="result_sets", + full_name="google.spanner.v1.ExecuteBatchDmlResponse.result_sets", + index=0, + number=1, + type=11, + cpp_type=10, + label=3, + has_default_value=False, + default_value=[], + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + _descriptor.FieldDescriptor( + name="status", + full_name="google.spanner.v1.ExecuteBatchDmlResponse.status", + index=1, + number=2, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=1942, + serialized_end=2054, ) @@ -808,8 +1090,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1490, - serialized_end=1562, + serialized_start=2056, + serialized_end=2128, ) @@ -865,8 +1147,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1366, - serialized_end=1440, + serialized_start=1391, + serialized_end=1465, ) _PARTITIONQUERYREQUEST = _descriptor.Descriptor( @@ -993,8 +1275,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1565, - serialized_end=1939, + serialized_start=2131, + serialized_end=2505, ) @@ -1140,8 +1422,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=1942, - serialized_end=2197, + serialized_start=2508, + serialized_end=2763, ) @@ -1179,8 +1461,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=2199, - serialized_end=2235, + serialized_start=2765, + serialized_end=2801, ) @@ -1236,8 +1518,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=2237, - serialized_end=2359, + serialized_start=2803, + serialized_end=2925, ) @@ -1419,8 +1701,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=2362, - serialized_end=2606, + serialized_start=2928, + serialized_end=3172, ) @@ -1476,8 +1758,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=2608, - serialized_end=2706, + serialized_start=3174, + serialized_end=3272, ) @@ -1577,8 +1859,8 @@ fields=[], ) ], - serialized_start=2709, - serialized_end=2903, + serialized_start=3275, + serialized_end=3469, ) @@ -1616,8 +1898,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=2905, - serialized_end=2975, + serialized_start=3471, + serialized_end=3541, ) @@ -1673,8 +1955,8 @@ syntax="proto3", extension_ranges=[], oneofs=[], - serialized_start=2977, - serialized_end=3035, + serialized_start=3543, + serialized_end=3601, ) _CREATESESSIONREQUEST.fields_by_name["session"].message_type = _SESSION @@ -1704,6 +1986,35 @@ ].message_type = _EXECUTESQLREQUEST_PARAMTYPESENTRY _EXECUTESQLREQUEST.fields_by_name["query_mode"].enum_type = _EXECUTESQLREQUEST_QUERYMODE _EXECUTESQLREQUEST_QUERYMODE.containing_type = _EXECUTESQLREQUEST +_EXECUTEBATCHDMLREQUEST_STATEMENT_PARAMTYPESENTRY.fields_by_name[ + "value" +].message_type = google_dot_cloud_dot_spanner__v1_dot_proto_dot_type__pb2._TYPE +_EXECUTEBATCHDMLREQUEST_STATEMENT_PARAMTYPESENTRY.containing_type = ( + _EXECUTEBATCHDMLREQUEST_STATEMENT +) +_EXECUTEBATCHDMLREQUEST_STATEMENT.fields_by_name[ + "params" +].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT +_EXECUTEBATCHDMLREQUEST_STATEMENT.fields_by_name[ + "param_types" +].message_type = _EXECUTEBATCHDMLREQUEST_STATEMENT_PARAMTYPESENTRY +_EXECUTEBATCHDMLREQUEST_STATEMENT.containing_type = _EXECUTEBATCHDMLREQUEST +_EXECUTEBATCHDMLREQUEST.fields_by_name[ + "transaction" +].message_type = ( + google_dot_cloud_dot_spanner__v1_dot_proto_dot_transaction__pb2._TRANSACTIONSELECTOR +) +_EXECUTEBATCHDMLREQUEST.fields_by_name[ + "statements" +].message_type = _EXECUTEBATCHDMLREQUEST_STATEMENT +_EXECUTEBATCHDMLRESPONSE.fields_by_name[ + "result_sets" +].message_type = ( + google_dot_cloud_dot_spanner__v1_dot_proto_dot_result__set__pb2._RESULTSET +) +_EXECUTEBATCHDMLRESPONSE.fields_by_name[ + "status" +].message_type = google_dot_rpc_dot_status__pb2._STATUS _PARTITIONQUERYREQUEST_PARAMTYPESENTRY.fields_by_name[ "value" ].message_type = google_dot_cloud_dot_spanner__v1_dot_proto_dot_type__pb2._TYPE @@ -1782,6 +2093,8 @@ DESCRIPTOR.message_types_by_name["ListSessionsResponse"] = _LISTSESSIONSRESPONSE DESCRIPTOR.message_types_by_name["DeleteSessionRequest"] = _DELETESESSIONREQUEST DESCRIPTOR.message_types_by_name["ExecuteSqlRequest"] = _EXECUTESQLREQUEST +DESCRIPTOR.message_types_by_name["ExecuteBatchDmlRequest"] = _EXECUTEBATCHDMLREQUEST +DESCRIPTOR.message_types_by_name["ExecuteBatchDmlResponse"] = _EXECUTEBATCHDMLRESPONSE DESCRIPTOR.message_types_by_name["PartitionOptions"] = _PARTITIONOPTIONS DESCRIPTOR.message_types_by_name["PartitionQueryRequest"] = _PARTITIONQUERYREQUEST DESCRIPTOR.message_types_by_name["PartitionReadRequest"] = _PARTITIONREADREQUEST @@ -2050,6 +2363,142 @@ _sym_db.RegisterMessage(ExecuteSqlRequest) _sym_db.RegisterMessage(ExecuteSqlRequest.ParamTypesEntry) +ExecuteBatchDmlRequest = _reflection.GeneratedProtocolMessageType( + "ExecuteBatchDmlRequest", + (_message.Message,), + dict( + Statement=_reflection.GeneratedProtocolMessageType( + "Statement", + (_message.Message,), + dict( + ParamTypesEntry=_reflection.GeneratedProtocolMessageType( + "ParamTypesEntry", + (_message.Message,), + dict( + DESCRIPTOR=_EXECUTEBATCHDMLREQUEST_STATEMENT_PARAMTYPESENTRY, + __module__="google.cloud.spanner_v1.proto.spanner_pb2" + # @@protoc_insertion_point(class_scope:google.spanner.v1.ExecuteBatchDmlRequest.Statement.ParamTypesEntry) + ), + ), + DESCRIPTOR=_EXECUTEBATCHDMLREQUEST_STATEMENT, + __module__="google.cloud.spanner_v1.proto.spanner_pb2", + __doc__="""A single DML statement. + + + Attributes: + sql: + Required. The DML string. + params: + The DML string can contain parameter placeholders. A parameter + placeholder consists of ``'@'`` followed by the parameter + name. Parameter names consist of any combination of letters, + numbers, and underscores. Parameters can appear anywhere that + a literal value is expected. The same parameter name can be + used more than once, for example: ``"WHERE id > @msg_id AND id + < @msg_id + 100"`` It is an error to execute an SQL statement + with unbound parameters. Parameter values are specified using + ``params``, which is a JSON object whose keys are parameter + names, and whose values are the corresponding parameter + values. + param_types: + It is not always possible for Cloud Spanner to infer the right + SQL type from a JSON value. For example, values of type + ``BYTES`` and values of type ``STRING`` both appear in [params + ][google.spanner.v1.ExecuteBatchDmlRequest.Statement.params] + as JSON strings. In these cases, ``param_types`` can be used + to specify the exact SQL type for some or all of the SQL + statement parameters. See the definition of + [Type][google.spanner.v1.Type] for more information about SQL + types. + """, + # @@protoc_insertion_point(class_scope:google.spanner.v1.ExecuteBatchDmlRequest.Statement) + ), + ), + DESCRIPTOR=_EXECUTEBATCHDMLREQUEST, + __module__="google.cloud.spanner_v1.proto.spanner_pb2", + __doc__="""The request for + [ExecuteBatchDml][google.spanner.v1.Spanner.ExecuteBatchDml] + + + Attributes: + session: + Required. The session in which the DML statements should be + performed. + transaction: + The transaction to use. A ReadWrite transaction is required. + Single-use transactions are not supported (to avoid replay). + The caller must either supply an existing transaction ID or + begin a new transaction. + statements: + The list of statements to execute in this batch. Statements + are executed serially, such that the effects of statement i + are visible to statement i+1. Each statement must be a DML + statement. Execution will stop at the first failed statement; + the remaining statements will not run. REQUIRES: + statements\_size() > 0. + seqno: + A per-transaction sequence number used to identify this + request. This is used in the same space as the seqno in + [ExecuteSqlRequest][Spanner.ExecuteSqlRequest]. See more + details in [ExecuteSqlRequest][Spanner.ExecuteSqlRequest]. + """, + # @@protoc_insertion_point(class_scope:google.spanner.v1.ExecuteBatchDmlRequest) + ), +) +_sym_db.RegisterMessage(ExecuteBatchDmlRequest) +_sym_db.RegisterMessage(ExecuteBatchDmlRequest.Statement) +_sym_db.RegisterMessage(ExecuteBatchDmlRequest.Statement.ParamTypesEntry) + +ExecuteBatchDmlResponse = _reflection.GeneratedProtocolMessageType( + "ExecuteBatchDmlResponse", + (_message.Message,), + dict( + DESCRIPTOR=_EXECUTEBATCHDMLRESPONSE, + __module__="google.cloud.spanner_v1.proto.spanner_pb2", + __doc__="""The response for + [ExecuteBatchDml][google.spanner.v1.Spanner.ExecuteBatchDml]. Contains a + list of [ResultSet][google.spanner.v1.ResultSet], one for each DML + statement that has successfully executed. If a statement fails, the + error is returned as part of the response payload. Clients can determine + whether all DML statements have run successfully, or if a statement + failed, using one of the following approaches: + + 1. Check if 'status' field is OkStatus. + 2. Check if result\_sets\_size() equals the number of statements in + [ExecuteBatchDmlRequest][Spanner.ExecuteBatchDmlRequest]. + + Example 1: A request with 5 DML statements, all executed successfully. + Result: A response with 5 ResultSets, one for each statement in the same + order, and an OK status. + + Example 2: A request with 5 DML statements. The 3rd statement has a + syntax error. Result: A response with 2 ResultSets, for the first 2 + statements that run successfully, and a syntax error (INVALID\_ARGUMENT) + status. From result\_set\_size() client can determine that the 3rd + statement has failed. + + + Attributes: + result_sets: + ResultSets, one for each statement in the request that ran + successfully, in the same order as the statements in the + request. Each [ResultSet][google.spanner.v1.ResultSet] will + not contain any rows. The + [ResultSetStats][google.spanner.v1.ResultSetStats] in each + [ResultSet][google.spanner.v1.ResultSet] will contain the + number of rows modified by the statement. Only the first + ResultSet in the response contains a valid + [ResultSetMetadata][google.spanner.v1.ResultSetMetadata]. + status: + If all DML statements are executed successfully, status will + be OK. Otherwise, the error status of the first failed + statement. + """, + # @@protoc_insertion_point(class_scope:google.spanner.v1.ExecuteBatchDmlResponse) + ), +) +_sym_db.RegisterMessage(ExecuteBatchDmlResponse) + PartitionOptions = _reflection.GeneratedProtocolMessageType( "PartitionOptions", (_message.Message,), @@ -2419,6 +2868,7 @@ DESCRIPTOR._options = None _SESSION_LABELSENTRY._options = None _EXECUTESQLREQUEST_PARAMTYPESENTRY._options = None +_EXECUTEBATCHDMLREQUEST_STATEMENT_PARAMTYPESENTRY._options = None _PARTITIONQUERYREQUEST_PARAMTYPESENTRY._options = None _SPANNER = _descriptor.ServiceDescriptor( @@ -2427,8 +2877,8 @@ file=DESCRIPTOR, index=0, serialized_options=None, - serialized_start=3038, - serialized_end=5217, + serialized_start=3604, + serialized_end=5978, methods=[ _descriptor.MethodDescriptor( name="CreateSession", @@ -2496,10 +2946,21 @@ '\202\323\344\223\002T"O/v1/{session=projects/*/instances/*/databases/*/sessions/*}:executeStreamingSql:\001*' ), ), + _descriptor.MethodDescriptor( + name="ExecuteBatchDml", + full_name="google.spanner.v1.Spanner.ExecuteBatchDml", + index=6, + containing_service=None, + input_type=_EXECUTEBATCHDMLREQUEST, + output_type=_EXECUTEBATCHDMLRESPONSE, + serialized_options=_b( + '\202\323\344\223\002P"K/v1/{session=projects/*/instances/*/databases/*/sessions/*}:executeBatchDml:\001*' + ), + ), _descriptor.MethodDescriptor( name="Read", full_name="google.spanner.v1.Spanner.Read", - index=6, + index=7, containing_service=None, input_type=_READREQUEST, output_type=google_dot_cloud_dot_spanner__v1_dot_proto_dot_result__set__pb2._RESULTSET, @@ -2510,7 +2971,7 @@ _descriptor.MethodDescriptor( name="StreamingRead", full_name="google.spanner.v1.Spanner.StreamingRead", - index=7, + index=8, containing_service=None, input_type=_READREQUEST, output_type=google_dot_cloud_dot_spanner__v1_dot_proto_dot_result__set__pb2._PARTIALRESULTSET, @@ -2521,7 +2982,7 @@ _descriptor.MethodDescriptor( name="BeginTransaction", full_name="google.spanner.v1.Spanner.BeginTransaction", - index=8, + index=9, containing_service=None, input_type=_BEGINTRANSACTIONREQUEST, output_type=google_dot_cloud_dot_spanner__v1_dot_proto_dot_transaction__pb2._TRANSACTION, @@ -2532,7 +2993,7 @@ _descriptor.MethodDescriptor( name="Commit", full_name="google.spanner.v1.Spanner.Commit", - index=9, + index=10, containing_service=None, input_type=_COMMITREQUEST, output_type=_COMMITRESPONSE, @@ -2543,7 +3004,7 @@ _descriptor.MethodDescriptor( name="Rollback", full_name="google.spanner.v1.Spanner.Rollback", - index=10, + index=11, containing_service=None, input_type=_ROLLBACKREQUEST, output_type=google_dot_protobuf_dot_empty__pb2._EMPTY, @@ -2554,7 +3015,7 @@ _descriptor.MethodDescriptor( name="PartitionQuery", full_name="google.spanner.v1.Spanner.PartitionQuery", - index=11, + index=12, containing_service=None, input_type=_PARTITIONQUERYREQUEST, output_type=_PARTITIONRESPONSE, @@ -2565,7 +3026,7 @@ _descriptor.MethodDescriptor( name="PartitionRead", full_name="google.spanner.v1.Spanner.PartitionRead", - index=12, + index=13, containing_service=None, input_type=_PARTITIONREADREQUEST, output_type=_PARTITIONRESPONSE, diff --git a/spanner/google/cloud/spanner_v1/proto/spanner_pb2_grpc.py b/spanner/google/cloud/spanner_v1/proto/spanner_pb2_grpc.py index 85106b9a6254..e3b64d9ddb99 100644 --- a/spanner/google/cloud/spanner_v1/proto/spanner_pb2_grpc.py +++ b/spanner/google/cloud/spanner_v1/proto/spanner_pb2_grpc.py @@ -56,6 +56,11 @@ def __init__(self, channel): request_serializer=google_dot_cloud_dot_spanner__v1_dot_proto_dot_spanner__pb2.ExecuteSqlRequest.SerializeToString, response_deserializer=google_dot_cloud_dot_spanner__v1_dot_proto_dot_result__set__pb2.PartialResultSet.FromString, ) + self.ExecuteBatchDml = channel.unary_unary( + "/google.spanner.v1.Spanner/ExecuteBatchDml", + request_serializer=google_dot_cloud_dot_spanner__v1_dot_proto_dot_spanner__pb2.ExecuteBatchDmlRequest.SerializeToString, + response_deserializer=google_dot_cloud_dot_spanner__v1_dot_proto_dot_spanner__pb2.ExecuteBatchDmlResponse.FromString, + ) self.Read = channel.unary_unary( "/google.spanner.v1.Spanner/Read", request_serializer=google_dot_cloud_dot_spanner__v1_dot_proto_dot_spanner__pb2.ReadRequest.SerializeToString, @@ -142,7 +147,9 @@ def ListSessions(self, request, context): raise NotImplementedError("Method not implemented!") def DeleteSession(self, request, context): - """Ends a session, releasing server resources associated with it. + """Ends a session, releasing server resources associated with it. This will + asynchronously trigger cancellation of any operations that are running with + this session. """ context.set_code(grpc.StatusCode.UNIMPLEMENTED) context.set_details("Method not implemented!") @@ -178,6 +185,31 @@ def ExecuteStreamingSql(self, request, context): context.set_details("Method not implemented!") raise NotImplementedError("Method not implemented!") + def ExecuteBatchDml(self, request, context): + """Executes a batch of SQL DML statements. This method allows many statements + to be run with lower latency than submitting them sequentially with + [ExecuteSql][google.spanner.v1.Spanner.ExecuteSql]. + + Statements are executed in order, sequentially. + [ExecuteBatchDmlResponse][Spanner.ExecuteBatchDmlResponse] will contain a + [ResultSet][google.spanner.v1.ResultSet] for each DML statement that has successfully executed. If a + statement fails, its error status will be returned as part of the + [ExecuteBatchDmlResponse][Spanner.ExecuteBatchDmlResponse]. Execution will + stop at the first failed statement; the remaining statements will not run. + + ExecuteBatchDml is expected to return an OK status with a response even if + there was an error while processing one of the DML statements. Clients must + inspect response.status to determine if there were any errors while + processing the request. + + See more details in + [ExecuteBatchDmlRequest][Spanner.ExecuteBatchDmlRequest] and + [ExecuteBatchDmlResponse][Spanner.ExecuteBatchDmlResponse]. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details("Method not implemented!") + raise NotImplementedError("Method not implemented!") + def Read(self, request, context): """Reads rows from the database using key lookups and scans, as a simple key/value style alternative to @@ -320,6 +352,11 @@ def add_SpannerServicer_to_server(servicer, server): request_deserializer=google_dot_cloud_dot_spanner__v1_dot_proto_dot_spanner__pb2.ExecuteSqlRequest.FromString, response_serializer=google_dot_cloud_dot_spanner__v1_dot_proto_dot_result__set__pb2.PartialResultSet.SerializeToString, ), + "ExecuteBatchDml": grpc.unary_unary_rpc_method_handler( + servicer.ExecuteBatchDml, + request_deserializer=google_dot_cloud_dot_spanner__v1_dot_proto_dot_spanner__pb2.ExecuteBatchDmlRequest.FromString, + response_serializer=google_dot_cloud_dot_spanner__v1_dot_proto_dot_spanner__pb2.ExecuteBatchDmlResponse.SerializeToString, + ), "Read": grpc.unary_unary_rpc_method_handler( servicer.Read, request_deserializer=google_dot_cloud_dot_spanner__v1_dot_proto_dot_spanner__pb2.ReadRequest.FromString, diff --git a/spanner/synth.metadata b/spanner/synth.metadata index 49b0a1ddbf59..9664c407bda9 100644 --- a/spanner/synth.metadata +++ b/spanner/synth.metadata @@ -1,5 +1,5 @@ { - "updateTime": "2019-02-26T13:25:33.405783Z", + "updateTime": "2019-03-05T19:26:32.763039Z", "sources": [ { "generator": { @@ -12,15 +12,15 @@ "git": { "name": "googleapis", "remote": "https://github.com/googleapis/googleapis.git", - "sha": "29f098cb03a9983cc9cb15993de5da64419046f2", - "internalRef": "235621085" + "sha": "8610b13d3da5e3230cf99c503558961626186249", + "internalRef": "236868372" } }, { "template": { "name": "python_library", "origin": "synthtool.gcp", - "version": "2019.1.16" + "version": "2019.2.26" } } ], diff --git a/spanner/tests/unit/gapic/v1/test_spanner_client_v1.py b/spanner/tests/unit/gapic/v1/test_spanner_client_v1.py index a89ffd8035b9..a5f05ba3a194 100644 --- a/spanner/tests/unit/gapic/v1/test_spanner_client_v1.py +++ b/spanner/tests/unit/gapic/v1/test_spanner_client_v1.py @@ -309,6 +309,55 @@ def test_execute_streaming_sql_exception(self): with pytest.raises(CustomException): client.execute_streaming_sql(session, sql) + def test_execute_batch_dml(self): + # Setup Expected Response + expected_response = {} + expected_response = spanner_pb2.ExecuteBatchDmlResponse(**expected_response) + + # Mock the API response + channel = ChannelStub(responses=[expected_response]) + patch = mock.patch("google.api_core.grpc_helpers.create_channel") + with patch as create_channel: + create_channel.return_value = channel + client = spanner_v1.SpannerClient() + + # Setup Request + session = client.session_path( + "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]" + ) + transaction = {} + statements = [] + seqno = 109325920 + + response = client.execute_batch_dml(session, transaction, statements, seqno) + assert expected_response == response + + assert len(channel.requests) == 1 + expected_request = spanner_pb2.ExecuteBatchDmlRequest( + session=session, transaction=transaction, statements=statements, seqno=seqno + ) + actual_request = channel.requests[0][1] + assert expected_request == actual_request + + def test_execute_batch_dml_exception(self): + # Mock the API response + channel = ChannelStub(responses=[CustomException()]) + patch = mock.patch("google.api_core.grpc_helpers.create_channel") + with patch as create_channel: + create_channel.return_value = channel + client = spanner_v1.SpannerClient() + + # Setup request + session = client.session_path( + "[PROJECT]", "[INSTANCE]", "[DATABASE]", "[SESSION]" + ) + transaction = {} + statements = [] + seqno = 109325920 + + with pytest.raises(CustomException): + client.execute_batch_dml(session, transaction, statements, seqno) + def test_read(self): # Setup Expected Response expected_response = {} From fcb892674633bc94cdbfc0312e09bc1d0e943b46 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 11 Dec 2018 09:43:29 -0500 Subject: [PATCH 2/6] Factor out DML parameter validation / conversion. --- .../google/cloud/spanner_v1/transaction.py | 42 +++++++++++++++---- spanner/tests/unit/test_transaction.py | 27 ++++++++++++ 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/spanner/google/cloud/spanner_v1/transaction.py b/spanner/google/cloud/spanner_v1/transaction.py index 853dafeb8c1a..12f5fb1a805a 100644 --- a/spanner/google/cloud/spanner_v1/transaction.py +++ b/spanner/google/cloud/spanner_v1/transaction.py @@ -130,6 +130,38 @@ def commit(self): del self._session._transaction return self.committed + @staticmethod + def _make_params_pb(params, param_types): + """Helper for :meth:`execute_update`. + + :type params: dict, {str -> column value} + :param params: values for parameter replacement. Keys must match + the names used in ``dml``. + + :type param_types: dict[str -> Union[dict, .types.Type]] + :param param_types: + (Optional) maps explicit types for one or more param values; + required if parameters are passed. + + :rtype: Union[None, :class:`Struct`] + :returns: a struct message for the passed params, or None + :raises ValueError: + If ``param_types`` is None but ``params`` is not None. + :raises ValueError: + If ``params`` is None but ``param_types`` is not None. + """ + if params is not None: + if param_types is None: + raise ValueError("Specify 'param_types' when passing 'params'.") + return Struct( + fields={key: _make_value_pb(value) for key, value in params.items()} + ) + else: + if param_types is not None: + raise ValueError("Specify 'params' when passing 'param_types'.") + + return None + def execute_update(self, dml, params=None, param_types=None, query_mode=None): """Perform an ``ExecuteSql`` API request with DML. @@ -153,15 +185,7 @@ def execute_update(self, dml, params=None, param_types=None, query_mode=None): :rtype: int :returns: Count of rows affected by the DML statement. """ - if params is not None: - if param_types is None: - raise ValueError("Specify 'param_types' when passing 'params'.") - params_pb = Struct( - fields={key: _make_value_pb(value) for key, value in params.items()} - ) - else: - params_pb = None - + params_pb = self._make_params_pb(params, param_types) database = self._session._database metadata = _metadata_with_prefix(database.name) transaction = self._make_txn_selector() diff --git a/spanner/tests/unit/test_transaction.py b/spanner/tests/unit/test_transaction.py index d46b4a7ada64..b684e3b53819 100644 --- a/spanner/tests/unit/test_transaction.py +++ b/spanner/tests/unit/test_transaction.py @@ -300,6 +300,33 @@ def test_commit_no_mutations(self): def test_commit_w_mutations(self): self._commit_helper(mutate=True) + def test__make_params_pb_w_params_wo_param_types(self): + session = _Session() + transaction = self._make_one(session) + + with self.assertRaises(ValueError): + transaction._make_params_pb(PARAMS, None) + + def test__make_params_pb_wo_params_w_param_types(self): + session = _Session() + transaction = self._make_one(session) + + with self.assertRaises(ValueError): + transaction._make_params_pb(None, PARAM_TYPES) + + def test__make_params_pb_w_params_w_param_types(self): + from google.protobuf.struct_pb2 import Struct + from google.cloud.spanner_v1._helpers import _make_value_pb + session = _Session() + transaction = self._make_one(session) + + params_pb = transaction._make_params_pb(PARAMS, PARAM_TYPES) + + expected_params = Struct( + fields={key: _make_value_pb(value) for (key, value) in PARAMS.items()} + ) + self.assertEqual(params_pb, expected_params) + def test_execute_update_other_error(self): database = _Database() database.spanner_api = self._make_spanner_api() From 868c925eee6477bd68cb04fdb3421c19a1a400f9 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 11 Dec 2018 09:43:29 -0500 Subject: [PATCH 3/6] Add 'Transaction.batch_update' method. --- .../google/cloud/spanner_v1/transaction.py | 52 ++++++++++ spanner/tests/unit/test_transaction.py | 94 +++++++++++++++++++ 2 files changed, 146 insertions(+) diff --git a/spanner/google/cloud/spanner_v1/transaction.py b/spanner/google/cloud/spanner_v1/transaction.py index 12f5fb1a805a..acabb702f595 100644 --- a/spanner/google/cloud/spanner_v1/transaction.py +++ b/spanner/google/cloud/spanner_v1/transaction.py @@ -205,6 +205,58 @@ def execute_update(self, dml, params=None, param_types=None, query_mode=None): self._execute_sql_count += 1 return response.stats.row_count_exact + def batch_update(self, statements): + """Perform a batch of DML statements via an ``ExecuteBatchDml`` request. + + :type statements: + Sequence[Union[ str, Tuple[str, Dict[str, Any], Dict[str, Union[dict, .types.Type]]]]] + + :param statements: + List of DML statements, with optional params / param types. + If passed, 'params' is a dict mapping names to the values + for parameter replacement. Keys must match the names used in the + corresponding DML statement. If 'params' is passed, 'param_types' + must also be passed, as a dict mapping names to the type of + value passed in 'params'. + + :rtype: + Tuple(status, Sequence[int]) + :returns: + Status code, plus counts of rows affected by each completed DML + statement. Note that if the staus code is not ``OK``, the + statement triggering the error will not have an entry in the + list, nor will any statements following that one. + """ + parsed = [] + for statement in statements: + if isinstance(statement, str): + parsed.append({"sql": statement}) + else: + dml, params, param_types = statement + params_pb = self._make_params_pb(params, param_types) + parsed.append( + {"sql": dml, "params": params_pb, "param_types": param_types} + ) + + database = self._session._database + metadata = _metadata_with_prefix(database.name) + transaction = self._make_txn_selector() + api = database.spanner_api + + response = api.execute_batch_dml( + self._session.name, + parsed, + transaction=transaction, + seqno=self._execute_sql_count, + metadata=metadata, + ) + + self._execute_sql_count += 1 + row_counts = [ + result_set.stats.row_count_exact for result_set in response.result_sets + ] + return response.status, row_counts + def __enter__(self): """Begin ``with`` block.""" self.begin() diff --git a/spanner/tests/unit/test_transaction.py b/spanner/tests/unit/test_transaction.py index b684e3b53819..c6d27dc1e438 100644 --- a/spanner/tests/unit/test_transaction.py +++ b/spanner/tests/unit/test_transaction.py @@ -317,6 +317,7 @@ def test__make_params_pb_wo_params_w_param_types(self): def test__make_params_pb_w_params_w_param_types(self): from google.protobuf.struct_pb2 import Struct from google.cloud.spanner_v1._helpers import _make_value_pb + session = _Session() transaction = self._make_one(session) @@ -398,6 +399,99 @@ def test_execute_update_new_transaction(self): def test_execute_update_w_count(self): self._execute_update_helper(count=1) + def test_batch_update_other_error(self): + database = _Database() + database.spanner_api = self._make_spanner_api() + database.spanner_api.execute_batch_dml.side_effect = RuntimeError() + session = _Session(database) + transaction = self._make_one(session) + transaction._transaction_id = self.TRANSACTION_ID + + with self.assertRaises(RuntimeError): + transaction.batch_update(statements=[DML_QUERY]) + + def _batch_update_helper(self, error_after=None, count=0): + from google.rpc.status_pb2 import Status + from google.protobuf.struct_pb2 import Struct + from google.cloud.spanner_v1.proto.result_set_pb2 import ResultSet + from google.cloud.spanner_v1.proto.result_set_pb2 import ResultSetStats + from google.cloud.spanner_v1.proto.spanner_pb2 import ExecuteBatchDmlResponse + from google.cloud.spanner_v1.proto.transaction_pb2 import TransactionSelector + from google.cloud.spanner_v1._helpers import _make_value_pb + + insert_dml = "INSERT INTO table(pkey, desc) VALUES (%pkey, %desc)" + insert_params = {"pkey": 12345, "desc": "DESCRIPTION"} + insert_param_types = {"pkey": "INT64", "desc": "STRING"} + update_dml = 'UPDATE table SET desc = desc + "-amended"' + delete_dml = "DELETE FROM table WHERE desc IS NULL" + + dml_statements = [ + (insert_dml, insert_params, insert_param_types), + update_dml, + delete_dml, + ] + + stats_pbs = [ + ResultSetStats(row_count_exact=1), + ResultSetStats(row_count_exact=2), + ResultSetStats(row_count_exact=3), + ] + if error_after is not None: + stats_pbs = stats_pbs[:error_after] + expected_status = Status(code=400) + else: + expected_status = Status(code=200) + expected_row_counts = [stats.row_count_exact for stats in stats_pbs] + + response = ExecuteBatchDmlResponse( + status=expected_status, + result_sets=[ResultSet(stats=stats_pb) for stats_pb in stats_pbs], + ) + database = _Database() + api = database.spanner_api = self._make_spanner_api() + api.execute_batch_dml.return_value = response + session = _Session(database) + transaction = self._make_one(session) + transaction._transaction_id = self.TRANSACTION_ID + transaction._execute_sql_count = count + + status, row_counts = transaction.batch_update(dml_statements) + + self.assertEqual(status, expected_status) + self.assertEqual(row_counts, expected_row_counts) + + expected_transaction = TransactionSelector(id=self.TRANSACTION_ID) + expected_insert_params = Struct( + fields={ + key: _make_value_pb(value) for (key, value) in insert_params.items() + } + ) + expected_statement_tuples = [ + (insert_dml, expected_insert_params, insert_param_types), + (update_dml, None, None), + (delete_dml, None, None), + ] + expected_statements = [ + {"sql": sql, "params": params, "param_types": param_types} + for sql, params, param_types in expected_statement_tuples + ] + + api.execute_batch_dml.assert_called_once_with( + self.SESSION_NAME, + expected_statements, + transaction=expected_transaction, + seqno=count, + metadata=[("google-cloud-resource-prefix", database.name)], + ) + + self.assertEqual(transaction._execute_sql_count, count + 1) + + def test_batch_update_wo_errors(self): + self._batch_update_helper() + + def test_batch_update_w_errors(self): + self._batch_update_helper(error_after=2, count=1) + def test_context_mgr_success(self): import datetime from google.cloud.spanner_v1.proto.spanner_pb2 import CommitResponse From 42487018c3ea1561b4e9a4b99e771c45136ff202 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 11 Dec 2018 09:43:29 -0500 Subject: [PATCH 4/6] Add mandated system tests for batch DML. --- spanner/tests/system/test_system.py | 139 ++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/spanner/tests/system/test_system.py b/spanner/tests/system/test_system.py index 1cd0ca2a7ea5..cc019028a4f7 100644 --- a/spanner/tests/system/test_system.py +++ b/spanner/tests/system/test_system.py @@ -776,6 +776,145 @@ def test_transaction_execute_update_then_insert_commit(self): # [END spanner_test_dml_update] # [END spanner_test_dml_with_mutation] + def test_transaction_batch_update_success(self): + # [START spanner_test_dml_with_mutation] + # [START spanner_test_dml_update] + retry = RetryInstanceState(_has_all_ddl) + retry(self._db.reload)() + + session = self._db.session() + session.create() + self.to_delete.append(session) + + with session.batch() as batch: + batch.delete(self.TABLE, self.ALL) + + insert_statement = list(self._generate_insert_statements())[0] + update_statement = ( + "UPDATE contacts SET email = @email " + "WHERE contact_id = @contact_id;", + {"contact_id": 1, "email": "phreddy@example.com"}, + {"contact_id": Type(code=INT64), "email": Type(code=STRING)}, + ) + delete_statement = ( + "DELETE contacts WHERE contact_id = @contact_id;", + {"contact_id": 1}, + {"contact_id": Type(code=INT64)}, + ) + + def unit_of_work(transaction, self): + rows = list(transaction.read(self.TABLE, self.COLUMNS, self.ALL)) + self.assertEqual(rows, []) + + status, row_counts = transaction.batch_update([ + insert_statement, + update_statement, + delete_statement, + ]) + self.assertEqual(status.code, 0) # XXX: where are values defined? + self.assertEqual(len(row_counts), 3) + for row_count in row_counts: + self.assertEqual(row_count, 1) + + session.run_in_transaction(unit_of_work, self) + + rows = list(session.read(self.TABLE, self.COLUMNS, self.ALL)) + self._check_rows_data(rows, []) + + def test_transaction_batch_update_and_execute_dml(self): + retry = RetryInstanceState(_has_all_ddl) + retry(self._db.reload)() + + session = self._db.session() + session.create() + self.to_delete.append(session) + + with session.batch() as batch: + batch.delete(self.TABLE, self.ALL) + + insert_statements = list(self._generate_insert_statements()) + update_statements = [( + "UPDATE contacts SET email = @email " + "WHERE contact_id = @contact_id;", + {"contact_id": 1, "email": "phreddy@example.com"}, + {"contact_id": Type(code=INT64), "email": Type(code=STRING)}, + )] + + delete_statement = "DELETE contacts WHERE TRUE;" + + def unit_of_work(transaction, self): + rows = list(transaction.read(self.TABLE, self.COLUMNS, self.ALL)) + self.assertEqual(rows, []) + + status, row_counts = transaction.batch_update( + insert_statements + update_statements) + self.assertEqual(status.code, 0) # XXX: where are values defined? + self.assertEqual(len(row_counts), len(insert_statements) + 1) + for row_count in row_counts: + self.assertEqual(row_count, 1) + + row_count = transaction.execute_update(delete_statement) + + self.assertEqual(row_count, len(insert_statements)) + + session.run_in_transaction(unit_of_work, self) + + rows = list(session.read(self.TABLE, self.COLUMNS, self.ALL)) + self._check_rows_data(rows, []) + + def test_transaction_batch_update_w_syntax_error(self): + retry = RetryInstanceState(_has_all_ddl) + retry(self._db.reload)() + + session = self._db.session() + session.create() + self.to_delete.append(session) + + with session.batch() as batch: + batch.delete(self.TABLE, self.ALL) + + insert_statement = list(self._generate_insert_statements())[0] + update_statement = ( + "UPDTAE contacts SET email = @email " + "WHERE contact_id = @contact_id;", + {"contact_id": 1, "email": "phreddy@example.com"}, + {"contact_id": Type(code=INT64), "email": Type(code=STRING)}, + ) + delete_statement = ( + "DELETE contacts WHERE contact_id = @contact_id;", + {"contact_id": 1}, + {"contact_id": Type(code=INT64)}, + ) + + with session.transaction() as transaction: + rows = list(transaction.read(self.TABLE, self.COLUMNS, self.ALL)) + self.assertEqual(rows, []) + + status, row_counts = transaction.batch_update([ + insert_statement, + update_statement, + delete_statement, + ]) + + self.assertEqual(status.code, 3) # XXX: where are values defined? + self.assertEqual(len(row_counts), 1) + for row_count in row_counts: + self.assertEqual(row_count, 1) + + def test_transaction_batch_update_wo_statements(self): + from google.api_core.exceptions import InvalidArgument + + retry = RetryInstanceState(_has_all_ddl) + retry(self._db.reload)() + + session = self._db.session() + session.create() + self.to_delete.append(session) + + with session.transaction() as transaction: + with self.assertRaises(InvalidArgument): + transaction.batch_update([]) + def test_execute_partitioned_dml(self): # [START spanner_test_dml_partioned_dml_update] retry = RetryInstanceState(_has_all_ddl) From 6e7cc973ee6b3dca9d7979d3f6403c206897bedd Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 5 Mar 2019 14:55:08 -0500 Subject: [PATCH 5/6] Accomodate re-ordered 'execute_batch_dml' arguments. --- spanner/google/cloud/spanner_v1/transaction.py | 4 ++-- spanner/tests/unit/test_transaction.py | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/spanner/google/cloud/spanner_v1/transaction.py b/spanner/google/cloud/spanner_v1/transaction.py index acabb702f595..b4eb21143ac7 100644 --- a/spanner/google/cloud/spanner_v1/transaction.py +++ b/spanner/google/cloud/spanner_v1/transaction.py @@ -244,9 +244,9 @@ def batch_update(self, statements): api = database.spanner_api response = api.execute_batch_dml( - self._session.name, - parsed, + session=self._session.name, transaction=transaction, + statements=parsed, seqno=self._execute_sql_count, metadata=metadata, ) diff --git a/spanner/tests/unit/test_transaction.py b/spanner/tests/unit/test_transaction.py index c6d27dc1e438..cceff89fcaac 100644 --- a/spanner/tests/unit/test_transaction.py +++ b/spanner/tests/unit/test_transaction.py @@ -466,20 +466,20 @@ def _batch_update_helper(self, error_after=None, count=0): key: _make_value_pb(value) for (key, value) in insert_params.items() } ) - expected_statement_tuples = [ - (insert_dml, expected_insert_params, insert_param_types), - (update_dml, None, None), - (delete_dml, None, None), - ] expected_statements = [ - {"sql": sql, "params": params, "param_types": param_types} - for sql, params, param_types in expected_statement_tuples + { + "sql": insert_dml, + "params": expected_insert_params, + "param_types": insert_param_types, + }, + {"sql": update_dml}, + {"sql": delete_dml}, ] api.execute_batch_dml.assert_called_once_with( - self.SESSION_NAME, - expected_statements, + session=self.SESSION_NAME, transaction=expected_transaction, + statements=expected_statements, seqno=count, metadata=[("google-cloud-resource-prefix", database.name)], ) From 6d986dd9d93ae119382c96ccb64cb82d68d2d34e Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 5 Mar 2019 14:55:37 -0500 Subject: [PATCH 6/6] Blacken. --- spanner/tests/system/test_system.py | 38 +++++++++++++---------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/spanner/tests/system/test_system.py b/spanner/tests/system/test_system.py index cc019028a4f7..f087b0c22a67 100644 --- a/spanner/tests/system/test_system.py +++ b/spanner/tests/system/test_system.py @@ -791,8 +791,7 @@ def test_transaction_batch_update_success(self): insert_statement = list(self._generate_insert_statements())[0] update_statement = ( - "UPDATE contacts SET email = @email " - "WHERE contact_id = @contact_id;", + "UPDATE contacts SET email = @email " "WHERE contact_id = @contact_id;", {"contact_id": 1, "email": "phreddy@example.com"}, {"contact_id": Type(code=INT64), "email": Type(code=STRING)}, ) @@ -806,11 +805,9 @@ def unit_of_work(transaction, self): rows = list(transaction.read(self.TABLE, self.COLUMNS, self.ALL)) self.assertEqual(rows, []) - status, row_counts = transaction.batch_update([ - insert_statement, - update_statement, - delete_statement, - ]) + status, row_counts = transaction.batch_update( + [insert_statement, update_statement, delete_statement] + ) self.assertEqual(status.code, 0) # XXX: where are values defined? self.assertEqual(len(row_counts), 3) for row_count in row_counts: @@ -833,12 +830,13 @@ def test_transaction_batch_update_and_execute_dml(self): batch.delete(self.TABLE, self.ALL) insert_statements = list(self._generate_insert_statements()) - update_statements = [( - "UPDATE contacts SET email = @email " - "WHERE contact_id = @contact_id;", - {"contact_id": 1, "email": "phreddy@example.com"}, - {"contact_id": Type(code=INT64), "email": Type(code=STRING)}, - )] + update_statements = [ + ( + "UPDATE contacts SET email = @email " "WHERE contact_id = @contact_id;", + {"contact_id": 1, "email": "phreddy@example.com"}, + {"contact_id": Type(code=INT64), "email": Type(code=STRING)}, + ) + ] delete_statement = "DELETE contacts WHERE TRUE;" @@ -847,7 +845,8 @@ def unit_of_work(transaction, self): self.assertEqual(rows, []) status, row_counts = transaction.batch_update( - insert_statements + update_statements) + insert_statements + update_statements + ) self.assertEqual(status.code, 0) # XXX: where are values defined? self.assertEqual(len(row_counts), len(insert_statements) + 1) for row_count in row_counts: @@ -875,8 +874,7 @@ def test_transaction_batch_update_w_syntax_error(self): insert_statement = list(self._generate_insert_statements())[0] update_statement = ( - "UPDTAE contacts SET email = @email " - "WHERE contact_id = @contact_id;", + "UPDTAE contacts SET email = @email " "WHERE contact_id = @contact_id;", {"contact_id": 1, "email": "phreddy@example.com"}, {"contact_id": Type(code=INT64), "email": Type(code=STRING)}, ) @@ -890,11 +888,9 @@ def test_transaction_batch_update_w_syntax_error(self): rows = list(transaction.read(self.TABLE, self.COLUMNS, self.ALL)) self.assertEqual(rows, []) - status, row_counts = transaction.batch_update([ - insert_statement, - update_statement, - delete_statement, - ]) + status, row_counts = transaction.batch_update( + [insert_statement, update_statement, delete_statement] + ) self.assertEqual(status.code, 3) # XXX: where are values defined? self.assertEqual(len(row_counts), 1)