From bbac05a39639f14722e369c3f4cbb1ff9e8341b9 Mon Sep 17 00:00:00 2001 From: Michael Herold Date: Mon, 3 Nov 2025 09:55:00 -0600 Subject: [PATCH 1/2] feat: Allow sending request tags against snapshots This change extends the `request_options` functionality for request tags and query priority to queries issued against snapshots. This allows for the use of read-only transactions with staleness semantics but still bucketing queries via tags for analysis purposes. --- .../lib/google/cloud/spanner/snapshot.rb | 18 +++++++++- .../spanner/snapshot/execute_query_test.rb | 33 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/google-cloud-spanner/lib/google/cloud/spanner/snapshot.rb b/google-cloud-spanner/lib/google/cloud/spanner/snapshot.rb index 38f15bea..4b73c6bf 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/snapshot.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/snapshot.rb @@ -131,6 +131,18 @@ def timestamp # available optimizer version. # * `:optimizer_statistics_package` (String) Statistics package to # use. Empty to use the database default. + # @param [Hash] request_options Common request options. + # + # * `:priority` (Symbol) The relative priority for requests. + # The priority acts as a hint to the Cloud Spanner scheduler + # and does not guarantee priority or order of execution. + # Valid values are `:PRIORITY_LOW`, `:PRIORITY_MEDIUM`, + # `:PRIORITY_HIGH`. If priority not set then default is + # `PRIORITY_UNSPECIFIED` is equivalent to `:PRIORITY_HIGH`. + # * `:tag` (String) A per-request tag which can be applied to + # queries or reads, used for statistics collection. Tag must be a + # valid identifier of the form: `[a-zA-Z][a-zA-Z0-9_\-]` between 2 + # and 64 characters in length. # @param [Hash] call_options A hash of values to specify the custom # call options, e.g., timeout, retries, etc. Call options are # optional. The following settings can be provided: @@ -304,13 +316,17 @@ def timestamp # end # def execute_query sql, params: nil, types: nil, query_options: nil, - call_options: nil, directed_read_options: nil + request_options: nil, call_options: nil, + directed_read_options: nil ensure_session! params, types = Convert.to_input_params_and_types params, types + request_options = Convert.to_request_options request_options, + tag_type: :request_tag session.execute_query sql, params: params, types: types, transaction: tx_selector, query_options: query_options, + request_options: request_options, call_options: call_options, directed_read_options: directed_read_options || @directed_read_options end 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 e0e8e1bd..c4023c94 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 @@ -324,6 +324,39 @@ assert_results results end + describe "priority request options" do + it "can execute a query" do + mock = Minitest::Mock.new + session.service.mocked_service = mock + expect_execute_streaming_sql results_enum, session.path, "SELECT * FROM users", + transaction: tx_selector, + request_options: { priority: :PRIORITY_MEDIUM }, + options: default_options + + results = snapshot.execute_query "SELECT * FROM users", + request_options: { priority: :PRIORITY_MEDIUM } + + mock.verify + + assert_results results + end + end + + it "can execute a query with request tag" do + mock = Minitest::Mock.new + session.service.mocked_service = mock + expect_execute_streaming_sql results_enum, session.path, "SELECT * FROM users", + transaction: tx_selector, + request_options: { request_tag: "Tag-1" }, + options: default_options + + results = snapshot.execute_query "SELECT * FROM users", request_options: { tag: "Tag-1" } + + mock.verify + + assert_results results + end + def assert_results results _(results).must_be_kind_of Google::Cloud::Spanner::Results From 3b71fafcab7a08b812b095cf9ffb5c530ee91873 Mon Sep 17 00:00:00 2001 From: Viacheslav Rostovtsev Date: Thu, 6 Nov 2025 17:24:11 +0000 Subject: [PATCH 2/2] feat: add request options to read --- .../lib/google/cloud/spanner/snapshot.rb | 15 +++++- .../spanner/snapshot/execute_query_test.rb | 37 ++++++++------- .../cloud/spanner/snapshot/read_test.rb | 46 +++++++++++++++++++ 3 files changed, 80 insertions(+), 18 deletions(-) diff --git a/google-cloud-spanner/lib/google/cloud/spanner/snapshot.rb b/google-cloud-spanner/lib/google/cloud/spanner/snapshot.rb index 4b73c6bf..b9572000 100644 --- a/google-cloud-spanner/lib/google/cloud/spanner/snapshot.rb +++ b/google-cloud-spanner/lib/google/cloud/spanner/snapshot.rb @@ -350,6 +350,18 @@ def execute_query sql, params: nil, types: nil, query_options: nil, # Optional. # @param [Integer] limit If greater than zero, no more than this number # of rows will be returned. The default is no limit. + # @param [Hash] request_options Common request options. + # + # * `:priority` (Symbol) The relative priority for requests. + # The priority acts as a hint to the Cloud Spanner scheduler + # and does not guarantee priority or order of execution. + # Valid values are `:PRIORITY_LOW`, `:PRIORITY_MEDIUM`, + # `:PRIORITY_HIGH`. If priority not set then default is + # `PRIORITY_UNSPECIFIED` is equivalent to `:PRIORITY_HIGH`. + # * `:tag` (String) A per-request tag which can be applied to + # queries or reads, used for statistics collection. Tag must be a + # valid identifier of the form: `[a-zA-Z][a-zA-Z0-9_\-]` between 2 + # and 64 characters in length. # @param [Hash] call_options A hash of values to specify the custom # call options, e.g., timeout, retries, etc. Call options are # optional. The following settings can be provided: @@ -396,7 +408,7 @@ def execute_query sql, params: nil, types: nil, query_options: nil, # end # def read table, columns, keys: nil, index: nil, limit: nil, - call_options: nil, directed_read_options: nil + request_options: nil, call_options: nil, directed_read_options: nil ensure_session! columns = Array(columns).map(&:to_s) @@ -404,6 +416,7 @@ def read table, columns, keys: nil, index: nil, limit: nil, session.read table, columns, keys: keys, index: index, limit: limit, transaction: tx_selector, + request_options: request_options, call_options: call_options, directed_read_options: directed_read_options || @directed_read_options end 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 c4023c94..0356422f 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 @@ -324,37 +324,40 @@ assert_results results end - describe "priority request options" do - it "can execute a query" do + describe "request options" do + it "can execute a query with priority option" do mock = Minitest::Mock.new session.service.mocked_service = mock + + request_options = { priority: :PRIORITY_MEDIUM } + expect_execute_streaming_sql results_enum, session.path, "SELECT * FROM users", transaction: tx_selector, - request_options: { priority: :PRIORITY_MEDIUM }, + request_options: request_options, options: default_options - results = snapshot.execute_query "SELECT * FROM users", - request_options: { priority: :PRIORITY_MEDIUM } + results = snapshot.execute_query "SELECT * FROM users", request_options: request_options mock.verify - assert_results results end - end - it "can execute a query with request tag" do - mock = Minitest::Mock.new - session.service.mocked_service = mock - expect_execute_streaming_sql results_enum, session.path, "SELECT * FROM users", - transaction: tx_selector, - request_options: { request_tag: "Tag-1" }, - options: default_options + it "can execute a query with request tag" do + mock = Minitest::Mock.new + session.service.mocked_service = mock - results = snapshot.execute_query "SELECT * FROM users", request_options: { tag: "Tag-1" } + request_options = { request_tag: "Tag-1" } - mock.verify + expect_execute_streaming_sql results_enum, session.path, "SELECT * FROM users", + transaction: tx_selector, + request_options: request_options, + options: default_options - assert_results results + results = snapshot.execute_query "SELECT * FROM users", request_options: request_options + + mock.verify + assert_results results + end end def assert_results results 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 5f4892fc..a006a5e8 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 @@ -280,6 +280,52 @@ assert_results results end + describe "request options" do + it "can execute a query with priority option" do + columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] + + request_options = { priority: :PRIORITY_MEDIUM } + + mock = Minitest::Mock.new + 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: request_options, order_by: nil, lock_hint: nil + }, default_options] + session.service.mocked_service = mock + + results = snapshot.read "my-table", columns, keys: [1, 2, 3], request_options: request_options + + mock.verify + + assert_results results + end + + it "can execute a query with request tag" do + columns = [:id, :name, :active, :age, :score, :updated_at, :birthday, :avatar, :project_ids] + + request_options = { request_tag: "Tag-1" } + + mock = Minitest::Mock.new + 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: request_options, order_by: nil, lock_hint: nil + }, default_options] + session.service.mocked_service = mock + + results = snapshot.read "my-table", columns, keys: [1, 2, 3], request_options: request_options + + mock.verify + + assert_results results + end + end + def assert_results results _(results).must_be_kind_of Google::Cloud::Spanner::Results