diff --git a/spanner/Gemfile b/spanner/Gemfile index 51217dfd5..431c3c4df 100644 --- a/spanner/Gemfile +++ b/spanner/Gemfile @@ -15,6 +15,7 @@ source "https://rubygems.org" gem "google-cloud-spanner" +gem "mutex_m" group :test do gem "google-apis-iam_v1" diff --git a/spanner/Gemfile.lock b/spanner/Gemfile.lock index bf34bf7d2..f09e7f98e 100644 --- a/spanner/Gemfile.lock +++ b/spanner/Gemfile.lock @@ -1,26 +1,31 @@ GEM remote: https://rubygems.org/ specs: - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) - base64 (0.2.0) - concurrent-ruby (1.2.3) + addressable (2.8.9) + public_suffix (>= 2.0.2, < 8.0) + base64 (0.3.0) + bigdecimal (3.3.1) + concurrent-ruby (1.3.6) declarative (0.0.20) diff-lcs (1.5.1) - faraday (2.9.0) - faraday-net_http (>= 2.0, < 3.2) - faraday-net_http (3.1.0) - net-http - faraday-retry (2.2.0) + faraday (2.14.1) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-net_http (3.4.2) + net-http (~> 0.5) + faraday-retry (2.4.0) faraday (~> 2.0) - gapic-common (0.21.1) + gapic-common (1.3.0) faraday (>= 1.9, < 3.a) faraday-retry (>= 1.0, < 3.a) - google-protobuf (~> 3.18) - googleapis-common-protos (>= 1.4.0, < 2.a) - googleapis-common-protos-types (>= 1.11.0, < 2.a) - googleauth (~> 1.9) - grpc (~> 1.59) + google-cloud-env (~> 2.2) + google-logging-utils (~> 0.1) + google-protobuf (~> 4.26) + googleapis-common-protos (~> 1.6) + googleapis-common-protos-types (~> 1.15) + googleauth (~> 1.12) + grpc (~> 1.66) google-apis-core (0.14.0) addressable (~> 2.5, >= 2.5.1) googleauth (~> 1.9) @@ -31,59 +36,69 @@ GEM rexml google-apis-iam_v1 (0.54.0) google-apis-core (>= 0.14.0, < 2.a) - google-cloud-core (1.6.1) + google-cloud-core (1.8.0) google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) - google-cloud-env (2.1.1) + google-cloud-env (2.3.1) + base64 (~> 0.2) faraday (>= 1.0, < 3.a) - google-cloud-errors (1.3.1) - google-cloud-spanner (2.21.0) + google-cloud-errors (1.5.0) + google-cloud-spanner (2.35.0) + bigdecimal (~> 3.0) concurrent-ruby (~> 1.0) - google-cloud-core (~> 1.5) - google-cloud-spanner-admin-database-v1 (~> 0.1) - google-cloud-spanner-admin-instance-v1 (~> 0.1) - google-cloud-spanner-v1 (~> 0.2) - google-cloud-spanner-admin-database-v1 (0.17.1) - gapic-common (>= 0.21.1, < 2.a) + 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) + gapic-common (~> 1.2) google-cloud-errors (~> 1.0) - grpc-google-iam-v1 (~> 1.1) - google-cloud-spanner-admin-instance-v1 (0.13.2) - gapic-common (>= 0.21.1, < 2.a) + grpc-google-iam-v1 (~> 1.11) + google-cloud-spanner-admin-instance-v1 (1.6.0) + gapic-common (>= 0.25.0, < 2.a) google-cloud-errors (~> 1.0) grpc-google-iam-v1 (~> 1.1) - google-cloud-spanner-v1 (0.24.0) - gapic-common (>= 0.21.1, < 2.a) + google-cloud-spanner-v1 (1.15.0) + gapic-common (~> 1.2) google-cloud-errors (~> 1.0) - google-protobuf (3.25.3) - googleapis-common-protos (1.5.0) - google-protobuf (~> 3.18) + google-logging-utils (0.2.0) + google-protobuf (4.34.0) + bigdecimal + rake (~> 13.3) + googleapis-common-protos (1.7.0) + google-protobuf (>= 3.18, < 5.a) googleapis-common-protos-types (~> 1.7) grpc (~> 1.41) - googleapis-common-protos-types (1.13.0) - google-protobuf (~> 3.18) - googleauth (1.11.0) + googleapis-common-protos-types (1.22.0) + google-protobuf (~> 4.26) + googleauth (1.16.2) faraday (>= 1.0, < 3.a) - google-cloud-env (~> 2.1) - jwt (>= 1.4, < 3.0) + google-cloud-env (~> 2.2) + google-logging-utils (~> 0.1) + jwt (>= 1.4, < 4.0) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) - grpc (1.62.0) - google-protobuf (~> 3.25) + grpc (1.78.1) + google-protobuf (>= 3.25, < 5.0) googleapis-common-protos-types (~> 1.0) - grpc-google-iam-v1 (1.7.0) - google-protobuf (~> 3.18) - googleapis-common-protos (~> 1.4) + grpc-google-iam-v1 (1.11.0) + google-protobuf (>= 3.18, < 5.a) + googleapis-common-protos (~> 1.7.0) grpc (~> 1.41) httpclient (2.8.3) - jwt (2.8.0) + json (2.19.1) + jwt (3.1.2) base64 + logger (1.7.0) mini_mime (1.1.5) - multi_json (1.15.0) - net-http (0.4.1) - uri + multi_json (1.19.1) + mutex_m (0.3.0) + net-http (0.9.1) + uri (>= 0.11.1) os (1.1.4) - public_suffix (5.0.4) + public_suffix (7.0.5) + rake (13.3.1) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) @@ -105,14 +120,14 @@ GEM rspec-support (3.13.1) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) - signet (0.19.0) + signet (0.21.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) - jwt (>= 1.5, < 3.0) + jwt (>= 1.5, < 4.0) multi_json (~> 1.10) trailblazer-option (0.1.2) uber (0.1.0) - uri (0.13.0) + uri (1.1.1) PLATFORMS ruby @@ -120,6 +135,7 @@ PLATFORMS DEPENDENCIES google-apis-iam_v1 google-cloud-spanner + mutex_m retriable rspec rspec_junit_formatter diff --git a/spanner/spanner_batch_write.rb b/spanner/spanner_batch_write.rb new file mode 100644 index 000000000..195776340 --- /dev/null +++ b/spanner/spanner_batch_write.rb @@ -0,0 +1,58 @@ +# Copyright 2026 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 +# +# 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_batch_write_at_least_once] +require "google/cloud/spanner" + +## +# This is a snippet for showcasing how to apply a batch of mutations groups. +# All mutations in a group are applied atomically. +# +# @param project_id [String] The ID of the Google Cloud project. +# @param instance_id [String] The ID of the spanner instance. +# @param database_id [String] The ID of the database. +# +def spanner_batch_write project_id:, instance_id:, database_id: + spanner = Google::Cloud::Spanner.new project: project_id + client = spanner.client instance_id, database_id + + results = client.batch_write do |b| + # First mutation group + b.mutation_group do |mg| + mg.upsert "Singers", [{ SingerId: 16, FirstName: "Scarlet", LastName: "Terry" }] + end + + # Second mutation group + b.mutation_group do |mg| + mg.upsert "Singers", [ + { SingerId: 17, FirstName: "Marc" }, + { SingerId: 18, FirstName: "Catalina", LastName: "Smith" } + ] + mg.upsert "Albums", [ + { SingerId: 17, AlbumId: 1, AlbumTitle: "Total Junk" }, + { SingerId: 18, AlbumId: 2, AlbumTitle: "Go, Go, Go" } + ] + end + end + + results.each do |response| + if response.ok? + puts "Mutation group indexes applied: #{response.indexes}" + else + puts "Mutation group failed to apply: #{response.indexes}" + puts "Error: #{response.status.message}" + end + end +end +# [END spanner_batch_write_at_least_once] diff --git a/spanner/spec/spanner_batch_write_spec.rb b/spanner/spec/spanner_batch_write_spec.rb new file mode 100644 index 000000000..867229005 --- /dev/null +++ b/spanner/spec/spanner_batch_write_spec.rb @@ -0,0 +1,39 @@ +# Copyright 2026 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 +# +# 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 "spec_helper" +require_relative "../spanner_batch_write" + +describe "spanner_batch_write" do + before :all do + create_singers_albums_database + end + + after :all do + cleanup_database_resources + end + + it "applies mutation groups" do + expect { + spanner_batch_write project_id: @project_id, + instance_id: @instance_id, + database_id: @database_id + }.to output(/Mutation group indexes applied: (\[0, 1\]|\[0\].*\[1\])/m).to_stdout + + # Verify that the records were inserted + client = @spanner.client @instance_id, @database_id + results = client.execute "SELECT COUNT(*) FROM Singers" + expect(results.rows.first[0]).to eq 3 # 16, 17, 18 + end +end