mirror of
https://github.com/predis/predis.git
synced 2026-08-30 20:21:31 +00:00
Added support for ZMPOP command (#831)
Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
This commit is contained in:
committed by
GitHub
parent
cfd6f030bc
commit
7904fed6da
@@ -113,6 +113,7 @@ use Predis\Command\CommandInterface;
|
||||
* @method $this zdiffstore(string $destination, array $keys)
|
||||
* @method $this zincrby($key, $increment, $member)
|
||||
* @method $this zinterstore($destination, array|string $keys, array $options = null)
|
||||
* @method $this zmpop(array $keys, string $modifier = 'min', int $count = 1)
|
||||
* @method $this zmscore(string $key, string ...$member)
|
||||
* @method $this zrandmember(string $key, int $count = 1, bool $withScores = false)
|
||||
* @method $this zrange($key, $start, $stop, array $options = null)
|
||||
|
||||
@@ -129,6 +129,7 @@ use Predis\Response\Status;
|
||||
* @method int zdiffstore(string $destination, array $keys)
|
||||
* @method string zincrby(string $key, int $increment, string $member)
|
||||
* @method int zinterstore(string $destination, array|string $keys, array $options = null)
|
||||
* @method array zmpop(array $keys, string $modifier = 'min', int $count = 1)
|
||||
* @method array zmscore(string $key, string ...$member)
|
||||
* @method array zpopmin(string $key, int $count = 1)
|
||||
* @method array zpopmax(string $key, int $count = 1)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Predis\Command\Redis;
|
||||
|
||||
use Predis\Command\Command as RedisCommand;
|
||||
use Predis\Command\Traits\Count;
|
||||
use Predis\Command\Traits\MinMaxModifier;
|
||||
use Predis\Command\Traits\Numkeys;
|
||||
use Predis\Command\Traits\Keys;
|
||||
|
||||
class ZMPOP extends RedisCommand
|
||||
{
|
||||
use Numkeys {
|
||||
Numkeys::setArguments as setNumkeys;
|
||||
}
|
||||
use Count {
|
||||
Count::setArguments as setCount;
|
||||
}
|
||||
use MinMaxModifier;
|
||||
use Keys;
|
||||
|
||||
protected static $keysArgumentPositionOffset = 0;
|
||||
protected static $countArgumentPositionOffset = 2;
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return 'ZMPOP';
|
||||
}
|
||||
|
||||
public function setArguments(array $arguments)
|
||||
{
|
||||
$this->setCount($arguments);
|
||||
$arguments = $this->getArguments();
|
||||
|
||||
$this->setNumkeys($arguments);
|
||||
$arguments = $this->getArguments();
|
||||
|
||||
$this->resolveModifier(2, $arguments);
|
||||
$this->unpackKeysArray(self::$keysArgumentPositionOffset + 1, $arguments);
|
||||
|
||||
parent::setArguments($arguments);
|
||||
}
|
||||
|
||||
public function parseResponse($data)
|
||||
{
|
||||
$key = array_shift($data);
|
||||
|
||||
if (null === $key) {
|
||||
return [$key];
|
||||
}
|
||||
|
||||
$data = $data[0];
|
||||
$parsedData = [];
|
||||
|
||||
for ($i = 0, $iMax = count($data); $i < $iMax; $i++) {
|
||||
for ($j = 0, $jMax = count($data[$i]); $j < $jMax; ++$j) {
|
||||
if ($data[$i][$j + 1] ?? false) {
|
||||
$parsedData[$data[$i][$j]] = $data[$i][++$j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_combine([$key], [$parsedData]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Predis\Command\Traits;
|
||||
|
||||
use UnexpectedValueException;
|
||||
|
||||
trait Count
|
||||
{
|
||||
private $countModifier = 'COUNT';
|
||||
|
||||
public function setArguments(array $arguments)
|
||||
{
|
||||
$argumentsLength = count($arguments);
|
||||
|
||||
if (static::$countArgumentPositionOffset >= $argumentsLength) {
|
||||
parent::setArguments($arguments);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($arguments[static::$countArgumentPositionOffset] < 1) {
|
||||
throw new UnexpectedValueException('Wrong count argument value or position offset');
|
||||
}
|
||||
|
||||
$countArgument = $arguments[static::$countArgumentPositionOffset];
|
||||
$argumentsBefore = array_slice($arguments, 0, static::$countArgumentPositionOffset);
|
||||
$argumentsAfter = array_slice($arguments, static::$countArgumentPositionOffset + 1);
|
||||
|
||||
parent::setArguments(array_merge(
|
||||
$argumentsBefore,
|
||||
[$this->countModifier],
|
||||
[$countArgument],
|
||||
$argumentsAfter
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Predis\Command\Traits;
|
||||
|
||||
use UnexpectedValueException;
|
||||
|
||||
trait MinMaxModifier
|
||||
{
|
||||
/**
|
||||
* @var array{string: string}
|
||||
*/
|
||||
private $modifierEnum = [
|
||||
'min' => 'MIN',
|
||||
'max' => 'MAX',
|
||||
];
|
||||
|
||||
public function resolveModifier(int $offset, array &$arguments): void
|
||||
{
|
||||
if ($offset >= count($arguments)) {
|
||||
$arguments[$offset] = $this->modifierEnum['min'];
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_string($arguments[$offset]) || !array_key_exists($arguments[$offset], $this->modifierEnum)) {
|
||||
throw new UnexpectedValueException('Wrong type of modifier given');
|
||||
}
|
||||
|
||||
$arguments[$offset] = $this->modifierEnum[$arguments[$offset]];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
namespace Predis\Command\Redis;
|
||||
|
||||
use Predis\Response\ServerException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class ZMPOP_Test extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function getExpectedCommand(): string
|
||||
{
|
||||
return ZMPOP::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function getExpectedId(): string
|
||||
{
|
||||
return 'ZMPOP';
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @dataProvider responsesProvider
|
||||
*/
|
||||
public function testParseResponse(array $actualResponse, array $expectedResponse): void
|
||||
{
|
||||
$this->assertSame($expectedResponse, $this->getCommand()->parseResponse($actualResponse));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @dataProvider sortedSetsProvider
|
||||
* @param array $sortedSetDictionary
|
||||
* @param string $key
|
||||
* @param string $modifier
|
||||
* @param int $count
|
||||
* @param array $expectedResponse
|
||||
* @param array $expectedModifiedSortedSet
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.0
|
||||
*/
|
||||
public function testReturnsPoppedElementsFromGivenSortedSet(
|
||||
array $sortedSetDictionary,
|
||||
string $key,
|
||||
string $modifier,
|
||||
int $count,
|
||||
array $expectedResponse,
|
||||
array $expectedModifiedSortedSet
|
||||
): void {
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd($key, ...$sortedSetDictionary);
|
||||
$actualResponse = $redis->zmpop([$key], $modifier, $count);
|
||||
|
||||
$this->assertSame($expectedResponse, $actualResponse);
|
||||
$this->assertSame($expectedModifiedSortedSet, $redis->zrange($key, 0, -1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.0
|
||||
*/
|
||||
public function testReturnsPoppedElementsFromFirstNonEmptySortedSet(): void
|
||||
{
|
||||
$emptySortedSetKey = 'empty';
|
||||
$sortedSetKey = 'key';
|
||||
$sortedSet = [1, 'member1', 2, 'member2', 3, 'member3'];
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd($sortedSetKey, ...$sortedSet);
|
||||
$actualResponse = $redis->zmpop([$emptySortedSetKey, $sortedSetKey]);
|
||||
|
||||
$this->assertSame([$sortedSetKey], array_keys($actualResponse));
|
||||
$this->assertSame(['member2', 'member3'], $redis->zrange($sortedSetKey, 0, -1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @dataProvider unexpectedValuesProvider
|
||||
* @param array $keys
|
||||
* @param string $modifier
|
||||
* @param int $count
|
||||
* @param string $expectedExceptionMessage
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.0
|
||||
*/
|
||||
public function testThrowsExceptionOnUnexpectedValueGiven(
|
||||
array $keys,
|
||||
string $modifier,
|
||||
int $count,
|
||||
string $expectedExceptionMessage
|
||||
): void {
|
||||
$redis = $this->getClient();
|
||||
|
||||
$this->expectException(UnexpectedValueException::class);
|
||||
$this->expectExceptionMessage($expectedExceptionMessage);
|
||||
|
||||
$redis->zmpop($keys, $modifier, $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 7.0
|
||||
*/
|
||||
public function testThrowsExceptionOnWrongType(): void
|
||||
{
|
||||
$this->expectException(ServerException::class);
|
||||
$this->expectExceptionMessage('Operation against a key holding the wrong kind of value');
|
||||
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->set('zmpop_foo', 'bar');
|
||||
$redis->zmpop(['zmpop_foo']);
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with one key' => [
|
||||
[['key1'], 'min', 1],
|
||||
[1, 'key1', 'MIN', 'COUNT', 1]
|
||||
],
|
||||
'with multiple keys' => [
|
||||
[['key1', 'key2', 'key3'], 'max', 1],
|
||||
[3, 'key1', 'key2', 'key3', 'MAX', 'COUNT', 1]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function responsesProvider(): array
|
||||
{
|
||||
return [
|
||||
'null-element array' => [
|
||||
[null],
|
||||
[null]
|
||||
],
|
||||
'two-element array' => [
|
||||
['key', [['member1', 1, 'member2', 2, 'member3', 3]]],
|
||||
['key' => ['member1' => 1, 'member2' => 2, 'member3' => 3]]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function sortedSetsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with MIN modifier' => [
|
||||
[1, 'member1', 2, 'member2', 3, 'member3'],
|
||||
'test-zmpop',
|
||||
'min',
|
||||
1,
|
||||
['test-zmpop' => ['member1' => '1']],
|
||||
['member2', 'member3'],
|
||||
],
|
||||
'with MAX modifier' => [
|
||||
[1, 'member1', 2, 'member2', 3, 'member3'],
|
||||
'test-zmpop',
|
||||
'max',
|
||||
1,
|
||||
['test-zmpop' => ['member3' => '3']],
|
||||
['member1', 'member2'],
|
||||
],
|
||||
'with non-default COUNT' => [
|
||||
[1, 'member1', 2, 'member2', 3, 'member3'],
|
||||
'test-zmpop',
|
||||
'max',
|
||||
2,
|
||||
['test-zmpop' => ['member3' => '3', 'member2' => '2']],
|
||||
['member1'],
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function unexpectedValuesProvider(): array
|
||||
{
|
||||
return [
|
||||
'wrong modifier' => [
|
||||
['key1', 'key2'],
|
||||
'wrong modifier',
|
||||
1,
|
||||
'Wrong type of modifier given'
|
||||
],
|
||||
'wrong count' => [
|
||||
['key1', 'key2'],
|
||||
'min',
|
||||
0,
|
||||
'Wrong count argument value or position offset'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Predis\Command\Traits;
|
||||
|
||||
use PredisTestCase;
|
||||
use Predis\Command\Command as RedisCommand;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class CountTest extends PredisTestCase
|
||||
{
|
||||
private $testClass;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->testClass = new class extends RedisCommand {
|
||||
use Count;
|
||||
|
||||
public static $countArgumentPositionOffset = 2;
|
||||
|
||||
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::$countArgumentPositionOffset = $offset;
|
||||
|
||||
$this->testClass->setArguments($arguments);
|
||||
|
||||
$this->assertSameValues($expectedResponse, $this->testClass->getArguments());
|
||||
}
|
||||
|
||||
public function testThrowsErrorOnWrongCountValue(): void
|
||||
{
|
||||
$this->testClass::$countArgumentPositionOffset = 0;
|
||||
|
||||
$this->expectException(UnexpectedValueException::class);
|
||||
$this->expectExceptionMessage('Wrong count argument value or position offset');
|
||||
|
||||
$this->testClass->setArguments([0]);
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with count argument' => [
|
||||
0,
|
||||
[2],
|
||||
['COUNT', 2]
|
||||
],
|
||||
'without count argument' => [
|
||||
2,
|
||||
['argument1', 'argument2'],
|
||||
['argument1', 'argument2']
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Predis\Command\Traits;
|
||||
|
||||
use PredisTestCase;
|
||||
use Predis\Command\Command as RedisCommand;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class MinMaxModifierTest extends PredisTestCase
|
||||
{
|
||||
private $testClass;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->testClass = new class extends RedisCommand {
|
||||
use MinMaxModifier;
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return 'test';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider argumentsProvider
|
||||
* @param int $offset
|
||||
* @param array $actualArguments
|
||||
* @param array $expectedArguments
|
||||
* @return void
|
||||
*/
|
||||
public function testResolveModifierModifyArrayCorrect(
|
||||
int $offset,
|
||||
array $actualArguments,
|
||||
array $expectedArguments
|
||||
): void {
|
||||
$this->testClass->resolveModifier($offset, $actualArguments);
|
||||
$this->assertSame($expectedArguments, $actualArguments);
|
||||
}
|
||||
|
||||
public function testThrowsExceptionOnWrongModifierValue(): void
|
||||
{
|
||||
$arguments = ['argument1', 'wrong modifier'];
|
||||
|
||||
$this->expectException(UnexpectedValueException::class);
|
||||
$this->expectExceptionMessage('Wrong type of modifier given');
|
||||
|
||||
$this->testClass->resolveModifier(1, $arguments);
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with modifier' => [
|
||||
0,
|
||||
['max'],
|
||||
['MAX']
|
||||
],
|
||||
'without modifier' => [
|
||||
2,
|
||||
['argument1', 'argument2'],
|
||||
['argument1', 'argument2', 'MIN']
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user