From 67499367314be00a6ef7281423219f435aebfbc0 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 28 Oct 2025 11:01:29 -0700 Subject: [PATCH 01/25] docs: adding type annotations and fixing documentation --- .../lib/google/cloud/spanner/backup/job.rb | 4 +- .../lib/google/cloud/spanner/batch_client.rb | 6 +- .../google/cloud/spanner/batch_snapshot.rb | 22 ++++++-- .../cloud/spanner/batch_update_results.rb | 9 ++- .../lib/google/cloud/spanner/client.rb | 31 +++++++++-- .../lib/google/cloud/spanner/commit.rb | 3 + .../google/cloud/spanner/commit_response.rb | 16 ++++-- .../lib/google/cloud/spanner/instance.rb | 4 +- .../lib/google/cloud/spanner/project.rb | 4 +- .../lib/google/cloud/spanner/results.rb | 18 +++--- .../lib/google/cloud/spanner/service.rb | 55 +++++++++++++++++-- .../lib/google/cloud/spanner/session.rb | 34 ++++++++---- .../lib/google/cloud/spanner/snapshot.rb | 4 +- .../lib/google/cloud/spanner/transaction.rb | 4 +- 14 files changed, 163 insertions(+), 51 deletions(-) diff --git a/google-cloud-spanner/lib/google/cloud/spanner/backup/job.rb b/google-cloud-spanner/lib/google/cloud/spanner/backup/job.rb index 486def5c..abbcad54 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/backup/job.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/backup/job.rb @@ -71,7 +71,9 @@ class Job ## # @private Creates a new Backup::Job instance. def initialize + # @type [::Gapic::Operation, nil] @grpc = nil + # @type [::Google::Cloud::Spanner::Service, nil] @service = nil end @@ -268,7 +270,7 @@ def cancel_time end # Create a new Backup::Job from a `Gapic::Operation` object. - # @param grpc [::Gapic::Operation`] The wrapped `Gapic::Operation` object. + # @param grpc [::Gapic::Operation`] Underlying `Gapic::Operation` object. # @param service [::Google::Cloud::Spanner::Service] A `Spanner::Service` reference. # @private # @return [::Google::Cloud::Spanner::Backup::Job] diff --git a/google-cloud-spanner/lib/google/cloud/spanner/batch_client.rb b/google-cloud-spanner/lib/google/cloud/spanner/batch_client.rb index 9f3550c7..4613b31b 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/batch_client.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/batch_client.rb @@ -110,13 +110,15 @@ def project end # The Spanner instance connected to. - # @return [Instance] + # @deprecated Use {Google::Cloud::Spanner::Admin::Instance#instance_admin} instead. + # @return [::Google::Cloud::Spanner::Instance] def instance @project.instance instance_id end # The Spanner database connected to. - # @return [Database] + # @deprecated Use {Google::Cloud::Spanner::Admin::Database#database_admin} instead. + # @return [::Google::Cloud::Spanner::Database] def database @project.database instance_id, database_id end diff --git a/google-cloud-spanner/lib/google/cloud/spanner/batch_snapshot.rb b/google-cloud-spanner/lib/google/cloud/spanner/batch_snapshot.rb index d602112c..d8da9b2e 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/batch_snapshot.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/batch_snapshot.rb @@ -68,8 +68,15 @@ class BatchSnapshot # @private Directed Read Options attr_reader :directed_read_options - ## - # @private Creates a BatchSnapshot object. + # Creates a new `Spanner::BatchSnapshot` instance. + # @param grpc [::Google::Cloud::Spanner::V1::Transaction] + # Underlying `V1::Transaction` object. + # @param session [::Google::Cloud::Spanner::Session] A `Spanner::Session` reference. + # @param directed_read_options [::Hash, nil] Optional. Client options used to set + # the `directed_read_options` for all ReadRequests and ExecuteSqlRequests. + # Converts to `V1::DirectedReadOptions`. Example option: `:exclude_replicas`. + # @private + # @return [::Google::Cloud::Spanner::BatchSnapshot] def initialize grpc, session, directed_read_options: nil @grpc = grpc @session = session @@ -852,9 +859,16 @@ def self.load data, service: nil, query_options: nil from_grpc transaction_grpc, Session.from_grpc(session_grpc, service, query_options: query_options) end - ## - # @private Creates a new BatchSnapshot instance from a + # Creates a new BatchSnapshot instance from a # `Google::Cloud::Spanner::V1::Transaction`. + # @param grpc [::Google::Cloud::Spanner::V1::Transaction] + # Underlying `V1::Transaction` object. + # @param session [::Google::Cloud::Spanner::Session] A `Spanner::Session` reference. + # @param directed_read_options [::Hash, nil] Optional. Client options used to set + # the `directed_read_options` for all ReadRequests and ExecuteSqlRequests. + # Converts to `V1::DirectedReadOptions`. Example option: `:exclude_replicas`. + # @private + # @return [::Google::Cloud::Spanner::BatchSnapshot] def self.from_grpc grpc, session, directed_read_options: nil new grpc, session, directed_read_options: directed_read_options end diff --git a/google-cloud-spanner/lib/google/cloud/spanner/batch_update_results.rb b/google-cloud-spanner/lib/google/cloud/spanner/batch_update_results.rb index 6467eae6..5b24d318 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/batch_update_results.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/batch_update_results.rb @@ -19,10 +19,15 @@ module Spanner ## # @private Helper class to process BatchDML response class BatchUpdateResults - ## Object of type - # Google::Cloud::Spanner::V1::ExecuteBatchDmlResponse + # The `V1::ExecuteBatchDmlResponse` object to process + # @private + # @return [::Google::Cloud::Spanner::V1::ExecuteBatchDmlResponse] attr_reader :grpc + # Initializes the `BatchUpdateResults` helper + # @private + # @param grpc [::Google::Cloud::Spanner::V1::ExecuteBatchDmlResponse] + # The response from `ExecuteBatchDml` rpc to process in this helper. def initialize grpc @grpc = grpc end diff --git a/google-cloud-spanner/lib/google/cloud/spanner/client.rb b/google-cloud-spanner/lib/google/cloud/spanner/client.rb index cc000ef3..8175a8a1 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/client.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/client.rb @@ -2516,8 +2516,13 @@ def ensure_service! raise "Must have active connection to service" unless @project.service end - ## - # Check for valid snapshot arguments + # Checks that the options hash contains exactly one valid single-use key. + # + # @param opts [::Hash, nil] The options hash to validate. + # @private + # @raise [ArgumentError] If the hash does not contain exactly one valid + # single-use key. + # @return [void] def validate_single_use_args! opts return true if opts.nil? || opts.empty? valid_keys = %i[strong timestamp read_timestamp staleness @@ -2531,8 +2536,26 @@ def validate_single_use_args! opts "#{valid_keys}" end - ## - # Create a single-use TransactionSelector + # Creates a selector for a single-use, read-only transaction. + # + # @param opts [::Hash] Options for creating the transaction selector. + # If those are `nil` or empty, a `nil` will be returned instead of a `V1::TransactionSelector`. + # @option opts [::Boolean] :strong + # Executes a strong read. + # @option opts [::Time, ::DateTime] :read_timestamp + # Executes a read at the provided time. Alias: `:timestamp`. + # @option opts [::Numeric] :exact_staleness + # Executes a read at a time that is exactly this stale (in seconds). + # Alias: `:staleness`. + # @option opts [::Time, ::DateTime] :min_read_timestamp + # Executes a read at a time that is at least this timestamp. + # Alias: `:bounded_timestamp`. + # @option opts [::Numeric] :max_staleness + # Executes a read at a time that is at most this stale (in seconds). + # Alias: `:bounded_staleness`. + # @private + # @return [V1::TransactionSelector, nil] The transaction selector object, or + # `nil` if the `opts` hash is nil or empty. def single_use_transaction opts return nil if opts.nil? || opts.empty? diff --git a/google-cloud-spanner/lib/google/cloud/spanner/commit.rb b/google-cloud-spanner/lib/google/cloud/spanner/commit.rb index 7b4639f0..29295180 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/commit.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/commit.rb @@ -45,6 +45,7 @@ class Commit ## # @private def initialize + # @type [Array] @mutations = [] end @@ -279,7 +280,9 @@ def delete table, keys = [] keys end + # Return the current mutations added to this `Spanner::Commit` object. # @private + # @return [Array] def mutations @mutations end diff --git a/google-cloud-spanner/lib/google/cloud/spanner/commit_response.rb b/google-cloud-spanner/lib/google/cloud/spanner/commit_response.rb index c4958b0d..89c95c99 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/commit_response.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/commit_response.rb @@ -53,8 +53,11 @@ module Spanner # puts commit_resp.stats.mutation_count # class CommitResponse - ## - # @private Creates a new CommitResponse instance. + # Creates a new CommitResponse instance. + # + # @param grpc [::Google::Cloud::Spanner::V1::CommitResponse] + # Underlying `V1::CommitResponse` object. + # @private def initialize grpc @grpc = grpc end @@ -74,10 +77,13 @@ def stats CommitStats.from_grpc @grpc.commit_stats if @grpc.commit_stats end - ## - # @private - # Creates a new Commit responsee instance from a + # Creates a new `Spanner::CommitResponse` instance from a # `Google::Cloud::Spanner::V1::CommitResponse`. + # + # @param grpc [::Google::Cloud::Spanner::V1::CommitResponse] + # Underlying `V1::CommitResponse` object. + # @private + # @return [::Google::Cloud::Spanner::CommitResponse] def self.from_grpc grpc new grpc end diff --git a/google-cloud-spanner/lib/google/cloud/spanner/instance.rb b/google-cloud-spanner/lib/google/cloud/spanner/instance.rb index 8cb5204b..77dee547 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/instance.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/instance.rb @@ -81,7 +81,7 @@ class Instance # Creates a new `Spanner::Instance` instance. # @param grpc [::Google::Cloud::Spanner::Admin::Instance::V1::Instance] - # The protobuf `V1::Instance` underlying object. + # Underlying `V1::Instance` object. # @param service [::Google::Cloud::Spanner::Service] A `Spanner::Service` reference. # @private def initialize grpc, service @@ -965,7 +965,7 @@ def test_permissions *permissions # Creates a new Instance instance from a # `Google::Cloud::Spanner::Admin::Instance::V1::Instance`. # @param grpc [::Google::Cloud::Spanner::Admin::Instance::V1::Instance] - # The protobuf `V1::Instance` underlying object. + # Underlying `V1::Instance` object. # @param service [::Google::Cloud::Spanner::Service] A `Spanner::Service` reference. # @private # @return [::Google::Cloud::Spanner::Instance] diff --git a/google-cloud-spanner/lib/google/cloud/spanner/project.rb b/google-cloud-spanner/lib/google/cloud/spanner/project.rb index 1b57a360..3ce06e53 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/project.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/project.rb @@ -567,7 +567,7 @@ def create_database instance_id, database_id, statements: [], # Spanner will wait for a replica in the list to become available, # requests may fail due to DEADLINE_EXCEEDED errors. # - # @return [Client] The newly created client. + # @return [::Google::Cloud::Spanner::Client] The newly created client. # # @example # require "google/cloud/spanner" @@ -649,7 +649,7 @@ def client instance_id, database_id, pool: {}, labels: nil, # Spanner will wait for a replica in the list to become available, # requests may fail due to DEADLINE_EXCEEDED errors. # - # @return [Client] The newly created client. + # @return [::Google::Cloud::Spanner::BatchClient] The newly created client. # # @example # require "google/cloud/spanner" diff --git a/google-cloud-spanner/lib/google/cloud/spanner/results.rb b/google-cloud-spanner/lib/google/cloud/spanner/results.rb index 02367277..a3a7345f 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/results.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/results.rb @@ -45,18 +45,12 @@ module Spanner # end # class Results - # The `V1::ResultSetMetadata` protobuf object from the first - # PartialResultSet. - # @private - # @return [::Google::Cloud::Spanner::V1::ResultSetMetadata] - attr_reader :metadata - # Creates a new Results instance. # @param service [::Google::Cloud::Spanner::Service] The `Spanner::Service` reference. # @param partial_result_sets [::Enumerable<::Google::Cloud::Spanner::V1::PartialResultSet>] # Raw enumerable from grpc `StreamingRead` call. # @param session_name [::String] Required. - # The session in which the transaction to be committed is running. + # The name of the session for the operation that created these Results. # Values are of the form: # `projects//instances//databases//sessions/`. # @param metadata [::Google::Cloud::Spanner::V1::ResultSetMetadata] ParialResultSet metadata object @@ -71,6 +65,12 @@ def initialize service, partial_result_sets, session_name, metadata, stats @stats = stats end + # The `V1::ResultSetMetadata` protobuf object from the first + # PartialResultSet. + # @private + # @return [::Google::Cloud::Spanner::V1::ResultSetMetadata] + attr_reader :metadata + ## # The read timestamp chosen for single-use snapshots (read-only # transactions). @@ -162,7 +162,7 @@ def rows should_retry_request = false end - # @type [::Google::Cloud::Spanner::V1::PartialResultsSet] + # @type [::Google::Cloud::Spanner::V1::PartialResultSet] grpc = @partial_result_sets.next # metadata should be set before the first iteration... @@ -327,7 +327,7 @@ def row_count_exact? # Raw enumerable from underlying grpc call. # @param service [::Google::Cloud::Spanner::Service] The `Spanner::Service` reference. # @param session_name [::String] Required. - # The session in which the transaction to be committed is running. + # The name of the session for the operation that created these Results. # Values are of the form: # `projects//instances//databases//sessions/`. # @private diff --git a/google-cloud-spanner/lib/google/cloud/spanner/service.rb b/google-cloud-spanner/lib/google/cloud/spanner/service.rb index 5c64ac64..c4397fff 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/service.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/service.rb @@ -352,17 +352,17 @@ def get_session session_name, call_options: nil # @param database_name [::String] The full name of the database. # @param labels [::Hash, nil] Optional. The labels to be applied to all sessions # created by the client. Example: `"team" => "billing-service"`. - # @param call_options [::Hash, nil] Optional. A hash of values to specify the custom - # call options. Example option `:timeout`. # @param database_role [::String, nil] Optional. The Spanner session creator role. # Example: `analyst`. # @param multiplexed [::Boolean] Optional. Default to `false`. # If `true`, specifies a multiplexed session. + # @param call_options [::Hash, nil] Optional. A hash of values to specify the custom + # call options. Example option `:timeout`. # @return [::Google::Cloud::Spanner::V1::Session] # @private def create_session database_name, labels: nil, - call_options: nil, database_role: nil, - multiplexed: false + database_role: nil, multiplexed: false, + call_options: nil route_to_leader = LARHeaders.create_session opts = default_options( session_name: database_name, @@ -617,13 +617,18 @@ def rollback session_name, transaction_id, call_options: nil # @param route_to_leader [::String, nil] Optional. The value to be sent # as `x-goog-spanner-route-to-leader` header for leader aware routing. # Expected values: `"true"` or `"false"`. + # @param mutation_key [::Google::Cloud::Spanner::V1::Mutation, nil] Optional. + # If a read-write transaction on a multiplexed session commit mutations + # without performing any reads or queries, one of the mutations from the mutation set + # must be sent as a mutation key for `BeginTransaction`. # @private # @return [::Google::Cloud::Spanner::V1::Transaction] def begin_transaction session_name, exclude_txn_from_change_streams: false, request_options: nil, call_options: nil, - route_to_leader: nil + route_to_leader: nil, + mutation_key: nil tx_opts = V1::TransactionOptions.new( read_write: V1::TransactionOptions::ReadWrite.new, exclude_txn_from_change_streams: exclude_txn_from_change_streams @@ -634,7 +639,8 @@ def begin_transaction session_name, request = { session: session_name, options: tx_opts, - request_options: request_options + request_options: request_options, + mutation_key: mutation_key } service.begin_transaction request, opts end @@ -657,6 +663,28 @@ def batch_write session_name, service.batch_write request, opts end + # Creates a specialized `V1::Transaction` object. Reads on that object will have + # at most one of following consistency properties: + # * reading all previously commited transactions + # * reading all data from a given timestamp + # * reading all data from a timestamp that is exactly a given value old + # (the last one sidesteps worries of client-server time skew). + # + # Having at _least_ one of those is not enforced so this can create normal transactions + # as well. + # Created transactions will include the the read timestamp chosen for the transaction. + # @param session_name [::String] Required. + # Required. The session in which the snapshot transaction is to be created.. + # Values are of the form: + # `projects//instances//databases//sessions/`. + # @param strong [::Boolean, nil] Optional. + # Whether this transaction should have strong consistency. + # @param timestamp [::String, ::Date ::Time, nil] Optional. + # Timestamp that the reads should be executed at. Reads are repeatable with this option. + # @param staleness [::Numeric, nil] Optional. + # The offset of staleness that the reads should be executed at. + # @param call_options [::Hash, nil] Optional. A hash of values to specify the custom + # call options. Example option `:timeout`. def create_snapshot session_name, strong: nil, timestamp: nil, staleness: nil, call_options: nil tx_opts = V1::TransactionOptions.new( @@ -829,6 +857,21 @@ def lib_name_with_prefix value << " gccl" end + # Creates new `Gapic::CallOptions` from typical parameters for Spanner RPC calls. + # + # @param session_name [::String, nil] Optional. + # The session name. Used to extract the routing header. The value will be + # used to send the old `google-cloud-resource-prefix` routing header. + # Expected values are of the form: + # `projects//instances//databases//sessions/`. + # If nil is specified nothing will be sent. + # @param call_options [::Hash, nil] Optional. A hash of values to specify the custom + # call options. Example option `:timeout`. + # @param route_to_leader [::String, nil] Optional. The value to be sent + # as `x-goog-spanner-route-to-leader` header for leader aware routing. + # Expected values: `"true"` or `"false"`. If nil is specified nothing will be sent. + # @private + # @return [::Gapic::CallOptions] def default_options session_name: nil, call_options: nil, route_to_leader: nil opts = {} metadata = {} diff --git a/google-cloud-spanner/lib/google/cloud/spanner/session.rb b/google-cloud-spanner/lib/google/cloud/spanner/session.rb index c1f6fbef..e832294c 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/session.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/session.rb @@ -44,8 +44,8 @@ module Spanner # class Session # The wrapped `V1::Session` protobuf session object. - # @return [::Google::Cloud::Spanner::V1::Session] # @private + # @return [::Google::Cloud::Spanner::V1::Session] attr_accessor :grpc # The `Spanner::Service` object. @@ -59,7 +59,7 @@ class Session # @return [::Hash, nil] attr_accessor :query_options - # Creates a new Session instance. + # Creates a new `Spanner::Session` instance. # @param grpc [::Google::Cloud::Spanner::V1::Session] Underlying `V1::Session` object. # @param service [::Google::Cloud::Spanner::Service] A `Spanner::Service` object. # @param query_options [::Hash, nil] Optional. A hash of values to specify the custom @@ -428,8 +428,8 @@ def execute_query sql, params: nil, types: nil, transaction: nil, # number of rows that were modified for each successful statement # before the error. # - # @return [Array] A list with the exact number of rows that - # were modified for each DML statement. + # @return [::Google::Cloud::Spanner::V1::ExecuteBatchDmlResponse] + # An unwrapped result of the service call -- a `V1::ExecuteBatchDmlResponse` object. # def batch_update transaction, seqno, request_options: nil, call_options: nil @@ -1369,9 +1369,15 @@ def rollback transaction_id true end - ## + # Explicitly begins a new transaction and creates a server-side transaction object. + # Unlike {#create_empty_transaction}, this method makes an immediate + # `BeginTransaction` RPC call. + # + # @param exclude_txn_from_change_streams [::Boolean] Optional. Defaults to `false`. + # When `exclude_txn_from_change_streams` is set to `true`, it prevents read + # or write transactions from being tracked in change streams. # @private - # Creates a new transaction object every time. + # @return [::Google::Cloud::Spanner::Transaction] def create_transaction exclude_txn_from_change_streams: false route_to_leader = LARHeaders.begin_transaction true tx_grpc = service.begin_transaction path, @@ -1380,10 +1386,15 @@ def create_transaction exclude_txn_from_change_streams: false Transaction.from_grpc tx_grpc, self, exclude_txn_from_change_streams: exclude_txn_from_change_streams end - ## + # Creates a new empty transaction wrapper without a server-side object. + # This is used for inline-begin transactions and does not make an RPC call. + # See {#create_transaction} for the RPC-based method. + # + # @param exclude_txn_from_change_streams [::Boolean] Optional. Defaults to `false`. + # When `exclude_txn_from_change_streams` is set to `true`, it prevents read + # or write transactions from being tracked in change streams. # @private - # Creates a new transaction object without the grpc object - # within it. Use it for inline-begin of a transaction. + # @return [::Google::Cloud::Spanner::Transaction] The new *empty* transaction object. def create_empty_transaction exclude_txn_from_change_streams: false Transaction.from_grpc nil, self, exclude_txn_from_change_streams: exclude_txn_from_change_streams end @@ -1433,7 +1444,8 @@ def release! end # Determines if the session has been idle longer than the given - # duration. + # duration in seconds. + # # @param duration_sec [::Numeric] interval in seconds # @private # @return [::Boolean] @@ -1442,7 +1454,7 @@ def idle_since? duration_sec Process.clock_gettime(Process::CLOCK_MONOTONIC) > @last_updated_at + duration_sec end - # Creates a new Session instance from a `V1::Session`. + # Creates a new `Spanner::Session` instance from a `V1::Session` object. # @param grpc [::Google::Cloud::Spanner::V1::Session] Underlying `V1::Session` object. # @param service [::Google::Cloud::Spanner::Service] A `Spanner::Service` ref. # @param query_options [::Hash, nil] Optional. A hash of values to specify the custom diff --git a/google-cloud-spanner/lib/google/cloud/spanner/snapshot.rb b/google-cloud-spanner/lib/google/cloud/spanner/snapshot.rb index b9572000..efdee45e 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/snapshot.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/snapshot.rb @@ -41,7 +41,9 @@ module Spanner # end # class Snapshot - # @private The Session object. + # A `V1::Session` reference. + # @private + # @return [::Google::Cloud::Spanner::V1::Session] attr_accessor :session ## diff --git a/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb b/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb index 8cd20031..a1470c95 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb @@ -1167,9 +1167,9 @@ def idle_since? duration session.idle_since? duration end - ## - # @private # All of the mutations created in the transaction block. + # @private + # @return [Array] def mutations @commit.mutations end From 7f9466f165ec1c11f937d018eb86d6f1bb0db653 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 28 Oct 2025 11:33:15 -0700 Subject: [PATCH 02/25] docs: update `validate_single_use_args!` in Client --- .../lib/google/cloud/spanner/client.rb | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/google-cloud-spanner/lib/google/cloud/spanner/client.rb b/google-cloud-spanner/lib/google/cloud/spanner/client.rb index 8175a8a1..c6518d5d 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/client.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/client.rb @@ -2524,16 +2524,21 @@ def ensure_service! # single-use key. # @return [void] def validate_single_use_args! opts - return true if opts.nil? || opts.empty? - valid_keys = %i[strong timestamp read_timestamp staleness - exact_staleness bounded_timestamp - min_read_timestamp bounded_staleness max_staleness] - if opts.keys.count == 1 && valid_keys.include?(opts.keys.first) - return true - end + # An empty options hash is valid. + return if opts.nil? || opts.empty? + + keys = opts.keys + + valid_keys = Set.new(%i[ + strong timestamp read_timestamp staleness exact_staleness + bounded_timestamp min_read_timestamp bounded_staleness max_staleness + ]).freeze + + # Raise an error unless there is exactly one key and it's in the valid set. + return if keys.length == 1 && valid_keys.include?(keys.first) raise ArgumentError, - "Must provide only one of the following single_use values: " \ - "#{valid_keys}" + "Options must contain exactly one of the following keys: " \ + "#{valid_keys.to_a.join ', '}" end # Creates a selector for a single-use, read-only transaction. From 569c3776070552a91aedc86b85a4a37a20c79e7d Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 28 Oct 2025 11:34:27 -0700 Subject: [PATCH 03/25] chore: delete unused methods from `transaction` --- .../lib/google/cloud/spanner/transaction.rb | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb b/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb index a1470c95..dad738ce 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb @@ -1143,30 +1143,6 @@ def commit_timestamp ColumnValue.commit_timestamp end - ## - # @private - # Keeps the transaction current by creating a new transaction. - def keepalive! - ensure_session! - @grpc = session.create_transaction.instance_variable_get :@grpc - end - - ## - # @private - # Permanently deletes the transaction and session. - def release! - ensure_session! - session.release! - end - - ## - # @private - # Determines if the transaction has been idle longer than the given - # duration. - def idle_since? duration - session.idle_since? duration - end - # All of the mutations created in the transaction block. # @private # @return [Array] From a6fd67a4c8c48713c54f2221a6d80088f0277e79 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 28 Oct 2025 11:38:04 -0700 Subject: [PATCH 04/25] feat: add SessionCreationOptions, (Multiplexed) SessionCache, keeping track of session creation time. --- .../lib/google/cloud/spanner/session.rb | 18 +++ .../lib/google/cloud/spanner/session_cache.rb | 125 ++++++++++++++++++ .../cloud/spanner/session_creation_options.rb | 70 ++++++++++ .../cloud/spanner/session_cache_test.rb | 113 ++++++++++++++++ 4 files changed, 326 insertions(+) create mode 100644 google-cloud-spanner/lib/google/cloud/spanner/session_cache.rb create mode 100644 google-cloud-spanner/lib/google/cloud/spanner/session_creation_options.rb create mode 100644 google-cloud-spanner/test/google/cloud/spanner/session_cache_test.rb diff --git a/google-cloud-spanner/lib/google/cloud/spanner/session.rb b/google-cloud-spanner/lib/google/cloud/spanner/session.rb index e832294c..40ad0aef 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/session.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/session.rb @@ -69,6 +69,7 @@ def initialize grpc, service, query_options: nil @grpc = grpc @service = service @query_options = query_options + @created_time = Process.clock_gettime Process::CLOCK_MONOTONIC end # The unique identifier for the project. @@ -1454,6 +1455,16 @@ def idle_since? duration_sec Process.clock_gettime(Process::CLOCK_MONOTONIC) > @last_updated_at + duration_sec end + # Determines if the session did exist for at least the given + # duration in seconds. + # + # @param duration_sec [::Numeric] interval in seconds + # @private + # @return [::Boolean] + def existed_since? duration_sec + Process.clock_gettime(Process::CLOCK_MONOTONIC) > @created_time + duration_sec + end + # Creates a new `Spanner::Session` instance from a `V1::Session` object. # @param grpc [::Google::Cloud::Spanner::V1::Session] Underlying `V1::Session` object. # @param service [::Google::Cloud::Spanner::Service] A `Spanner::Service` ref. @@ -1473,6 +1484,13 @@ def session protected + # Whether this session is multiplexed. + # @private + # @return [::Boolean] + def multiplexed? + @grpc.multiplexed + end + ## # @private Raise an error unless an active connection to the service is # available. diff --git a/google-cloud-spanner/lib/google/cloud/spanner/session_cache.rb b/google-cloud-spanner/lib/google/cloud/spanner/session_cache.rb new file mode 100644 index 00000000..af27340e --- /dev/null +++ b/google-cloud-spanner/lib/google/cloud/spanner/session_cache.rb @@ -0,0 +1,125 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +require "concurrent" +require "google/cloud/spanner/session" +require "google/cloud/spanner/session_creation_options" + +module Google + module Cloud + module Spanner + # Cache for the multiplex `{Google::Cloud::Spanner::Session}` instance. + # @private + class SessionCache + # Time in seconds before this SessionCache will refresh the session. + # Counted from the session creation time (not from last usage). + # This is specific to multiplex sessions. + # The backend can keep sessions alive for quite a bit longer (28 days) but + # we perform refresh after 7 days. + # @private + SESSION_REFRESH_SEC = 7 * 24 * 3600 + + # Create a single-session "cache" for multiplex sessions. + # @param service [::Google::Cloud::Spanner::Service] A `Spanner::Service` reference. + # @param session_creation_options [::Google::Cloud::Spanner::SessionCreationOptions] Required. + # @private + def initialize service, session_creation_options + @service = service + @session_creation_options = session_creation_options + @mutex = Mutex.new + @session = nil + end + + # Yields the current session to run an operation (or a series of operations) on. + # @yield session A session to run requests on + # @yieldparam session [::Google::Cloud::Spanner::Session] + # @private + # @yieldreturn [::Object] The result of the operation that ran on a session. + # @return [::Object] The value returned by the yielded block. + def with_session + ensure_session! + yield @session + end + + # Re-initializes the session in the session cache. + # @private + # @return [::Boolean] + def reset! + @mutex.synchronize do + @session = create_new_session + end + + true + end + + # Closes the pool. This is a NOP for Multiplex Session Cache since + # multiplex sessions don't require cleanup. + # @private + # @return [::Boolean] + def close + true + end + + # Returns the current session. For use in the `{Spanner::BatchClient}` + # where usage pattern is incompatible with `with_session`. + # For other uses please use `with_session` instead. + # @private + # @return [::Google::Cloud::Spanner::Session] + def session + ensure_session! + @session + end + + private + + # Ensures that a single session exists and is current. + # @private + # @return [nil] + def ensure_session! + return unless @session.nil? || @session.existed_since?(SESSION_REFRESH_SEC) + + @mutex.synchronize do + return unless @session.nil? || @session.existed_since?(SESSION_REFRESH_SEC) + @session = create_new_session + end + + nil + end + + # Creates a new multiplexed `Spanner::Session`. + # @private + # @return [::Google::Cloud::Spanner::Session] + def create_new_session + ensure_service! + grpc = @service.create_session( + @session_creation_options.database_path, + labels: @session_creation_options.session_labels, + database_role: @session_creation_options.session_creator_role, + multiplexed: true + ) + + Session.from_grpc grpc, @service, query_options: @session_creation_options.query_options + end + + # Raise an error unless an active connection to the service is available. + # @private + # @raise [::StandardError] + # @return [void] + def ensure_service! + raise "Must have active connection to service" unless @service + end + end + end + end +end diff --git a/google-cloud-spanner/lib/google/cloud/spanner/session_creation_options.rb b/google-cloud-spanner/lib/google/cloud/spanner/session_creation_options.rb new file mode 100644 index 00000000..de4381d8 --- /dev/null +++ b/google-cloud-spanner/lib/google/cloud/spanner/session_creation_options.rb @@ -0,0 +1,70 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +module Google + module Cloud + module Spanner + # Options for creating new sessions that clients use + # to parametrize Pool and SessionCache. + # Example: session labels. + # @private + class SessionCreationOptions + # The full path to the Spanner database. Values are of the form: + # `projects//instances//databases/. + # @private + # @return [::String] + attr_reader :database_path + + # The labels to be applied to all sessions created by the client. + # Optional. Example: `"team" => "billing-service"`. + # @private + # @return [::Hash, nil] + attr_reader :session_labels + + # The Spanner session creator role. + # Optional. Example: `analyst`. + # @return [::String, nil] + attr_reader :session_creator_role + + # A hash of values to specify the custom query options for executing SQL query. + # Optional. Example option: `:optimizer_version`. + # @private + # @return [::Hash, nil] + attr_reader :query_options + + # Creates a new SessionCreationOptions object. + # @param database_path [::String] + # The full path to the Spanner database. Values are of the form: + # `projects//instances//databases/. + # @param session_labels [::Hash, nil] Optional. The labels to be applied to all sessions + # created by the client. Example: `"team" => "billing-service"`. + # @param session_creator_role [::String, nil] Optional. The Spanner session creator role. + # Example: `analyst`. + # @param query_options [::Hash, nil] Optional. A hash of values to specify the custom + # query options for executing SQL query. Example option: `:optimizer_version`. + # @private + def initialize database_path:, session_labels: nil, session_creator_role: nil, query_options: nil + if database_path.nil? || database_path.empty? + raise ArgumentError, "database_path is required for session creation options" + end + + @database_path = database_path + @session_labels = session_labels + @session_creator_role = session_creator_role + @query_options = query_options + end + end + end + end +end diff --git a/google-cloud-spanner/test/google/cloud/spanner/session_cache_test.rb b/google-cloud-spanner/test/google/cloud/spanner/session_cache_test.rb new file mode 100644 index 00000000..88c4a812 --- /dev/null +++ b/google-cloud-spanner/test/google/cloud/spanner/session_cache_test.rb @@ -0,0 +1,113 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +require "helper" + +describe Google::Cloud::Spanner::SessionCache, :mock_spanner do + let(:instance_id) { "my-instance-id" } + let(:database_id) { "my-database-id" } + let(:session_id) { "session1" } + let(:session_grpc) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id), multiplexed: true } + let(:new_session_grpc) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, "new-session"), multiplexed: true } + let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } + let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } + let(:session_creation_options) { ::Google::Cloud::Spanner::SessionCreationOptions.new database_path: database_path(instance_id, database_id) } + + it "creates a new session with with_session if one does not exist" do + mock = Minitest::Mock.new + mock.expect :create_session, new_session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request}, default_options] + spanner.service.mocked_service = mock + + cache = Google::Cloud::Spanner::SessionCache.new spanner.service, session_creation_options + _(cache.instance_variable_get :@session).must_be_nil + + cache.with_session do |yielded_session| + _(yielded_session.session_id).must_equal "new-session" + end + + mock.verify + end + + it "creates a new session with #session if one does not exist" do + mock = Minitest::Mock.new + mock.expect :create_session, new_session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request}, default_options] + spanner.service.mocked_service = mock + + cache = Google::Cloud::Spanner::SessionCache.new spanner.service, session_creation_options + _(cache.instance_variable_get :@session).must_be_nil + + new_session = cache.session + + mock.verify + _(new_session.session_id).must_equal "new-session" + end + + it "uses the existing session with with_session if it is not old" do + mock = Minitest::Mock.new + spanner.service.mocked_service = mock # No calls expected + + cache = Google::Cloud::Spanner::SessionCache.new spanner.service, session_creation_options + cache.instance_variable_set :@session, session + + cache.with_session do |yielded_session| + _(yielded_session.session_id).must_equal session_id + end + + mock.verify + end + + it "creates a new session with with_session if the existing one is old" do + mock = Minitest::Mock.new + mock.expect :create_session, new_session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + spanner.service.mocked_service = mock + + # Make session seem old + old_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - Google::Cloud::Spanner::SessionCache::SESSION_REFRESH_SEC - 1 + session.instance_variable_set :@created_time, old_time + + cache = Google::Cloud::Spanner::SessionCache.new spanner.service, session_creation_options + cache.instance_variable_set :@session, session + + yielded_session = nil + cache.with_session do |yielded_session| + _(yielded_session.session_id).must_equal "new-session" + end + + mock.verify + end + + it "resets the session" do + mock = Minitest::Mock.new + mock.expect :create_session, new_session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request}, default_options] + spanner.service.mocked_service = mock + + cache = Google::Cloud::Spanner::SessionCache.new spanner.service, session_creation_options + cache.instance_variable_set :@session, session + _(cache.reset!).must_equal true + cache.with_session do |yielded_session| + _(yielded_session.session_id).must_equal "new-session" + end + + mock.verify + end + + it "close is a no-op" do + mock = Minitest::Mock.new + spanner.service.mocked_service = mock + + cache = Google::Cloud::Spanner::SessionCache.new spanner.service, session_creation_options + _(cache.close).must_equal true + mock.verify + end +end From 0a0e57182db4e5216e0521bc2d55dc8790626dbe Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 28 Oct 2025 11:48:54 -0700 Subject: [PATCH 05/25] chore: move session creation from Client into Pool and make Pool depend on Service instead of Client. --- .../lib/google/cloud/spanner/client.rb | 50 ------------- .../lib/google/cloud/spanner/pool.rb | 71 +++++++++++++++++-- 2 files changed, 65 insertions(+), 56 deletions(-) diff --git a/google-cloud-spanner/lib/google/cloud/spanner/client.rb b/google-cloud-spanner/lib/google/cloud/spanner/client.rb index c6518d5d..f2fb9f15 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/client.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/client.rb @@ -2446,56 +2446,6 @@ def reset @pool.reset end - # Creates a new Session objece. - # @param multiplexed [::Boolean] Optional. Default to `false`. - # If `true`, specifies a multiplexed session. - # @private - # @return [::Google::Cloud::Spanner::Session] - def create_new_session multiplexed: false - ensure_service! - grpc = @project.service.create_session \ - Admin::Database::V1::DatabaseAdmin::Paths.database_path( - project: project_id, instance: instance_id, database: database_id - ), - labels: @session_labels, - database_role: @database_role, - multiplexed: multiplexed - - Session.from_grpc grpc, @project.service, query_options: @query_options - end - - ## - # @private - # Creates a batch of new session objects of size `total`. - # Makes multiple RPCs if necessary. Returns empty array if total is 0. - def batch_create_new_sessions total - sessions = [] - remaining = total - while remaining.positive? - sessions += batch_create_sessions remaining - remaining = total - sessions.count - end - sessions - end - - ## - # @private - # The response may have fewer sessions than requested in the RPC. - # - def batch_create_sessions session_count - ensure_service! - resp = @project.service.batch_create_sessions \ - Admin::Database::V1::DatabaseAdmin::Paths.database_path( - project: project_id, instance: instance_id, database: database_id - ), - session_count, - labels: @session_labels, - database_role: @database_role - resp.session.map do |grpc| - Session.from_grpc grpc, @project.service, query_options: @query_options - end - end - # @private def to_s "(project_id: #{project_id}, instance_id: #{instance_id}, " \ diff --git a/google-cloud-spanner/lib/google/cloud/spanner/pool.rb b/google-cloud-spanner/lib/google/cloud/spanner/pool.rb index 4f57ebf3..5f11f5d7 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/pool.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/pool.rb @@ -16,6 +16,7 @@ require "concurrent" require "google/cloud/spanner/errors" require "google/cloud/spanner/session" +require "google/cloud/spanner/session_creation_options" module Google module Cloud @@ -37,7 +38,9 @@ class Pool attr_accessor :sessions_in_use # Creates a new Session pool that manages non-multiplexed sessions. - # @param client [::Google::Cloud::Spanner::Client] A `Spanner::Client` reference + # @param service [::Google::Cloud::Spanner::Service] A `Spanner::Service` reference. + # @param session_creation_options [::Google::Cloud::Spanner::SessionCreationOptions] Required. + # Options used for session creation. E.g. session labels. # @param min [::Integer] Min number of sessions to keep # @param max [::Integer] Max number of sessions to keep # @param keepalive [::Numeric] How long after their last usage the sessions can be reclaimed @@ -46,9 +49,10 @@ class Pool # @param threads [::Integer, nil] Number of threads in the thread pool that is used for keepalive and # release session actions. If `nil` the Pool will choose a reasonable default. # @private - def initialize client, min: 10, max: 100, keepalive: 1800, + def initialize service, session_creation_options, min: 10, max: 100, keepalive: 1800, fail: true, threads: nil - @client = client + @service = service + @session_creation_options = session_creation_options @min = min @max = max @keepalive = keepalive @@ -127,7 +131,7 @@ def checkin_session session nil end - def reset + def reset! close init @@ -137,6 +141,7 @@ def reset true end + alias reset reset! def close shutdown @@ -181,7 +186,7 @@ def init # init the keepalive task create_keepalive_task! # init session stack - @sessions_available = @client.batch_create_new_sessions @min + @sessions_available = batch_create_new_sessions @min @sessions_in_use = {} end @@ -209,7 +214,7 @@ def new_session! end begin - session = @client.create_new_session + session = create_new_session rescue StandardError => e @mutex.synchronize do @new_sessions_in_process -= 1 @@ -239,6 +244,60 @@ def create_keepalive_task! def future(&) Concurrent::Future.new(executor: @thread_pool, &).execute end + + # Creates a new `Spanner::Session`. + # @private + # @return [::Google::Cloud::Spanner::Session] + def create_new_session + ensure_service! + grpc = @service.create_session( + @session_creation_options.database_path, + labels: @session_creation_options.session_labels, + database_role: @session_creation_options.session_creator_role + ) + + Session.from_grpc grpc, @service, query_options: @query_options + end + + # Creates a batch of new session objects of size `total`. + # Makes multiple RPCs if necessary. Returns empty array if total is 0. + # @private + # @return [::Array<::Google::Cloud::Spanner::Session>] + def batch_create_new_sessions total + sessions = [] + remaining = total + while remaining.positive? + sessions += batch_create_sessions remaining + remaining = total - sessions.count + end + sessions + end + + # Tries to creates a batch of new session objects of size `session_count`. + # The response may have fewer sessions than requested in the RPC. + # @private + # @return [::Array<::Google::Cloud::Spanner::Session>] + def batch_create_sessions session_count + ensure_service! + resp = @service.batch_create_sessions( + @session_creation_options.database_path, + session_count, + labels: @session_creation_options.session_labels, + database_role: @session_creation_options.session_creator_role + ) + + resp.session.map do |grpc| + Session.from_grpc grpc, @service, query_options: @query_options + end + end + + # Raise an error unless an active connection to the service is available. + # @private + # @raise [::StandardError] + # @return [void] + def ensure_service! + raise "Must have active connection to service" unless @service + end end end end From 4c472b83cd1673c5f68fdf1f883fdfd5b676ce53 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 28 Oct 2025 11:51:44 -0700 Subject: [PATCH 06/25] feat: Use SessionCache in both Client and BatchClient, deprecate pool options. --- .../lib/google/cloud/spanner/batch_client.rb | 19 ++++++++----- .../lib/google/cloud/spanner/client.rb | 26 ++++++++++++++---- .../lib/google/cloud/spanner/project.rb | 27 +++++-------------- 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/google-cloud-spanner/lib/google/cloud/spanner/batch_client.rb b/google-cloud-spanner/lib/google/cloud/spanner/batch_client.rb index 4613b31b..9b3730ad 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/batch_client.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/batch_client.rb @@ -16,6 +16,8 @@ require "google/cloud/spanner/errors" require "google/cloud/spanner/project" require "google/cloud/spanner/session" +require "google/cloud/spanner/session_cache" +require "google/cloud/spanner/session_creation_options" require "google/cloud/spanner/batch_snapshot" module Google @@ -83,6 +85,16 @@ def initialize project, instance_id, database_id, session_labels: nil, @session_labels = session_labels @query_options = query_options @directed_read_options = directed_read_options + + session_creation_options = SessionCreationOptions.new( + database_path: Admin::Database::V1::DatabaseAdmin::Paths.database_path( + project: @project.service.project, instance: instance_id, database: database_id + ), + session_labels: @session_labels, + query_options: @query_options + ) + + @session_cache = SessionCache.new @project.service, session_creation_options end # The unique identifier for the project. @@ -430,12 +442,7 @@ def ensure_service! # @return [::Google::Cloud::Spanner::Session] def session ensure_service! - grpc = @project.service.create_session \ - V1::Spanner::Paths.database_path( - project: project_id, instance: instance_id, database: database_id - ), - labels: @session_labels - Session.from_grpc grpc, @project.service, query_options: @query_options + @session_cache.session end ## diff --git a/google-cloud-spanner/lib/google/cloud/spanner/client.rb b/google-cloud-spanner/lib/google/cloud/spanner/client.rb index f2fb9f15..2009352d 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/client.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/client.rb @@ -16,7 +16,8 @@ require "google/cloud/spanner/errors" require "google/cloud/spanner/project" require "google/cloud/spanner/data" -require "google/cloud/spanner/pool" +require "google/cloud/spanner/session_cache" +require "google/cloud/spanner/session_creation_options" require "google/cloud/spanner/session" require "google/cloud/spanner/transaction" require "google/cloud/spanner/snapshot" @@ -55,14 +56,17 @@ class Client # @private IS_TRANSACTION_RUNNING_KEY = "ruby_spanner_is_transaction_running".freeze + # rubocop:disable Lint/UnusedMethodArgument + # Creates a new Spanner Client instance. # @param project [::Google::Cloud::Spanner::Project] A `Spanner::Project` ref. # @param instance_id [::String] Instance id, e.g. `"my-instance"`. # @param database_id [::String] Database id, e.g. `"my-database"`. # @param session_labels [::Hash, nil] Optional. The labels to be applied to all sessions # created by the client. Example: `"team" => "billing-service"`. - # @param pool_opts [::Hash] Optional. `Spanner::Pool` creation options. - # Example parameter: `:keepalive`. + # @param pool_opts [::Hash] Optional. Defaults to `{}`. Deprecated. + # @deprecated This parameter is non-functional since the multiplexed SessionCache does not require + # pool options. # @param query_options [::Hash, nil] Optional. A hash of values to specify the custom # query options for executing SQL query. Example parameter `:optimizer_version`. # @param database_role [::String, nil] Optional. The Spanner session creator role. @@ -79,11 +83,23 @@ def initialize project, instance_id, database_id, session_labels: nil, @database_id = database_id @database_role = database_role @session_labels = session_labels - @directed_read_options = directed_read_options - @pool = Pool.new self, **pool_opts @query_options = query_options + @directed_read_options = directed_read_options + + session_creation_options = SessionCreationOptions.new( + database_path: Admin::Database::V1::DatabaseAdmin::Paths.database_path( + project: @project.service.project, instance: instance_id, database: database_id + ), + session_labels: @session_labels, + session_creator_role: @database_role, + query_options: @query_options + ) + + @pool = SessionCache.new @project.service, session_creation_options end + # rubocop:enable Lint/UnusedMethodArgument + # The unique identifier for the project. # @return [String] def project_id diff --git a/google-cloud-spanner/lib/google/cloud/spanner/project.rb b/google-cloud-spanner/lib/google/cloud/spanner/project.rb index 3ce06e53..11b37f0e 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/project.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/project.rb @@ -501,6 +501,8 @@ def create_database instance_id, database_id, statements: [], Database::Job.from_grpc grpc, service end + # rubocop:disable Lint/UnusedMethodArgument + ## # Creates a Cloud Spanner client. A client is used to read and/or modify # data in a Cloud Spanner database. @@ -509,25 +511,9 @@ def create_database instance_id, database_id, statements: [], # Required. # @param [String] database_id The unique identifier for the database. # Required. - # @param [Hash] pool Settings to control how and when sessions are - # managed by the client. The following settings can be provided: - # - # * `:min` (Integer) Minimum number of sessions that the client will - # maintain at any point in time. The default is 10. - # * `:max` (Integer) Maximum number of sessions that the client will - # have at any point in time. The default is 100. - # * `:keepalive` (Numeric) The amount of time a session can be idle - # before an attempt is made to prevent the idle sessions from being - # closed by the Cloud Spanner service. The default is 1800 (30 - # minutes). - # * `:fail` (true/false) When `true` the client raises a - # {SessionLimitError} when the client has allocated the `max` number - # of sessions. When `false` the client blocks until a session - # becomes available. The default is `true`. - # * `:threads` (Integer) The number of threads in the thread pool. The - # default is twice the number of available CPUs. - # * `:write_ratio` (Float) Deprecated. This field is no longer needed - # and will be removed in a future release. + # @param [Hash] pool Optional. Defaults to `{}`. Deprecated. + # @deprecated This parameter is non-functional since the multiplexed SessionCache does not require + # pool options. # @param [Hash] labels The labels to be applied to all sessions # created by the client. Cloud Labels are a flexible and lightweight # mechanism for organizing cloud resources into groups that reflect a @@ -596,12 +582,13 @@ def client instance_id, database_id, pool: {}, labels: nil, end Client.new self, instance_id, database_id, session_labels: labels, - pool_opts: valid_session_pool_options(pool), query_options: query_options, database_role: database_role, directed_read_options: directed_read_options end + # rubocop:enable Lint/UnusedMethodArgument + ## # Creates a Cloud Spanner batch client. A batch client is used to read # data across multiple machines or processes. From 7493cf0dcd5068f18a029ac7316139889587b5ac Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 28 Oct 2025 11:52:49 -0700 Subject: [PATCH 07/25] chore: Remove `reload!` from sessions; fix: make keepalive multiplexed-aware, change `release`. --- .../lib/google/cloud/spanner/session.rb | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/google-cloud-spanner/lib/google/cloud/spanner/session.rb b/google-cloud-spanner/lib/google/cloud/spanner/session.rb index 40ad0aef..209d11e6 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/session.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/session.rb @@ -1400,29 +1400,16 @@ def create_empty_transaction exclude_txn_from_change_streams: false Transaction.from_grpc nil, self, exclude_txn_from_change_streams: exclude_txn_from_change_streams end - ## - # Reloads the session resource. Useful for determining if the session is - # still valid on the Spanner API. - def reload! - ensure_service! - @grpc = service.get_session path - @last_updated_at = Process.clock_gettime Process::CLOCK_MONOTONIC - self - rescue Google::Cloud::NotFoundError - labels = @grpc.labels.to_h unless @grpc.labels.to_h.empty? - @grpc = service.create_session \ - V1::Spanner::Paths.database_path( - project: project_id, instance: instance_id, database: database_id - ), - labels: labels - @last_updated_at = Process.clock_gettime Process::CLOCK_MONOTONIC - self - end - - ## + # If the session is non-multiplexed, keeps the session alive by executing `"SELECT 1"`. + # This method will re-create the session if necessary. + # For multiplexed session the keepalive is not required and this method immediately returns `true`. # @private - # Keeps the session alive by executing `"SELECT 1"`. + # @return [::Boolean] + # `true` if the session is multiplexed or if the keepalive was successful for non-multiplexed session, + # `false` if the non-multiplexed session was not found and the had to be recreated. def keepalive! + return true if multiplexed? + ensure_service! route_to_leader = LARHeaders.execute_query false execute_query "SELECT 1", route_to_leader: route_to_leader @@ -1437,9 +1424,12 @@ def keepalive! false end - ## - # Permanently deletes the session. + # Permanently deletes the session unless this session is multiplexed. + # Multiplexed sessions can not be deleted, and this method immediately returns. + # @private + # @return [void] def release! + return if multiplexed? ensure_service! service.delete_session path end From 2723314a12bfd4aa0f304f942565facbbc21ae71 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 28 Oct 2025 11:53:52 -0700 Subject: [PATCH 08/25] chore: slight refactor and docs in Snapshot; fix: client uses `directed read option` when creating Snapshot transactions. --- .../lib/google/cloud/spanner/client.rb | 2 +- .../lib/google/cloud/spanner/snapshot.rb | 34 ++++++++++++++----- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/google-cloud-spanner/lib/google/cloud/spanner/client.rb b/google-cloud-spanner/lib/google/cloud/spanner/client.rb index 2009352d..a3120863 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/client.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/client.rb @@ -2278,7 +2278,7 @@ def snapshot strong: nil, timestamp: nil, read_timestamp: nil, staleness: staleness || exact_staleness, call_options: call_options Thread.current[IS_TRANSACTION_RUNNING_KEY] = true - snp = Snapshot.from_grpc snp_grpc, session, @directed_read_options + snp = Snapshot.from_grpc snp_grpc, session, directed_read_options: @directed_read_options yield snp if block_given? ensure Thread.current[IS_TRANSACTION_RUNNING_KEY] = nil diff --git a/google-cloud-spanner/lib/google/cloud/spanner/snapshot.rb b/google-cloud-spanner/lib/google/cloud/spanner/snapshot.rb index efdee45e..bc8e3353 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/snapshot.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/snapshot.rb @@ -46,6 +46,21 @@ class Snapshot # @return [::Google::Cloud::Spanner::V1::Session] attr_accessor :session + # Creates a new `Spanner::Snapshot` instance. + # @param grpc [::Google::Cloud::Spanner::V1::Transaction] + # Underlying `V1::Transaction` object. + # @param session [::Google::Cloud::Spanner::Session] A `Spanner::Session` reference. + # @param directed_read_options [::Hash, nil] Optional. Client options used to set + # the `directed_read_options` for all ReadRequests and ExecuteSqlRequests. + # Converts to `V1::DirectedReadOptions`. Example option: `:exclude_replicas`. + # @private + # @return [::Google::Cloud::Spanner::Snapshot] + def initialize grpc, session, directed_read_options: nil + @grpc = grpc + @session = session + @directed_read_options = directed_read_options + end + ## # Identifier of the transaction results were run in. # @return [String] The transaction id. @@ -538,15 +553,18 @@ def range beginning, ending, exclude_begin: false, exclude_end: false exclude_end: exclude_end end - ## - # @private Creates a new Snapshot instance from a + # Creates a new `Spanner::Snapshot` instance from a # `Google::Cloud::Spanner::V1::Transaction`. - def self.from_grpc grpc, session, directed_read_options - new.tap do |s| - s.instance_variable_set :@grpc, grpc - s.instance_variable_set :@session, session - s.instance_variable_set :@directed_read_options, directed_read_options - end + # @param grpc [::Google::Cloud::Spanner::V1::Transaction] + # Underlying `V1::Transaction` object. + # @param session [::Google::Cloud::Spanner::Session] A `Spanner::Session` reference. + # @param directed_read_options [::Hash, nil] Optional. Client options used to set + # the `directed_read_options` for all ReadRequests and ExecuteSqlRequests. + # Converts to `V1::DirectedReadOptions`. Example option: `:exclude_replicas`. + # @private + # @return [::Google::Cloud::Spanner::Snapshot] + def self.from_grpc grpc, session, directed_read_options: nil + new grpc, session, directed_read_options: directed_read_options end protected From 56895f0120858bdf693fdfe996d83a3af53032e1 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 28 Oct 2025 11:56:55 -0700 Subject: [PATCH 09/25] fix: do not explicitly create transactions during retry --- .../lib/google/cloud/spanner/client.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/google-cloud-spanner/lib/google/cloud/spanner/client.rb b/google-cloud-spanner/lib/google/cloud/spanner/client.rb index a3120863..31ae47d7 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/client.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/client.rb @@ -2143,8 +2143,8 @@ def transaction deadline: 120, exclude_txn_from_change_streams: false, Thread.current[IS_TRANSACTION_RUNNING_KEY] = true yield tx - unless tx.existing_transaction? - # This can happen if the yielded `tx` object was only used to add mutations. + if tx.mutations.any? && !tx.existing_transaction? + # This typically will happen if the yielded `tx` object was only used to add mutations. # Then it never called any RPCs and didn't create a server-side Transaction object. # In which case we should make an explicit BeginTransaction call here. tx.safe_begin_transaction!( @@ -2154,7 +2154,7 @@ def transaction deadline: 120, exclude_txn_from_change_streams: false, ) end - transaction_id = tx.transaction_id + transaction_id = tx.transaction_id if tx.existing_transaction? commit_resp = @project.service.commit( tx.session.path, tx.mutations, @@ -2173,8 +2173,12 @@ def transaction deadline: 120, exclude_txn_from_change_streams: false, check_and_propagate_err! e, (current_time - start_time > deadline) # Sleep the amount from RetryDelay, or incremental backoff sleep(delay_from_aborted(e) || backoff *= 1.3) + # Create new transaction on the session and retry the block - tx = session.create_transaction exclude_txn_from_change_streams: exclude_txn_from_change_streams + tx = session.create_empty_transaction exclude_txn_from_change_streams: exclude_txn_from_change_streams + if request_options + tx.transaction_tag = request_options[:transaction_tag] + end retry rescue StandardError => e # Rollback transaction when handling unexpected error @@ -2458,8 +2462,8 @@ def close ## # Reset the client sessions. # - def reset - @pool.reset + def reset! + @pool.reset! end # @private From 41526a043dfffb8113f306caa4cb6ef4cc95e553 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 28 Oct 2025 12:40:19 -0700 Subject: [PATCH 10/25] feat: precommit token to Transaction, using in Client, piping through Service --- .../lib/google/cloud/spanner/client.rb | 3 +- .../lib/google/cloud/spanner/service.rb | 8 ++- .../lib/google/cloud/spanner/transaction.rb | 70 ++++++++++++++++++- 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/google-cloud-spanner/lib/google/cloud/spanner/client.rb b/google-cloud-spanner/lib/google/cloud/spanner/client.rb index 31ae47d7..51222dc0 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/client.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/client.rb @@ -2162,7 +2162,8 @@ def transaction deadline: 120, exclude_txn_from_change_streams: false, exclude_txn_from_change_streams: exclude_txn_from_change_streams, commit_options: commit_options, request_options: request_options, - call_options: call_options + call_options: call_options, + precommit_token: tx.precommit_token ) resp = CommitResponse.from_grpc commit_resp commit_options ? resp : resp.timestamp diff --git a/google-cloud-spanner/lib/google/cloud/spanner/service.rb b/google-cloud-spanner/lib/google/cloud/spanner/service.rb index c4397fff..8a820c11 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/service.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/service.rb @@ -530,11 +530,15 @@ def partition_query session_name, sql, transaction, params: nil, # Example option: `:priority`. # @param call_options [::Hash, nil] Optional. A hash of values to specify the custom # call options. Example option `:timeout`. + # @param precommit_token [::Google::Cloud::Spanner::V1::MultiplexedSessionPrecommitToken, nil] Optional. + # If the read-write transaction was executed on a multiplexed session, then a precommit token + # with the highest sequence number received in this transaction attempt must be included. # @private # @return [::Google::Cloud::Spanner::V1::CommitResponse] def commit session_name, mutations = [], transaction_id: nil, exclude_txn_from_change_streams: false, - commit_options: nil, request_options: nil, call_options: nil + commit_options: nil, request_options: nil, call_options: nil, + precommit_token: nil route_to_leader = LARHeaders.commit tx_opts = nil if transaction_id.nil? @@ -549,7 +553,7 @@ def commit session_name, mutations = [], request = { session: session_name, transaction_id: transaction_id, single_use_transaction: tx_opts, mutations: mutations, - request_options: request_options + request_options: request_options, precommit_token: precommit_token } request = add_commit_options request, commit_options diff --git a/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb b/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb index dad738ce..7d965444 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb @@ -97,6 +97,13 @@ class Transaction # @return [::Boolean] attr_accessor :exclude_txn_from_change_streams + # A token that is required when committing an RW transaction over a multiplexed session + # It can be read when transaction is created (either by BeginTransaction or by inlined begin), + # or from a previous operation within existing transaction. + # @private + # @return [::Google::Cloud::Spanner::V1::MultiplexedSessionPrecommitToken, nil] + attr_accessor :precommit_token + # Creates a new `Spanner::Transaction` instance from a `V1::Transaction` object. # @param grpc [::Google::Cloud::Spanner::V1::Transaction] Underlying `V1::Transaction` object. # @param session [::Google::Cloud::Spanner::Session] The session this transaction is running in. @@ -130,6 +137,14 @@ def initialize grpc, session, exclude_txn_from_change_streams # create a transaction must be synchronized, and any logic that depends on # the state of transaction creation must also be synchronized. @mutex = Mutex.new + + # Precommit token is a piece of server-side bookkeeping pushed onto client-side + # as a part of MultiplexedSession update. Briefly, for a given read-write transaction on a + # Multiplexed session the client library must: + # 1. From all read operations, store the most recently received token. + # 2. Include this final token in the CommitRequest. + # @type [::Google::Cloud::Spanner::V1::MultiplexedSessionPrecommitToken, nil] + @precommit_token = nil end ## @@ -398,7 +413,9 @@ def execute_query sql, params: nil, types: nil, query_options: nil, request_options: request_options, call_options: call_options, route_to_leader: route_to_leader - @grpc ||= results.transaction + + update_wrapped_transaction! results.transaction + results end end @@ -676,11 +693,59 @@ def batch_update request_options: nil, call_options: nil, &block request_options: request_options, call_options: call_options, &block batch_update_results = BatchUpdateResults.new response - @grpc ||= batch_update_results.transaction + update_wrapped_transaction! batch_update_results.transaction + response.result_sets.each do |result_set| + update_precommit_token! result_set.precommit_token if result_set.precommit_token + end batch_update_results.row_counts end end + # Updates this `Spanner::Transaction` with a new underlying `V1::Transaction` object. + # This happens when this `Spanner::Transaction` is in a empty-wrapper mode + # (it was created by `Google::Cloud::Spanner::Session#create_empty_transaction`). + # In that mode the inner wrapped `grpc` object representing the `V1::Transaction` is nil, + # and (almost all) service request run using the "inline-begin" transactions. + # As part of "inline-begin", a new `V1::Transaction` is created server-side, returned with the + # results, and in turn should be saved as the new `grpc` object. + # + # ! This method is expected to be called from within `safe_execute()` method's block! + # + # This method also updates the precommit token, if the new underlying `V1::Transaction` has it. + # + # This is a mutator method. + # @param new_transaction [::Google::Cloud::Spanner::V1::Transaction] + # `V1::Transaction` object that was created on the server-side. + # @private + # @return [void] + def update_wrapped_transaction! new_transaction + return unless @grpc.nil? + return if new_transaction.nil? + + @grpc = new_transaction + update_precommit_token! new_transaction.precommit_token + end + + # Updates this transaction's precommit token but only if: + # * new token exists + # * new token's seq_num is greater. + # + # ! This method is expected to be called from within `safe_execute()` method's block! + # + # This is a mutator method. + # @param new_precommit_token [::Google::Cloud::Spanner::V1::MultiplexedSessionPrecommitToken, nil] + # the new precommit token, if any, from the latest service operation (e.g. from a ResultSet from a read). + # @private + # @return [void] + def update_precommit_token! new_precommit_token + if !new_precommit_token.nil? && ( + @precommit_token.nil? || + new_precommit_token.seq_num > @precommit_token.seq_num + ) + @precommit_token = new_precommit_token + end + end + ## # Read rows from a database table, as a simple alternative to # {#execute_query}. @@ -756,6 +821,7 @@ def read table, columns, keys: nil, index: nil, limit: nil, call_options: call_options, route_to_leader: route_to_leader @grpc ||= results.transaction + update_wrapped_transaction! results.transaction results end end From 3d016a8d3a32c057ece3313442d4227a66b60d37 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 28 Oct 2025 12:42:18 -0700 Subject: [PATCH 11/25] feat: add precommit token notification when iterating through Results --- .../lib/google/cloud/spanner/results.rb | 49 ++++++++++++++++--- .../lib/google/cloud/spanner/session.rb | 17 +++++-- .../lib/google/cloud/spanner/transaction.rb | 12 +++-- 3 files changed, 64 insertions(+), 14 deletions(-) diff --git a/google-cloud-spanner/lib/google/cloud/spanner/results.rb b/google-cloud-spanner/lib/google/cloud/spanner/results.rb index a3a7345f..e2563ab9 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/results.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/results.rb @@ -56,13 +56,23 @@ class Results # @param metadata [::Google::Cloud::Spanner::V1::ResultSetMetadata] ParialResultSet metadata object # @param stats [::Google::Cloud::Spanner::V1::ResultSetStats] Query plan and execution statistics # for the statement that produced this streaming result set. + # @param precommit_token_notify [::Proc, nil] Optional. + # The notification function for the precommit token. # @private - def initialize service, partial_result_sets, session_name, metadata, stats + def initialize service, partial_result_sets, session_name, metadata, stats, precommit_token_notify: nil @service = service @partial_result_sets = partial_result_sets @session_name = session_name @metadata = metadata @stats = stats + + # The notification function for the precommit token. + # `Results` object will see precommit tokens while iterating if it were created + # in the context of an RW transaction on a multiplexed session. + # Precommit token is a passthrough parameter that that transaction will need to supply in order to Commit. + # There can be multiple precommit tokens in the stream + # (should be at least 2 -- with the first and last PartialResultSet). + @precommit_token_notify = precommit_token_notify end # The `V1::ResultSetMetadata` protobuf object from the first @@ -169,6 +179,14 @@ def rows @metadata ||= grpc.metadata @stats ||= grpc.stats + # The precommit token should be issued on first and last stream element. + # These two precommit tokens can be different. + # If these Results are created in the context of a `Spanner::Transaction`, + # that `Transaction` object is the one keeping track of the precommit token and should be notified. + if grpc.precommit_token && @precommit_token_notify + @precommit_token_notify.call(grpc.precommit_token) + end + buffered_responses << grpc if (grpc.resume_token && grpc.resume_token != "") || @@ -330,14 +348,23 @@ def row_count_exact? # The name of the session for the operation that created these Results. # Values are of the form: # `projects//instances//databases//sessions/`. + # @param precommit_token_notify [::Proc, nil] Optional. + # The notification function for the precommit token. # @private # @return [::Google::Cloud::Spanner::Results] - def self.from_partial_result_sets partial_result_sets, service, session_name + def self.from_partial_result_sets partial_result_sets, service, session_name, precommit_token_notify: nil # @type [::Google::Cloud::Spanner::V1::PartialResultSet] partial_result_set = partial_result_sets.peek metadata = partial_result_set.metadata stats = partial_result_set.stats - new service, partial_result_sets, session_name, metadata, stats + results = new service, partial_result_sets, session_name, metadata, stats, + precommit_token_notify: precommit_token_notify + + if partial_result_set.precommit_token && precommit_token_notify + precommit_token_notify.call partial_result_set.precommit_token + end + + results rescue GRPC::BadStatus => e raise Google::Cloud::Error.from_error(e) end @@ -355,10 +382,14 @@ def self.from_partial_result_sets partial_result_sets, service, session_name # that were sent to the `service.execute_streaming_sql`. This hash joins params needed to # construct `::Gapic::CallOptions`, e.g. `call_options` and header-related `route_to_leader` # with params specific to `execute_streaming_sql`, such as `seqno`. + # @param precommit_token_notify [::Proc, nil] Optional. + # The notification function for the precommit token. # @private # @return [::Google::Cloud::Spanner::Results] - def self.from_execute_query_response response, service, session_name, sql, execute_query_options - from_partial_result_sets(response, service, session_name).tap do |results| + def self.from_execute_query_response response, service, session_name, sql, execute_query_options, + precommit_token_notify: nil + from_partial_result_sets(response, service, session_name, + precommit_token_notify: precommit_token_notify).tap do |results| execute_query_options_copy = execute_query_options.dup unless results.metadata.transaction.nil? execute_query_options_copy[:transaction] = V1::TransactionSelector.new id: results.metadata.transaction.id @@ -384,10 +415,14 @@ def self.from_execute_query_response response, service, session_name, sql, execu # that were sent to the `service.streaming_read_table`. This hash joins params needed to # construct `::Gapic::CallOptions`, e.g. `call_options` and header-related `route_to_leader` # with params specific to `streaming_read_table`, such as `keys`. + # @param precommit_token_notify [::Proc, nil] Optional. + # The notification function for the precommit token. # @private # @return [::Google::Cloud::Spanner::Results] - def self.from_read_response response, service, session_name, table, columns, read_options - from_partial_result_sets(response, service, session_name).tap do |results| + def self.from_read_response response, service, session_name, table, columns, read_options, + precommit_token_notify: nil + from_partial_result_sets(response, service, session_name, + precommit_token_notify: precommit_token_notify).tap do |results| read_options_copy = read_options.dup unless results.metadata.transaction.nil? read_options_copy[:transaction] = V1::TransactionSelector.new id: results.metadata.transaction.id diff --git a/google-cloud-spanner/lib/google/cloud/spanner/session.rb b/google-cloud-spanner/lib/google/cloud/spanner/session.rb index 209d11e6..83a3cc15 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/session.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/session.rb @@ -217,6 +217,9 @@ def path # * `:retry_codes` (`Array`) - The error codes that should # trigger a retry. # + # @param precommit_token_notify [::Proc, nil] Optional. + # The notification function for the precommit token. + # # @return [Google::Cloud::Spanner::Results] The results of the query # execution. # @@ -358,7 +361,8 @@ def path def execute_query sql, params: nil, types: nil, transaction: nil, partition_token: nil, seqno: nil, query_options: nil, request_options: nil, call_options: nil, data_boost_enabled: nil, - directed_read_options: nil, route_to_leader: nil + directed_read_options: nil, route_to_leader: nil, + precommit_token_notify: nil ensure_service! query_options = merge_if_present query_options, @query_options @@ -374,7 +378,8 @@ def execute_query sql, params: nil, types: nil, transaction: nil, response = service.execute_streaming_sql path, sql, **execute_query_options - results = Results.from_execute_query_response response, service, path, sql, execute_query_options + results = Results.from_execute_query_response response, service, path, sql, execute_query_options, + precommit_token_notify: precommit_token_notify @last_updated_at = Process.clock_gettime Process::CLOCK_MONOTONIC results end @@ -507,6 +512,9 @@ def batch_update transaction, seqno, request_options: nil, # To see the available options refer to # ['Google::Cloud::Spanner::V1::ReadRequest::LockHint'](https://cloud.google.com/ruby/docs/reference/google-cloud-spanner-v1/latest/Google-Cloud-Spanner-V1-ReadRequest-LockHint) # + # @param precommit_token_notify [::Proc, nil] Optional. + # The notification function for the precommit token. + # # @return [Google::Cloud::Spanner::Results] The results of the read # operation. # @@ -526,7 +534,7 @@ def batch_update transaction, seqno, request_options: nil, def read table, columns, keys: nil, index: nil, limit: nil, transaction: nil, partition_token: nil, request_options: nil, call_options: nil, data_boost_enabled: nil, directed_read_options: nil, - route_to_leader: nil, order_by: nil, lock_hint: nil + route_to_leader: nil, order_by: nil, lock_hint: nil, precommit_token_notify: nil ensure_service! read_options = { @@ -545,7 +553,8 @@ def read table, columns, keys: nil, index: nil, limit: nil, response = service.streaming_read_table \ path, table, columns, **read_options - results = Results.from_read_response response, service, path, table, columns, read_options + results = Results.from_read_response response, service, path, table, columns, read_options, + precommit_token_notify: precommit_token_notify @last_updated_at = Process.clock_gettime Process::CLOCK_MONOTONIC diff --git a/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb b/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb index 7d965444..ca8504f6 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb @@ -412,7 +412,8 @@ def execute_query sql, params: nil, types: nil, query_options: nil, query_options: query_options, request_options: request_options, call_options: call_options, - route_to_leader: route_to_leader + route_to_leader: route_to_leader, + precommit_token_notify: method(:update_precommit_token!) update_wrapped_transaction! results.transaction @@ -590,6 +591,11 @@ def execute_update sql, params: nil, types: nil, query_options: nil, query_options: query_options, request_options: request_options, call_options: call_options + + # Since this method is calling `execute_query`, the transaction is going to be updated, + # and the `results` object is going to be set up with precommit token notification reference, + # so we don't need to do anything special here. + # Stream all PartialResultSet to get ResultSetStats results.rows.to_a # Raise an error if there is not a row count returned @@ -819,8 +825,8 @@ def read table, columns, keys: nil, index: nil, limit: nil, transaction: tx_selector, request_options: request_options, call_options: call_options, - route_to_leader: route_to_leader - @grpc ||= results.transaction + route_to_leader: route_to_leader, + precommit_token_notify: method(:update_precommit_token!) update_wrapped_transaction! results.transaction results end From f4060dc130d19d0e4972f7ece865417f69a50b6b Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 28 Oct 2025 12:44:35 -0700 Subject: [PATCH 12/25] feat: mutation key in transaction, usage in client, piping through service --- .../lib/google/cloud/spanner/client.rb | 12 ++++++++++-- .../lib/google/cloud/spanner/service.rb | 4 ++-- .../lib/google/cloud/spanner/transaction.rb | 10 ++++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/google-cloud-spanner/lib/google/cloud/spanner/client.rb b/google-cloud-spanner/lib/google/cloud/spanner/client.rb index 51222dc0..5a87ac4f 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/client.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/client.rb @@ -1959,7 +1959,8 @@ def batch_write exclude_txn_from_change_streams: false, # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/BlockLength - + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity ## # Creates a transaction for reads and writes that execute atomically at @@ -2147,10 +2148,14 @@ def transaction deadline: 120, exclude_txn_from_change_streams: false, # This typically will happen if the yielded `tx` object was only used to add mutations. # Then it never called any RPCs and didn't create a server-side Transaction object. # In which case we should make an explicit BeginTransaction call here. + + mutation_key = tx.mutations[0] + tx.safe_begin_transaction!( exclude_from_change_streams: exclude_txn_from_change_streams, request_options: request_options, - call_options: call_options + call_options: call_options, + mutation_key: mutation_key ) end @@ -2197,6 +2202,9 @@ def transaction deadline: 120, exclude_txn_from_change_streams: false, # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/BlockLength + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/PerceivedComplexity + ## # Creates a snapshot read-only transaction for reads that execute diff --git a/google-cloud-spanner/lib/google/cloud/spanner/service.rb b/google-cloud-spanner/lib/google/cloud/spanner/service.rb index 8a820c11..7ae6f979 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/service.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/service.rb @@ -703,7 +703,7 @@ def create_snapshot session_name, strong: nil, timestamp: nil, ) opts = default_options session_name: session_name, call_options: call_options - request = { session: session_name, options: tx_opts } + request = { session: session_name, options: tx_opts, mutation_key: nil } service.begin_transaction request, opts end @@ -718,7 +718,7 @@ def create_pdml session_name, opts = default_options session_name: session_name, call_options: call_options, route_to_leader: route_to_leader - request = { session: session_name, options: tx_opts } + request = { session: session_name, options: tx_opts, mutation_key: nil } service.begin_transaction request, opts end diff --git a/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb b/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb index ca8504f6..7ab84cba 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb @@ -1255,10 +1255,15 @@ def no_existing_transaction? # Example option: `:priority`. # @param call_options [::Hash, nil] Optional. A hash of values to specify the custom # call options. Example option `:timeout`. + # @param mutation_key [::Google::Cloud::Spanner::V1::Mutation, nil] Optional. + # If a read-write transaction on a multiplexed session commit mutations + # without performing any reads or queries, one of the mutations from the mutation set + # must be sent as a mutation key for `BeginTransaction`. # @private # @return [::Google::Cloud::Spanner::V1::Transaction, nil] The new transaction # object, or `nil` if a transaction already exists. - def safe_begin_transaction! exclude_from_change_streams: false, request_options: nil, call_options: nil + def safe_begin_transaction! exclude_from_change_streams: false, request_options: nil, call_options: nil, + mutation_key: nil @mutex.synchronize do return if existing_transaction? ensure_session! @@ -1268,7 +1273,8 @@ def safe_begin_transaction! exclude_from_change_streams: false, request_options: exclude_txn_from_change_streams: exclude_from_change_streams, request_options: request_options, call_options: call_options, - route_to_leader: route_to_leader + route_to_leader: route_to_leader, + mutation_key: mutation_key ) end end From c061cc5be48c04947b6065dca93e4c5c8b93f0a1 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 28 Oct 2025 12:46:18 -0700 Subject: [PATCH 13/25] tests: deleted tests for deleted methods --- .../google/cloud/spanner/client/close_test.rb | 64 ------------- .../google/cloud/spanner/client/reset_test.rb | 71 -------------- .../cloud/spanner/session/reload_test.rb | 93 ------------------- .../spanner/transaction/keepalive_test.rb | 40 -------- .../cloud/spanner/transaction/release_test.rb | 37 -------- 5 files changed, 305 deletions(-) delete mode 100644 google-cloud-spanner/test/google/cloud/spanner/client/close_test.rb delete mode 100644 google-cloud-spanner/test/google/cloud/spanner/client/reset_test.rb delete mode 100644 google-cloud-spanner/test/google/cloud/spanner/session/reload_test.rb delete mode 100644 google-cloud-spanner/test/google/cloud/spanner/transaction/keepalive_test.rb delete mode 100644 google-cloud-spanner/test/google/cloud/spanner/transaction/release_test.rb diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/close_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/close_test.rb deleted file mode 100644 index 1b4fe833..00000000 --- a/google-cloud-spanner/test/google/cloud/spanner/client/close_test.rb +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2017 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -require "helper" - -describe Google::Cloud::Spanner::Client, :close, :mock_spanner do - let(:instance_id) { "my-instance-id" } - let(:database_id) { "my-database-id" } - let(:session_id) { "session123" } - let(:session_grpc) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id) } - let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } - let(:default_options) { ::Gapic::CallOptions.new "google-cloud-resource-prefix" => database_path(instance_id, database_id) } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0, max: 4 } } - let(:pool) { client.instance_variable_get :@pool } - let(:default_options) { - ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } - } - - before do - session.instance_variable_set :@last_updated_at, Time.now - p = client.instance_variable_get :@pool - p.sessions_available = [session] - p.sessions_in_use = {} - end - - it "deletes sessions when closed" do - mock = Minitest::Mock.new - mock.expect :delete_session, nil, [{name: session_grpc.name}, default_options] - session.service.mocked_service = mock - - client.close - - shutdown_pool! pool - - mock.verify - end - - it "cannot be used after being closed" do - mock = Minitest::Mock.new - mock.expect :delete_session, nil, [{name: session_grpc.name}, default_options] - session.service.mocked_service = mock - - client.close - - assert_raises Google::Cloud::Spanner::ClientClosedError do - client.execute_query "SELECT 1" - end - - shutdown_pool! pool - - mock.verify - end -end diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/reset_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/reset_test.rb deleted file mode 100644 index e947b34d..00000000 --- a/google-cloud-spanner/test/google/cloud/spanner/client/reset_test.rb +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -require "helper" - -describe Google::Cloud::Spanner::Client, :close, :mock_spanner do - let(:instance_id) { "my-instance-id" } - let(:database_id) { "my-database-id" } - let(:session_id) { "session123" } - let(:session_grpc) { - Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id) - } - let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } - let(:default_options) { - ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } - } - let(:batch_create_sessions_grpc) { - Google::Cloud::Spanner::V1::BatchCreateSessionsResponse.new session: [session_grpc] - } - let :results_hash do - { - metadata: { - row_type: { - fields: [ - { type: { code: :INT64 } } - ] - } - }, - values: [ - { string_value: "1" } - ] - } - end - let(:results_grpc) { Google::Cloud::Spanner::V1::PartialResultSet.new results_hash } - let(:results_enum) { Array(results_grpc).to_enum } - - it "reset client sessions and able to query database" do - mock = Minitest::Mock.new - mock.expect :batch_create_sessions, batch_create_sessions_grpc, [ - { database: database_path(instance_id, database_id), session_count: 1, session_template: nil }, - default_options - ] - mock.expect :delete_session, nil, [{ name: session_grpc.name }, default_options] - mock.expect :batch_create_sessions, batch_create_sessions_grpc, [ - { database: database_path(instance_id, database_id), session_count: 1, session_template: nil }, - default_options - ] - spanner.service.mocked_service = mock - expect_execute_streaming_sql results_enum, session.path, "SELECT 1", options: default_options - - client = spanner.client instance_id, database_id, pool: { min: 1, max: 1 } - _(client.reset).must_equal true - _(client.execute_query("SELECT 1").rows.first.values).must_equal [1] - - pool = client.instance_variable_get :@pool - shutdown_pool! pool - - mock.verify - end -end diff --git a/google-cloud-spanner/test/google/cloud/spanner/session/reload_test.rb b/google-cloud-spanner/test/google/cloud/spanner/session/reload_test.rb deleted file mode 100644 index 51f3c348..00000000 --- a/google-cloud-spanner/test/google/cloud/spanner/session/reload_test.rb +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright 2016 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -require "helper" - -describe Google::Cloud::Spanner::Session, :reload, :mock_spanner do - let(:instance_id) { "my-instance-id" } - let(:database_id) { "my-database-id" } - let(:session_id) { "session123" } - let(:session_grpc) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id) } - let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } - let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } - - let(:labels) { { "env" => "production" } } - let(:session_grpc_labels) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id), labels: labels } - let(:session_labels) { Google::Cloud::Spanner::Session.from_grpc session_grpc_labels, spanner.service } - - it "can reload itself" do - mock = Minitest::Mock.new - mock.expect :get_session, session_grpc, [{ name: session_grpc.name }, default_options] - session.service.mocked_service = mock - - _(session).must_be_kind_of Google::Cloud::Spanner::Session - - session.reload! - - mock.verify - - _(session).must_be_kind_of Google::Cloud::Spanner::Session - - _(session.project_id).must_equal "test" - _(session.instance_id).must_equal "my-instance-id" - _(session.database_id).must_equal "my-database-id" - _(session.session_id).must_equal "session123" - end - - it "can recreate itself if error is raised on reload" do - mock = Minitest::Mock.new - def mock.get_session *args - grpc_error = GRPC::NotFound.new 5, "not found" - raise Google::Cloud::Error.from_error grpc_error - end - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - session.service.mocked_service = mock - - _(session).must_be_kind_of Google::Cloud::Spanner::Session - - session.reload! - - mock.verify - - _(session).must_be_kind_of Google::Cloud::Spanner::Session - - _(session.project_id).must_equal "test" - _(session.instance_id).must_equal "my-instance-id" - _(session.database_id).must_equal "my-database-id" - _(session.session_id).must_equal "session123" - end - - it "can recreate itself with labels if error on reload" do - mock = Minitest::Mock.new - def mock.get_session *args - grpc_error = GRPC::NotFound.new 5, "not found" - raise Google::Cloud::Error.from_error grpc_error - end - mock.expect :create_session, session_grpc_labels, [{ database: database_path(instance_id, database_id), session: Google::Cloud::Spanner::V1::Session.new(labels: labels) }, default_options] - session_labels.service.mocked_service = mock - - _(session_labels).must_be_kind_of Google::Cloud::Spanner::Session - - session_labels.reload! - - mock.verify - - _(session_labels).must_be_kind_of Google::Cloud::Spanner::Session - - _(session_labels.project_id).must_equal "test" - _(session_labels.instance_id).must_equal "my-instance-id" - _(session_labels.database_id).must_equal "my-database-id" - _(session_labels.session_id).must_equal "session123" - end -end diff --git a/google-cloud-spanner/test/google/cloud/spanner/transaction/keepalive_test.rb b/google-cloud-spanner/test/google/cloud/spanner/transaction/keepalive_test.rb deleted file mode 100644 index e186cf03..00000000 --- a/google-cloud-spanner/test/google/cloud/spanner/transaction/keepalive_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2017 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -require "helper" - -describe Google::Cloud::Spanner::Session, :keepalive, :mock_spanner do - let(:instance_id) { "my-instance-id" } - let(:database_id) { "my-database-id" } - let(:session_id) { "session123" } - let(:session_grpc) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id) } - let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } - let(:transaction_id) { "tx789" } - let(:transaction_grpc) { Google::Cloud::Spanner::V1::Transaction.new id: transaction_id } - let(:transaction) { Google::Cloud::Spanner::Transaction.from_grpc transaction_grpc, session } - let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } - let(:tx_opts) { Google::Cloud::Spanner::V1::TransactionOptions.new(read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new) } - - it "creates new transaction when calling keepalive" do - mock = Minitest::Mock.new - mock.expect :begin_transaction, transaction_grpc, [{ - session: session_grpc.name, options: tx_opts, request_options: nil - }, default_options] - spanner.service.mocked_service = mock - - transaction.keepalive! - - mock.verify - end -end diff --git a/google-cloud-spanner/test/google/cloud/spanner/transaction/release_test.rb b/google-cloud-spanner/test/google/cloud/spanner/transaction/release_test.rb deleted file mode 100644 index aece23a2..00000000 --- a/google-cloud-spanner/test/google/cloud/spanner/transaction/release_test.rb +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2017 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -require "helper" - -describe Google::Cloud::Spanner::Session, :release, :mock_spanner do - let(:instance_id) { "my-instance-id" } - let(:database_id) { "my-database-id" } - let(:session_id) { "session123" } - let(:session_grpc) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id) } - let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } - let(:transaction_id) { "tx789" } - let(:transaction_grpc) { Google::Cloud::Spanner::V1::Transaction.new id: transaction_id } - let(:transaction) { Google::Cloud::Spanner::Transaction.from_grpc transaction_grpc, session } - let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } - - it "can release itself" do - mock = Minitest::Mock.new - mock.expect :delete_session, nil, [{ name: session_grpc.name}, default_options] - spanner.service.mocked_service = mock - - transaction.release! - - mock.verify - end -end From 7af16db02279d3e29d4870f02e28d1abe7a0b056 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 28 Oct 2025 12:46:44 -0700 Subject: [PATCH 14/25] tests: moved test --- .../google/cloud/spanner/{client => pool}/threads_test.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) rename google-cloud-spanner/test/google/cloud/spanner/{client => pool}/threads_test.rb (83%) diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/threads_test.rb b/google-cloud-spanner/test/google/cloud/spanner/pool/threads_test.rb similarity index 83% rename from google-cloud-spanner/test/google/cloud/spanner/client/threads_test.rb rename to google-cloud-spanner/test/google/cloud/spanner/pool/threads_test.rb index 4caeedca..9ae264ff 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/threads_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/pool/threads_test.rb @@ -20,23 +20,21 @@ let(:session_id) { "session123" } let(:session_grpc) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id) } let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } + let(:session_creation_options) { ::Google::Cloud::Spanner::SessionCreationOptions.new database_path: database_path(instance_id, database_id)} it "creates a thread pool with the number of threads specified" do mock = Minitest::Mock.new # mock.expect :delete_session, nil, [session_grpc.name, options: default_options] session.service.mocked_service = mock - client = spanner.client instance_id, database_id, pool: { min: 0, max: 4, threads: 13 } - pool = client.instance_variable_get :@pool + pool = Google::Cloud::Spanner::Pool.new(spanner.service, session_creation_options, min: 0, max: 4, threads: 13) threads = pool.instance_variable_get :@threads thread_pool = pool.instance_variable_get :@thread_pool _(threads).must_equal 13 _(thread_pool.max_length).must_equal 13 - client.close - - shutdown_client! client + shutdown_pool! pool mock.verify end From bb415fb3de4e24a8bcdc06f7b508921b0ae893c7 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 28 Oct 2025 12:47:05 -0700 Subject: [PATCH 15/25] tests: new test for precommit tokens --- .../transaction_results_precommit_test.rb | 332 ++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 google-cloud-spanner/test/google/cloud/spanner/client/transaction_results_precommit_test.rb diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/transaction_results_precommit_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/transaction_results_precommit_test.rb new file mode 100644 index 00000000..3ab449a6 --- /dev/null +++ b/google-cloud-spanner/test/google/cloud/spanner/client/transaction_results_precommit_test.rb @@ -0,0 +1,332 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +require "helper" + +describe Google::Cloud::Spanner::Client, :transaction, :mock_spanner do + let(:instance_id) { "my-instance-id" } + let(:database_id) { "my-database-id" } + let(:session_id) { "session123" } + let(:session_grpc) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id) } + let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } + let(:tx_selector_inline_begin) do + Google::Cloud::Spanner::V1::TransactionSelector.new( + begin: Google::Cloud::Spanner::V1::TransactionOptions.new( + read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new( + read_lock_mode: :READ_LOCK_MODE_UNSPECIFIED + ) + ) + ) + end + + let (:tx_id) {"$abc123"} + let(:tx) do + { + id: tx_id, + } + end + + let(:tx_selector_with_id) { Google::Cloud::Spanner::V1::TransactionSelector.new id: tx_id } + + let(:client) { spanner.client instance_id, database_id } + + let(:precommit_token_0) {"hello"} + let(:precommit_token_1) {"goodbye"} + + + describe :read do + let :results_hash1_tx do + { + metadata: { + row_type: { + fields: [ + { name: "id", type: { code: :INT64 } }, + { name: "name", type: { code: :STRING } }, + { name: "active", type: { code: :BOOL } }, + { name: "age", type: { code: :INT64 } }, + { name: "score", type: { code: :FLOAT64 } }, + { name: "updated_at", type: { code: :TIMESTAMP } }, + { name: "birthday", type: { code: :DATE} }, + { name: "avatar", type: { code: :BYTES } }, + { name: "project_ids", type: { code: :ARRAY, + array_element_type: { code: :INT64 } } } + ] + }, + transaction: tx + }, + precommit_token: { + precommit_token: precommit_token_0, + seq_num: 0, + } + } + end + let :results_hash2 do + { + values: [ + { string_value: "1" }, + { string_value: "Charlie" } + ], + } + end + let :results_hash3 do + { + values: [ + { bool_value: true}, + { string_value: "29" } + ] + } + end + let :results_hash4 do + { + values: [ + { number_value: 0.9 }, + { string_value: "2017-01-02T03:04:05.060000000Z" } + ], + } + end + let :results_hash5 do + { + values: [ + { string_value: "1950-01-01" }, + { string_value: "aW1hZ2U=" }, + ] + } + end + let :results_hash6 do + { + values: [ + { list_value: { values: [ { string_value: "1"}, + { string_value: "2"}, + { string_value: "3"} ]}} + ], + precommit_token: { + precommit_token: precommit_token_1, + seq_num: 1, + } + } + end + let :partial_results6 do + # need this to grab a protobuf form of precommit token for commit mock + Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash6) + end + let(:results_enum) do + [ + Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash1_tx), + Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash2), + Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash3), + Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash4), + Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash5), + partial_results6 + ].to_enum + end + + let(:commit_resp) do + Google::Cloud::Spanner::V1::CommitResponse.new( + commit_timestamp: Google::Cloud::Spanner::Convert.time_to_timestamp(Time.now), + commit_stats: Google::Cloud::Spanner::V1::CommitResponse::CommitStats.new( + mutation_count: 5 + ) + ) + end + + it "read updates precommit token on transaction" do + columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] + + service_mock = Minitest::Mock.new + service_mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + + streaming_read_request = [{ + session: session_grpc.name, + table: "my-table", + columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], + key_set: Google::Cloud::Spanner::V1::KeySet.new(all: true), + transaction: tx_selector_inline_begin, + index: nil, limit: nil, resume_token: nil, partition_token: nil, + request_options: nil, + order_by: nil, lock_hint: nil + }, default_options] + + service_mock.expect :streaming_read, RaiseableEnumerator.new(results_enum), streaming_read_request + + commit_request = [{ + session: session_grpc.name, + transaction_id: tx_id, + single_use_transaction: nil, + mutations: [], + request_options: nil, + precommit_token: partial_results6.precommit_token + }, default_options] + + service_mock.expect :commit, commit_resp, commit_request + + spanner.service.mocked_service = service_mock + + # @type [::Google::Cloud::Spanner::Client] + sp_client = client + + sp_client.transaction do |tx| + results = tx.read("my-table", columns) + _(tx.precommit_token.precommit_token).must_equal precommit_token_0 + _(tx.precommit_token.seq_num).must_equal 0 + results.rows.to_a + _(tx.precommit_token.precommit_token).must_equal precommit_token_1 + _(tx.precommit_token.seq_num).must_equal 1 + end + + shutdown_client! sp_client + + service_mock.verify + end + end + + describe :execute_sql do + let(:sql_query) { "SELECT * FROM users" } + + let :metadata_result do + { + metadata: { + row_type: { + fields: [ + { name: "id", type: { code: :INT64 } }, + { name: "name", type: { code: :STRING } }, + { name: "active", type: { code: :BOOL } }, + { name: "age", type: { code: :INT64 } }, + { name: "score", type: { code: :FLOAT64 } }, + { name: "updated_at", type: { code: :TIMESTAMP } }, + { name: "birthday", type: { code: :DATE} }, + { name: "avatar", type: { code: :BYTES } }, + { name: "project_ids", type: { code: :ARRAY, + array_element_type: { code: :INT64 } } } + ] + }, + transaction: tx + }, + precommit_token: { + precommit_token: precommit_token_0, + seq_num: 0, + } + } + end + let :partial_row_1 do + { + values: [ + { string_value: "1" }, + { string_value: "Charlie" } + ], + } + end + let :partial_row_2 do + { + values: [ + { bool_value: true}, + { string_value: "29" } + ] + } + end + let :partial_row_3 do + { + values: [ + { number_value: 0.9 }, + { string_value: "2017-01-02T03:04:05.060000000Z" } + ], + } + end + let :partial_row_4 do + { + values: [ + { string_value: "1950-01-01" }, + { string_value: "aW1hZ2U=" }, + ] + } + end + let :partial_row_5 do + { + values: [ + { list_value: { values: [ { string_value: "1"}, + { string_value: "2"}, + { string_value: "3"} ]}} + ], + precommit_token: { + precommit_token: precommit_token_1, + seq_num: 1, + } + } + end + + let(:partial_results_5) do + # need this to grab a protobuf form of precommit token for commit mock + Google::Cloud::Spanner::V1::PartialResultSet.new(partial_row_5) + end + + let(:commit_resp) do + Google::Cloud::Spanner::V1::CommitResponse.new( + commit_timestamp: Google::Cloud::Spanner::Convert.time_to_timestamp(Time.now), + ) + end + + it "execute_query updates precommit token on transaction" do + resulting_stream = [ + Google::Cloud::Spanner::V1::PartialResultSet.new(metadata_result), + Google::Cloud::Spanner::V1::PartialResultSet.new(partial_row_1), + Google::Cloud::Spanner::V1::PartialResultSet.new(partial_row_2), + Google::Cloud::Spanner::V1::PartialResultSet.new(partial_row_3), + Google::Cloud::Spanner::V1::PartialResultSet.new(partial_row_4), + partial_results_5 + ].to_enum + + service_mock = Minitest::Mock.new + service_mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + + execute_streaming_sql_request = [{ + session: session_grpc.name, + sql: sql_query, + transaction: tx_selector_inline_begin, + params: nil, param_types: nil, + resume_token: nil, partition_token: nil, + seqno: 1, + query_options: nil, request_options: nil, directed_read_options: nil + }, default_options] + + service_mock.expect :execute_streaming_sql, RaiseableEnumerator.new(resulting_stream), execute_streaming_sql_request + + commit_request = [{ + session: session_grpc.name, + transaction_id: tx_id, + single_use_transaction: nil, + mutations: [], + request_options: nil, + precommit_token: partial_results_5.precommit_token + }, default_options] + + service_mock.expect :commit, commit_resp, commit_request + + spanner.service.mocked_service = service_mock + + # @type [::Google::Cloud::Spanner::Client] + sp_client = client + + sp_client.transaction do |tx| + results = tx.execute_query sql_query + _(tx.precommit_token.precommit_token).must_equal precommit_token_0 + _(tx.precommit_token.seq_num).must_equal 0 + results.rows.to_a + _(tx.precommit_token.precommit_token).must_equal precommit_token_1 + _(tx.precommit_token.seq_num).must_equal 1 + end + + shutdown_client! sp_client + service_mock.verify + end + end +end From 8b4c036ea7c2d0019e36dafb70953b649feb5d8b Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 28 Oct 2025 16:09:36 -0700 Subject: [PATCH 16/25] tests: messy rest of tests --- .../spanner/client/batch_update_test.rb | 10 + .../acceptance/spanner/client/reset_test.rb | 2 +- .../support/doctest_helper.rb | 18 +- .../google/cloud/spanner/batch_client_test.rb | 36 ++-- .../google/cloud/spanner/client/admin_test.rb | 2 +- .../cloud/spanner/client/batch_write_test.rb | 4 +- .../client/commit_field_values_test.rb | 26 +-- .../cloud/spanner/client/commit_test.rb | 98 ++++----- .../spanner/client/commit_timestamp_test.rb | 2 +- .../client/commit_transaction_tag_test.rb | 22 +-- .../client/execute_partition_update_test.rb | 90 ++++----- .../client/execute_query_resume_test.rb | 14 +- .../client/execute_query_single_use_test.rb | 20 +- .../spanner/client/execute_query_test.rb | 60 +++--- .../cloud/spanner/client/fields_for_test.rb | 4 +- .../google/cloud/spanner/client/range_test.rb | 2 +- .../cloud/spanner/client/read_error_test.rb | 4 +- .../client/read_resume_buffer_bound_test.rb | 8 +- .../cloud/spanner/client/read_resume_test.rb | 4 +- .../spanner/client/read_single_use_test.rb | 20 +- .../google/cloud/spanner/client/read_test.rb | 36 ++-- .../cloud/spanner/client/snapshot_test.rb | 11 +- .../client/transaction_results_retry_test.rb | 10 +- .../spanner/client/transaction_retry_test.rb | 85 +++----- .../client/transaction_rollback_test.rb | 8 +- .../cloud/spanner/client/transaction_test.rb | 187 ++++++++++++++---- .../client/commit_test.rb | 2 +- .../client/delete_test.rb | 2 +- .../client/execute_query_test.rb | 2 +- .../client/insert_test.rb | 2 +- .../leader_aware_routing/client/read_test.rb | 2 +- .../client/replace_test.rb | 2 +- .../client/update_test.rb | 2 +- .../client/upsert_test.rb | 2 +- .../session/get_session_test.rb | 25 ++- .../transaction/batch_update_test.rb | 2 +- .../transaction/execute_query_test.rb | 2 +- .../transaction/read_test.rb | 2 +- .../pool/batch_create_sessions_test.rb | 9 +- .../google/cloud/spanner/pool/close_test.rb | 21 +- .../spanner/pool/keepalive_or_release_test.rb | 15 +- .../pool/new_sessions_in_process_test.rb | 10 +- .../test/google/cloud/spanner/pool_test.rb | 8 +- .../test/google/cloud/spanner/project_test.rb | 12 +- .../spanner/results/anonymous_struct_test.rb | 2 +- .../results/deeply_nested_list_test.rb | 2 +- .../spanner/results/duplicate_struct_test.rb | 2 +- .../cloud/spanner/results/duplicate_test.rb | 2 +- .../spanner/results/empty_field_names_test.rb | 2 +- .../spanner/results/empty_fields_test.rb | 2 +- .../cloud/spanner/results/empty_rows_test.rb | 2 +- .../spanner/results/from_enum_single_test.rb | 2 +- .../cloud/spanner/results/from_enum_test.rb | 2 +- .../cloud/spanner/results/merge_test.rb | 16 +- .../spanner/results/nested_struct_test.rb | 2 +- .../cloud/spanner/results/row_count_test.rb | 6 +- .../cloud/spanner/results/timestamp_test.rb | 2 +- .../test/google/cloud/spanner/service_test.rb | 9 +- .../spanner/session/commit_proto_test.rb | 16 +- .../cloud/spanner/session/commit_test.rb | 22 +-- .../cloud/spanner/session/keepalive_test.rb | 14 ++ .../spanner/snapshot/execute_query_test.rb | 4 +- .../cloud/spanner/snapshot/metadata_test.rb | 2 +- .../cloud/spanner/snapshot/range_test.rb | 2 +- .../cloud/spanner/snapshot/read_test.rb | 4 +- .../spanner/transaction/batch_update_test.rb | 24 ++- .../transaction/concurrent_queries_test.rb | 3 +- .../test/google/cloud/spanner_test.rb | 4 +- google-cloud-spanner/test/helper.rb | 26 ++- 69 files changed, 596 insertions(+), 483 deletions(-) diff --git a/google-cloud-spanner/acceptance/spanner/client/batch_update_test.rb b/google-cloud-spanner/acceptance/spanner/client/batch_update_test.rb index b3cbf372..5fa16c28 100644 --- a/google-cloud-spanner/acceptance/spanner/client/batch_update_test.rb +++ b/google-cloud-spanner/acceptance/spanner/client/batch_update_test.rb @@ -149,6 +149,11 @@ end it "raises BatchUpdateError when the first statement in Batch DML is a syntax error for #{dialect}" do + # ** When using the emulator with multiplexed sessions** + # the BatchUpdate transaction in this test will not get cleaned up and that will cause the + # "The emulator only supports one transaction at a time." failure. + skip if emulator_enabled? + prior_results = db[dialect].execute_sql "SELECT * FROM accounts" _(prior_results.rows.count).must_equal 3 db[dialect].transaction do |tx| @@ -198,6 +203,11 @@ describe "request options for #{dialect}" do it "execute batch update with priority options for #{dialect}" do + # ** When using the emulator with multiplexed sessions** + # the BatchUpdate transaction in this test will not get cleaned up and that will cause the + # "The emulator only supports one transaction at a time." failure. + skip if emulator_enabled? + db[dialect].transaction do |tx| row_counts = tx.batch_update request_options: { priority: :PRIORITY_HIGH } do |b| b.batch_update insert_dml[dialect], params: insert_params[dialect] diff --git a/google-cloud-spanner/acceptance/spanner/client/reset_test.rb b/google-cloud-spanner/acceptance/spanner/client/reset_test.rb index 15280a22..2a598afc 100644 --- a/google-cloud-spanner/acceptance/spanner/client/reset_test.rb +++ b/google-cloud-spanner/acceptance/spanner/client/reset_test.rb @@ -18,7 +18,7 @@ let(:db) { spanner_client } it "closes all sessions and creates new sessions" do - _(db.reset).must_equal true + _(db.reset!).must_equal true _(db.execute_query("SELECT 1").rows.first.values).must_equal [1] end end diff --git a/google-cloud-spanner/support/doctest_helper.rb b/google-cloud-spanner/support/doctest_helper.rb index 317c6784..7ebd67df 100644 --- a/google-cloud-spanner/support/doctest_helper.rb +++ b/google-cloud-spanner/support/doctest_helper.rb @@ -282,7 +282,7 @@ def verify_mock_params actual, expected doctest.before "Google::Cloud::Spanner::BatchClient" do mock_spanner do |mock, mock_instances, mock_databases| - mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: nil }, Hash] + mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: default_session_request }, Hash] mock.expect :begin_transaction, tx_resp do |req| verify_mock_params(req, session: "session-name", options: Google::Cloud::Spanner::V1::TransactionOptions) end @@ -300,7 +300,7 @@ def verify_mock_params actual, expected doctest.before "Google::Cloud::Spanner::BatchSnapshot" do mock_spanner do |mock, mock_instances, mock_databases| - mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: nil }, Hash] + mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: default_session_request }, Hash] mock.expect :begin_transaction, tx_resp do |req| verify_mock_params(req, session: "session-name", options: Google::Cloud::Spanner::V1::TransactionOptions) end @@ -316,7 +316,7 @@ def verify_mock_params actual, expected doctest.before "Google::Cloud::Spanner::BatchSnapshot#partition_query" do mock_spanner do |mock, mock_instances, mock_databases| - mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: nil }, Hash] + mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: default_session_request }, Hash] mock.expect :begin_transaction, tx_resp do |req| verify_mock_params(req, session: "session-name", options: Google::Cloud::Spanner::V1::TransactionOptions) end @@ -332,7 +332,7 @@ def verify_mock_params actual, expected doctest.before "Google::Cloud::Spanner::BatchSnapshot#execute" do mock_spanner do |mock, mock_instances, mock_databases| - mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: nil }, Hash] + mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: default_session_request }, Hash] 5.times do mock.expect :begin_transaction, tx_resp do |req| verify_mock_params(req, session: "session-name", options: Google::Cloud::Spanner::V1::TransactionOptions) @@ -349,7 +349,7 @@ def verify_mock_params actual, expected doctest.before "Google::Cloud::Spanner::BatchSnapshot#execute_sql" do mock_spanner do |mock, mock_instances, mock_databases| - mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: nil }, Hash] + mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: default_session_request }, Hash] 5.times do mock.expect :begin_transaction, tx_resp do |req| verify_mock_params(req, session: "session-name", options: Google::Cloud::Spanner::V1::TransactionOptions) @@ -366,7 +366,7 @@ def verify_mock_params actual, expected doctest.before "Google::Cloud::Spanner::BatchSnapshot#execute_partition" do mock_spanner do |mock, mock_instances, mock_databases| - mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: nil }, Hash] + mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: default_session_request }, Hash] mock.expect :begin_transaction, tx_resp do |req| verify_mock_params(req, session: "session-name", options: Google::Cloud::Spanner::V1::TransactionOptions) end @@ -382,7 +382,7 @@ def verify_mock_params actual, expected doctest.before "Google::Cloud::Spanner::BatchSnapshot#execute_sql_partition" do mock_spanner do |mock, mock_instances, mock_databases| - mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: nil }, Hash] + mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: default_session_request }, Hash] mock.expect :begin_transaction, tx_resp do |req| verify_mock_params(req, session: "session-name", options: Google::Cloud::Spanner::V1::TransactionOptions) end @@ -419,7 +419,7 @@ def verify_mock_params actual, expected doctest.before "Google::Cloud::Spanner::Partition" do mock_spanner do |mock, mock_instances, mock_databases| - mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: nil }, Hash] + mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: default_session_request }, Hash] mock.expect :begin_transaction, tx_resp do |req| verify_mock_params(req, session: "session-name", options: Google::Cloud::Spanner::V1::TransactionOptions) end @@ -473,7 +473,7 @@ def verify_mock_params actual, expected doctest.before "Google::Cloud::Spanner::Project#batch_client" do mock_spanner do |mock, mock_instances, mock_databases| - mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: nil }, Hash] + mock.expect :create_session, session_grpc, [{ database: "projects/my-project/instances/my-instance/databases/my-database", session: default_session_request }, Hash] mock.expect :begin_transaction, tx_resp do |req| verify_mock_params(req, session: "session-name", options: Google::Cloud::Spanner::V1::TransactionOptions) end diff --git a/google-cloud-spanner/test/google/cloud/spanner/batch_client_test.rb b/google-cloud-spanner/test/google/cloud/spanner/batch_client_test.rb index b93c8563..4d98b70e 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/batch_client_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/batch_client_test.rb @@ -82,8 +82,8 @@ it "creates a batch_snapshot" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, mutation_key: nil}, default_options] spanner.service.mocked_service = mock batch_snapshot = batch_client.batch_snapshot @@ -97,10 +97,10 @@ it "creates a batch_snapshot with session labels" do mock = Minitest::Mock.new - session_labels_grpc = Google::Cloud::Spanner::V1::Session.new labels: labels + session_labels_grpc = Google::Cloud::Spanner::V1::Session.new labels: labels, multiplexed: true session_labels_resp_grpc = Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id), labels: labels mock.expect :create_session, session_labels_resp_grpc, [{ database: database_path(instance_id, database_id), session: session_labels_grpc }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock batch_snapshot = batch_client_labels.batch_snapshot @@ -117,8 +117,8 @@ it "creates a batch_snapshot with strong timestamp bound" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock batch_snapshot = batch_client.batch_snapshot strong: true @@ -139,8 +139,8 @@ it "creates a batch_snapshot with timestamp option (Time)" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock batch_snapshot = batch_client.batch_snapshot timestamp: snapshot_time @@ -154,8 +154,8 @@ it "creates a batch_snapshot with read_timestamp option (Time)" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock batch_snapshot = batch_client.batch_snapshot read_timestamp: snapshot_time @@ -169,8 +169,8 @@ it "creates a batch_snapshot with timestamp option (DateTime)" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock batch_snapshot = batch_client.batch_snapshot timestamp: snapshot_datetime @@ -184,8 +184,8 @@ it "creates a batch_snapshot with read_timestamp option (DateTime)" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock batch_snapshot = batch_client.batch_snapshot read_timestamp: snapshot_datetime @@ -205,8 +205,8 @@ it "creates a batch_snapshot with the staleness option" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock batch_snapshot = batch_client.batch_snapshot staleness: snapshot_staleness @@ -220,8 +220,8 @@ it "creates a batch_snapshot with the exact_staleness option" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock batch_snapshot = batch_client.batch_snapshot exact_staleness: snapshot_staleness diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/admin_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/admin_test.rb index 4267436d..0310f68b 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/admin_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/admin_test.rb @@ -21,7 +21,7 @@ let(:session_grpc) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id) } let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } let(:default_options) { ::Gapic::CallOptions.new "google-cloud-resource-prefix" => database_path(instance_id, database_id) } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } after do shutdown_client! client diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/batch_write_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/batch_write_test.rb index 788be14f..7ddb895d 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/batch_write_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/batch_write_test.rb @@ -20,7 +20,7 @@ let(:session_id) { "session123" } let(:session_grpc) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id) } let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } let(:user_mutations) { [ Google::Cloud::Spanner::V1::Mutation.new( @@ -116,7 +116,7 @@ it "batch writes using groups of mutations" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request}, default_options] mock.expect :batch_write, responses_enum, [{ session: session_grpc.name, mutation_groups: mutation_groups, request_options: nil, exclude_txn_from_change_streams: true }, default_options] spanner.service.mocked_service = mock diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/commit_field_values_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/commit_field_values_test.rb index 97d1ee57..d72f05a5 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/commit_field_values_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/commit_field_values_test.rb @@ -38,7 +38,7 @@ let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } describe "commit_timestamp" do it "commits using a block" do @@ -70,8 +70,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock timestamp = client.commit do |c| @@ -98,8 +98,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil}, default_options] spanner.service.mocked_service = mock timestamp = client.update "users", [{ id: 1, name: "Charlie", updated_at: client.commit_timestamp }] @@ -121,8 +121,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil}, default_options] spanner.service.mocked_service = mock timestamp = client.insert "users", [{ id: 2, name: "Harvey", updated_at: client.commit_timestamp }] @@ -144,8 +144,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil}, default_options] spanner.service.mocked_service = mock timestamp = client.upsert "users", [{ id: 3, name: "Marley", updated_at: client.commit_timestamp }] @@ -167,8 +167,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil}, default_options] spanner.service.mocked_service = mock timestamp = client.save "users", [{ id: 3, name: "Marley", updated_at: client.commit_timestamp }] @@ -190,8 +190,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil}, default_options] spanner.service.mocked_service = mock timestamp = client.replace "users", [{ id: 4, name: "Henry", updated_at: client.commit_timestamp }] diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/commit_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/commit_test.rb index d6415e1c..08052481 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/commit_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/commit_test.rb @@ -38,7 +38,7 @@ exclude_txn_from_change_streams: true } let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } let(:mutations) { [ Google::Cloud::Spanner::V1::Mutation.new( @@ -79,8 +79,8 @@ it "commits using a block" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock timestamp = client.commit do |c| @@ -108,8 +108,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock timestamp = client.update "users", [{ id: 1, name: "Charlie", active: false }] @@ -131,8 +131,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock timestamp = client.insert "users", [{ id: 2, name: "Harvey", active: true }] @@ -154,8 +154,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock timestamp = client.upsert "users", [{ id: 3, name: "Marley", active: false }] @@ -177,8 +177,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock timestamp = client.save "users", [{ id: 3, name: "Marley", active: false }] @@ -200,8 +200,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock timestamp = client.replace "users", [{ id: 4, name: "Henry", active: true }] @@ -226,8 +226,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock timestamp = client.delete "users", [1, 2, 3, 4, 5] @@ -257,8 +257,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock timestamp = client.delete "users", [time1, time2, time3, time4] @@ -281,8 +281,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock timestamp = client.delete "users", 1..100 @@ -307,8 +307,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock timestamp = client.delete "users", 5 @@ -335,8 +335,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock timestamp = client.delete "users", time5 @@ -357,8 +357,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock timestamp = client.delete "users" @@ -389,8 +389,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, expect_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, expect_options] spanner.service.mocked_service = mock timestamp = client.delete "users", call_options: call_options @@ -413,8 +413,8 @@ call_options = { timeout: timeout, retry_policy: retry_policy } mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, expect_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, expect_options] spanner.service.mocked_service = mock timestamp = client.commit call_options: call_options do |c| @@ -441,8 +441,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts_with_change_stream_exclusion, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts_with_change_stream_exclusion, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock timestamp = client.delete "users", [], exclude_txn_from_change_streams: true @@ -464,8 +464,8 @@ ) )] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_stats_resp_grpc, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, return_commit_stats: true, request_options: nil}, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_stats_resp_grpc, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, return_commit_stats: true, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock commit_resp = client.commit commit_options: commit_options do |c| @@ -488,8 +488,8 @@ ) ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_stats_resp_grpc, [{session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, return_commit_stats: true, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_stats_resp_grpc, [{session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, return_commit_stats: true, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock commit_resp = client.insert "users", [{ id: 2, name: "Harvey", active: true }], commit_options: commit_options @@ -510,8 +510,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_stats_resp_grpc, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, return_commit_stats: true, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_stats_resp_grpc, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, return_commit_stats: true, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock commit_resp = client.update "users", [{ id: 1, name: "Charlie", active: false }], commit_options: commit_options @@ -532,8 +532,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_stats_resp_grpc, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, return_commit_stats: true, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_stats_resp_grpc, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, return_commit_stats: true, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock commit_resp = client.save "users", [{ id: 3, name: "Marley", active: false }], commit_options: commit_options @@ -557,8 +557,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_stats_resp_grpc, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, return_commit_stats: true, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_stats_resp_grpc, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, return_commit_stats: true, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock commit_resp = client.delete "users", [1, 2, 3, 4, 5], commit_options: commit_options @@ -579,8 +579,8 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :commit, commit_stats_resp_grpc, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, return_commit_stats: true, request_options: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_stats_resp_grpc, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, return_commit_stats: true, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock commit_resp = client.replace "users", [{ id: 4, name: "Henry", updated_at: client.commit_timestamp }], commit_options: commit_options @@ -600,8 +600,8 @@ commit_options[:max_commit_delay] = 120 commit_delay_duration = Google::Cloud::Spanner::Convert.number_to_duration(120, millisecond: true) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] - mock.expect :commit, commit_stats_resp_grpc, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, return_commit_stats: true, max_commit_delay: commit_delay_duration, request_options: nil}, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_stats_resp_grpc, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, return_commit_stats: true, max_commit_delay: commit_delay_duration, request_options: nil, precommit_token: nil }, default_options] spanner.service.mocked_service = mock commit_resp = client.commit commit_options: commit_options do |c| @@ -684,11 +684,11 @@ it "commits using a block" do mock = Minitest::Mock.new mock.expect :create_session, session_grpc, [{ - database: database_path(instance_id, database_id), session: nil + database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, - single_use_transaction: tx_opts, request_options: request_options + single_use_transaction: tx_opts, request_options: request_options, precommit_token: nil }, default_options] spanner.service.mocked_service = mock @@ -710,12 +710,12 @@ def mock_commit_request mutations, request_options: nil mock = Minitest::Mock.new mock.expect :create_session, session_grpc, [{ - database: database_path(instance_id, database_id), session: nil + database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, - request_options: request_options + request_options: request_options, precommit_token: nil }, default_options] spanner.service.mocked_service = mock diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/commit_timestamp_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/commit_timestamp_test.rb index fbcd1d69..e4178b3f 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/commit_timestamp_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/commit_timestamp_test.rb @@ -21,7 +21,7 @@ let(:session_grpc) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id) } let(:commit_resp) { Google::Cloud::Spanner::V1::CommitResponse.new commit_timestamp: Google::Protobuf::Timestamp.new() } let(:tx_opts) { Google::Cloud::Spanner::V1::TransactionOptions.new(read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new) } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } after do shutdown_client! client diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/commit_transaction_tag_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/commit_transaction_tag_test.rb index 95369486..04f17819 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/commit_transaction_tag_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/commit_transaction_tag_test.rb @@ -24,7 +24,7 @@ let(:commit_resp) { Google::Cloud::Spanner::V1::CommitResponse.new commit_timestamp: commit_timestamp } let(:tx_opts) { Google::Cloud::Spanner::V1::TransactionOptions.new(read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new) } let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } it "commits using a block" do mutations = [ @@ -37,10 +37,10 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, - single_use_transaction: tx_opts, request_options: { transaction_tag: "Tag-1"} + single_use_transaction: tx_opts, request_options: { transaction_tag: "Tag-1"}, precommit_token: nil }, default_options] spanner.service.mocked_service = mock @@ -65,10 +65,10 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, - single_use_transaction: tx_opts, request_options: { transaction_tag: "Tag-2" } + single_use_transaction: tx_opts, request_options: { transaction_tag: "Tag-2" }, precommit_token: nil }, default_options] spanner.service.mocked_service = mock @@ -92,10 +92,10 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, - single_use_transaction: tx_opts, request_options: { transaction_tag: "Tag-3" } + single_use_transaction: tx_opts, request_options: { transaction_tag: "Tag-3" }, precommit_token: nil }, default_options] spanner.service.mocked_service = mock @@ -119,10 +119,10 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, - single_use_transaction: tx_opts, request_options: { transaction_tag: "Tag-4" } + single_use_transaction: tx_opts, request_options: { transaction_tag: "Tag-4" }, precommit_token: nil }, default_options] spanner.service.mocked_service = mock @@ -149,10 +149,10 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: nil}, default_options] + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, - single_use_transaction: tx_opts, request_options: { transaction_tag: "Tag-5" } + single_use_transaction: tx_opts, request_options: { transaction_tag: "Tag-5" }, precommit_token: nil }, default_options] spanner.service.mocked_service = mock diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/execute_partition_update_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/execute_partition_update_test.rb index 89d245db..6e618624 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/execute_partition_update_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/execute_partition_update_test.rb @@ -29,7 +29,7 @@ Google::Cloud::Spanner::V1::TransactionOptions.new partitioned_dml: Google::Cloud::Spanner::V1::TransactionOptions::PartitionedDml.new, exclude_txn_from_change_streams: true } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } let(:results_grpc) { Google::Cloud::Spanner::V1::PartialResultSet.new( @@ -48,8 +48,8 @@ it "can execute a PDML statement" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET active = true", transaction: tx_selector, options: default_options @@ -62,8 +62,8 @@ it "can execute a PDML statement with bool param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET active = @active", transaction: tx_selector, params: Google::Protobuf::Struct.new(fields: { "active" => Google::Protobuf::Value.new(bool_value: true) }), param_types: { "active" => Google::Cloud::Spanner::V1::Type.new(code: :BOOL) }, options: default_options @@ -76,8 +76,8 @@ it "can execute a PDML statement with int param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET age = @age", transaction: tx_selector, params: Google::Protobuf::Struct.new(fields: { "age" => Google::Protobuf::Value.new(string_value: "29") }), param_types: { "age" => Google::Cloud::Spanner::V1::Type.new(code: :INT64) }, options: default_options @@ -90,8 +90,8 @@ it "can execute a PDML statement with float param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET score = @score", transaction: tx_selector, params: Google::Protobuf::Struct.new(fields: { "score" => Google::Protobuf::Value.new(number_value: 0.9) }), param_types: { "score" => Google::Cloud::Spanner::V1::Type.new(code: :FLOAT64) }, options: default_options @@ -106,8 +106,8 @@ timestamp = Time.parse "2017-01-01 20:04:05.06 -0700" mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET updated_at = @updated_at", transaction: tx_selector, params: Google::Protobuf::Struct.new(fields: { "updated_at" => Google::Protobuf::Value.new(string_value: "2017-01-02T03:04:05.060000000Z") }), param_types: { "updated_at" => Google::Cloud::Spanner::V1::Type.new(code: :TIMESTAMP) }, options: default_options @@ -122,8 +122,8 @@ date = Date.parse "2017-01-02" mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET birthday = @birthday", transaction: tx_selector, params: Google::Protobuf::Struct.new(fields: { "birthday" => Google::Protobuf::Value.new(string_value: "2017-01-02") }), param_types: { "birthday" => Google::Cloud::Spanner::V1::Type.new(code: :DATE) }, options: default_options @@ -136,8 +136,8 @@ it "can execute a PDML statement with String param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET name = @name", transaction: tx_selector, params: Google::Protobuf::Struct.new(fields: { "name" => Google::Protobuf::Value.new(string_value: "Charlie") }), param_types: { "name" => Google::Cloud::Spanner::V1::Type.new(code: :STRING) }, options: default_options @@ -152,8 +152,8 @@ file = StringIO.new "contents" mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET avatar = @avatar", transaction: tx_selector, params: Google::Protobuf::Struct.new(fields: { "avatar" => Google::Protobuf::Value.new(string_value: Base64.strict_encode64("contents")) }), param_types: { "avatar" => Google::Cloud::Spanner::V1::Type.new(code: :BYTES) }, options: default_options @@ -166,8 +166,8 @@ it "can execute a PDML statement with an Array param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET project_ids = @list", transaction: tx_selector, params: Google::Protobuf::Struct.new(fields: { "list" => Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "1"), Google::Protobuf::Value.new(string_value: "2"), Google::Protobuf::Value.new(string_value: "3")])) }), param_types: { "list" => Google::Cloud::Spanner::V1::Type.new(code: :ARRAY, array_element_type: Google::Cloud::Spanner::V1::Type.new(code: :INT64)) }, options: default_options @@ -180,8 +180,8 @@ it "can execute a PDML statement with an empty Array param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET project_ids = @list", transaction: tx_selector, params: Google::Protobuf::Struct.new(fields: { "list" => Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [])) }), param_types: { "list" => Google::Cloud::Spanner::V1::Type.new(code: :ARRAY, array_element_type: Google::Cloud::Spanner::V1::Type.new(code: :INT64)) }, options: default_options @@ -194,8 +194,8 @@ it "can execute a PDML statement with a simple Hash param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET settings = @dict", transaction: tx_selector, params: Google::Protobuf::Struct.new(fields: { "dict" => Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "production")])) }), param_types: { "dict" => Google::Cloud::Spanner::V1::Type.new(code: :STRUCT, struct_type: Google::Cloud::Spanner::V1::StructType.new(fields: [Google::Cloud::Spanner::V1::StructType::Field.new(name: "env", type: Google::Cloud::Spanner::V1::Type.new(code: :STRING))])) }, options: default_options @@ -208,8 +208,8 @@ it "can execute a PDML statement with a complex Hash param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET settings = @dict", transaction: tx_selector, params: Google::Protobuf::Struct.new(fields: { "dict" => Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "production"), Google::Protobuf::Value.new(number_value: 0.9), Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "1"), Google::Protobuf::Value.new(string_value: "2"), Google::Protobuf::Value.new(string_value: "3")] )) ])) }), param_types: { "dict" => Google::Cloud::Spanner::V1::Type.new(code: :STRUCT, struct_type: Google::Cloud::Spanner::V1::StructType.new(fields: [Google::Cloud::Spanner::V1::StructType::Field.new(name: "env", type: Google::Cloud::Spanner::V1::Type.new(code: :STRING)), Google::Cloud::Spanner::V1::StructType::Field.new(name: "score", type: Google::Cloud::Spanner::V1::Type.new(code: :FLOAT64)), Google::Cloud::Spanner::V1::StructType::Field.new(name: "project_ids", type: Google::Cloud::Spanner::V1::Type.new(code: :ARRAY, array_element_type: Google::Cloud::Spanner::V1::Type.new(code: :INT64)))] )) }, options: default_options @@ -222,8 +222,8 @@ it "can execute a PDML statement with an empty Hash param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET settings = @dict", transaction: tx_selector, params: Google::Protobuf::Struct.new(fields: { "dict" => Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [])) }), param_types: { "dict" => Google::Cloud::Spanner::V1::Type.new(code: :STRUCT, struct_type: Google::Cloud::Spanner::V1::StructType.new(fields: [])) }, options: default_options @@ -240,8 +240,8 @@ no_stats_results_enum = Array(no_stats_results_grpc).to_enum mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql no_stats_results_enum, session_grpc.name, "UPDATE users SET active = true", transaction: tx_selector, options: default_options @@ -255,8 +255,8 @@ it "can execute a PDML statement with query options" do expect_query_options = { optimizer_version: "1", optimizer_statistics_package: "auto_20191128_14_47_22UTC" } mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET active = true", transaction: tx_selector, options: default_options, query_options: expect_query_options @@ -269,10 +269,10 @@ it "can execute a PDML statement with query options (environment variable or client-level)" do expect_query_options = { optimizer_version: "1", optimizer_statistics_package: "auto_20191128_14_47_22UTC" } - new_client = spanner.client instance_id, database_id, pool: { min: 0 }, query_options: expect_query_options + new_client = spanner.client instance_id, database_id, query_options: expect_query_options mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET active = true", transaction: tx_selector, options: default_options, query_options: expect_query_options @@ -285,10 +285,10 @@ it "can execute a PDML statement with query options that query-level configs merge over environment variable or client-level configs" do expect_query_options = { optimizer_version: "2", optimizer_statistics_package: "auto_20191128_14_47_22UTC" } - new_client = spanner.client instance_id, database_id, pool: { min: 0 }, query_options: { optimizer_version: "1", optimizer_statistics_package: "auto_20191128_14_47_22UTC" } + new_client = spanner.client instance_id, database_id, query_options: { optimizer_version: "1", optimizer_statistics_package: "auto_20191128_14_47_22UTC" } mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET active = true", transaction: tx_selector, options: default_options, query_options: expect_query_options @@ -311,8 +311,8 @@ call_options = { timeout: timeout, retry_policy: retry_policy } mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET active = true", transaction: tx_selector, options: expect_options @@ -326,8 +326,8 @@ describe "priority request options" do it "execute a PDML statement" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET active = true", @@ -346,8 +346,8 @@ it "can execute a PDML statement with request tag" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{session: session_grpc.name, options: pdml_tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{session: session_grpc.name, options: pdml_tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET active = true", transaction: tx_selector, request_options: { request_tag: "Tag-2" }, @@ -362,8 +362,8 @@ it "can execute a PDML statement while excluding from change streams" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{session: session_grpc.name, options: pdml_tx_opts_with_change_stream_exclusion }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{session: session_grpc.name, options: pdml_tx_opts_with_change_stream_exclusion, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "UPDATE users SET active = true", transaction: tx_selector, options: default_options diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/execute_query_resume_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/execute_query_resume_test.rb index f9692bfc..717f3b91 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/execute_query_resume_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/execute_query_resume_test.rb @@ -102,7 +102,7 @@ end let(:service_mock) { Minitest::Mock.new } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } before do spanner.service.mocked_service = service_mock @@ -128,7 +128,7 @@ Google::Cloud::Spanner::V1::PartialResultSet.new(partial_row_4), Google::Cloud::Spanner::V1::PartialResultSet.new(partial_row_5) ].to_enum - service_mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + service_mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql RaiseableEnumerator.new(resulting_stream_1), session_grpc.name, "SELECT * FROM users", options: default_options expect_execute_streaming_sql RaiseableEnumerator.new(resulting_stream_2), session_grpc.name, "SELECT * FROM users", resume_token: "abc123", options: default_options @@ -151,7 +151,7 @@ resulting_stream_3 = [ Google::Cloud::Spanner::V1::PartialResultSet.new(full_row) ].to_enum - service_mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + service_mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql RaiseableEnumerator.new(resulting_stream_1), session_grpc.name, "SELECT * FROM users", options: default_options expect_execute_streaming_sql RaiseableEnumerator.new(resulting_stream_2), session_grpc.name, "SELECT * FROM users", options: default_options expect_execute_streaming_sql RaiseableEnumerator.new(resulting_stream_3), session_grpc.name, "SELECT * FROM users", options: default_options @@ -173,7 +173,7 @@ resulting_stream_3 = [ Google::Cloud::Spanner::V1::PartialResultSet.new(full_row) ].to_enum - service_mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + service_mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql RaiseableEnumerator.new(resulting_stream_1), session_grpc.name, "SELECT * FROM users", options: default_options expect_execute_streaming_sql RaiseableEnumerator.new(resulting_stream_2), session_grpc.name, "SELECT * FROM users", options: default_options expect_execute_streaming_sql RaiseableEnumerator.new(resulting_stream_3), session_grpc.name, "SELECT * FROM users", options: default_options @@ -196,7 +196,7 @@ resulting_stream_3 = [ Google::Cloud::Spanner::V1::PartialResultSet.new(full_row) ].to_enum - service_mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + service_mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql RaiseableEnumerator.new(resulting_stream_1), session_grpc.name, "SELECT * FROM users", options: default_options expect_execute_streaming_sql RaiseableEnumerator.new(resulting_stream_2), session_grpc.name, "SELECT * FROM users", options: default_options expect_execute_streaming_sql RaiseableEnumerator.new(resulting_stream_3), session_grpc.name, "SELECT * FROM users", options: default_options @@ -212,7 +212,7 @@ Google::Cloud::Spanner::V1::PartialResultSet.new(metadata_result), GRPC::Internal.new("INTERNAL: Generic (Internal server error)"), ].to_enum - service_mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + service_mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql RaiseableEnumerator.new(resulting_stream_1), session_grpc.name, "SELECT * FROM users", options: default_options assert_raises Google::Cloud::Error do @@ -226,7 +226,7 @@ Google::Cloud::Spanner::V1::PartialResultSet.new(metadata_result), Google::Cloud::InternalError.new("INTERNAL: Generic (Internal server error)"), ].to_enum - service_mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + service_mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql RaiseableEnumerator.new(resulting_stream_1), session_grpc.name, "SELECT * FROM users", options: default_options assert_raises Google::Cloud::Error do diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/execute_query_single_use_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/execute_query_single_use_test.rb index 2a0b7f21..fdc5c17f 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/execute_query_single_use_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/execute_query_single_use_test.rb @@ -55,7 +55,7 @@ end let(:results_grpc) { Google::Cloud::Spanner::V1::PartialResultSet.new results_hash } let(:results_enum) { Array(results_grpc).to_enum } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } let(:time_obj) { Time.parse "2014-10-02T15:01:23.045123456Z" } let(:timestamp) { Google::Cloud::Spanner::Convert.time_to_timestamp time_obj } let(:duration) { Google::Cloud::Spanner::Convert.number_to_duration 120 } @@ -70,7 +70,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: transaction, options: default_options @@ -93,7 +93,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: transaction, options: default_options @@ -116,7 +116,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: transaction, options: default_options @@ -139,7 +139,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: transaction, options: default_options @@ -162,7 +162,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: transaction, options: default_options @@ -185,7 +185,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: transaction, options: default_options @@ -208,7 +208,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: transaction, options: default_options @@ -231,7 +231,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: transaction, options: default_options @@ -254,7 +254,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: transaction, options: default_options diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/execute_query_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/execute_query_test.rb index 4fa934e5..f6f78e2c 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/execute_query_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/execute_query_test.rb @@ -55,11 +55,11 @@ end let(:results_grpc) { Google::Cloud::Spanner::V1::PartialResultSet.new results_hash } let(:results_enum) { Array(results_grpc).to_enum } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } it "can execute a simple query" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", options: default_options @@ -74,7 +74,7 @@ it "can execute a query using execute alias" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", options: default_options @@ -89,7 +89,7 @@ it "can execute a query using execute_sql alias" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", options: default_options @@ -104,7 +104,7 @@ it "can execute a query using query alias" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", options: default_options @@ -119,7 +119,7 @@ it "can execute a query with bool param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users WHERE active = @active", params: Google::Protobuf::Struct.new(fields: { "active" => Google::Protobuf::Value.new(bool_value: true) }), param_types: { "active" => Google::Cloud::Spanner::V1::Type.new(code: :BOOL) }, options: default_options @@ -134,7 +134,7 @@ it "can execute a query with int param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users WHERE age = @age", params: Google::Protobuf::Struct.new(fields: { "age" => Google::Protobuf::Value.new(string_value: "29") }), param_types: { "age" => Google::Cloud::Spanner::V1::Type.new(code: :INT64) }, options: default_options @@ -149,7 +149,7 @@ it "can execute a query with float param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users WHERE score = @score", params: Google::Protobuf::Struct.new(fields: { "score" => Google::Protobuf::Value.new(number_value: 0.9) }), param_types: { "score" => Google::Cloud::Spanner::V1::Type.new(code: :FLOAT64) }, options: default_options @@ -166,7 +166,7 @@ timestamp = Time.parse "2017-01-01 20:04:05.06 -0700" mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users WHERE updated_at = @updated_at", params: Google::Protobuf::Struct.new(fields: { "updated_at" => Google::Protobuf::Value.new(string_value: "2017-01-02T03:04:05.060000000Z") }), param_types: { "updated_at" => Google::Cloud::Spanner::V1::Type.new(code: :TIMESTAMP) }, options: default_options @@ -183,7 +183,7 @@ date = Date.parse "2017-01-02" mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users WHERE birthday = @birthday", params: Google::Protobuf::Struct.new(fields: { "birthday" => Google::Protobuf::Value.new(string_value: "2017-01-02") }), param_types: { "birthday" => Google::Cloud::Spanner::V1::Type.new(code: :DATE) }, options: default_options @@ -198,7 +198,7 @@ it "can execute a query with String param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users WHERE name = @name", params: Google::Protobuf::Struct.new(fields: { "name" => Google::Protobuf::Value.new(string_value: "Charlie") }), param_types: { "name" => Google::Cloud::Spanner::V1::Type.new(code: :STRING) }, options: default_options @@ -215,7 +215,7 @@ file = StringIO.new "contents" mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users WHERE avatar = @avatar", params: Google::Protobuf::Struct.new(fields: { "avatar" => Google::Protobuf::Value.new(string_value: Base64.strict_encode64("contents")) }), param_types: { "avatar" => Google::Cloud::Spanner::V1::Type.new(code: :BYTES) }, options: default_options @@ -230,7 +230,7 @@ it "can execute a query with an Array param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users WHERE project_ids = @list", params: Google::Protobuf::Struct.new(fields: { "list" => Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "1"), Google::Protobuf::Value.new(string_value: "2"), Google::Protobuf::Value.new(string_value: "3")])) }), param_types: { "list" => Google::Cloud::Spanner::V1::Type.new(code: :ARRAY, array_element_type: Google::Cloud::Spanner::V1::Type.new(code: :INT64)) }, options: default_options @@ -245,7 +245,7 @@ it "can execute a query with an empty Array param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users WHERE project_ids = @list", params: Google::Protobuf::Struct.new(fields: { "list" => Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [])) }), param_types: { "list" => Google::Cloud::Spanner::V1::Type.new(code: :ARRAY, array_element_type: Google::Cloud::Spanner::V1::Type.new(code: :INT64)) }, options: default_options @@ -260,7 +260,7 @@ it "can execute a query with a simple Hash param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users WHERE settings = @dict", params: Google::Protobuf::Struct.new(fields: { "dict" => Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "production")])) }), param_types: { "dict" => Google::Cloud::Spanner::V1::Type.new(code: :STRUCT, struct_type: Google::Cloud::Spanner::V1::StructType.new(fields: [Google::Cloud::Spanner::V1::StructType::Field.new(name: "env", type: Google::Cloud::Spanner::V1::Type.new(code: :STRING))])) }, options: default_options @@ -275,7 +275,7 @@ it "can execute a query with a complex Hash param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users WHERE settings = @dict", params: Google::Protobuf::Struct.new(fields: { "dict" => Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "production"), Google::Protobuf::Value.new(number_value: 0.9), Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "1"), Google::Protobuf::Value.new(string_value: "2"), Google::Protobuf::Value.new(string_value: "3")] )) ])) }), param_types: { "dict" => Google::Cloud::Spanner::V1::Type.new(code: :STRUCT, struct_type: Google::Cloud::Spanner::V1::StructType.new(fields: [Google::Cloud::Spanner::V1::StructType::Field.new(name: "env", type: Google::Cloud::Spanner::V1::Type.new(code: :STRING)), Google::Cloud::Spanner::V1::StructType::Field.new(name: "score", type: Google::Cloud::Spanner::V1::Type.new(code: :FLOAT64)), Google::Cloud::Spanner::V1::StructType::Field.new(name: "project_ids", type: Google::Cloud::Spanner::V1::Type.new(code: :ARRAY, array_element_type: Google::Cloud::Spanner::V1::Type.new(code: :INT64)))] )) }, options: default_options @@ -290,7 +290,7 @@ it "can execute a query with an Array of Hashes" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users WHERE STRUCT(name, email) IN UNNEST(@data)", params: Google::Protobuf::Struct.new(fields: { "data" => Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "mike"), Google::Protobuf::Value.new(string_value: "mike@example.net")] )), Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "chris"), Google::Protobuf::Value.new(string_value: "chris@example.net")] ))] )) } ), param_types: { "data" => Google::Cloud::Spanner::V1::Type.new(code: :ARRAY, array_element_type: Google::Cloud::Spanner::V1::Type.new(code: :STRUCT, struct_type: Google::Cloud::Spanner::V1::StructType.new(fields: [ Google::Cloud::Spanner::V1::StructType::Field.new(name: "name", type: Google::Cloud::Spanner::V1::Type.new(code: :STRING)), Google::Cloud::Spanner::V1::StructType::Field.new(name: "email", type: Google::Cloud::Spanner::V1::Type.new(code: :STRING))] ))) }, options: default_options @@ -303,7 +303,7 @@ it "can execute a query with an Array of STRUCTs" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users WHERE STRUCT(name, email) IN UNNEST(@data)", params: Google::Protobuf::Struct.new(fields: { "data" => Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "mike"), Google::Protobuf::Value.new(string_value: "mike@example.net")] )), Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "chris"), Google::Protobuf::Value.new(string_value: "chris@example.net")] ))] )) } ), param_types: { "data" => Google::Cloud::Spanner::V1::Type.new(code: :ARRAY, array_element_type: Google::Cloud::Spanner::V1::Type.new(code: :STRUCT, struct_type: Google::Cloud::Spanner::V1::StructType.new(fields: [ Google::Cloud::Spanner::V1::StructType::Field.new(name: "name", type: Google::Cloud::Spanner::V1::Type.new(code: :STRING)), Google::Cloud::Spanner::V1::StructType::Field.new(name: "email", type: Google::Cloud::Spanner::V1::Type.new(code: :STRING))] ))) }, options: default_options @@ -317,7 +317,7 @@ it "can execute a query with an empty Hash param" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users WHERE settings = @dict", params: Google::Protobuf::Struct.new(fields: { "dict" => Google::Protobuf::Value.new(list_value: Google::Protobuf::ListValue.new(values: [])) }), param_types: { "dict" => Google::Cloud::Spanner::V1::Type.new(code: :STRUCT, struct_type: Google::Cloud::Spanner::V1::StructType.new(fields: [])) }, options: default_options @@ -333,7 +333,7 @@ it "can execute a simple query with query options" do expect_query_options = { optimizer_version: "4", optimizer_statistics_package: "auto_20191128_14_47_22UTC" } mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", options: default_options, query_options: expect_query_options @@ -348,9 +348,9 @@ it "can execute a simple query with query options (client-level)" do expect_query_options = { optimizer_version: "4", optimizer_statistics_package: "auto_20191128_14_47_22UTC" } - new_client = spanner.client instance_id, database_id, pool: { min: 0 }, query_options: expect_query_options + new_client = spanner.client instance_id, database_id, query_options: expect_query_options mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", options: default_options, query_options: expect_query_options @@ -375,7 +375,7 @@ call_options = { timeout: timeout, retry_policy: retry_policy } mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", options: expect_options @@ -391,7 +391,7 @@ describe "priority request options" do it "can execute a query" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", request_options: { priority: :PRIORITY_MEDIUM }, @@ -410,7 +410,7 @@ it "can execute a query with request tag" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", request_options: { request_tag: "Tag-1" }, options: default_options @@ -434,7 +434,7 @@ auto_failover_disabled: true }} mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", options: default_options, directed_read_options: expect_directed_read_options @@ -456,9 +456,9 @@ ], auto_failover_disabled: true }} - new_client = spanner.client instance_id, database_id, pool: { min: 0 }, directed_read_options: expect_directed_read_options + new_client = spanner.client instance_id, database_id, directed_read_options: expect_directed_read_options mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", options: default_options, directed_read_options: expect_directed_read_options @@ -488,9 +488,9 @@ ], auto_failover_disabled: true }} - new_client = spanner.client instance_id, database_id, pool: { min: 0 }, directed_read_options: client_level_directed_read_options + new_client = spanner.client instance_id, database_id, directed_read_options: client_level_directed_read_options mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", options: default_options, directed_read_options: request_level_directed_read_options diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/fields_for_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/fields_for_test.rb index fd1b3937..3ad08637 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/fields_for_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/fields_for_test.rb @@ -42,11 +42,11 @@ end let(:results_grpc) { Google::Cloud::Spanner::V1::PartialResultSet.new results_hash } let(:results_enum) { Array(results_grpc).to_enum } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } it "can get a table's fields" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users WHERE 1 = 0", options: default_options diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/range_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/range_test.rb index 16884eb7..e7bb004e 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/range_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/range_test.rb @@ -19,7 +19,7 @@ let(:database_id) { "my-database-id" } let(:session_id) { "session123" } let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } after do shutdown_client! client diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/read_error_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/read_error_test.rb index e391b35b..209dd85b 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/read_error_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/read_error_test.rb @@ -94,13 +94,13 @@ Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash6) ].to_enum end - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } it "raises unhandled errors" do columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, RaiseableEnumerator.new(results_enum1), [{ session: session_grpc.name, table: "my-table", diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/read_resume_buffer_bound_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/read_resume_buffer_bound_test.rb index 04dff614..7186b42e 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/read_resume_buffer_bound_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/read_resume_buffer_bound_test.rb @@ -81,7 +81,7 @@ ] } end - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } let(:columns) { [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] } it "returns all rows even when there is no resume_token" do @@ -105,7 +105,7 @@ ].to_enum mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, no_tokens_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -149,7 +149,7 @@ ].to_enum mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, all_tokens_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -194,7 +194,7 @@ ].to_enum mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, RaiseableEnumerator.new(bounds_with_abort_enum), [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/read_resume_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/read_resume_test.rb index 16a62a63..1d782dfb 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/read_resume_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/read_resume_test.rb @@ -101,13 +101,13 @@ Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash6) ].to_enum end - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } it "resumes broken response streams" do columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, RaiseableEnumerator.new(results_enum1), [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/read_single_use_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/read_single_use_test.rb index 1c37d52c..a25d9869 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/read_single_use_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/read_single_use_test.rb @@ -56,7 +56,7 @@ let(:results_enum) do [Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash)].to_enum end - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } let(:columns) { [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] } let(:time_obj) { Time.parse "2014-10-02T15:01:23.045123456Z" } let(:timestamp) { Google::Cloud::Spanner::Convert.time_to_timestamp time_obj } @@ -72,7 +72,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -102,7 +102,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -132,7 +132,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -162,7 +162,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -192,7 +192,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -222,7 +222,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -252,7 +252,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -282,7 +282,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -312,7 +312,7 @@ ) mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/read_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/read_test.rb index 3e70a640..aed1f563 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/read_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/read_test.rb @@ -68,17 +68,17 @@ Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash2), Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash3)].to_enum end - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } let(:labels) { { "env" => "production" } } - let(:client_labels) { spanner.client instance_id, database_id, pool: { min: 0 }, labels: labels } + let(:client_labels) { spanner.client instance_id, database_id, labels: labels } it "can read all rows" do columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] # puts Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash2).inspect # puts Google::Cloud::Spanner::V1::PartialResultSet.new(Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash2)).inspect mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -101,7 +101,7 @@ columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -126,7 +126,7 @@ columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -151,7 +151,7 @@ columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -178,7 +178,7 @@ columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -201,7 +201,7 @@ columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -224,7 +224,7 @@ columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -248,7 +248,7 @@ columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -271,7 +271,7 @@ columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -294,7 +294,7 @@ columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] mock = Minitest::Mock.new - session_labels_grpc = Google::Cloud::Spanner::V1::Session.new labels: labels + session_labels_grpc = Google::Cloud::Spanner::V1::Session.new labels: labels, multiplexed: true session_labels_resp_grpc = Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id), labels: labels mock.expect :create_session, session_labels_resp_grpc, [{ database: database_path(instance_id, database_id), session: session_labels_grpc }, default_options] mock.expect :streaming_read, results_enum, [{ @@ -328,7 +328,7 @@ columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -352,7 +352,7 @@ columns = [:id, :name] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name"], @@ -374,7 +374,7 @@ it "can read with request tag" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id"], @@ -405,7 +405,7 @@ columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -436,7 +436,7 @@ columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :streaming_read, results_enum, [{ session: session_grpc.name, table: "my-table", columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], @@ -445,7 +445,7 @@ request_options: nil, directed_read_options: expect_directed_read_options, order_by: nil, lock_hint: nil }, default_options] spanner.service.mocked_service = mock - client = spanner.client instance_id, database_id, pool: { min: 0 }, directed_read_options: expect_directed_read_options + client = spanner.client instance_id, database_id, directed_read_options: expect_directed_read_options results = client.read "my-table", columns, keys: 1, limit: 1 diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/snapshot_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/snapshot_test.rb index 13e020f1..b96fc49e 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/snapshot_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/snapshot_test.rb @@ -22,7 +22,6 @@ let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } let(:transaction_id) { "tx789" } let(:transaction_grpc) { Google::Cloud::Spanner::V1::Transaction.new id: transaction_id } - let(:snapshot) { Google::Cloud::Spanner::Snapshot.from_grpc transaction_grpc, session } let(:tx_selector) { Google::Cloud::Spanner::V1::TransactionSelector.new id: transaction_id } let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } let :results_hash do @@ -60,14 +59,14 @@ end let(:results_grpc) { Google::Cloud::Spanner::V1::PartialResultSet.new results_hash } let(:results_enum) { Array(results_grpc).to_enum } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } let(:snp_opts) { Google::Cloud::Spanner::V1::TransactionOptions::ReadOnly.new return_read_timestamp: true } let(:tx_opts) { Google::Cloud::Spanner::V1::TransactionOptions.new read_only: snp_opts } def mock_builder mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, mutation_key: nil }, default_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector, options: default_options mock @@ -272,8 +271,8 @@ def mock_builder call_options = { timeout: timeout, retry_policy: retry_policy } mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts }, expect_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, mutation_key: nil }, expect_options] spanner.service.mocked_service = mock expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector, options: default_options diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/transaction_results_retry_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/transaction_results_retry_test.rb index 2787860c..b9c65d85 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/transaction_results_retry_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/transaction_results_retry_test.rb @@ -1,4 +1,4 @@ -# Copyright 2017 Google LLC +# Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -39,7 +39,7 @@ let(:tx_selector_with_id) { Google::Cloud::Spanner::V1::TransactionSelector.new id: tx_id } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } describe :read do let :results_hash1_tx do @@ -174,7 +174,7 @@ transaction_id: tx_id, single_use_transaction: nil, mutations: [], - request_options: nil + request_options: nil, precommit_token: nil }, default_options] service_mock.expect :commit, commit_resp, commit_request @@ -286,7 +286,7 @@ ].to_enum service_mock = Minitest::Mock.new - service_mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + service_mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] execute_streaming_sql_request_1 = [{ session: session_grpc.name, @@ -316,7 +316,7 @@ transaction_id: tx_id, single_use_transaction: nil, mutations: [], - request_options: nil + request_options: nil, precommit_token: nil }, default_options] service_mock.expect :commit, commit_resp, commit_request diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/transaction_retry_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/transaction_retry_test.rb index efac9838..3abce0fe 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/transaction_retry_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/transaction_retry_test.rb @@ -70,7 +70,7 @@ end let(:results_grpc) { Google::Cloud::Spanner::V1::PartialResultSet.new results_hash } let(:results_enum) { Array(results_grpc).to_enum } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } let(:tx_opts) { Google::Cloud::Spanner::V1::TransactionOptions.new(read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new) } it "retries aborted transactions without retry metadata" do @@ -85,13 +85,9 @@ mock = Minitest::Mock.new spanner.service.mocked_service = mock - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options - - mock.expect :begin_transaction, transaction_grpc, [{ - session: session_grpc.name, options: tx_opts, request_options: nil - }, default_options] - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_id, seqno: 1, options: default_options def mock.commit *args # first time called this will raise @@ -137,13 +133,9 @@ def mock.commit *args mock = Minitest::Mock.new spanner.service.mocked_service = mock - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options - - mock.expect :begin_transaction, transaction_grpc, [{ - session: session_grpc.name, options: tx_opts, request_options: nil - }, default_options] - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_id, seqno: 1, options: default_options def mock.commit *args # first time called this will raise @@ -187,13 +179,9 @@ def mock.commit *args mock = Minitest::Mock.new spanner.service.mocked_service = mock - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options - - mock.expect :begin_transaction, transaction_grpc, [{ - session: session_grpc.name, options: tx_opts, request_options: nil - }, default_options] - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_id, seqno: 1, options: default_options def mock.commit *args # first time called this will raise @@ -237,18 +225,10 @@ def mock.commit *args mock = Minitest::Mock.new spanner.service.mocked_service = mock - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options - - mock.expect :begin_transaction, transaction_grpc, [{ - session: session_grpc.name, options: tx_opts, request_options: nil - }, default_options] - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_id, seqno: 1, options: default_options - - mock.expect :begin_transaction, transaction_grpc, [{ - session: session_grpc.name, options: tx_opts, request_options: nil - }, default_options] - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_id, seqno: 1, options: default_options def mock.commit *args # first time called this will raise @@ -297,28 +277,12 @@ def mock.commit *args mock = Minitest::Mock.new spanner.service.mocked_service = mock - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options - - mock.expect :begin_transaction, transaction_grpc, [{ - session: session_grpc.name, options: tx_opts, request_options: nil - }, default_options] - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_id, seqno: 1, options: default_options - - mock.expect :begin_transaction, transaction_grpc, [{ - session: session_grpc.name, options: tx_opts, request_options: nil - }, default_options] - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_id, seqno: 1, options: default_options - - mock.expect :begin_transaction, transaction_grpc, [{ - session: session_grpc.name, options: tx_opts, request_options: nil - }, default_options] - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_id, seqno: 1, options: default_options - - mock.expect :begin_transaction, transaction_grpc, [{ - session: session_grpc.name, options: tx_opts, request_options: nil - }, default_options] - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_id, seqno: 1, options: default_options def mock.commit *args raise GRPC::Aborted.new "aborted" @@ -369,13 +333,9 @@ def mock.commit *args mock = Minitest::Mock.new spanner.service.mocked_service = mock - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options - - mock.expect :begin_transaction, transaction_grpc, [{ - session: session_grpc.name, options: tx_opts, request_options: nil - }, default_options] - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_id, seqno: 1, options: default_options def mock.commit *args # first time called this will raise @@ -419,13 +379,12 @@ def mock.commit *args mock = Minitest::Mock.new spanner.service.mocked_service = mock - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] - mock.expect :begin_transaction, transaction_grpc, [{ - session: session_grpc.name, - options: tx_opts, - request_options: nil - }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + # this test does not expect begin_transaction to be called explicitly since + # the client does not do anything with the transaction. + # this is the only case where `Service#commit` from `Client#transaction` is called with + # a single-use def mock.commit *args # first time called this will raise diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/transaction_rollback_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/transaction_rollback_test.rb index 5ca08d64..bafb3cad 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/transaction_rollback_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/transaction_rollback_test.rb @@ -67,13 +67,13 @@ end let(:results_grpc) { Google::Cloud::Spanner::V1::PartialResultSet.new results_hash } let(:results_enum) { Array(results_grpc).to_enum } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } let(:tx_opts) { Google::Cloud::Spanner::V1::TransactionOptions.new(read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new) } it "will rollback and not pass on the error when using Rollback" do mock = Minitest::Mock.new spanner.service.mocked_service = mock - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options mock.expect :rollback, nil, [{ session: session_grpc.name, transaction_id: transaction_id }, default_options] @@ -98,7 +98,7 @@ it "will rollback and pass on the error" do mock = Minitest::Mock.new spanner.service.mocked_service = mock - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options mock.expect :rollback, nil, [{ session: session_grpc.name, transaction_id: transaction_id }, default_options] @@ -123,7 +123,7 @@ it "does not allow nested transactions" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] spanner.service.mocked_service = mock nested_error = assert_raises RuntimeError do diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/transaction_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/transaction_test.rb index f08bc72b..e5d07ff2 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/transaction_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/transaction_test.rb @@ -33,6 +33,21 @@ ) ) end + let(:tx_no_dml_options) do + Google::Cloud::Spanner::V1::TransactionOptions.new( + read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new( + read_lock_mode: :READ_LOCK_MODE_UNSPECIFIED + ) + ) + end + let(:tx_no_dml_options_excluding_from_change_streams) do + Google::Cloud::Spanner::V1::TransactionOptions.new( + read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new( + read_lock_mode: :READ_LOCK_MODE_UNSPECIFIED + ), + exclude_txn_from_change_streams: true + ) + end let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } let :results_hash do { @@ -84,7 +99,7 @@ ) } let(:update_results_enum) { Array(update_results_grpc).to_enum } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } let(:tx_opts) { Google::Cloud::Spanner::V1::TransactionOptions.new(read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new) } let(:tx_opts_exclude) do Google::Cloud::Spanner::V1::TransactionOptions.new( @@ -145,11 +160,11 @@ it "can execute a simple query" do mock = Minitest::Mock.new spanner.service.mocked_service = mock - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector, seqno: 1, options: default_options mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: [], transaction_id: transaction_id, - single_use_transaction: nil, request_options: nil + single_use_transaction: nil, request_options: nil, precommit_token: nil }, default_options] results = nil @@ -177,11 +192,12 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, - request_options: nil + request_options: nil, + mutation_key: mutations[0] }, default_options] mock.expect :commit, commit_resp, [{ @@ -189,7 +205,8 @@ mutations: mutations, transaction_id: transaction_id, single_use_transaction: nil, - request_options: nil + request_options: nil, + precommit_token: nil }, default_options] spanner.service.mocked_service = mock @@ -215,18 +232,19 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, - request_options: nil + request_options: nil, + mutation_key: mutations[0] }, default_options] mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: transaction_id, - single_use_transaction: nil, + single_use_transaction: nil, precommit_token: nil, request_options: nil }, default_options] spanner.service.mocked_service = mock @@ -252,18 +270,19 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, - request_options: nil + request_options: nil, + mutation_key: mutations[0] }, default_options] mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: transaction_id, - single_use_transaction: nil, + single_use_transaction: nil, precommit_token: nil, request_options: nil }, default_options] spanner.service.mocked_service = mock @@ -289,18 +308,19 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, - request_options: nil + request_options: nil, + mutation_key: mutations[0] }, default_options] mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: transaction_id, - single_use_transaction: nil, + single_use_transaction: nil, precommit_token: nil, request_options: nil }, default_options] spanner.service.mocked_service = mock @@ -326,18 +346,19 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, - request_options: nil + request_options: nil, + mutation_key: mutations[0] }, default_options] mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: transaction_id, - single_use_transaction: nil, + single_use_transaction: nil, precommit_token: nil, request_options: nil }, default_options] spanner.service.mocked_service = mock @@ -366,18 +387,19 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, - request_options: nil + request_options: nil, + mutation_key: mutations[0] }, default_options] mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: transaction_id, - single_use_transaction: nil, + single_use_transaction: nil, precommit_token: nil, request_options: nil }, default_options] spanner.service.mocked_service = mock @@ -404,11 +426,12 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, - request_options: nil + request_options: nil, + mutation_key: mutations[0] }, default_options] mock.expect :commit, commit_resp, [{ @@ -416,6 +439,7 @@ mutations: mutations, transaction_id: transaction_id, single_use_transaction: nil, + precommit_token: nil, request_options: nil }, default_options] spanner.service.mocked_service = mock @@ -444,18 +468,19 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, - request_options: nil + request_options: nil, + mutation_key: mutations[0] }, default_options] mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: transaction_id, - single_use_transaction: nil, + single_use_transaction: nil, precommit_token: nil, request_options: nil }, default_options] spanner.service.mocked_service = mock @@ -480,18 +505,19 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, - request_options: nil + request_options: nil, + mutation_key: mutations[0] }, default_options] mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: transaction_id, - single_use_transaction: nil, + single_use_transaction: nil, precommit_token: nil, request_options: nil }, default_options] spanner.service.mocked_service = mock @@ -516,18 +542,19 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts_exclude, - request_options: nil + request_options: nil, + mutation_key: mutations[0] }, default_options] mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: transaction_id, - single_use_transaction: nil, + single_use_transaction: nil, precommit_token: nil, request_options: nil }, default_options] spanner.service.mocked_service = mock @@ -580,7 +607,7 @@ ] mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] # Since the yielded transaction object was only used to add mutations, # we expect an explicit `begin_transaction` call, and subsequently @@ -588,14 +615,15 @@ mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, - request_options: nil + request_options: nil, + mutation_key: mutations[0] }, default_options] mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: transaction_id, - single_use_transaction: nil, + single_use_transaction: nil, precommit_token: nil, request_options: nil }, default_options] spanner.service.mocked_service = mock @@ -627,11 +655,11 @@ mock = Minitest::Mock.new spanner.service.mocked_service = mock - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector, seqno: 1, options: default_options mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: [], transaction_id: transaction_id, - single_use_transaction: nil, request_options: nil + single_use_transaction: nil, request_options: nil, precommit_token: nil }, expect_options] results = nil @@ -655,14 +683,15 @@ it "commits multiple mutations" do mock = Minitest::Mock.new - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] # Since the yielded transaction object was only used to add mutations, # we expect an explicit `begin_transaction` call, and subsequently # the id of the transaction returned to be issued in the `commit` request. mock.expect :begin_transaction, transaction_grpc, [{ session: session_grpc.name, options: tx_opts, - request_options: request_options + request_options: request_options, + mutation_key: mutations[0] }, default_options] mock.expect :commit, commit_resp, [{ @@ -670,6 +699,7 @@ mutations: mutations, transaction_id: transaction_id, single_use_transaction: nil, + precommit_token: nil, request_options: request_options }, default_options] spanner.service.mocked_service = mock @@ -691,11 +721,11 @@ it "execute query" do mock = Minitest::Mock.new spanner.service.mocked_service = mock - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector, seqno: 1, request_options: request_options, options: default_options mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: [], transaction_id: transaction_id, - single_use_transaction: nil, request_options: nil + single_use_transaction: nil, request_options: nil, precommit_token: nil }, default_options] timestamp = client.transaction do |tx| @@ -709,6 +739,77 @@ end end + it "will use a precommit token from Transaction if there is only BeginTransacton request" do + # This test "describes" an undesirable behavior that is being kept for backward compatibility + # `Spanner::Transaction#transaction_id` will call BeginTransaction if there is no active server-side `V1::Transaction` + # associated with that `Spanner::Transaction` object. + # Please note that the BeginTransaction does not supply a mutation key (because `transaction_id` does not have any idea of it). + + mutations = [ + Google::Cloud::Spanner::V1::Mutation.new( + update: Google::Cloud::Spanner::V1::Mutation::Write.new( + table: "users", columns: %w(id name active), + values: [Google::Cloud::Spanner::Convert.object_to_grpc_value([1, "Charlie", false]).list_value] + ) + ) + ] + + mock = Minitest::Mock.new + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ + session: session_grpc.name, + options: tx_opts, + request_options: nil, + mutation_key: nil + }, default_options] + + mock.expect :commit, commit_resp, [{ + session: session_grpc.name, + mutations: mutations, + transaction_id: transaction_id, + single_use_transaction: nil, + request_options: nil, + precommit_token: nil + }, default_options] + + spanner.service.mocked_service = mock + + timestamp = client.transaction do |tx| + id = tx.transaction_id # unfortunately transaction_id is not side-effect free and will call BeginTransaction if there is no one already + tx.update "users", [{ id: 1, name: "Charlie", active: false }] + end + _(timestamp).must_equal commit_time + + shutdown_client! client + + mock.verify + end + + it "will run a single-use transaction commit when the end-user does not do anything with the yielded transaction" do + mock = Minitest::Mock.new + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + + # The transaction method would typically run explicit begin transaction + # except for the "empty" case where single-use is used instead. + mock.expect :commit, commit_resp, [{ + session: session_grpc.name, + mutations: [], + transaction_id: nil, + single_use_transaction: tx_no_dml_options, + request_options: nil, + precommit_token: nil + }, default_options] + + spanner.service.mocked_service = mock + + timestamp = client.transaction do |tx| + end + + shutdown_client! client + + mock.verify + end + it "can execute a trasaction with transaction and request tag" do mutations = [ Google::Cloud::Spanner::V1::Mutation.new( @@ -721,7 +822,7 @@ mock = Minitest::Mock.new spanner.service.mocked_service = mock - mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: nil }, default_options] + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector, seqno: 1, @@ -734,7 +835,7 @@ mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: transaction_id, - single_use_transaction: nil, request_options: { transaction_tag: "Tag-1" } + single_use_transaction: nil, request_options: { transaction_tag: "Tag-1" }, precommit_token: nil }, default_options] diff --git a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/commit_test.rb b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/commit_test.rb index b45d32da..03717d05 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/commit_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/commit_test.rb @@ -22,7 +22,7 @@ let(:commit_time) { Time.now } let(:commit_timestamp) { Google::Cloud::Spanner::Convert.time_to_timestamp commit_time } let(:commit_resp) { Google::Cloud::Spanner::V1::CommitResponse.new commit_timestamp: commit_timestamp } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } after do shutdown_client! client diff --git a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/delete_test.rb b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/delete_test.rb index a1e8703f..9cb4e2a3 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/delete_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/delete_test.rb @@ -22,7 +22,7 @@ let(:commit_time) { Time.now } let(:commit_timestamp) { Google::Cloud::Spanner::Convert.time_to_timestamp commit_time } let(:commit_resp) { Google::Cloud::Spanner::V1::CommitResponse.new commit_timestamp: commit_timestamp } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } after do shutdown_client! client diff --git a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/execute_query_test.rb b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/execute_query_test.rb index f3973800..04657fd9 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/execute_query_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/execute_query_test.rb @@ -14,7 +14,7 @@ require "helper" -describe Google::Cloud::Spanner::Pool, :mock_spanner do +describe Google::Cloud::Spanner::Client, :mock_spanner do let(:instance_id) { "my-instance-id" } let(:database_id) { "my-database-id" } let(:session_id) { "session1" } diff --git a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/insert_test.rb b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/insert_test.rb index 94bd56ea..8288597c 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/insert_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/insert_test.rb @@ -22,7 +22,7 @@ let(:commit_time) { Time.now } let(:commit_timestamp) { Google::Cloud::Spanner::Convert.time_to_timestamp commit_time } let(:commit_resp) { Google::Cloud::Spanner::V1::CommitResponse.new commit_timestamp: commit_timestamp } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } after do shutdown_client! client diff --git a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/read_test.rb b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/read_test.rb index 35cbcad1..035a7189 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/read_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/read_test.rb @@ -14,7 +14,7 @@ require "helper" -describe Google::Cloud::Spanner::Pool, :mock_spanner do +describe Google::Cloud::Spanner::Client, :mock_spanner do let(:instance_id) { "my-instance-id" } let(:database_id) { "my-database-id" } let(:session_id) { "session1" } diff --git a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/replace_test.rb b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/replace_test.rb index 56394bae..d000417c 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/replace_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/replace_test.rb @@ -22,7 +22,7 @@ let(:commit_time) { Time.now } let(:commit_timestamp) { Google::Cloud::Spanner::Convert.time_to_timestamp commit_time } let(:commit_resp) { Google::Cloud::Spanner::V1::CommitResponse.new commit_timestamp: commit_timestamp } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } after do shutdown_client! client diff --git a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/update_test.rb b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/update_test.rb index 7e0075a7..cceb3c27 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/update_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/update_test.rb @@ -22,7 +22,7 @@ let(:commit_time) { Time.now } let(:commit_timestamp) { Google::Cloud::Spanner::Convert.time_to_timestamp commit_time } let(:commit_resp) { Google::Cloud::Spanner::V1::CommitResponse.new commit_timestamp: commit_timestamp } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } after do shutdown_client! client diff --git a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/upsert_test.rb b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/upsert_test.rb index d05ac13b..746eafda 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/upsert_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/client/upsert_test.rb @@ -22,7 +22,7 @@ let(:commit_time) { Time.now } let(:commit_timestamp) { Google::Cloud::Spanner::Convert.time_to_timestamp commit_time } let(:commit_resp) { Google::Cloud::Spanner::V1::CommitResponse.new commit_timestamp: commit_timestamp } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0 } } + let(:client) { spanner.client instance_id, database_id } after do shutdown_client! client diff --git a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/session/get_session_test.rb b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/session/get_session_test.rb index a145ae45..56a6c650 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/session/get_session_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/session/get_session_test.rb @@ -14,35 +14,34 @@ require "helper" -describe Google::Cloud::Spanner::Session, :reload, :mock_spanner do +describe Google::Cloud::Spanner::Service, :get_sessions, :mock_spanner do let(:instance_id) { "my-instance-id" } let(:database_id) { "my-database-id" } let(:session_id) { "session123" } - let(:session_grpc) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id) } - let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } - + let(:session_full_path) { session_path(instance_id, database_id, session_id) } + let(:service) {spanner.service} + it "does not send header x-goog-spanner-route-to-leader when LAR is disabled" do mock = Minitest::Mock.new - mock.expect :get_session, session_grpc do |request, gapic_options| + mock.expect :get_session, nil do |request, gapic_options| !gapic_options.metadata.key? "x-goog-spanner-route-to-leader" end - session.service.mocked_service = mock - spanner.service.enable_leader_aware_routing = false - - session.reload! + service.mocked_service = mock + service.enable_leader_aware_routing = false + service.get_session session_full_path mock.verify end it "sends header x-goog-spanner-route-to-leader when LAR is enabled" do mock = Minitest::Mock.new - mock.expect :get_session, session_grpc do |request, gapic_options| + mock.expect :get_session, nil do |request, gapic_options| gapic_options.metadata["x-goog-spanner-route-to-leader"] == 'true' end - session.service.mocked_service = mock - spanner.service.enable_leader_aware_routing = true + service.mocked_service = mock + service.enable_leader_aware_routing = true - session.reload! + service.get_session session_full_path mock.verify end diff --git a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/transaction/batch_update_test.rb b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/transaction/batch_update_test.rb index 8c717f88..3ddc9737 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/transaction/batch_update_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/transaction/batch_update_test.rb @@ -14,7 +14,7 @@ require "helper" -describe Google::Cloud::Spanner::Pool, :mock_spanner do +describe Google::Cloud::Spanner::Transaction, :mock_spanner do let(:instance_id) { "my-instance-id" } let(:database_id) { "my-database-id" } let(:session_id) { "session1" } diff --git a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/transaction/execute_query_test.rb b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/transaction/execute_query_test.rb index 3b4561ba..5508982a 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/transaction/execute_query_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/transaction/execute_query_test.rb @@ -14,7 +14,7 @@ require "helper" -describe Google::Cloud::Spanner::Pool, :mock_spanner do +describe Google::Cloud::Spanner::Transaction, :mock_spanner do let(:instance_id) { "my-instance-id" } let(:database_id) { "my-database-id" } let(:session_id) { "session1" } diff --git a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/transaction/read_test.rb b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/transaction/read_test.rb index 80d583cc..e48e09ef 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/transaction/read_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/leader_aware_routing/transaction/read_test.rb @@ -14,7 +14,7 @@ require "helper" -describe Google::Cloud::Spanner::Pool, :mock_spanner do +describe Google::Cloud::Spanner::Transaction, :mock_spanner do let(:instance_id) { "my-instance-id" } let(:database_id) { "my-database-id" } let(:session_id) { "session1" } diff --git a/google-cloud-spanner/test/google/cloud/spanner/pool/batch_create_sessions_test.rb b/google-cloud-spanner/test/google/cloud/spanner/pool/batch_create_sessions_test.rb index f66f1860..25ec7010 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/pool/batch_create_sessions_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/pool/batch_create_sessions_test.rb @@ -13,17 +13,14 @@ # limitations under the License. require "helper" +require "google/cloud/spanner/pool" describe Google::Cloud::Spanner::Pool, :batch_create_sessions, :mock_spanner do let(:instance_id) { "my-instance-id" } let(:database_id) { "my-database-id" } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0, max: 4 } } let(:tx_opts) { Google::Cloud::Spanner::V1::TransactionOptions.new(read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new) } let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } - - after do - shutdown_client! client - end + let(:session_creation_options) { ::Google::Cloud::Spanner::SessionCreationOptions.new database_path: database_path(instance_id, database_id)} it "calls batch_create_sessions until min number of sessions are returned" do mock = Minitest::Mock.new @@ -41,7 +38,7 @@ mock.expect :batch_create_sessions, sessions_1, [{ database: database_path(instance_id, database_id), session_count: 2, session_template: nil }, default_options] mock.expect :batch_create_sessions, sessions_2, [{ database: database_path(instance_id, database_id), session_count: 1, session_template: nil }, default_options] - pool = Google::Cloud::Spanner::Pool.new client, min: 2 + pool = Google::Cloud::Spanner::Pool.new(spanner.service, session_creation_options, min: 2, max: 4) shutdown_pool! pool diff --git a/google-cloud-spanner/test/google/cloud/spanner/pool/close_test.rb b/google-cloud-spanner/test/google/cloud/spanner/pool/close_test.rb index 38f21b00..dddbec85 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/pool/close_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/pool/close_test.rb @@ -14,6 +14,8 @@ require "helper" +require "google/cloud/spanner/pool" + describe Google::Cloud::Spanner::Pool, :close, :mock_spanner do let(:instance_id) { "my-instance-id" } let(:database_id) { "my-database-id" } @@ -21,24 +23,16 @@ let(:session_grpc) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id) } let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0, max: 4 } } - let(:pool) do - session.instance_variable_set :@last_updated_at, Time.now - p = client.instance_variable_get :@pool - p.sessions_available = [session] - p.sessions_in_use = {} - p - end - - after do - shutdown_client! client - end + let(:session_creation_options) { ::Google::Cloud::Spanner::SessionCreationOptions.new database_path: database_path(instance_id, database_id)} it "deletes sessions when closed" do mock = Minitest::Mock.new mock.expect :delete_session, nil, [{ name: session_grpc.name }, default_options] session.service.mocked_service = mock + pool = Google::Cloud::Spanner::Pool.new(spanner.service, session_creation_options, min: 0, max: 4) + pool.sessions_available = [session] + pool.sessions_in_use = {} pool.close shutdown_pool! pool @@ -51,6 +45,9 @@ mock.expect :delete_session, nil, [{ name: session_grpc.name }, default_options] session.service.mocked_service = mock + pool = Google::Cloud::Spanner::Pool.new(spanner.service, session_creation_options, min: 0, max: 4) + pool.sessions_available = [session] + pool.sessions_in_use = {} pool.close shutdown_pool! pool diff --git a/google-cloud-spanner/test/google/cloud/spanner/pool/keepalive_or_release_test.rb b/google-cloud-spanner/test/google/cloud/spanner/pool/keepalive_or_release_test.rb index 37cd7d10..a18a92a0 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/pool/keepalive_or_release_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/pool/keepalive_or_release_test.rb @@ -13,6 +13,7 @@ # limitations under the License. require "helper" +require "google/cloud/spanner/pool" describe Google::Cloud::Spanner::Pool, :keepalive_or_release, :mock_spanner do let(:instance_id) { "my-instance-id" } @@ -31,8 +32,14 @@ let(:transaction) { Google::Cloud::Spanner::Transaction.from_grpc transaction_grpc, session } let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } let(:tx_opts) { Google::Cloud::Spanner::V1::TransactionOptions.new(read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new) } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0, max: 4 } } - let(:pool) { client.instance_variable_get :@pool } + let(:session_creation_options) { ::Google::Cloud::Spanner::SessionCreationOptions.new database_path: database_path(instance_id, database_id)} + let(:pool) do + session.instance_variable_set :@last_updated_at, Time.now + p = Google::Cloud::Spanner::Pool.new(spanner.service, session_creation_options, min: 0, max: 4) + p.sessions_available = [session] + p.sessions_in_use = {} + p + end let :results_hash do { metadata: { @@ -53,10 +60,6 @@ pool.instance_variable_get(:@keepalive_task).shutdown end - after do - shutdown_client! client - end - it "calls keepalive on the sessions that need it" do # update the session so it was last updated an hour ago session.instance_variable_set :@last_updated_at, Process::clock_gettime(Process::CLOCK_MONOTONIC) - 60*60 diff --git a/google-cloud-spanner/test/google/cloud/spanner/pool/new_sessions_in_process_test.rb b/google-cloud-spanner/test/google/cloud/spanner/pool/new_sessions_in_process_test.rb index e6c478bc..14a328ff 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/pool/new_sessions_in_process_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/pool/new_sessions_in_process_test.rb @@ -13,6 +13,7 @@ # limitations under the License. require "helper" +require "google/cloud/spanner/pool" describe Google::Cloud::Spanner::Pool, :new_sessions_in_process, :mock_spanner do let(:instance_id) { "my-instance-id" } @@ -21,20 +22,15 @@ let(:session_grpc) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id) } let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0, max: 4 } } - let(:tx_opts) { Google::Cloud::Spanner::V1::TransactionOptions.new(read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new) } + let(:session_creation_options) { ::Google::Cloud::Spanner::SessionCreationOptions.new database_path: database_path(instance_id, database_id)} let(:pool) do session.instance_variable_set :@last_updated_at, Time.now - p = client.instance_variable_get :@pool + p = Google::Cloud::Spanner::Pool.new(spanner.service, session_creation_options, min: 0, max: 4) p.sessions_available = [session] p.sessions_in_use = {} p end - after do - shutdown_client! client - end - it "does not increment new_sessions_in_process when create_session raises an error" do stub = Object.new def stub.create_session *args diff --git a/google-cloud-spanner/test/google/cloud/spanner/pool_test.rb b/google-cloud-spanner/test/google/cloud/spanner/pool_test.rb index f227a080..68f9cad0 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/pool_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/pool_test.rb @@ -27,19 +27,15 @@ let(:session_grpc_4) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id_4) } let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } - let(:client) { spanner.client instance_id, database_id, pool: { min: 0, max: 4 } } + let(:session_creation_options) { ::Google::Cloud::Spanner::SessionCreationOptions.new database_path: database_path(instance_id, database_id)} let(:pool) do session.instance_variable_set :@last_updated_at, Time.now - p = client.instance_variable_get :@pool + p = Google::Cloud::Spanner::Pool.new(spanner.service, session_creation_options, min: 0, max: 4) p.sessions_available = [session] p.sessions_in_use = {} p end - after do - shutdown_client! client - end - it "can checkout and checkin a session" do _(pool.sessions_available.size).must_equal 1 _(pool.sessions_in_use.size).must_equal 0 diff --git a/google-cloud-spanner/test/google/cloud/spanner/project_test.rb b/google-cloud-spanner/test/google/cloud/spanner/project_test.rb index ea8555a0..ba1eb9fd 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/project_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/project_test.rb @@ -42,13 +42,19 @@ _(spanner.universe_domain).must_equal universe end + it "creates client with multiplexed pool by default" do + mock = Minitest::Mock.new + spanner.service.mocked_service = mock + + client = spanner.client instance_id, database_id + _(client.instance_variable_get :@pool).must_be_kind_of ::Google::Cloud::Spanner::SessionCache + end + it "creates client with database role" do mock = Minitest::Mock.new - request_session = Google::Cloud::Spanner::V1::Session.new labels: nil, creator_role: "test_role" - mock.expect :batch_create_sessions, batch_create_sessions_grpc, [Hash,::Gapic::CallOptions] spanner.service.mocked_service = mock - client = spanner.client instance_id, database_id, pool: { min: 1, max: 1 }, database_role: "test-role" + client = spanner.client instance_id, database_id, database_role: "test-role" _(client.database_role).must_equal "test-role" end end diff --git a/google-cloud-spanner/test/google/cloud/spanner/results/anonymous_struct_test.rb b/google-cloud-spanner/test/google/cloud/spanner/results/anonymous_struct_test.rb index a9888c56..51dd9d03 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/results/anonymous_struct_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/results/anonymous_struct_test.rb @@ -36,7 +36,7 @@ let(:results_enum) do [Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash)].to_enum end - let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name } + let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name } it "handles anonymous structs" do _(results).must_be_kind_of Google::Cloud::Spanner::Results diff --git a/google-cloud-spanner/test/google/cloud/spanner/results/deeply_nested_list_test.rb b/google-cloud-spanner/test/google/cloud/spanner/results/deeply_nested_list_test.rb index a634d556..f1251a4b 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/results/deeply_nested_list_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/results/deeply_nested_list_test.rb @@ -175,7 +175,7 @@ Google::Cloud::Spanner::V1::PartialResultSet.new(results_values8), Google::Cloud::Spanner::V1::PartialResultSet.new(results_values9)].to_enum end - let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name } + let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name } it "handles nested structs" do _(results).must_be_kind_of Google::Cloud::Spanner::Results diff --git a/google-cloud-spanner/test/google/cloud/spanner/results/duplicate_struct_test.rb b/google-cloud-spanner/test/google/cloud/spanner/results/duplicate_struct_test.rb index 9ce7197d..5c1c1af2 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/results/duplicate_struct_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/results/duplicate_struct_test.rb @@ -36,7 +36,7 @@ let(:results_enum) do [Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash)].to_enum end - let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name } + let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name } it "handles duplicate structs" do _(results).must_be_kind_of Google::Cloud::Spanner::Results diff --git a/google-cloud-spanner/test/google/cloud/spanner/results/duplicate_test.rb b/google-cloud-spanner/test/google/cloud/spanner/results/duplicate_test.rb index 9bd1d03a..5819161a 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/results/duplicate_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/results/duplicate_test.rb @@ -47,7 +47,7 @@ [Google::Cloud::Spanner::V1::PartialResultSet.new(results_types), Google::Cloud::Spanner::V1::PartialResultSet.new(results_values)].to_enum end - let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name } + let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name } it "handles duplicate names" do _(results).must_be_kind_of Google::Cloud::Spanner::Results diff --git a/google-cloud-spanner/test/google/cloud/spanner/results/empty_field_names_test.rb b/google-cloud-spanner/test/google/cloud/spanner/results/empty_field_names_test.rb index 4af20e6b..f8a43b00 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/results/empty_field_names_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/results/empty_field_names_test.rb @@ -47,7 +47,7 @@ [Google::Cloud::Spanner::V1::PartialResultSet.new(results_types), Google::Cloud::Spanner::V1::PartialResultSet.new(results_values)].to_enum end - let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name } + let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name } it "handles empty field names" do _(results).must_be_kind_of Google::Cloud::Spanner::Results diff --git a/google-cloud-spanner/test/google/cloud/spanner/results/empty_fields_test.rb b/google-cloud-spanner/test/google/cloud/spanner/results/empty_fields_test.rb index da39bbb7..f90c4c8d 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/results/empty_fields_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/results/empty_fields_test.rb @@ -32,7 +32,7 @@ let(:results_enum) do [Google::Cloud::Spanner::V1::PartialResultSet.new(results_types)].to_enum end - let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name } + let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name } it "handles empty field names" do _(results).must_be_kind_of Google::Cloud::Spanner::Results diff --git a/google-cloud-spanner/test/google/cloud/spanner/results/empty_rows_test.rb b/google-cloud-spanner/test/google/cloud/spanner/results/empty_rows_test.rb index 87ca7243..3cdac6a7 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/results/empty_rows_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/results/empty_rows_test.rb @@ -37,7 +37,7 @@ let(:results_enum) do [Google::Cloud::Spanner::V1::PartialResultSet.new(results_types)].to_enum end - let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name } + let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name } it "handles empty field names" do _(results).must_be_kind_of Google::Cloud::Spanner::Results diff --git a/google-cloud-spanner/test/google/cloud/spanner/results/from_enum_single_test.rb b/google-cloud-spanner/test/google/cloud/spanner/results/from_enum_single_test.rb index eb3090a4..951a73d3 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/results/from_enum_single_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/results/from_enum_single_test.rb @@ -51,7 +51,7 @@ let(:results_enum) do [Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash)].to_enum end - let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name } + let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name } it "exists" do _(results).must_be_kind_of Google::Cloud::Spanner::Results diff --git a/google-cloud-spanner/test/google/cloud/spanner/results/from_enum_test.rb b/google-cloud-spanner/test/google/cloud/spanner/results/from_enum_test.rb index 132b54da..8b389800 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/results/from_enum_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/results/from_enum_test.rb @@ -63,7 +63,7 @@ Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash2), Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash3)].to_enum end - let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name } + let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name } it "exists" do _(results).must_be_kind_of Google::Cloud::Spanner::Results diff --git a/google-cloud-spanner/test/google/cloud/spanner/results/merge_test.rb b/google-cloud-spanner/test/google/cloud/spanner/results/merge_test.rb index 6f731c2c..d51280df 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/results/merge_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/results/merge_test.rb @@ -25,7 +25,7 @@ { values: [{ string_value: "ghi" }] } ] results_enum = results_hashes.map { |hash| Google::Cloud::Spanner::V1::PartialResultSet.new hash }.to_enum - results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name + results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name _(results).must_be_kind_of Google::Cloud::Spanner::Results @@ -51,7 +51,7 @@ { values: [{ list_value: { values: [{ string_value: "i" }, { string_value: "jkl" }] }}]} ] results_enum = results_hashes.map { |hash| Google::Cloud::Spanner::V1::PartialResultSet.new hash }.to_enum - results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name + results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name _(results).must_be_kind_of Google::Cloud::Spanner::Results @@ -77,7 +77,7 @@ { values: [{ list_value: { values: [{ null_value: "NULL_VALUE" }, { string_value: "jkl" }] }}]} ] results_enum = results_hashes.map { |hash| Google::Cloud::Spanner::V1::PartialResultSet.new hash }.to_enum - results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name + results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name _(results).must_be_kind_of Google::Cloud::Spanner::Results @@ -103,7 +103,7 @@ { values: [{ list_value: { values: [{ string_value: "" }, { string_value: "jkl" }] }}]} ] results_enum = results_hashes.map { |hash| Google::Cloud::Spanner::V1::PartialResultSet.new hash }.to_enum - results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name + results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name _(results).must_be_kind_of Google::Cloud::Spanner::Results @@ -129,7 +129,7 @@ { values: [{ list_value: { values: [{ string_value: "ghi" }] }}]} ] results_enum = results_hashes.map { |hash| Google::Cloud::Spanner::V1::PartialResultSet.new hash }.to_enum - results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name + results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name _(results).must_be_kind_of Google::Cloud::Spanner::Results @@ -155,7 +155,7 @@ { values: [{ list_value: { values: [{ null_value: "NULL_VALUE" }, { string_value: "5" }] }}]} ] results_enum = results_hashes.map { |hash| Google::Cloud::Spanner::V1::PartialResultSet.new hash }.to_enum - results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name + results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name _(results).must_be_kind_of Google::Cloud::Spanner::Results @@ -181,7 +181,7 @@ { values: [{ list_value: { values: [{ null_value: "NULL_VALUE" }, { number_value: 3.0 }] }}]} ] results_enum = results_hashes.map { |hash| Google::Cloud::Spanner::V1::PartialResultSet.new hash }.to_enum - results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name + results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name _(results).must_be_kind_of Google::Cloud::Spanner::Results @@ -208,7 +208,7 @@ { values: [{ string_value: "f" }] } ] results_enum = results_hashes.map { |hash| Google::Cloud::Spanner::V1::PartialResultSet.new hash }.to_enum - results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name + results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name _(results).must_be_kind_of Google::Cloud::Spanner::Results diff --git a/google-cloud-spanner/test/google/cloud/spanner/results/nested_struct_test.rb b/google-cloud-spanner/test/google/cloud/spanner/results/nested_struct_test.rb index 3344f7f2..7b905666 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/results/nested_struct_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/results/nested_struct_test.rb @@ -38,7 +38,7 @@ let(:results_enum) do [Google::Cloud::Spanner::V1::PartialResultSet.new(results_hash)].to_enum end - let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name } + let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name } it "handles nested structs" do _(results).must_be_kind_of Google::Cloud::Spanner::Results diff --git a/google-cloud-spanner/test/google/cloud/spanner/results/row_count_test.rb b/google-cloud-spanner/test/google/cloud/spanner/results/row_count_test.rb index dd894c7e..7999653e 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/results/row_count_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/results/row_count_test.rb @@ -30,7 +30,7 @@ ) ].to_enum - results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name + results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name results.rows.to_a # force all results to be processed _(results).must_be :row_count_exact? @@ -53,7 +53,7 @@ ) ].to_enum - results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name + results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name results.rows.to_a # force all results to be processed _(results).must_be :row_count_lower_bound? @@ -73,7 +73,7 @@ ) ].to_enum - results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name + results = Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name results.rows.to_a # force all results to be processed _(results).wont_be :row_count_exact? diff --git a/google-cloud-spanner/test/google/cloud/spanner/results/timestamp_test.rb b/google-cloud-spanner/test/google/cloud/spanner/results/timestamp_test.rb index 81bcb25b..ce365aae 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/results/timestamp_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/results/timestamp_test.rb @@ -37,7 +37,7 @@ let(:results_enum) do [Google::Cloud::Spanner::V1::PartialResultSet.new(results_types)].to_enum end - let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request&.name } + let(:results) { Google::Cloud::Spanner::Results.from_partial_result_sets results_enum, spanner.service, default_session_request.name } it "knows it has a timestamp" do _(results).must_be_kind_of Google::Cloud::Spanner::Results diff --git a/google-cloud-spanner/test/google/cloud/spanner/service_test.rb b/google-cloud-spanner/test/google/cloud/spanner/service_test.rb index b9d5f1dd..21fe0401 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/service_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/service_test.rb @@ -94,7 +94,8 @@ read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new, exclude_txn_from_change_streams: true ), - request_options: nil + request_options: nil, + mutation_key: nil } expected_result = Object.new mocked_service.expect :begin_transaction, expected_result, [expected_request, expected_call_opts] @@ -116,7 +117,8 @@ exclude_txn_from_change_streams: true ), mutations: [], - request_options: nil + request_options: nil, + precommit_token: nil, } expected_result = Object.new mocked_service.expect :commit, expected_result, [expected_request, expected_call_opts] @@ -135,7 +137,8 @@ options: Google::Cloud::Spanner::V1::TransactionOptions.new( partitioned_dml: Google::Cloud::Spanner::V1::TransactionOptions::PartitionedDml.new, exclude_txn_from_change_streams: true - ) + ), + mutation_key: nil } expected_result = Object.new mocked_service.expect :begin_transaction, expected_result, [expected_request, expected_call_opts] diff --git a/google-cloud-spanner/test/google/cloud/spanner/session/commit_proto_test.rb b/google-cloud-spanner/test/google/cloud/spanner/session/commit_proto_test.rb index 82c9c227..80f658b3 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/session/commit_proto_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/session/commit_proto_test.rb @@ -75,7 +75,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock @@ -104,7 +104,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.update "users", [users[1]] _(timestamp).must_equal commit_time @@ -123,7 +123,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.insert "users", [users[2]] @@ -143,7 +143,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.upsert "users", [users[3]] @@ -163,7 +163,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.replace "users", [users[3]] @@ -184,7 +184,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.delete "users", 1..100 @@ -207,7 +207,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.delete "users", 5 @@ -226,7 +226,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.delete "users" diff --git a/google-cloud-spanner/test/google/cloud/spanner/session/commit_test.rb b/google-cloud-spanner/test/google/cloud/spanner/session/commit_test.rb index 074fcd4d..cf1bc65d 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/session/commit_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/session/commit_test.rb @@ -68,7 +68,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil}, default_options] session.service.mocked_service = mock @@ -95,7 +95,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.update "users", [{ id: 1, name: "Charlie", active: false }] @@ -115,7 +115,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.insert "users", [{ id: 2, name: "Harvey", active: true }] @@ -135,7 +135,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.upsert "users", [{ id: 3, name: "Marley", active: false }] @@ -155,7 +155,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.save "users", [{ id: 3, name: "Marley", active: false }] @@ -175,7 +175,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.replace "users", [{ id: 4, name: "Henry", active: true }] @@ -198,7 +198,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.delete "users", [1, 2, 3, 4, 5] @@ -219,7 +219,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.delete "users", 1..100 @@ -242,7 +242,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.delete "users", 5 @@ -261,7 +261,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.delete "users" @@ -280,7 +280,7 @@ ] mock = Minitest::Mock.new - mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts_with_change_stream_exclusion, request_options: nil }, default_options] + mock.expect :commit, commit_resp, [{ session: session.path, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts_with_change_stream_exclusion, request_options: nil, precommit_token: nil }, default_options] session.service.mocked_service = mock timestamp = session.delete "users", exclude_txn_from_change_streams: true diff --git a/google-cloud-spanner/test/google/cloud/spanner/session/keepalive_test.rb b/google-cloud-spanner/test/google/cloud/spanner/session/keepalive_test.rb index 4081a574..9d4b9fd4 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/session/keepalive_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/session/keepalive_test.rb @@ -20,6 +20,9 @@ let(:session_id) { "session123" } let(:session_grpc) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id) } let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } + let(:session_grpc_multiplexed) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id), multiplexed: true } + let(:session_multiplexed) { Google::Cloud::Spanner::Session.from_grpc session_grpc_multiplexed, spanner.service } + let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } let :results_hash do { @@ -42,6 +45,17 @@ let(:session_grpc_labels) { Google::Cloud::Spanner::V1::Session.new name: session_path(instance_id, database_id, session_id), labels: labels } let(:session_labels) { Google::Cloud::Spanner::Session.from_grpc session_grpc_labels, spanner.service } + it "will NOP on keepalive if the session is multiplexed" do + mock = Minitest::Mock.new + session_multiplexed.service.mocked_service = mock + + result = session_multiplexed.keepalive! + + _(result).must_equal true + + mock.verify + end + it "can call keepalive" do mock = Minitest::Mock.new session.service.mocked_service = mock diff --git a/google-cloud-spanner/test/google/cloud/spanner/snapshot/execute_query_test.rb b/google-cloud-spanner/test/google/cloud/spanner/snapshot/execute_query_test.rb index 0356422f..22ad649c 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/snapshot/execute_query_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/snapshot/execute_query_test.rb @@ -22,7 +22,7 @@ let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } let(:transaction_id) { "tx789" } let(:transaction_grpc) { Google::Cloud::Spanner::V1::Transaction.new id: transaction_id } - let(:snapshot) { Google::Cloud::Spanner::Snapshot.from_grpc transaction_grpc, session, nil } + let(:snapshot) { Google::Cloud::Spanner::Snapshot.from_grpc transaction_grpc, session } let(:tx_selector) { Google::Cloud::Spanner::V1::TransactionSelector.new id: transaction_id } let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } let :results_hash do @@ -294,7 +294,7 @@ mock = Minitest::Mock.new session.service.mocked_service = mock expect_execute_streaming_sql results_enum, session.path, "SELECT * FROM users", transaction: tx_selector, options: default_options, directed_read_options: expect_directed_read_options - snapshot = Google::Cloud::Spanner::Snapshot.from_grpc transaction_grpc, session, expect_directed_read_options + snapshot = Google::Cloud::Spanner::Snapshot.from_grpc transaction_grpc, session, directed_read_options: expect_directed_read_options results = snapshot.execute_query "SELECT * FROM users" mock.verify diff --git a/google-cloud-spanner/test/google/cloud/spanner/snapshot/metadata_test.rb b/google-cloud-spanner/test/google/cloud/spanner/snapshot/metadata_test.rb index a9d33512..16b521bc 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/snapshot/metadata_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/snapshot/metadata_test.rb @@ -24,7 +24,7 @@ let(:time_obj) { Time.parse "2014-10-02T15:01:23.045123456Z" } let(:timestamp) { Google::Cloud::Spanner::Convert.time_to_timestamp time_obj } let(:transaction_grpc) { Google::Cloud::Spanner::V1::Transaction.new id: transaction_id, read_timestamp: timestamp } - let(:snapshot) { Google::Cloud::Spanner::Snapshot.from_grpc transaction_grpc, session, nil } + let(:snapshot) { Google::Cloud::Spanner::Snapshot.from_grpc transaction_grpc, session } it "knows it has a transaction_id" do _(snapshot).must_be_kind_of Google::Cloud::Spanner::Snapshot diff --git a/google-cloud-spanner/test/google/cloud/spanner/snapshot/range_test.rb b/google-cloud-spanner/test/google/cloud/spanner/snapshot/range_test.rb index d0057def..4d465e27 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/snapshot/range_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/snapshot/range_test.rb @@ -22,7 +22,7 @@ let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } let(:transaction_id) { "tx789" } let(:transaction_grpc) { Google::Cloud::Spanner::V1::Transaction.new id: transaction_id } - let(:snapshot) { Google::Cloud::Spanner::Snapshot.from_grpc transaction_grpc, session, nil } + let(:snapshot) { Google::Cloud::Spanner::Snapshot.from_grpc transaction_grpc, session } it "creates an inclusive range" do range = snapshot.range 1, 100 diff --git a/google-cloud-spanner/test/google/cloud/spanner/snapshot/read_test.rb b/google-cloud-spanner/test/google/cloud/spanner/snapshot/read_test.rb index a006a5e8..1308c106 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/snapshot/read_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/snapshot/read_test.rb @@ -22,7 +22,7 @@ let(:session) { Google::Cloud::Spanner::Session.from_grpc session_grpc, spanner.service } let(:transaction_id) { "tx789" } let(:transaction_grpc) { Google::Cloud::Spanner::V1::Transaction.new id: transaction_id } - let(:snapshot) { Google::Cloud::Spanner::Snapshot.from_grpc transaction_grpc, session, nil } + let(:snapshot) { Google::Cloud::Spanner::Snapshot.from_grpc transaction_grpc, session } let(:tx_selector) { Google::Cloud::Spanner::V1::TransactionSelector.new id: transaction_id } let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } let :results_hash1 do @@ -272,7 +272,7 @@ }, default_options] - snapshot = Google::Cloud::Spanner::Snapshot.from_grpc transaction_grpc, session, expect_directed_read_options + snapshot = Google::Cloud::Spanner::Snapshot.from_grpc transaction_grpc, session, directed_read_options: expect_directed_read_options results = snapshot.read "my-table", ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], keys: 1 mock.verify diff --git a/google-cloud-spanner/test/google/cloud/spanner/transaction/batch_update_test.rb b/google-cloud-spanner/test/google/cloud/spanner/transaction/batch_update_test.rb index 7917af09..33c964a4 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/transaction/batch_update_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/transaction/batch_update_test.rb @@ -34,6 +34,8 @@ let(:timestamp) { Time.parse "2017-01-01 20:04:05.06 -0700" } let(:date) { Date.parse "2017-01-02" } let(:file) { StringIO.new "contents" } + let(:precommit_token_0) { "hello" } + let(:precommit_token_1) { "goodbye" } it "can execute a single DML query" do mock = Minitest::Mock.new @@ -50,6 +52,9 @@ mock.verify + _(transaction.transaction_id).must_equal transaction_id + _(transaction.precommit_token.precommit_token).must_equal precommit_token_1 + _(row_counts.count).must_equal 1 _(row_counts.first).must_equal 1 end @@ -217,6 +222,22 @@ def statement_grpc sql, params: nil, param_types: {} sql: sql, params: params, param_types: param_types end + def batch_result_sets_precommit_token index, is_last + if is_last + Google::Cloud::Spanner::V1::MultiplexedSessionPrecommitToken.new( + precommit_token: precommit_token_1, + seq_num: 1 + ) + elsif index == 0 + Google::Cloud::Spanner::V1::MultiplexedSessionPrecommitToken.new( + precommit_token: precommit_token_0, + seq_num: 0 + ) + else + nil + end + end + def batch_result_sets_metadata_grpc begin_transaction if begin_transaction Google::Cloud::Spanner::V1::ResultSetMetadata.new( @@ -235,7 +256,8 @@ def batch_result_sets_grpc count, row_count_exact: 1 metadata: batch_result_sets_metadata_grpc(index == 0), # include transaction in first result set stats: Google::Cloud::Spanner::V1::ResultSetStats.new( row_count_exact: row_count_exact - ) + ), + precommit_token: batch_result_sets_precommit_token(index, index == count - 1) ) end end diff --git a/google-cloud-spanner/test/google/cloud/spanner/transaction/concurrent_queries_test.rb b/google-cloud-spanner/test/google/cloud/spanner/transaction/concurrent_queries_test.rb index 9509e2c4..a8dc69e2 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/transaction/concurrent_queries_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/transaction/concurrent_queries_test.rb @@ -37,7 +37,8 @@ { session: session.path, options: tx_opts, - request_options: nil + request_options: nil, + mutation_key: nil } end let :results_hash do diff --git a/google-cloud-spanner/test/google/cloud/spanner_test.rb b/google-cloud-spanner/test/google/cloud/spanner_test.rb index 2eaac8a6..be5df902 100644 --- a/google-cloud-spanner/test/google/cloud/spanner_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner_test.rb @@ -414,7 +414,7 @@ def quota_project_credentials.is_a? target Google::Cloud::Spanner::Credentials.stub :default, default_credentials do credentials = OpenStruct.new(client: OpenStruct.new(updater_proc: Proc.new {})) new_spanner = Google::Cloud::Spanner.new - new_client = new_spanner.client "instance-id", "database-id", pool: { min: 0 }, query_options: expect_query_options + new_client = new_spanner.client "instance-id", "database-id", query_options: expect_query_options _(new_client.query_options).must_equal expect_query_options end end @@ -429,7 +429,7 @@ def quota_project_credentials.is_a? target Google::Cloud::Spanner::Credentials.stub :default, default_credentials do credentials = OpenStruct.new(client: OpenStruct.new(updater_proc: Proc.new {})) new_spanner = Google::Cloud::Spanner.new - new_client = new_spanner.client "instance-id", "database-id", pool: { min: 0 }, query_options: { optimizer_version: "1", optimizer_statistics_package: "auto_20191128_14_47_22UTC" } + new_client = new_spanner.client "instance-id", "database-id", query_options: { optimizer_version: "1", optimizer_statistics_package: "auto_20191128_14_47_22UTC" } _(new_client.query_options).must_equal expect_query_options end end diff --git a/google-cloud-spanner/test/helper.rb b/google-cloud-spanner/test/helper.rb index e552c48f..5fe9f81a 100644 --- a/google-cloud-spanner/test/helper.rb +++ b/google-cloud-spanner/test/helper.rb @@ -33,25 +33,35 @@ class MockSpanner < Minitest::Spec Google::Cloud::Spanner::Service.new(project, credentials, enable_leader_aware_routing: false) ) end - let(:default_session_request) {nil} + let(:default_session_request) {Google::Cloud::Spanner::V1::Session.new name: "", labels: {}, creator_role: "", multiplexed: true} # Register this spec type for when :spanner is used. register_spec_type(self) do |desc, *addl| addl.include? :mock_spanner end + # Shutdown client is complicated if the default non-multiplex pool is used + # since we don't want to do a slow-shutdown for that every time. + # If a SessionCache is used the cleanup is simplified + # @param client [::Google::Cloud::Spanner::Client] def shutdown_client! client # extract the pool + # @type [::Google::Cloud::Spanner::Pool, ::Google::Cloud::Spanner::SessionCache] pool = client.instance_variable_get :@pool - # remove all sessions so we don't have to handle the calls to session_delete - pool.sessions_available = [] - pool.sessions_in_use = {} - # close the client - client.close + if (pool.is_a? ::Google::Cloud::Spanner::SessionCache) + client.close + else + # remove all sessions so we don't have to handle the calls to session_delete + pool.sessions_available = [] + pool.sessions_in_use = {} - # close the client - shutdown_pool! pool + # close the client + client.close + + # close the client + shutdown_pool! pool + end end def shutdown_pool! pool From e3120e0a4bede8ffbcdcae88c157ec65a511679b Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Fri, 31 Oct 2025 01:50:49 +0000 Subject: [PATCH 17/25] chore: let's just have it here --- google-cloud-spanner/Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/google-cloud-spanner/Gemfile b/google-cloud-spanner/Gemfile index cb0c22cc..668639b5 100644 --- a/google-cloud-spanner/Gemfile +++ b/google-cloud-spanner/Gemfile @@ -20,5 +20,6 @@ gem "rake" gem "redcarpet", "~> 3.0" gem "simplecov", "~> 0.22" gem "solargraph", group: :development, require: false +gem "pry", group: :development, require: false gem "yard", "~> 0.9" gem "yard-doctest", "~> 0.1.17", group: :development From 6763886c98c4b07dae6bc385f11b2277ec4a3483 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Fri, 31 Oct 2025 02:20:24 +0000 Subject: [PATCH 18/25] feat: add previous_transaction_id to transaction, service, and session --- .../lib/google/cloud/spanner/service.rb | 18 ++++++- .../lib/google/cloud/spanner/session.rb | 11 ++-- .../lib/google/cloud/spanner/transaction.rb | 51 ++++++++++++++++--- 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/google-cloud-spanner/lib/google/cloud/spanner/service.rb b/google-cloud-spanner/lib/google/cloud/spanner/service.rb index 7ae6f979..6743345c 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/service.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/service.rb @@ -625,6 +625,10 @@ def rollback session_name, transaction_id, call_options: nil # If a read-write transaction on a multiplexed session commit mutations # without performing any reads or queries, one of the mutations from the mutation set # must be sent as a mutation key for `BeginTransaction`. + # @param previous_transaction_id [::String, nil] Optional. + # An id of the previous transaction, if this new transaction wrapper is being created + # as a part of a retry. Previous transaction id should be added to TransactionOptions + # of a new ReadWrite transaction when retry is attempted. # @private # @return [::Google::Cloud::Spanner::V1::Transaction] def begin_transaction session_name, @@ -632,11 +636,21 @@ def begin_transaction session_name, request_options: nil, call_options: nil, route_to_leader: nil, - mutation_key: nil + mutation_key: nil, + previous_transaction_id: nil + read_write = if previous_transaction_id.nil? + V1::TransactionOptions::ReadWrite.new + else + V1::TransactionOptions::ReadWrite.new( + multiplexed_session_previous_transaction_id: previous_transaction_id + ) + end + tx_opts = V1::TransactionOptions.new( - read_write: V1::TransactionOptions::ReadWrite.new, + read_write: read_write, exclude_txn_from_change_streams: exclude_txn_from_change_streams ) + opts = default_options session_name: session_name, call_options: call_options, route_to_leader: route_to_leader diff --git a/google-cloud-spanner/lib/google/cloud/spanner/session.rb b/google-cloud-spanner/lib/google/cloud/spanner/session.rb index 83a3cc15..ee91b5e1 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/session.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/session.rb @@ -1403,10 +1403,15 @@ def create_transaction exclude_txn_from_change_streams: false # @param exclude_txn_from_change_streams [::Boolean] Optional. Defaults to `false`. # When `exclude_txn_from_change_streams` is set to `true`, it prevents read # or write transactions from being tracked in change streams. + # @param previous_transaction_id [::String, nil] Optional. + # An id of the previous transaction, if this new transaction wrapper is being created + # as a part of a retry. Previous transaction id should be added to TransactionOptions + # of a new ReadWrite transaction when retry is attempted. # @private - # @return [::Google::Cloud::Spanner::Transaction] The new *empty* transaction object. - def create_empty_transaction exclude_txn_from_change_streams: false - Transaction.from_grpc nil, self, exclude_txn_from_change_streams: exclude_txn_from_change_streams + # @return [::Google::Cloud::Spanner::Transaction] The new *empty-wrapper* transaction object. + def create_empty_transaction exclude_txn_from_change_streams: false, previous_transaction_id: nil + Transaction.from_grpc nil, self, exclude_txn_from_change_streams: exclude_txn_from_change_streams, +previous_transaction_id: previous_transaction_id end # If the session is non-multiplexed, keeps the session alive by executing `"SELECT 1"`. diff --git a/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb b/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb index 7ab84cba..d40aaf99 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb @@ -104,19 +104,35 @@ class Transaction # @return [::Google::Cloud::Spanner::V1::MultiplexedSessionPrecommitToken, nil] attr_accessor :precommit_token + # An id of the previous transaction, if this new transaction wrapper is being created + # as a part of a retry. Previous transaction id should be added to TransactionOptions + # of a new ReadWrite transaction when retry is attempted. + # @private + # @return [::String, nil] + attr_reader :previous_transaction_id + # Creates a new `Spanner::Transaction` instance from a `V1::Transaction` object. # @param grpc [::Google::Cloud::Spanner::V1::Transaction] Underlying `V1::Transaction` object. # @param session [::Google::Cloud::Spanner::Session] The session this transaction is running in. # @param exclude_txn_from_change_streams [::Boolean] # When `exclude_txn_from_change_streams` is set to `true`, it prevents read # or write transactions from being tracked in change streams. + # @param previous_transaction_id [::String, nil] Optional. + # An id of the previous transaction, if this new transaction wrapper is being created + # as a part of a retry. Previous transaction id should be added to TransactionOptions + # of a new ReadWrite transaction when retry is attempted. # @private # @return [::Google::Cloud::Spanner::Transaction] - def initialize grpc, session, exclude_txn_from_change_streams + def initialize grpc, session, exclude_txn_from_change_streams, previous_transaction_id: nil @grpc = grpc @session = session @exclude_txn_from_change_streams = exclude_txn_from_change_streams + # throwing away empty strings for simplicity + unless previous_transaction_id.nil? || previous_transaction_id.empty? + @previous_transaction_id = previous_transaction_id + end + @commit = Commit.new @seqno = 0 @exclude_txn_from_change_streams = false @@ -820,6 +836,8 @@ def read table, columns, keys: nil, index: nil, limit: nil, request_options = build_request_options request_options route_to_leader = LARHeaders.read true + # require "pry" + # binding.pry safe_execute do results = session.read table, columns, keys: keys, index: index, limit: limit, transaction: tx_selector, @@ -1228,10 +1246,14 @@ def mutations # @param exclude_txn_from_change_streams [::Boolean] Optional. Defaults to `false`. # When `exclude_txn_from_change_streams` is set to `true`, it prevents read # or write transactions from being tracked in change streams. + # @param previous_transaction_id [::String, nil] Optional. + # An id of the previous transaction, if this new transaction wrapper is being created + # as a part of a retry. Previous transaction id should be added to TransactionOptions + # of a new ReadWrite transaction when retry is attempted. # @private # @return [::Google::Cloud::Spanner::Transaction] - def self.from_grpc grpc, session, exclude_txn_from_change_streams: false - new grpc, session, exclude_txn_from_change_streams + def self.from_grpc grpc, session, exclude_txn_from_change_streams: false, previous_transaction_id: nil + new grpc, session, exclude_txn_from_change_streams, previous_transaction_id: previous_transaction_id end ## @@ -1262,19 +1284,25 @@ def no_existing_transaction? # @private # @return [::Google::Cloud::Spanner::V1::Transaction, nil] The new transaction # object, or `nil` if a transaction already exists. - def safe_begin_transaction! exclude_from_change_streams: false, request_options: nil, call_options: nil, - mutation_key: nil + def safe_begin_transaction! exclude_from_change_streams: false, request_options: nil, call_options: nil + # If this transaction contains mutations, we should select one to serve as a "mutation key" + mutation_key = mutations[0] if mutations.any? + @mutex.synchronize do return if existing_transaction? ensure_session! route_to_leader = LARHeaders.begin_transaction true + + # TODO: [virost@, 2025-10] fix this so it uses tx_selector + # instead of re-creating it within `Service#begin_transaction` @grpc = service.begin_transaction( session.path, exclude_txn_from_change_streams: exclude_from_change_streams, request_options: request_options, call_options: call_options, route_to_leader: route_to_leader, - mutation_key: mutation_key + mutation_key: mutation_key, + previous_transaction_id: previous_transaction_id ) end end @@ -1320,9 +1348,18 @@ def safe_execute # @return [::Google::Cloud::Spanner::V1::TransactionSelector] def tx_selector exclude_txn_from_change_streams: false return V1::TransactionSelector.new id: transaction_id if existing_transaction? + + read_write = if @previous_transaction_id.nil? + V1::TransactionOptions::ReadWrite.new + else + V1::TransactionOptions::ReadWrite.new( + multiplexed_session_previous_transaction_id: @previous_transaction_id + ) + end + V1::TransactionSelector.new( begin: V1::TransactionOptions.new( - read_write: V1::TransactionOptions::ReadWrite.new, + read_write: read_write, exclude_txn_from_change_streams: exclude_txn_from_change_streams ) ) From b6575f3b9b9ef267ed7c7c0ab14f127030ab2608 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Fri, 31 Oct 2025 02:21:18 +0000 Subject: [PATCH 19/25] feat: refactor client to always run the explicit transaction in Client#transaction method; send previous_transaction_id --- .../lib/google/cloud/spanner/client.rb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/google-cloud-spanner/lib/google/cloud/spanner/client.rb b/google-cloud-spanner/lib/google/cloud/spanner/client.rb index 5a87ac4f..2019de22 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/client.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/client.rb @@ -2144,22 +2144,19 @@ def transaction deadline: 120, exclude_txn_from_change_streams: false, Thread.current[IS_TRANSACTION_RUNNING_KEY] = true yield tx - if tx.mutations.any? && !tx.existing_transaction? + unless tx.existing_transaction? # This typically will happen if the yielded `tx` object was only used to add mutations. # Then it never called any RPCs and didn't create a server-side Transaction object. # In which case we should make an explicit BeginTransaction call here. - mutation_key = tx.mutations[0] - tx.safe_begin_transaction!( exclude_from_change_streams: exclude_txn_from_change_streams, request_options: request_options, - call_options: call_options, - mutation_key: mutation_key + call_options: call_options ) end - transaction_id = tx.transaction_id if tx.existing_transaction? + transaction_id = tx.transaction_id commit_resp = @project.service.commit( tx.session.path, tx.mutations, @@ -2181,7 +2178,11 @@ def transaction deadline: 120, exclude_txn_from_change_streams: false, sleep(delay_from_aborted(e) || backoff *= 1.3) # Create new transaction on the session and retry the block - tx = session.create_empty_transaction exclude_txn_from_change_streams: exclude_txn_from_change_streams + previous_transaction_id = tx.transaction_id if tx.existing_transaction? + tx = session.create_empty_transaction( + exclude_txn_from_change_streams: exclude_txn_from_change_streams, + previous_transaction_id: previous_transaction_id + ) if request_options tx.transaction_tag = request_options[:transaction_tag] end From bb49f6fbfa0b094ff80242be53f1ab65dfd613a4 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Fri, 31 Oct 2025 02:22:19 +0000 Subject: [PATCH 20/25] test: always run explicit transaction --- .../cloud/spanner/client/transaction_test.rb | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/transaction_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/transaction_test.rb index e5d07ff2..b21a4a4a 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/transaction_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/transaction_test.rb @@ -157,6 +157,37 @@ ] } + it "can read rows by id" do + columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] + + mock = Minitest::Mock.new + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :streaming_read, results_enum, [{ + session: session_grpc.name, table: "my-table", + columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], + key_set: Google::Cloud::Spanner::V1::KeySet.new(keys: [Google::Cloud::Spanner::Convert.object_to_grpc_value([1]).list_value, Google::Cloud::Spanner::Convert.object_to_grpc_value([2]).list_value, Google::Cloud::Spanner::Convert.object_to_grpc_value([3]).list_value]), + transaction: tx_selector, index: nil, limit: nil, resume_token: nil, partition_token: nil, + request_options: nil, + order_by: nil, lock_hint: nil + }, default_options] + mock.expect :commit, commit_resp, [{ + session: session_grpc.name, mutations: [], transaction_id: transaction_id, + single_use_transaction: nil, request_options: nil, precommit_token: nil + }, default_options] + session.service.mocked_service = mock + + results = nil + timestamp = client.transaction do |tx| + _(tx).must_be_kind_of Google::Cloud::Spanner::Transaction + results = tx.read "my-table", columns, keys: [1, 2, 3] + end + _(timestamp).must_equal commit_time + + mock.verify + + assert_results results + end + it "can execute a simple query" do mock = Minitest::Mock.new spanner.service.mocked_service = mock @@ -785,17 +816,21 @@ mock.verify end - it "will run a single-use transaction commit when the end-user does not do anything with the yielded transaction" do + it "will run begin transaction when the end-user does not do anything with the yielded transaction" do mock = Minitest::Mock.new mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :begin_transaction, transaction_grpc, [{ + session: session_grpc.name, + options: tx_opts, + request_options: nil, + mutation_key: nil + }, default_options] - # The transaction method would typically run explicit begin transaction - # except for the "empty" case where single-use is used instead. mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: [], - transaction_id: nil, - single_use_transaction: tx_no_dml_options, + transaction_id: transaction_id, + single_use_transaction: nil, request_options: nil, precommit_token: nil }, default_options] From 4d5a4f77b01381e53360ad509ce29fddaa8bab2f Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Fri, 31 Oct 2025 02:22:44 +0000 Subject: [PATCH 21/25] test: previous_transaction_id --- .../spanner/client/batch_update_test.rb | 4 +- .../spanner/client/transaction_retry_test.rb | 157 ++++++++++++++++-- 2 files changed, 145 insertions(+), 16 deletions(-) diff --git a/google-cloud-spanner/acceptance/spanner/client/batch_update_test.rb b/google-cloud-spanner/acceptance/spanner/client/batch_update_test.rb index 5fa16c28..b3e2ef0f 100644 --- a/google-cloud-spanner/acceptance/spanner/client/batch_update_test.rb +++ b/google-cloud-spanner/acceptance/spanner/client/batch_update_test.rb @@ -116,7 +116,9 @@ /3:(No statements in batch DML request|Request must contain at least one DML statement)/ ) end - _(timestamp).must_be_kind_of Time + + # Emulator does not return timestamp for multiplex empty transaction commits for some reason + _(timestamp).must_be_kind_of Time unless emulator_enabled? end it "executes multiple DML statements in a batch with syntax error for #{dialect}" do diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/transaction_retry_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/transaction_retry_test.rb index 3abce0fe..916e3629 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/transaction_retry_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/transaction_retry_test.rb @@ -33,6 +33,15 @@ ) ) end + let(:tx_selector_begin_retry_1) do + Google::Cloud::Spanner::V1::TransactionSelector.new( + begin: Google::Cloud::Spanner::V1::TransactionOptions.new( + read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new( + multiplexed_session_previous_transaction_id: transaction_id + ) + ) + ) + end let(:default_options) { ::Gapic::CallOptions.new metadata: { "google-cloud-resource-prefix" => database_path(instance_id, database_id) } } let :results_hash do { @@ -87,7 +96,7 @@ spanner.service.mocked_service = mock mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin_retry_1, seqno: 1, options: default_options def mock.commit *args # first time called this will raise @@ -135,7 +144,7 @@ def mock.commit *args spanner.service.mocked_service = mock mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin_retry_1, seqno: 1, options: default_options def mock.commit *args # first time called this will raise @@ -181,7 +190,7 @@ def mock.commit *args spanner.service.mocked_service = mock mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin_retry_1, seqno: 1, options: default_options def mock.commit *args # first time called this will raise @@ -227,8 +236,8 @@ def mock.commit *args spanner.service.mocked_service = mock mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin_retry_1, seqno: 1, options: default_options + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin_retry_1, seqno: 1, options: default_options def mock.commit *args # first time called this will raise @@ -265,6 +274,123 @@ def mock.commit *args mock.verify end + it "sets previous_transaction_id when retrying multiple aborted transactions after transaction is created" do + # Other test methods in this file elide the details of multiple retries for the sake of directness. + # So e.g. they have the second call return the results object with the same transaction as the first call. + # This method takes pains to model things a bit closer to actual flow. + + columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] + mutations = [ + Google::Cloud::Spanner::V1::Mutation.new( + update: Google::Cloud::Spanner::V1::Mutation::Write.new( + table: "users", columns: %w(id name active), + values: [Google::Cloud::Spanner::Convert.object_to_grpc_value([1, "Charlie", false]).list_value] + ) + ) + ] + + mock = Minitest::Mock.new + spanner.service.mocked_service = mock + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + + mock.expect :streaming_read, results_enum, [{ + session: session_grpc.name, table: "my-table", + columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], + key_set: Google::Cloud::Spanner::V1::KeySet.new(keys: [Google::Cloud::Spanner::Convert.object_to_grpc_value([1]).list_value, Google::Cloud::Spanner::Convert.object_to_grpc_value([2]).list_value, Google::Cloud::Spanner::Convert.object_to_grpc_value([3]).list_value]), + transaction: tx_selector_begin, index: nil, limit: nil, resume_token: nil, partition_token: nil, + request_options: nil, + order_by: nil, lock_hint: nil + }, default_options] + # first execute_streaming_sql is not a retry, it is a request that runs on a transaction that streaming_read created + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_id, seqno: 2, options: default_options + + + # On the first retry, the initial transaction is `previous` in inline-begin of `streaming_read` + # And the first retry transaction is created, returned from `streaming_read` and then used in the `execute_streaming_sql` + transaction_id_retry_1 = "tx_retry1" + tx_selector_retry_1 = Google::Cloud::Spanner::V1::TransactionSelector.new id: "tx_retry1" + + hash_retry1 = results_hash.clone + hash_retry1[:metadata][:transaction][:id] = "tx_retry1" + results_enum_retry_1 = Array(Google::Cloud::Spanner::V1::PartialResultSet.new(hash_retry1)).to_enum + + mock.expect :streaming_read, results_enum_retry_1, [{ + session: session_grpc.name, table: "my-table", + columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], + key_set: Google::Cloud::Spanner::V1::KeySet.new(keys: [Google::Cloud::Spanner::Convert.object_to_grpc_value([1]).list_value, Google::Cloud::Spanner::Convert.object_to_grpc_value([2]).list_value, Google::Cloud::Spanner::Convert.object_to_grpc_value([3]).list_value]), + transaction: tx_selector_begin_retry_1, index: nil, limit: nil, resume_token: nil, partition_token: nil, + request_options: nil, + order_by: nil, lock_hint: nil + }, default_options] + expect_execute_streaming_sql results_enum_retry_1, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_retry_1, seqno: 2, options: default_options + + # On the second retry, the first retry transaction is `previous` in inline-begin of `streaming_read` + tx_selector_begin_retry_2 =Google::Cloud::Spanner::V1::TransactionSelector.new( + begin: Google::Cloud::Spanner::V1::TransactionOptions.new( + read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new(multiplexed_session_previous_transaction_id: transaction_id_retry_1) + ) + ) + # And the second retry transaction is created, returned from `streaming_read` and then used in the `execute_streaming_sql` + transaction_id_retry_2 = "tx_retry2" + tx_selector_retry_2 = Google::Cloud::Spanner::V1::TransactionSelector.new id: "tx_retry2" + + hash_retry2 = results_hash.clone + hash_retry2[:metadata][:transaction][:id] = transaction_id_retry_2 + results_enum_retry_2 = Array(Google::Cloud::Spanner::V1::PartialResultSet.new(hash_retry2)).to_enum + + mock.expect :streaming_read, results_enum_retry_2, [{ + session: session_grpc.name, table: "my-table", + columns: ["id", "name", "active", "age", "score", "updated_at", "birthday", "avatar", "project_ids"], + key_set: Google::Cloud::Spanner::V1::KeySet.new(keys: [Google::Cloud::Spanner::Convert.object_to_grpc_value([1]).list_value, Google::Cloud::Spanner::Convert.object_to_grpc_value([2]).list_value, Google::Cloud::Spanner::Convert.object_to_grpc_value([3]).list_value]), + transaction: tx_selector_begin_retry_2, index: nil, limit: nil, resume_token: nil, partition_token: nil, + request_options: nil, + order_by: nil, lock_hint: nil + }, default_options] + + expect_execute_streaming_sql results_enum_retry_2, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_retry_2, seqno: 2, options: default_options + # after this the second retry transaction will be committed + + def mock.commit *args + # first time called this will raise + if @called == nil + @called = false + raise GRPC::Aborted.new "aborted" + end + if @called == false + @called = true + raise GRPC::Aborted.new "aborted", create_retry_info_metadata(30, 0) + end + + # we are committing the second retry transaction + raise unless args[0][:transaction_id] == "tx_retry2" + + # third call will return correct response + Google::Cloud::Spanner::V1::CommitResponse.new commit_timestamp: Google::Protobuf::Timestamp.new() + end + mock.expect :sleep, nil, [1.3] + mock.expect :sleep, nil, [30] + + client.define_singleton_method :sleep do |count| + # call the mock to satisfy the expectation + mock.sleep count + end + + results = nil + client.transaction do |tx| + _(tx).must_be_kind_of Google::Cloud::Spanner::Transaction + # this tx_read creates a transaction, id of which we should see as `previous` in the retries + _read_res = tx.read "my-table", columns, keys: [1, 2, 3] + results = tx.execute_query "SELECT * FROM users" + tx.update "users", [{ id: 1, name: "Charlie", active: false }] + end + + assert_results results + + shutdown_client! client + + mock.verify + end + it "retries with incremental backoff until deadline has passed" do mutations = [ Google::Cloud::Spanner::V1::Mutation.new( @@ -279,10 +405,10 @@ def mock.commit *args spanner.service.mocked_service = mock mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin_retry_1, seqno: 1, options: default_options + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin_retry_1, seqno: 1, options: default_options + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin_retry_1, seqno: 1, options: default_options + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin_retry_1, seqno: 1, options: default_options def mock.commit *args raise GRPC::Aborted.new "aborted" @@ -335,7 +461,7 @@ def mock.commit *args spanner.service.mocked_service = mock mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options - expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin, seqno: 1, options: default_options + expect_execute_streaming_sql results_enum, session_grpc.name, "SELECT * FROM users", transaction: tx_selector_begin_retry_1, seqno: 1, options: default_options def mock.commit *args # first time called this will raise @@ -380,11 +506,12 @@ def mock.commit *args mock = Minitest::Mock.new spanner.service.mocked_service = mock mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] - - # this test does not expect begin_transaction to be called explicitly since - # the client does not do anything with the transaction. - # this is the only case where `Service#commit` from `Client#transaction` is called with - # a single-use + mock.expect :begin_transaction, transaction_grpc, [{ + session: session_grpc.name, + options: tx_opts, + request_options: nil, + mutation_key: nil + }, default_options] def mock.commit *args # first time called this will raise From fd3f4fde7a65bb09eb5a183ab8c97c03b7d0aed2 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 4 Nov 2025 18:32:41 +0000 Subject: [PATCH 22/25] feat: retry commit protocol, add in-place retry to Session#commit and Client#transaction --- .../lib/google/cloud/spanner/client.rb | 32 +++++-- .../lib/google/cloud/spanner/session.rb | 24 +++-- .../cloud/spanner/client/commit_test.rb | 27 ++++++ .../cloud/spanner/client/transaction_test.rb | 94 +++++++++++++++++++ 4 files changed, 161 insertions(+), 16 deletions(-) diff --git a/google-cloud-spanner/lib/google/cloud/spanner/client.rb b/google-cloud-spanner/lib/google/cloud/spanner/client.rb index 2019de22..3a5ed3e2 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/client.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/client.rb @@ -2157,16 +2157,28 @@ def transaction deadline: 120, exclude_txn_from_change_streams: false, end transaction_id = tx.transaction_id - commit_resp = @project.service.commit( - tx.session.path, - tx.mutations, - transaction_id: transaction_id, - exclude_txn_from_change_streams: exclude_txn_from_change_streams, - commit_options: commit_options, - request_options: request_options, - call_options: call_options, - precommit_token: tx.precommit_token - ) + + # This "inner retry" mechanism is for Commit Response protocol. + # Unlike the retry on `Aborted` errors it will not re-create a transaction. + # This is intentional, as these retries are not related to e.g. + # transactions deadlocking, so it's OK to retry "as-is". + should_retry = true + while should_retry + commit_resp = @project.service.commit( + tx.session.path, + tx.mutations, + transaction_id: transaction_id, + exclude_txn_from_change_streams: exclude_txn_from_change_streams, + commit_options: commit_options, + request_options: request_options, + call_options: call_options, + precommit_token: tx.precommit_token + ) + + tx.precommit_token = commit_resp.precommit_token + should_retry = !commit_resp.precommit_token.nil? + end + resp = CommitResponse.from_grpc commit_resp commit_options ? resp : resp.timestamp rescue GRPC::Aborted, diff --git a/google-cloud-spanner/lib/google/cloud/spanner/session.rb b/google-cloud-spanner/lib/google/cloud/spanner/session.rb index ee91b5e1..54d0c2ee 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/session.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/session.rb @@ -685,12 +685,24 @@ def commit transaction_id: nil, exclude_txn_from_change_streams: false, ensure_service! commit = Commit.new yield commit - commit_resp = service.commit path, commit.mutations, - transaction_id: transaction_id, - exclude_txn_from_change_streams: exclude_txn_from_change_streams, - commit_options: commit_options, - request_options: request_options, - call_options: call_options + + should_retry = true + # @type [Google::Cloud::Spanner::V1::MultiplexedSessionPrecommitToken] + precommit_token = nil + while should_retry + commit_resp = service.commit(path, + commit.mutations, + transaction_id: transaction_id, + exclude_txn_from_change_streams: exclude_txn_from_change_streams, + commit_options: commit_options, + request_options: request_options, + call_options: call_options, + precommit_token: precommit_token) + + precommit_token = commit_resp.precommit_token + should_retry = !precommit_token.nil? + end + @last_updated_at = Process.clock_gettime Process::CLOCK_MONOTONIC resp = CommitResponse.from_grpc commit_resp commit_options ? resp : resp.timestamp diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/commit_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/commit_test.rb index 08052481..a42c6f2e 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/commit_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/commit_test.rb @@ -22,6 +22,11 @@ let(:commit_time) { Time.now } let(:commit_timestamp) { Google::Cloud::Spanner::Convert.time_to_timestamp commit_time } let(:commit_resp) { Google::Cloud::Spanner::V1::CommitResponse.new commit_timestamp: commit_timestamp } + let(:precommit_token_0) {Google::Cloud::Spanner::V1::MultiplexedSessionPrecommitToken.new seq_num: 0, precommit_token: "token0"} + let(:commit_resp_precommit_0) { Google::Cloud::Spanner::V1::CommitResponse.new commit_timestamp: commit_timestamp, precommit_token: precommit_token_0 } + let(:precommit_token_1) {Google::Cloud::Spanner::V1::MultiplexedSessionPrecommitToken.new seq_num: 1, precommit_token: "token1"} + let(:commit_resp_precommit_1) { Google::Cloud::Spanner::V1::CommitResponse.new commit_timestamp: commit_timestamp, precommit_token: precommit_token_1 } + let(:commit_stats_grpc) { Google::Cloud::Spanner::V1::CommitResponse::CommitStats.new( mutation_count: 5 @@ -97,6 +102,28 @@ mock.verify end + it "retries commits using a block when precommit token is returned" do + mock = Minitest::Mock.new + mock.expect :create_session, session_grpc, [{database: database_path(instance_id, database_id), session: default_session_request }, default_options] + mock.expect :commit, commit_resp_precommit_0, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: nil }, default_options] + mock.expect :commit, commit_resp_precommit_1, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: precommit_token_0 }, default_options] + mock.expect :commit, commit_resp, [{ session: session_grpc.name, mutations: mutations, transaction_id: nil, single_use_transaction: tx_opts, request_options: nil, precommit_token: precommit_token_1 }, default_options] + spanner.service.mocked_service = mock + + timestamp = client.commit do |c| + c.update "users", [{ id: 1, name: "Charlie", active: false }] + c.insert "users", [{ id: 2, name: "Harvey", active: true }] + c.upsert "users", [{ id: 3, name: "Marley", active: false }] + c.replace "users", [{ id: 4, name: "Henry", active: true }] + c.delete "users", [1, 2, 3, 4, 5] + end + _(timestamp).must_equal commit_time + + shutdown_client! client + + mock.verify + end + it "updates directly" do mutations = [ Google::Cloud::Spanner::V1::Mutation.new( diff --git a/google-cloud-spanner/test/google/cloud/spanner/client/transaction_test.rb b/google-cloud-spanner/test/google/cloud/spanner/client/transaction_test.rb index b21a4a4a..b7bf60b2 100644 --- a/google-cloud-spanner/test/google/cloud/spanner/client/transaction_test.rb +++ b/google-cloud-spanner/test/google/cloud/spanner/client/transaction_test.rb @@ -109,6 +109,10 @@ let(:commit_time) { Time.now } let(:commit_timestamp) { Google::Cloud::Spanner::Convert.time_to_timestamp commit_time } let(:commit_resp) { Google::Cloud::Spanner::V1::CommitResponse.new commit_timestamp: commit_timestamp } + let(:precommit_token_0) {Google::Cloud::Spanner::V1::MultiplexedSessionPrecommitToken.new seq_num: 0, precommit_token: "token0"} + let(:commit_resp_precommit_0) { Google::Cloud::Spanner::V1::CommitResponse.new commit_timestamp: commit_timestamp, precommit_token: precommit_token_0 } + let(:precommit_token_1) {Google::Cloud::Spanner::V1::MultiplexedSessionPrecommitToken.new seq_num: 1, precommit_token: "token1"} + let(:commit_resp_precommit_1) { Google::Cloud::Spanner::V1::CommitResponse.new commit_timestamp: commit_timestamp, precommit_token: precommit_token_1 } let(:commit_stats_grpc) { Google::Cloud::Spanner::V1::CommitResponse::CommitStats.new( mutation_count: 5 @@ -673,6 +677,96 @@ mock.verify end + it "retries 'as is' commits with multiple mutations when precommit token is returned" do + mutations = [ + Google::Cloud::Spanner::V1::Mutation.new( + update: Google::Cloud::Spanner::V1::Mutation::Write.new( + table: "users", columns: %w(id name active address), + values: [Google::Cloud::Spanner::Convert.object_to_grpc_value([1, "Charlie", false, { postcode: 1234 }]).list_value] + ) + ), + Google::Cloud::Spanner::V1::Mutation.new( + insert: Google::Cloud::Spanner::V1::Mutation::Write.new( + table: "users", columns: %w(id name active), + values: [Google::Cloud::Spanner::Convert.object_to_grpc_value([2, "Harvey", true]).list_value] + ) + ), + Google::Cloud::Spanner::V1::Mutation.new( + insert_or_update: Google::Cloud::Spanner::V1::Mutation::Write.new( + table: "users", columns: %w(id name active), + values: [Google::Cloud::Spanner::Convert.object_to_grpc_value([3, "Marley", false]).list_value] + ) + ), + Google::Cloud::Spanner::V1::Mutation.new( + replace: Google::Cloud::Spanner::V1::Mutation::Write.new( + table: "users", columns: %w(id name active), + values: [Google::Cloud::Spanner::Convert.object_to_grpc_value([4, "Henry", true]).list_value] + ) + ), + Google::Cloud::Spanner::V1::Mutation.new( + delete: Google::Cloud::Spanner::V1::Mutation::Delete.new( + table: "users", key_set: Google::Cloud::Spanner::V1::KeySet.new( + keys: [1, 2, 3, 4, 5].map do |i| + Google::Cloud::Spanner::Convert.object_to_grpc_value([i]).list_value + end + ) + ) + ) + ] + + mock = Minitest::Mock.new + mock.expect :create_session, session_grpc, [{ database: database_path(instance_id, database_id), session: default_session_request }, default_options] + + # Since the yielded transaction object was only used to add mutations, + # we expect an explicit `begin_transaction` call, and subsequently + # the id of the transaction returned to be issued in the `commit` request. + mock.expect :begin_transaction, transaction_grpc, [{ + session: session_grpc.name, + options: tx_opts, + request_options: nil, + mutation_key: mutations[0] + }, default_options] + + mock.expect :commit, commit_resp_precommit_0, [{ + session: session_grpc.name, + mutations: mutations, + transaction_id: transaction_id, + single_use_transaction: nil, precommit_token: nil, + request_options: nil + }, default_options] + + mock.expect :commit, commit_resp_precommit_1, [{ + session: session_grpc.name, + mutations: mutations, + transaction_id: transaction_id, + single_use_transaction: nil, precommit_token: precommit_token_0, + request_options: nil + }, default_options] + + mock.expect :commit, commit_resp, [{ + session: session_grpc.name, + mutations: mutations, + transaction_id: transaction_id, + single_use_transaction: nil, precommit_token: precommit_token_1, + request_options: nil + }, default_options] + + spanner.service.mocked_service = mock + + timestamp = client.transaction do |tx| + tx.update "users", [{ id: 1, name: "Charlie", active: false, address: { "postcode" => 1234 } }] + tx.insert "users", [{ id: 2, name: "Harvey", active: true }] + tx.upsert "users", [{ id: 3, name: "Marley", active: false }] + tx.replace "users", [{ id: 4, name: "Henry", active: true }] + tx.delete "users", [1, 2, 3, 4, 5] + end + _(timestamp).must_equal commit_time + + shutdown_client! client + + mock.verify + end + it "can execute a simple query with custom timeout and retry policy" do timeout = 30 retry_policy = { From a2d63994704353609657f910c2f7b64b2d2e0d5e Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Tue, 4 Nov 2025 18:32:53 +0000 Subject: [PATCH 23/25] fixup: pry position, yardocs --- google-cloud-spanner/Gemfile | 2 +- .../lib/google/cloud/spanner/transaction.rb | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/google-cloud-spanner/Gemfile b/google-cloud-spanner/Gemfile index 668639b5..09569c4c 100644 --- a/google-cloud-spanner/Gemfile +++ b/google-cloud-spanner/Gemfile @@ -16,10 +16,10 @@ gem "minitest", "~> 5.25" gem "minitest-autotest", "~> 1.0" gem "minitest-focus", "~> 1.4" gem "minitest-rg", "~> 5.3" +gem "pry", group: :development, require: false gem "rake" gem "redcarpet", "~> 3.0" gem "simplecov", "~> 0.22" gem "solargraph", group: :development, require: false -gem "pry", group: :development, require: false gem "yard", "~> 0.9" gem "yard-doctest", "~> 0.1.17", group: :development diff --git a/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb b/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb index d40aaf99..47cb3000 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb @@ -1277,15 +1277,14 @@ def no_existing_transaction? # Example option: `:priority`. # @param call_options [::Hash, nil] Optional. A hash of values to specify the custom # call options. Example option `:timeout`. - # @param mutation_key [::Google::Cloud::Spanner::V1::Mutation, nil] Optional. - # If a read-write transaction on a multiplexed session commit mutations - # without performing any reads or queries, one of the mutations from the mutation set - # must be sent as a mutation key for `BeginTransaction`. # @private # @return [::Google::Cloud::Spanner::V1::Transaction, nil] The new transaction # object, or `nil` if a transaction already exists. def safe_begin_transaction! exclude_from_change_streams: false, request_options: nil, call_options: nil - # If this transaction contains mutations, we should select one to serve as a "mutation key" + # If a read-write transaction on a multiplexed session commit mutations + # without performing any reads or queries, one of the mutations from the mutation set + # must be sent as a mutation key for `BeginTransaction`. + # @type [::Google::Cloud::Spanner::V1::Mutation, nil] mutation_key = mutations[0] if mutations.any? @mutex.synchronize do From 7c885c9aa21a8f2a2c58a76a326c76ecf49dd7d4 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Thu, 6 Nov 2025 17:52:44 +0000 Subject: [PATCH 24/25] fix: broken acceptance tests --- .../acceptance/spanner/client/batch_update_test.rb | 11 +++++++++-- .../spanner/fine_grain_access_control_test.rb | 3 ++- .../lib/google/cloud/spanner/transaction.rb | 2 -- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/google-cloud-spanner/acceptance/spanner/client/batch_update_test.rb b/google-cloud-spanner/acceptance/spanner/client/batch_update_test.rb index b3e2ef0f..526ab5eb 100644 --- a/google-cloud-spanner/acceptance/spanner/client/batch_update_test.rb +++ b/google-cloud-spanner/acceptance/spanner/client/batch_update_test.rb @@ -158,7 +158,9 @@ prior_results = db[dialect].execute_sql "SELECT * FROM accounts" _(prior_results.rows.count).must_equal 3 - db[dialect].transaction do |tx| + # @type [::Google::Cloud::Spanner::Client] + dbd = db[dialect] + dbd.transaction do |tx| begin _(tx.no_existing_transaction?).must_equal true tx.batch_update do |b| @@ -167,10 +169,15 @@ rescue Google::Cloud::Spanner::BatchUpdateError => e _(e.cause).must_be_kind_of Google::Cloud::InvalidArgumentError _(e.cause.message).must_equal "Statement 0: 'UPDDDD accounts' is not valid DML." + rescue ::Google::Cloud::InternalError => e + # [TODO virost@ 2025-11] This is accomodating a temporary backend regression, + # after 2026-01 this rescue clause should be removed. + _(e.cause.code).must_equal 13 + _(e.cause.details).must_equal "Internal error encountered." end _(tx.no_existing_transaction?).must_equal true end - prior_results = db[dialect].execute_sql "SELECT * FROM accounts" + prior_results = dbd.execute_sql "SELECT * FROM accounts" _(prior_results.rows.count).must_equal 3 end diff --git a/google-cloud-spanner/acceptance/spanner/fine_grain_access_control_test.rb b/google-cloud-spanner/acceptance/spanner/fine_grain_access_control_test.rb index 11de75db..6375658f 100644 --- a/google-cloud-spanner/acceptance/spanner/fine_grain_access_control_test.rb +++ b/google-cloud-spanner/acceptance/spanner/fine_grain_access_control_test.rb @@ -72,7 +72,8 @@ skip if emulator_enabled? error = assert_raises Google::Cloud::PermissionDeniedError do - db.client $spanner_instance_id, $spanner_database_id, database_role: "unknown" + db_client = db.client $spanner_instance_id, $spanner_database_id, database_role: "unknown" + (db_client.instance_variable_get :@pool).with_session { |s| } # rubocop:disable Lint/EmptyBlock end assert_includes error.message, "Role not found: unknown" diff --git a/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb b/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb index 47cb3000..24cc8571 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/transaction.rb @@ -836,8 +836,6 @@ def read table, columns, keys: nil, index: nil, limit: nil, request_options = build_request_options request_options route_to_leader = LARHeaders.read true - # require "pry" - # binding.pry safe_execute do results = session.read table, columns, keys: keys, index: index, limit: limit, transaction: tx_selector, From e3bc8674cf13372e3bb238fb630d7d60afc503c5 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Mon, 17 Nov 2025 19:04:08 +0000 Subject: [PATCH 25/25] fixup: review comments --- .../acceptance/spanner/client/batch_update_test.rb | 6 +++--- google-cloud-spanner/lib/google/cloud/spanner/client.rb | 6 ++---- google-cloud-spanner/lib/google/cloud/spanner/project.rb | 7 +++---- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/google-cloud-spanner/acceptance/spanner/client/batch_update_test.rb b/google-cloud-spanner/acceptance/spanner/client/batch_update_test.rb index 526ab5eb..4903ce7a 100644 --- a/google-cloud-spanner/acceptance/spanner/client/batch_update_test.rb +++ b/google-cloud-spanner/acceptance/spanner/client/batch_update_test.rb @@ -118,7 +118,7 @@ end # Emulator does not return timestamp for multiplex empty transaction commits for some reason - _(timestamp).must_be_kind_of Time unless emulator_enabled? + _(timestamp).must_be_kind_of Time end it "executes multiple DML statements in a batch with syntax error for #{dialect}" do @@ -151,7 +151,7 @@ end it "raises BatchUpdateError when the first statement in Batch DML is a syntax error for #{dialect}" do - # ** When using the emulator with multiplexed sessions** + # ** When using the emulator with multiplexed sessions ** # the BatchUpdate transaction in this test will not get cleaned up and that will cause the # "The emulator only supports one transaction at a time." failure. skip if emulator_enabled? @@ -212,7 +212,7 @@ describe "request options for #{dialect}" do it "execute batch update with priority options for #{dialect}" do - # ** When using the emulator with multiplexed sessions** + # ** When using the emulator with multiplexed sessions ** # the BatchUpdate transaction in this test will not get cleaned up and that will cause the # "The emulator only supports one transaction at a time." failure. skip if emulator_enabled? diff --git a/google-cloud-spanner/lib/google/cloud/spanner/client.rb b/google-cloud-spanner/lib/google/cloud/spanner/client.rb index 3a5ed3e2..460c4009 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/client.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/client.rb @@ -56,8 +56,6 @@ class Client # @private IS_TRANSACTION_RUNNING_KEY = "ruby_spanner_is_transaction_running".freeze - # rubocop:disable Lint/UnusedMethodArgument - # Creates a new Spanner Client instance. # @param project [::Google::Cloud::Spanner::Project] A `Spanner::Project` ref. # @param instance_id [::String] Instance id, e.g. `"my-instance"`. @@ -86,6 +84,8 @@ def initialize project, instance_id, database_id, session_labels: nil, @query_options = query_options @directed_read_options = directed_read_options + _pool_opts = pool_opts # unused. Here only to avoid having to disable Rubocop's Lint/UnusedMethodArgument + session_creation_options = SessionCreationOptions.new( database_path: Admin::Database::V1::DatabaseAdmin::Paths.database_path( project: @project.service.project, instance: instance_id, database: database_id @@ -98,8 +98,6 @@ def initialize project, instance_id, database_id, session_labels: nil, @pool = SessionCache.new @project.service, session_creation_options end - # rubocop:enable Lint/UnusedMethodArgument - # The unique identifier for the project. # @return [String] def project_id diff --git a/google-cloud-spanner/lib/google/cloud/spanner/project.rb b/google-cloud-spanner/lib/google/cloud/spanner/project.rb index 11b37f0e..3799cbf7 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/project.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/project.rb @@ -501,8 +501,6 @@ def create_database instance_id, database_id, statements: [], Database::Job.from_grpc grpc, service end - # rubocop:disable Lint/UnusedMethodArgument - ## # Creates a Cloud Spanner client. A client is used to read and/or modify # data in a Cloud Spanner database. @@ -580,6 +578,9 @@ def client instance_id, database_id, pool: {}, labels: nil, else query_options = query_options.merge @query_options unless @query_options.nil? end + + _pool = pool # unused. Here only to avoid having to disable Rubocop's Lint/UnusedMethodArgument + Client.new self, instance_id, database_id, session_labels: labels, query_options: query_options, @@ -587,8 +588,6 @@ def client instance_id, database_id, pool: {}, labels: nil, directed_read_options: directed_read_options end - # rubocop:enable Lint/UnusedMethodArgument - ## # Creates a Cloud Spanner batch client. A batch client is used to read # data across multiple machines or processes.