From e09ede6c1845f3b6c93b353a18e4fa5270b0de77 Mon Sep 17 00:00:00 2001 From: skuruppu Date: Thu, 9 Apr 2026 08:06:24 +0000 Subject: [PATCH] feat(spanner): samples to set transaction options Adds samples that show how to set the isolation level and read lock mode at the Spanner client level and at a transaction level. If it's set at the client level, all RW transactions will set this value. The transaction-level setting allows users to override the client-level setting or to set the values for specific transactions. --- spanner/Gemfile.lock | 20 ++++---- spanner/spanner_isolation_level.rb | 36 ++++++++++++++ spanner/spanner_read_lock_mode.rb | 36 ++++++++++++++ spanner/spec/spanner_isolation_level_spec.rb | 51 ++++++++++++++++++++ spanner/spec/spanner_read_lock_mode_spec.rb | 51 ++++++++++++++++++++ 5 files changed, 184 insertions(+), 10 deletions(-) create mode 100644 spanner/spanner_isolation_level.rb create mode 100644 spanner/spanner_read_lock_mode.rb create mode 100644 spanner/spec/spanner_isolation_level_spec.rb create mode 100644 spanner/spec/spanner_read_lock_mode_spec.rb diff --git a/spanner/Gemfile.lock b/spanner/Gemfile.lock index f09e7f98e..198c6c072 100644 --- a/spanner/Gemfile.lock +++ b/spanner/Gemfile.lock @@ -1,7 +1,7 @@ GEM remote: https://rubygems.org/ specs: - addressable (2.8.9) + addressable (2.9.0) public_suffix (>= 2.0.2, < 8.0) base64 (0.3.0) bigdecimal (3.3.1) @@ -42,15 +42,15 @@ GEM google-cloud-env (2.3.1) base64 (~> 0.2) faraday (>= 1.0, < 3.a) - google-cloud-errors (1.5.0) - google-cloud-spanner (2.35.0) + google-cloud-errors (1.6.0) + google-cloud-spanner (2.36.0) bigdecimal (~> 3.0) concurrent-ruby (~> 1.0) google-cloud-core (~> 1.7) google-cloud-spanner-admin-database-v1 (~> 1.4) google-cloud-spanner-admin-instance-v1 (~> 1.6) google-cloud-spanner-v1 (~> 1.6) - google-cloud-spanner-admin-database-v1 (1.11.1) + google-cloud-spanner-admin-database-v1 (1.13.1) gapic-common (~> 1.2) google-cloud-errors (~> 1.0) grpc-google-iam-v1 (~> 1.11) @@ -58,11 +58,11 @@ GEM gapic-common (>= 0.25.0, < 2.a) google-cloud-errors (~> 1.0) grpc-google-iam-v1 (~> 1.1) - google-cloud-spanner-v1 (1.15.0) + google-cloud-spanner-v1 (1.18.0) gapic-common (~> 1.2) google-cloud-errors (~> 1.0) google-logging-utils (0.2.0) - google-protobuf (4.34.0) + google-protobuf (4.34.1) bigdecimal rake (~> 13.3) googleapis-common-protos (1.7.0) @@ -79,7 +79,7 @@ GEM multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) - grpc (1.78.1) + grpc (1.80.0) google-protobuf (>= 3.25, < 5.0) googleapis-common-protos-types (~> 1.0) grpc-google-iam-v1 (1.11.0) @@ -87,18 +87,18 @@ GEM googleapis-common-protos (~> 1.7.0) grpc (~> 1.41) httpclient (2.8.3) - json (2.19.1) + json (2.19.5) jwt (3.1.2) base64 logger (1.7.0) mini_mime (1.1.5) - multi_json (1.19.1) + multi_json (1.21.1) mutex_m (0.3.0) net-http (0.9.1) uri (>= 0.11.1) os (1.1.4) public_suffix (7.0.5) - rake (13.3.1) + rake (13.4.2) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) diff --git a/spanner/spanner_isolation_level.rb b/spanner/spanner_isolation_level.rb new file mode 100644 index 000000000..b1d3434e6 --- /dev/null +++ b/spanner/spanner_isolation_level.rb @@ -0,0 +1,36 @@ +# Copyright 2026 Google, Inc +# +# 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 +# +# http://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. + +# [START spanner_isolation_level] +require "google/cloud/spanner" + +def spanner_isolation_level project_id:, instance_id:, database_id: + # Instantiates a client with isolation_level: :SERIALIZABLE + spanner = Google::Cloud::Spanner.new project: project_id + client = spanner.client instance_id, database_id, isolation_level: :SERIALIZABLE + + # Overrides isolation_level to :REPEATABLE_READ at transaction level + client.transaction isolation_level: :REPEATABLE_READ do |tx| + results = tx.execute_query "SELECT AlbumTitle FROM Albums WHERE SingerId = 1 AND AlbumId = 1" + + results.rows.each do |row| + puts "AlbumTitle: #{row[:AlbumTitle]}" + end + + row_count = tx.execute_update "UPDATE Albums SET AlbumTitle = 'A New Title' WHERE SingerId = 1 AND AlbumId = 1" + + puts "#{row_count} records updated." + end +end +# [END spanner_isolation_level] diff --git a/spanner/spanner_read_lock_mode.rb b/spanner/spanner_read_lock_mode.rb new file mode 100644 index 000000000..85b8eaac4 --- /dev/null +++ b/spanner/spanner_read_lock_mode.rb @@ -0,0 +1,36 @@ +# Copyright 2026 Google, Inc +# +# 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 +# +# http://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. + +# [START spanner_read_lock_mode] +require "google/cloud/spanner" + +def spanner_read_lock_mode project_id:, instance_id:, database_id: + # Instantiates a client with read_lock_mode: :OPTIMISTIC + spanner = Google::Cloud::Spanner.new project: project_id + client = spanner.client instance_id, database_id, read_lock_mode: :OPTIMISTIC + + # Overrides read_lock_mode to :PESSIMISTIC at transaction level + client.transaction read_lock_mode: :PESSIMISTIC do |tx| + results = tx.execute_query "SELECT AlbumTitle FROM Albums WHERE SingerId = 2 AND AlbumId = 1" + + results.rows.each do |row| + puts "AlbumTitle: #{row[:AlbumTitle]}" + end + + row_count = tx.execute_update "UPDATE Albums SET AlbumTitle = 'A New Title' WHERE SingerId = 2 AND AlbumId = 1" + + puts "#{row_count} records updated." + end +end +# [END spanner_read_lock_mode] diff --git a/spanner/spec/spanner_isolation_level_spec.rb b/spanner/spec/spanner_isolation_level_spec.rb new file mode 100644 index 000000000..726c4f5e6 --- /dev/null +++ b/spanner/spec/spanner_isolation_level_spec.rb @@ -0,0 +1,51 @@ +# Copyright 2026 Google, Inc +# +# 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 +# +# http://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_relative "../spanner_isolation_level" +require "rspec" +require "google/cloud/spanner" + +describe "Spanner Isolation Level Options" do + it "runs isolation level snippet successfully" do + if ENV["GOOGLE_CLOUD_SPANNER_TEST_INSTANCE"].nil? || ENV["GOOGLE_CLOUD_SPANNER_PROJECT"].nil? + skip "GOOGLE_CLOUD_SPANNER_TEST_INSTANCE and/or GOOGLE_CLOUD_SPANNER_PROJECT not defined" + end + + @project_id = ENV["GOOGLE_CLOUD_SPANNER_PROJECT"] + @instance_id = ENV["GOOGLE_CLOUD_SPANNER_TEST_INSTANCE"] + @seed = SecureRandom.hex 8 + @database_id = "test_db_#{@seed}" + @spanner = Google::Cloud::Spanner.new project: @project_id + @instance = @spanner.instance @instance_id + + unless @instance.database @database_id + real_stdout = $stdout + $stdout = StringIO.new + create_database project_id: @project_id, + instance_id: @instance_id, + database_id: @database_id + $stdout = real_stdout + end + + client = @spanner.client @instance_id, @database_id + client.insert "Singers", [{ SingerId: 1, FirstName: "Test" }] + client.insert "Albums", [{ SingerId: 1, AlbumId: 1, AlbumTitle: "Old Title" }] + + expect { + spanner_isolation_level project_id: @project_id, instance_id: @instance_id, database_id: @database_id + }.to output(/AlbumTitle: Old Title\n1 records updated./).to_stdout + + @instance.database(@database_id).drop + end +end diff --git a/spanner/spec/spanner_read_lock_mode_spec.rb b/spanner/spec/spanner_read_lock_mode_spec.rb new file mode 100644 index 000000000..e5611c774 --- /dev/null +++ b/spanner/spec/spanner_read_lock_mode_spec.rb @@ -0,0 +1,51 @@ +# Copyright 2026 Google, Inc +# +# 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 +# +# http://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_relative "../spanner_read_lock_mode" +require "rspec" +require "google/cloud/spanner" + +describe "Spanner Read Lock Mode Options" do + it "runs read lock mode snippet successfully" do + if ENV["GOOGLE_CLOUD_SPANNER_TEST_INSTANCE"].nil? || ENV["GOOGLE_CLOUD_SPANNER_PROJECT"].nil? + skip "GOOGLE_CLOUD_SPANNER_TEST_INSTANCE and/or GOOGLE_CLOUD_SPANNER_PROJECT not defined" + end + + @project_id = ENV["GOOGLE_CLOUD_SPANNER_PROJECT"] + @instance_id = ENV["GOOGLE_CLOUD_SPANNER_TEST_INSTANCE"] + @seed = SecureRandom.hex 8 + @database_id = "test_db_#{@seed}" + @spanner = Google::Cloud::Spanner.new project: @project_id + @instance = @spanner.instance @instance_id + + unless @instance.database @database_id + real_stdout = $stdout + $stdout = StringIO.new + create_database project_id: @project_id, + instance_id: @instance_id, + database_id: @database_id + $stdout = real_stdout + end + + client = @spanner.client @instance_id, @database_id + client.insert "Singers", [{ SingerId: 2, FirstName: "Test" }] + client.insert "Albums", [{ SingerId: 2, AlbumId: 1, AlbumTitle: "Old Title" }] + + expect { + spanner_read_lock_mode project_id: @project_id, instance_id: @instance_id, database_id: @database_id + }.to output(/AlbumTitle: Old Title\n1 records updated./).to_stdout + + @instance.database(@database_id).drop + end +end