Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion google-cloud-spanner/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ PATH
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-v1 (~> 1.12)

GEM
remote: https://rubygems.org/
Expand Down
2 changes: 1 addition & 1 deletion google-cloud-spanner/google-cloud-spanner.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ Gem::Specification.new do |gem|
gem.add_dependency "google-cloud-core", "~> 1.7"
gem.add_dependency "google-cloud-spanner-admin-database-v1", "~> 1.4"
gem.add_dependency "google-cloud-spanner-admin-instance-v1", "~> 1.6"
gem.add_dependency "google-cloud-spanner-v1", "~> 1.6"
gem.add_dependency "google-cloud-spanner-v1", "~> 1.12"
end
133 changes: 133 additions & 0 deletions google-cloud-spanner/lib/google/cloud/spanner/commit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,124 @@ def delete table, keys = []
keys
end

##
# Sends a message to a queue.
#
# All changes are accumulated in memory until the block passed to
# {Client#commit} completes.
#
# @param [String] queue The name of the queue.
# @param [Object, Array<Object>] key A single key or array of composite key parts for the message.
# @param [String, IO, StringIO, Hash, Google::Protobuf::MessageExts] payload
# The message payload. It is converted the same way as column values
# (see {#upsert}), so its type must match the queue's payload column,
# such as a `String` for `STRING` or a `StringIO` for `BYTES`:
# * `String` - Sent as text.
# * `IO`, `StringIO`, `File` - Sent as bytes (Base64-encoded). Ruby has
# no bytes type, so wrap binary data in a `StringIO`.
# * `Hash` - Serialized to a JSON string.
# * `Google::Protobuf::MessageExts` - Serialized and Base64-encoded.
# @param [Time, nil] deliver_time The time at which Spanner begins
# trying to deliver the message. Optional. If not set, the message is
# delivered immediately. A time in the past is replaced with a time
# closer to the current time.
#
# @return [Object, Array<Object>] The key of the message.
#
# @raise [ArgumentError] If the payload type is unsupported.
#
# @example
# require "google/cloud/spanner"
#
# spanner = Google::Cloud::Spanner.new
#
# db = spanner.client "my-instance", "my-database"
#
# # OrderQueue has a STRING(MAX) payload column.
# db.commit do |c|
# c.send_message "OrderQueue", 101, "Order payload"
# end
#
# @example Send binary data to a queue with a BYTES payload column
# require "google/cloud/spanner"
#
# spanner = Google::Cloud::Spanner.new
#
# db = spanner.client "my-instance", "my-database"
#
# db.commit do |c|
# c.send_message "LabelQueue", 101, StringIO.new(File.binread("label.png"))
# end
#
# @example Deliver the message in five minutes
# require "google/cloud/spanner"
#
# spanner = Google::Cloud::Spanner.new
#
# db = spanner.client "my-instance", "my-database"
#
# db.commit do |c|
# c.send_message "OrderQueue", 101, "Order payload",
# deliver_time: Time.now + 300
# end
#
def send_message queue, key, payload, deliver_time: nil
send_mutation = V1::Mutation::Send.new(
queue: queue,
key: key_list_value(key),
payload: Convert.object_to_grpc_value(payload),
deliver_time: Convert.time_to_timestamp(deliver_time)
)
@mutations += [V1::Mutation.new(send: send_mutation)]
key
end

##
# Acknowledges a message in a queue, which removes it from the queue.
#
# All changes are accumulated in memory until the block passed to
# {Client#commit} completes.
#
# @param [String] queue The name of the queue.
# @param [Object, Array<Object>] key A single key or array of composite key parts to match.
# @param [Boolean] ignore_not_found If `true`, the ack succeeds even if
# the message doesn't exist. Optional. The default is `false`, which
# fails the commit with `NOT_FOUND` when the message doesn't exist.
#
# @return [Object, Array<Object>] The key of the message.
#
# @example
# require "google/cloud/spanner"
#
# spanner = Google::Cloud::Spanner.new
#
# db = spanner.client "my-instance", "my-database"
#
# db.commit do |c|
# c.ack_message "OrderQueue", 101
# end
#
# @example Ack a message whether or not it exists
# require "google/cloud/spanner"
#
# spanner = Google::Cloud::Spanner.new
#
# db = spanner.client "my-instance", "my-database"
#
# db.commit do |c|
# c.ack_message "OrderQueue", 101, ignore_not_found: true
# end
#
def ack_message queue, key, ignore_not_found: false
ack_mutation = V1::Mutation::Ack.new(
queue: queue,
key: key_list_value(key),
ignore_not_found: ignore_not_found
)
@mutations += [V1::Mutation.new(ack: ack_mutation)]
key
end

# Return the current mutations added to this `Spanner::Commit` object.
# @private
# @return [Array<Google::Cloud::Spanner::V1::Mutation>]
Expand Down Expand Up @@ -325,6 +443,21 @@ def mutations_from_rows table, rows, type
rows
end

##
# @private
# Generates a `ListValue` protobuf containing gRPC values for the given primary key.
#
# @param [Object, Array<Object>] key A scalar key, or an array of a composite key's parts.
# @return [Google::Protobuf::ListValue]
#
def key_list_value key
key = [key] unless key.is_a? Array
key_list = key.map do |k|
Convert.object_to_grpc_value k
end
Google::Protobuf::ListValue.new values: key_list
end

def key_set keys
return V1::KeySet.new all: true if keys.nil?
keys = [keys] unless keys.is_a? Array
Expand Down
73 changes: 73 additions & 0 deletions google-cloud-spanner/lib/google/cloud/spanner/transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,79 @@ def delete table, keys = []
@commit.delete table, keys
end

##
# Sends a message to a queue.
#
# All changes are accumulated in memory until the block passed to
# {Client#transaction} completes.
#
# @param [String] queue The name of the queue.
# @param [Object, Array<Object>] key A single key or array of composite key parts for the message.
# @param [String, IO, StringIO, Hash, Google::Protobuf::MessageExts] payload
# The message payload. It is converted the same way as column values
# (see {#upsert}), so its type must match the queue's payload column,
# such as a `String` for `STRING` or a `StringIO` for `BYTES`:
# * `String` - Sent as text.
# * `IO`, `StringIO`, `File` - Sent as bytes (Base64-encoded). Ruby has
# no bytes type, so wrap binary data in a `StringIO`.
# * `Hash` - Serialized to a JSON string.
# * `Google::Protobuf::MessageExts` - Serialized and Base64-encoded.
# @param [Time, nil] deliver_time The time at which Spanner begins
# trying to deliver the message. Optional. If not set, the message is
# delivered immediately. A time in the past is replaced with a time
# closer to the current time.
#
# @return [Object, Array<Object>] The key of the message.
#
# @raise [ArgumentError] If the payload type is unsupported.
#
# @example Send a message in the same transaction as the row it describes
# require "google/cloud/spanner"
#
# spanner = Google::Cloud::Spanner.new
# db = spanner.client "my-instance", "my-database"
#
# # OrderQueue has a STRING(MAX) payload column.
# db.transaction do |tx|
# tx.insert "orders", [{ id: 101, status: "NEW" }]
# tx.send_message "OrderQueue", 101, "Order payload"
# end
#
def send_message queue, key, payload, deliver_time: nil
ensure_session!
@commit.send_message queue, key, payload, deliver_time: deliver_time
end

##
# Acknowledges a message in a queue, which removes it from the queue.
#
# All changes are accumulated in memory until the block passed to
# {Client#transaction} completes.
#
# @param [String] queue The name of the queue.
# @param [Object, Array<Object>] key A single key or array of composite key parts to match.
# @param [Boolean] ignore_not_found If `true`, the ack succeeds even if
# the message doesn't exist. Optional. The default is `false`, which
# fails the commit with `NOT_FOUND` when the message doesn't exist.
#
# @return [Object, Array<Object>] The key of the message.
#
# @example Ack a message in the same transaction as the write it caused
# require "google/cloud/spanner"
#
# spanner = Google::Cloud::Spanner.new
# db = spanner.client "my-instance", "my-database"
#
# db.transaction do |tx|
# tx.update "orders", [{ id: 101, status: "PROCESSED" }]
# tx.ack_message "OrderQueue", 101
# end
#
def ack_message queue, key, ignore_not_found: false
ensure_session!
@commit.ack_message queue, key, ignore_not_found: ignore_not_found
end

##
# @private
# Returns the field names and types for a table.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,53 @@
mock.verify
end

it "sends and acks queue messages" do
mutations = [
Google::Cloud::Spanner::V1::Mutation.new(
send: Google::Cloud::Spanner::V1::Mutation::Send.new(
queue: "TestQueue",
key: Google::Cloud::Spanner::Convert.object_to_grpc_value([101]).list_value,
payload: Google::Cloud::Spanner::Convert.object_to_grpc_value("Hello, Queues!")
)
),
Google::Cloud::Spanner::V1::Mutation.new(
ack: Google::Cloud::Spanner::V1::Mutation::Ack.new(
queue: "TestQueue",
key: Google::Cloud::Spanner::Convert.object_to_grpc_value([102]).list_value,
ignore_not_found: false
)
)
]

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: mutations[0]
}, default_options]

mock.expect :commit, commit_resp, [{
session: session_grpc.name,
mutations: mutations,
transaction_id: transaction_id,
single_use_transaction: nil, precommit_token: nil,
request_options: nil
}, default_options]
spanner.service.mocked_service = mock

timestamp = client.transaction do |tx|
tx.send_message "TestQueue", 101, "Hello, Queues!"
tx.ack_message "TestQueue", 102
end
_(timestamp).must_equal commit_time

shutdown_client! client

mock.verify
end

it "deletes multiple rows of key ranges" do
mutations = [
Google::Cloud::Spanner::V1::Mutation.new(
Expand Down
Loading
Loading