diff --git a/lib/redis.rb b/lib/redis.rb index 4746ec463..353726555 100644 --- a/lib/redis.rb +++ b/lib/redis.rb @@ -656,6 +656,41 @@ def move(key, db) end end + # Copy a value from one key to another. + # + # @example Copy a value to another key + # redis.set "foo", "value" + # # => "OK" + # redis.copy "foo", "bar" + # # => true + # redis.get "bar" + # # => "value" + # + # @example Copy a value to a key in another database + # redis.set "foo", "value" + # # => "OK" + # redis.copy "foo", "bar", db: 2 + # # => true + # redis.select 2 + # # => "OK" + # redis.get "bar" + # # => "value" + # + # @param [String] source + # @param [String] destination + # @param [Integer] db + # @param [Boolean] replace removes the `destination` key before copying value to it + # @return [Boolean] whether the key was copied or not + def copy(source, destination, db: nil, replace: false) + command = [:copy, source, destination] + command << "DB" << db if db + command << "REPLACE" if replace + + synchronize do |client| + client.call(command, &Boolify) + end + end + def object(*args) synchronize do |client| client.call([:object] + args) diff --git a/lib/redis/distributed.rb b/lib/redis/distributed.rb index 4662a136d..c199a12cd 100644 --- a/lib/redis/distributed.rb +++ b/lib/redis/distributed.rb @@ -215,6 +215,13 @@ def move(key, db) node_for(key).move(key, db) end + # Copy a value from one key to another. + def copy(source, destination, **options) + ensure_same_node(:copy, [source, destination]) do |node| + node.copy(source, destination, **options) + end + end + # Return a random key from the keyspace. def randomkey raise CannotDistribute, :randomkey diff --git a/test/cluster_commands_on_value_types_test.rb b/test/cluster_commands_on_value_types_test.rb index 257e4dd2e..07a369206 100644 --- a/test/cluster_commands_on_value_types_test.rb +++ b/test/cluster_commands_on_value_types_test.rb @@ -11,4 +11,8 @@ class TestClusterCommandsOnValueTypes < Minitest::Test def test_move assert_raises(Redis::CommandError) { super } end + + def test_copy + assert_raises(Redis::CommandError) { super } + end end diff --git a/test/distributed_commands_on_value_types_test.rb b/test/distributed_commands_on_value_types_test.rb index f882537b5..e15b7f52d 100644 --- a/test/distributed_commands_on_value_types_test.rb +++ b/test/distributed_commands_on_value_types_test.rb @@ -127,4 +127,12 @@ def test_migrate r.migrate("foo", {}) end end + + def test_copy + r.set("foo", "s1") + + assert_raises Redis::Distributed::CannotDistribute do + r.copy("foo", "bar") + end + end end diff --git a/test/helper.rb b/test/helper.rb index 0a98354db..d99f5af50 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -158,6 +158,11 @@ def target_version(target) end end + def with_db(index) + r.select(index) + yield + end + def omit_version(min_ver) skip("Requires Redis > #{min_ver}") if version < min_ver end diff --git a/test/lint/value_types.rb b/test/lint/value_types.rb index e23715b7c..66b88818f 100644 --- a/test/lint/value_types.rb +++ b/test/lint/value_types.rb @@ -162,5 +162,41 @@ def test_move assert_equal "s1", r.get("foo") assert_equal "s3", r.get("bar") end + + def test_copy + target_version("6.2") do + with_db(14) do + r.flushdb + + r.set "foo", "s1" + r.set "bar", "s2" + + assert r.copy("foo", "baz") + assert_equal "s1", r.get("baz") + + assert !r.copy("foo", "bar") + assert r.copy("foo", "bar", replace: true) + assert_equal "s1", r.get("bar") + end + + with_db(15) do + r.set "foo", "s3" + r.set "bar", "s4" + end + + with_db(14) do + assert r.copy("foo", "baz", db: 15) + assert_equal "s1", r.get("foo") + + assert !r.copy("foo", "bar", db: 15) + assert r.copy("foo", "bar", db: 15, replace: true) + end + + with_db(15) do + assert_equal "s1", r.get("baz") + assert_equal "s1", r.get("bar") + end + end + end end end