From dda95f8fc3e92dec24356714c071719cd9e9c5ea Mon Sep 17 00:00:00 2001 From: Taishi Kasuga Date: Sat, 30 Sep 2023 14:38:32 +0900 Subject: [PATCH] Support transactions for cluster client --- cluster/lib/redis/cluster.rb | 3 ++ cluster/lib/redis/cluster/client.rb | 3 +- cluster/redis-clustering.gemspec | 2 +- cluster/test/client_transactions_test.rb | 43 +++++++++++++++++-- cluster/test/commands_on_transactions_test.rb | 8 +++- 5 files changed, 53 insertions(+), 6 deletions(-) diff --git a/cluster/lib/redis/cluster.rb b/cluster/lib/redis/cluster.rb index 10e37e087..d7a06f6bf 100644 --- a/cluster/lib/redis/cluster.rb +++ b/cluster/lib/redis/cluster.rb @@ -38,6 +38,9 @@ def initialize(errors, error_message = 'Command errors were replied on any node' class AmbiguousNodeError < BaseError end + class TransactionConsistencyError < BaseError + end + def connection raise NotImplementedError, "Redis::Cluster doesn't implement #connection" end diff --git a/cluster/lib/redis/cluster/client.rb b/cluster/lib/redis/cluster/client.rb index fc6c118ff..2d72e2dcd 100644 --- a/cluster/lib/redis/cluster/client.rb +++ b/cluster/lib/redis/cluster/client.rb @@ -9,7 +9,8 @@ class Client < RedisClient::Cluster RedisClient::Cluster::InitialSetupError => Redis::Cluster::InitialSetupError, RedisClient::Cluster::OrchestrationCommandNotSupported => Redis::Cluster::OrchestrationCommandNotSupported, RedisClient::Cluster::AmbiguousNodeError => Redis::Cluster::AmbiguousNodeError, - RedisClient::Cluster::ErrorCollection => Redis::Cluster::CommandErrorCollection + RedisClient::Cluster::ErrorCollection => Redis::Cluster::CommandErrorCollection, + RedisClient::Cluster::Transaction::ConsistencyError => Redis::Cluster::TransactionConsistencyError ).freeze class << self diff --git a/cluster/redis-clustering.gemspec b/cluster/redis-clustering.gemspec index 81c7c86e3..af1dea33a 100644 --- a/cluster/redis-clustering.gemspec +++ b/cluster/redis-clustering.gemspec @@ -47,5 +47,5 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.7.0' s.add_runtime_dependency('redis', s.version) - s.add_runtime_dependency('redis-cluster-client', '>= 0.6.1') + s.add_runtime_dependency('redis-cluster-client', '>= 0.7.0') end diff --git a/cluster/test/client_transactions_test.rb b/cluster/test/client_transactions_test.rb index 8b290a805..b60a8bc4d 100644 --- a/cluster/test/client_transactions_test.rb +++ b/cluster/test/client_transactions_test.rb @@ -6,9 +6,46 @@ class TestClusterClientTransactions < Minitest::Test include Helper::Cluster - def test_cluster_client_does_not_support_transaction - assert_raises(Redis::Cluster::AmbiguousNodeError) do - redis.multi { |r| r.set('key', 'foo') } + def test_cluster_client_does_support_transaction_by_single_key + actual = redis.multi do |r| + r.set('counter', '0') + r.incr('counter') + r.incr('counter') + end + + assert_equal(['OK', 1, 2], actual) + assert_equal('2', redis.get('counter')) + end + + def test_cluster_client_does_support_transaction_by_hashtag + actual = redis.multi do |r| + r.mset('{key}1', 1, '{key}2', 2) + r.mset('{key}3', 3, '{key}4', 4) + end + + assert_equal(%w[OK OK], actual) + assert_equal(%w[1 2 3 4], redis.mget('{key}1', '{key}2', '{key}3', '{key}4')) + end + + def test_cluster_client_does_not_support_transaction_by_multiple_keys + assert_raises(Redis::Cluster::TransactionConsistencyError) do + redis.multi do |r| + r.set('key1', 1) + r.set('key2', 2) + r.set('key3', 3) + r.set('key4', 4) + end + end + + assert_raises(Redis::Cluster::TransactionConsistencyError) do + redis.multi do |r| + r.mset('key1', 1, 'key2', 2) + r.mset('key3', 3, 'key4', 4) + end + end + + (1..4).each do |i| + assert_nil(redis.get("key#{i}")) end end end diff --git a/cluster/test/commands_on_transactions_test.rb b/cluster/test/commands_on_transactions_test.rb index e2964e027..7935ba4a8 100644 --- a/cluster/test/commands_on_transactions_test.rb +++ b/cluster/test/commands_on_transactions_test.rb @@ -20,9 +20,15 @@ def test_exec end def test_multi - assert_raises(Redis::Cluster::AmbiguousNodeError) do + assert_raises(LocalJumpError) do redis.multi end + + assert_raises(ArgumentError) do + redis.multi {} + end + + assert_equal([1], redis.multi { |r| r.incr('counter') }) end def test_unwatch