Extended server functionality by implementing FAILOVER command (#875)

* Added arrayable arguments classes

* Updated count trait to accept additional modifier argument

* Added new With traits, moved into separate directory

* Added traits for geo command arguments resolving

* Remove BaseWith trait, not working with traits nesting

* Added AscDesc trait

* Rename AscDesc traint into Sorting

* Fixed trait keyword

* Removed unnecessary traits

* Updated count trait to handle default argument value

* Fixed With traits offset check condition

* Added GeoFrom, GeoBy traits

* Fixes for Count and Sorting traits

* Fixed variable names in with traits

* [WIP] Added GEOSEARCH command support, without test coverage

* Changed functionality to support only lower case units (Redis 6.0 support)

* Changed namespace for WITHSCORES trait

* Added more test coverage, added response parsing

* Added test coverage for tratis

* Added support for FAILOVER command

Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
This commit is contained in:
Vladyslav Vildanov
2022-12-29 22:19:17 +02:00
committed by GitHub
parent b8cc7c643a
commit 721c829042
9 changed files with 428 additions and 0 deletions
+2
View File
@@ -13,6 +13,7 @@ namespace Predis;
use Predis\Command\Argument\Geospatial\ByInterface;
use Predis\Command\Argument\Geospatial\FromInterface;
use Predis\Command\Argument\Server\To;
use Predis\Command\CommandInterface;
/**
@@ -48,6 +49,7 @@ use Predis\Command\CommandInterface;
* @method $this bzmpop(int $timeout, array $keys, string $modifier = 'min', int $count = 1)
* @method $this decr($key)
* @method $this decrby($key, $decrement)
* @method $this failover(?To $to = null, bool $abort = false, int $timeout = -1)
* @method $this get($key)
* @method $this getbit($key, $offset)
* @method $this getex(string $key, $modifier = '', $value = false)
+2
View File
@@ -13,6 +13,7 @@ namespace Predis;
use Predis\Command\Argument\Geospatial\ByInterface;
use Predis\Command\Argument\Geospatial\FromInterface;
use Predis\Command\Argument\Server\To;
use Predis\Command\CommandInterface;
use Predis\Command\FactoryInterface;
use Predis\Configuration\OptionsInterface;
@@ -57,6 +58,7 @@ use Predis\Response\Status;
* @method array bzmpop(int $timeout, array $keys, string $modifier = 'min', int $count = 1)
* @method int decr(string $key)
* @method int decrby(string $key, int $decrement)
* @method Status failover(?To $to = null, bool $abort = false, int $timeout = -1)
* @method string|null get(string $key)
* @method int getbit(string $key, $offset)
* @method int|null getex(string $key, $modifier = '', $value = false)
+47
View File
@@ -0,0 +1,47 @@
<?php
namespace Predis\Command\Argument\Server;
use Predis\Command\Argument\ArrayableArgument;
class To implements ArrayableArgument
{
private const KEYWORD = 'TO';
private const FORCE_KEYWORD = 'FORCE';
/**
* @var string
*/
private $host;
/**
* @var int
*/
private $port;
/**
* @var bool
*/
private $isForce;
public function __construct(string $host, int $port, bool $isForce = false)
{
$this->host = $host;
$this->port = $port;
$this->isForce = $isForce;
}
/**
* @inheritDoc
*/
public function toArray(): array
{
$arguments = [self::KEYWORD, $this->host, $this->port];
if ($this->isForce) {
$arguments[] = self::FORCE_KEYWORD;
}
return $arguments;
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
use Predis\Command\Traits\Timeout;
use Predis\Command\Traits\To\ServerTo;
class FAILOVER extends RedisCommand
{
use ServerTo {
ServerTo::setArguments as setTo;
}
use Timeout {
Timeout::setArguments as setTimeout;
}
protected static $toArgumentPositionOffset = 0;
protected static $timeoutArgumentPositionOffset = 2;
public function getId()
{
return 'FAILOVER';
}
public function setArguments(array $arguments)
{
if (array_key_exists(1, $arguments) && false !== $arguments[1]) {
$arguments[1] = 'ABORT';
}
$this->setTimeout($arguments);
$arguments = $this->getArguments();
$this->setTo($arguments);
$this->filterArguments();
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace Predis\Command\Traits;
use UnexpectedValueException;
trait Timeout
{
private static $timeoutModifier = 'TIMEOUT';
public function setArguments(array $arguments)
{
$argumentsLength = count($arguments);
if (static::$timeoutArgumentPositionOffset >= $argumentsLength) {
parent::setArguments($arguments);
return;
}
if ($arguments[static::$timeoutArgumentPositionOffset] === -1) {
array_splice($arguments, static::$timeoutArgumentPositionOffset, 1, [false]);
parent::setArguments($arguments);
return;
}
if ($arguments[static::$timeoutArgumentPositionOffset] < 1) {
throw new UnexpectedValueException('Wrong timeout argument value or position offset');
}
$argument = $arguments[static::$timeoutArgumentPositionOffset];
$argumentsBefore = array_slice($arguments, 0, static::$timeoutArgumentPositionOffset);
$argumentsAfter = array_slice($arguments, static::$timeoutArgumentPositionOffset + 1);
parent::setArguments(array_merge(
$argumentsBefore,
[self::$timeoutModifier],
[$argument],
$argumentsAfter
));
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace Predis\Command\Traits\To;
use Predis\Command\Argument\Server\To;
trait ServerTo
{
public function setArguments(array $arguments)
{
$argumentsLength = count($arguments);
if (static::$toArgumentPositionOffset >= $argumentsLength) {
parent::setArguments($arguments);
return;
}
/** @var To|null $toArgument */
$toArgument = $arguments[static::$toArgumentPositionOffset];
if (null === $toArgument) {
array_splice($arguments, static::$toArgumentPositionOffset, 1, [false]);
parent::setArguments($arguments);
return;
}
$argumentsBefore = array_slice($arguments, 0, static::$toArgumentPositionOffset);
$argumentsAfter = array_slice($arguments, static::$toArgumentPositionOffset + 1);
parent::setArguments(array_merge(
$argumentsBefore,
$toArgument->toArray(),
$argumentsAfter
));
}
}
@@ -0,0 +1,116 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Redis;
use Predis\Command\Argument\Server\To;
use UnexpectedValueException;
/**
* @group commands
* @group realm-server
*/
class FAILOVER_Test extends PredisCommandTestCase
{
/**
* {@inheritdoc}
*/
protected function getExpectedCommand(): string
{
return FAILOVER::class;
}
/**
* {@inheritdoc}
*/
protected function getExpectedId(): string
{
return 'FAILOVER';
}
/**
* @group disconnected
* @dataProvider argumentsProvider
*/
public function testFilterArguments(array $actualArguments, array $expectedArguments): void
{
$command = $this->getCommand();
$command->setArguments($actualArguments);
$this->assertSameValues($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 testPerformFailoverOfConnectedReplica(): void
{
$this->markTestSkipped('Test requires configured replica node connected to master');
}
/**
* @group connected
* @return void
* @requiresRedisVersion >= 6.2.0
*/
public function testThrowsExceptionOnUnexpectedValueGiven(): void
{
$redis = $this->getClient();
$this->expectException(UnexpectedValueException::class);
$this->expectExceptionMessage('Wrong timeout argument value or position offset');
$redis->failover(null, false, 0);
}
public function argumentsProvider(): array
{
return [
'without optional arguments - no arguments' => [
[],
[]
],
'without optional arguments - default arguments' => [
[null, false, -1],
[]
],
'with TO argument - no FORCE' => [
[new To('test', 9999)],
['TO', 'test', 9999]
],
'with TO argument - with FORCE' => [
[new To('test', 9999, true)],
['TO', 'test', 9999, 'FORCE']
],
'with ABORT modifier' => [
[null, true],
['ABORT']
],
'with TIMEOUT argument' => [
[null, false, 10],
['TIMEOUT', 10]
],
'with all arguments' => [
[new To('test', 9999, true), true, 10],
['TO', 'test', 9999, 'FORCE', 'ABORT', 'TIMEOUT', 10]
]
];
}
}
@@ -0,0 +1,76 @@
<?php
namespace Predis\Command\Traits;
use PredisTestCase;
use Predis\Command\Command as RedisCommand;
use UnexpectedValueException;
class TimeoutTest extends PredisTestCase
{
private $testClass;
protected function setUp(): void
{
parent::setUp();
$this->testClass = new class extends RedisCommand {
use Timeout;
public static $timeoutArgumentPositionOffset = 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::$timeoutArgumentPositionOffset = $offset;
$this->testClass->setArguments($arguments);
$this->assertSameValues($expectedResponse, $this->testClass->getArguments());
}
/**
* @return void
*/
public function testThrowsExceptionOnUnexpectedValueGiven(): void
{
$this->expectException(UnexpectedValueException::class);
$this->expectExceptionMessage('Wrong timeout argument value or position offset');
$this->testClass->setArguments([0]);
}
public function argumentsProvider(): array
{
return [
'with TIMEOUT argument' => [
0,
[10],
['TIMEOUT', 10]
],
'with wrong offset given' => [
1,
[],
[]
],
'with default value' => [
0,
[-1],
[false]
]
];
}
}
@@ -0,0 +1,70 @@
<?php
namespace Predis\Command\Traits\To;
use Predis\Command\Argument\Server\To;
use PredisTestCase;
use Predis\Command\Command as RedisCommand;
class ServerToTest extends PredisTestCase
{
private $testClass;
protected function setUp(): void
{
parent::setUp();
$this->testClass = new class extends RedisCommand {
use ServerTo;
public static $toArgumentPositionOffset = 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::$toArgumentPositionOffset = $offset;
$this->testClass->setArguments($arguments);
$this->assertSameValues($expectedResponse, $this->testClass->getArguments());
}
public function argumentsProvider(): array
{
return [
'with TO argument - no FORCE' => [
0,
[new To('host', 9999)],
['TO', 'host', 9999]
],
'with TO argument - with FORCE' => [
0,
[new To('host', 9999, true)],
['TO', 'host', 9999, 'FORCE']
],
'with wrong offset given' => [
1,
[],
[]
],
'with default value' => [
0,
[null],
[false]
]
];
}
}