Extended Key Space support by implementing COPY command (#866)

* Added support for COPY command

* Revert getCLient signature changes, used createClient instead

Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
This commit is contained in:
Vladyslav Vildanov
2022-12-29 22:18:08 +02:00
committed by GitHub
parent b47aa7ad3a
commit b8cc7c643a
9 changed files with 363 additions and 1 deletions
+1
View File
@@ -18,6 +18,7 @@ use Predis\Command\CommandInterface;
/**
* Interface defining a client-side context such as a pipeline or transaction.
*
* @method $this copy(string $source, string $destination, int $db = -1, bool $replace = false)
* @method $this del(array|string $keys)
* @method $this dump($key)
* @method $this exists($key)
+1
View File
@@ -27,6 +27,7 @@ use Predis\Response\Status;
* and more friendly interface to ease programming which is described in the
* following list of methods:
*
* @method int copy(string $source, string $destination, int $db = -1, bool $replace = false)
* @method int del(string[]|string $keyOrKeys, string ...$keys = null)
* @method string|null dump(string $key)
* @method int exists(string $key)
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
use Predis\Command\Traits\DB;
use Predis\Command\Traits\Replace;
/**
* @link https://redis.io/commands/copy/
*
* This command copies the value stored at the source key to the destination key.
*/
class COPY extends RedisCommand
{
use DB {
DB::setArguments as setDB;
}
use Replace {
Replace::setArguments as setReplace;
}
protected static $dbArgumentPositionOffset = 2;
public function getId()
{
return 'COPY';
}
public function setArguments(array $arguments)
{
$this->setDB($arguments);
$arguments = $this->getArguments();
$this->setReplace($arguments);
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace Predis\Command\Traits;
use UnexpectedValueException;
trait DB
{
private $dbModifier = 'DB';
public function setArguments(array $arguments)
{
$argumentsLength = count($arguments);
if (static::$dbArgumentPositionOffset >= $argumentsLength) {
parent::setArguments($arguments);
return;
}
if (!is_numeric($arguments[static::$dbArgumentPositionOffset])) {
throw new UnexpectedValueException('DB argument should be a valid numeric value');
}
if ($arguments[static::$dbArgumentPositionOffset] < 0) {
array_splice($arguments, static::$dbArgumentPositionOffset, 1);
parent::setArguments($arguments);
return;
}
$argument = $arguments[static::$dbArgumentPositionOffset];
$argumentsBefore = array_slice($arguments, 0, static::$dbArgumentPositionOffset);
$argumentsAfter = array_slice($arguments, static::$dbArgumentPositionOffset + 1);
parent::setArguments(array_merge(
$argumentsBefore,
[$this->dbModifier],
[$argument],
$argumentsAfter
));
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace Predis\Command\Traits;
use Predis\Command\Command;
/**
* @mixin Command
*/
trait Replace
{
public function setArguments(array $arguments)
{
$replace = array_pop($arguments);
if (is_bool($replace) && $replace) {
$arguments[] = 'REPLACE';
} else if (!is_bool($replace)) {
$arguments[] = $replace;
}
parent::setArguments($arguments);
}
}
-1
View File
@@ -51,7 +51,6 @@ abstract class PredisCommandTestCase extends PredisTestCase
* Returns a new client instance.
*
* @param bool $flushdb Flush selected database before returning the client
*
* @return Client
*/
public function getClient(bool $flushdb = true): Client
+135
View File
@@ -0,0 +1,135 @@
<?php
namespace Predis\Command\Redis;
class COPY_Test extends PredisCommandTestCase
{
/**
* @inheritDoc
*/
protected function getExpectedCommand(): string
{
return COPY::class;
}
/**
* @inheritDoc
*/
protected function getExpectedId(): string
{
return 'COPY';
}
/**
* @group disconnected
* @dataProvider argumentsProvider
*/
public function testFilterArguments(array $actualArguments, array $expectedArguments): void
{
$command = $this->getCommand();
$command->setArguments($actualArguments);
$this->assertSame($expectedArguments, $command->getArguments());
}
/**
* @group disconnected
*/
public function testParseResponse(): void
{
$this->assertSame(1, $this->getCommand()->parseResponse(1));
}
/**
* @group connected
* @return void
* @requiresRedisVersion >= 6.2.0
*/
public function testSuccessfullyCopyValueOnNonExistingDestinationKey(): void
{
$redis = $this->getClient();
$redis->set('key', 'value');
$actualResponse = $redis->copy('key', 'destination');
$this->assertSame(1, $actualResponse);
$this->assertSame($redis->get('key'), $redis->get('destination'));
}
/**
* @group connected
* @return void
* @requiresRedisVersion >= 6.2.0
*/
public function testSuccessfullyCopyValueFromSourceToAnotherDb(): void
{
$defaultDatabaseIndexClient = $this->getClient();
$defaultDatabaseIndexClient->set('key', 'value');
$copyResponse = $defaultDatabaseIndexClient->copy('key', 'new_key', 14);
$anotherDatabaseIndexClient = $this->createClient(['database' => 14], null, false);
$actualValue = $anotherDatabaseIndexClient->get('new_key');
$anotherDatabaseIndexClient->flushdb();
$this->assertNull($anotherDatabaseIndexClient->get('new_key'));
$this->assertSame(1, $copyResponse);
$this->assertSame('value', $actualValue);
}
/**
* @group connected
* @return void
* @requiresRedisVersion >= 6.2.0
*/
public function testDoNotCopyValueOnAlreadyExistingDestinationKey(): void
{
$redis = $this->getClient();
$redis->set('key', 'value');
$redis->set('destination', 'destination_value');
$actualResponse = $redis->copy('key', 'destination');
$this->assertSame(0, $actualResponse);
$this->assertSame('destination_value', $redis->get('destination'));
}
/**
* @group connected
* @return void
* @requiresRedisVersion >= 6.2.0
*/
public function testSuccessfullyCopyValueWithReplaceArgumentOnAlreadyExistingDestinationKey(): void
{
$redis = $this->getClient();
$redis->set('key', 'value');
$redis->set('destination', 'destination_value');
$actualResponse = $redis->copy('key', 'destination', -1, true);
$this->assertSame(1, $actualResponse);
$this->assertSame('value', $redis->get('destination'));
}
public function argumentsProvider(): array
{
return [
'with default arguments' => [
['source', 'destination'],
['source', 'destination']
],
'with DB argument' => [
['source', 'destination', 1],
['source', 'destination', 'DB', 1]
],
'with replace argument' => [
['source', 'destination', -1, true],
['source', 'destination', 'REPLACE']
],
'with all arguments' => [
['source', 'destination', 1, true],
['source', 'destination', 'DB', 1, 'REPLACE']
]
];
}
}
+78
View File
@@ -0,0 +1,78 @@
<?php
namespace Predis\Command\Traits;
use Predis\Command\Command as RedisCommand;
use PredisTestCase;
use UnexpectedValueException;
class DbTest extends PredisTestCase
{
private $testClass;
protected function setUp(): void
{
parent::setUp();
$this->testClass = new class extends RedisCommand {
use DB;
public static $dbArgumentPositionOffset = 0;
public function getId()
{
return 'test';
}
};
}
/**
* @dataProvider argumentsProvider
* @param int $offset
* @param array $arguments
* @param array $expectedResponse
* @return void
*/
public function testReturnsCorrectArguments(int $offset, array $arguments, array $expectedResponse): void
{
$this->testClass::$dbArgumentPositionOffset = $offset;
$this->testClass->setArguments($arguments);
$this->assertSame($expectedResponse, $this->testClass->getArguments());
}
/**
* @return void
*/
public function testThrowsErrorOnUnexpectedValueGiven(): void
{
$this->testClass::$dbArgumentPositionOffset = 0;
$this->expectException(UnexpectedValueException::class);
$this->expectExceptionMessage('DB argument should be a valid numeric value');
$this->testClass->setArguments(['wrong']);
}
public function argumentsProvider(): array
{
return [
'with positive integer db argument' => [
0,
[1],
['DB', 1]
],
'with wrong offset' => [
1,
[1],
[1]
],
'with negative integer db argument' => [
1,
['argument1', -1],
['argument1']
]
];
}
}
@@ -0,0 +1,47 @@
<?php
namespace Predis\Command\Traits;
use Predis\Command\Command as RedisCommand;
use PredisTestCase;
class ReplaceTest extends PredisTestCase
{
private $testClass;
protected function setUp(): void
{
parent::setUp();
$this->testClass = new class extends RedisCommand {
use Replace;
public function getId()
{
return 'test';
}
};
}
/**
* @dataProvider argumentsProvider
* @param array $arguments
* @param array $expectedResponse
* @return void
*/
public function testReturnsCorrectArguments(array $arguments, array $expectedResponse): void
{
$this->testClass->setArguments($arguments);
$this->assertSame($expectedResponse, $this->testClass->getArguments());
}
public function argumentsProvider(): array
{
return [
'with boolean - true' => [[true],['REPLACE']],
'with boolean - false' => [[false], []],
'with non boolean' => [['string'], ['string']],
];
}
}