Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extended Sorted Set support by adding BZPOPMIN command #862

Merged
merged 8 commits into from
Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/ClientContextInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
* @method $this bitop($operation, $destkey, $key)
* @method $this bitfield($key, $subcommand, ...$subcommandArg)
* @method $this bitpos($key, $bit, $start = null, $end = null)
* @method $this bzpopmin(array $keys, int $timeout)
* @method $this bzmpop(int $timeout, array $keys, string $modifier = 'min', int $count = 1)
* @method $this decr($key)
* @method $this decrby($key, $decrement)
Expand Down
1 change: 1 addition & 0 deletions src/ClientInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
* @method int bitop($operation, $destkey, $key)
* @method array|null bitfield(string $key, $subcommand, ...$subcommandArg)
* @method int bitpos(string $key, $bit, $start = null, $end = null)
* @method array bzpopmin(array $keys, int $timeout)
* @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)
Expand Down
46 changes: 46 additions & 0 deletions src/Command/Redis/BZPOPMIN.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace Predis\Command\Redis;

use Predis\Command\Traits\Keys;
use Predis\Command\Command as RedisCommand;

/**
* @link https://redis.io/commands/bzpopmin/
*
* BZPOPMIN is the blocking variant of the sorted set ZPOPMIN primitive.
*
* It is the blocking version because it blocks the connection when there are
* no members to pop from any of the given sorted sets.
* A member with the lowest score is popped from first sorted set that is non-empty,
* with the given keys being checked in the order that they are given.
*/
class BZPOPMIN extends RedisCommand
{
use Keys {
Keys::setArguments as setKeys;
}

protected static $keysArgumentPositionOffset = 0;

public function getId()
{
return 'BZPOPMIN';
}

public function setArguments(array $arguments)
{
$this->setKeys($arguments, false);
}

public function parseResponse($data)
{
$key = array_shift($data);

if (null === $key) {
return [$key];
}

return array_combine([$key], [[$data[0] => $data[1]]]);
}
}
11 changes: 8 additions & 3 deletions src/Command/Traits/Keys.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/
trait Keys
{
public function setArguments(array $arguments)
public function setArguments(array $arguments, bool $withNumkeys = true)
{
$argumentsLength = count($arguments);

Expand All @@ -22,10 +22,15 @@ public function setArguments(array $arguments)
}

$keysArgument = $arguments[static::$keysArgumentPositionOffset];
$numkeys = count($keysArgument);
$argumentsBeforeKeys = array_slice($arguments, 0, static::$keysArgumentPositionOffset);
$argumentsAfterKeys = array_slice($arguments, static::$keysArgumentPositionOffset + 1);

parent::setArguments(array_merge($argumentsBeforeKeys, [$numkeys], $keysArgument, $argumentsAfterKeys));
if ($withNumkeys) {
$numkeys = count($keysArgument);
parent::setArguments(array_merge($argumentsBeforeKeys, [$numkeys], $keysArgument, $argumentsAfterKeys));
return;
}

parent::setArguments(array_merge($argumentsBeforeKeys, $keysArgument, $argumentsAfterKeys));
}
}
122 changes: 122 additions & 0 deletions tests/Predis/Command/Redis/BZPOPMIN_Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php

namespace Predis\Command\Redis;

use Predis\Response\ServerException;
use UnexpectedValueException;

class BZPOPMIN_Test extends PredisCommandTestCase
{
/**
* @inheritDoc
*/
protected function getExpectedCommand(): string
{
return BZPOPMIN::class;
}

/**
* @inheritDoc
*/
protected function getExpectedId(): string
{
return 'BZPOPMIN';
}

/**
* @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
* @return void
* @requiresRedisVersion >= 5.0.0
*/
public function testReturnsPoppedMinElementFromGivenNonEmptySortedSet(): void
{
$redis = $this->getClient();
$sortedSetDictionary = [1, 'member1', 2, 'member2', 3, 'member3'];
$expectedResponse = ['test-bzpopmin' => ['member1' => '1']];
$expectedModifiedSortedSet = ['member2', 'member3'];

$redis->zadd('test-bzpopmin', ...$sortedSetDictionary);

$this->assertSame($expectedResponse, $redis->bzpopmin(['empty sorted set','test-bzpopmin'], 0));
$this->assertSame($expectedModifiedSortedSet, $redis->zrange('test-bzpopmin', 0, -1));
}

/**
* @group connected
* @return void
* @requiresRedisVersion >= 5.0.0
*/
public function testThrowsExceptionOnUnexpectedValueGiven(): void
{
$redis = $this->getClient();

$this->expectException(UnexpectedValueException::class);
$this->expectExceptionMessage('Wrong keys argument type or position offset');

$redis->bzpopmin(1, 0);
}

/**
* @group connected
* @requiresRedisVersion >= 5.0.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('bzpopmin_foo', 'bar');
$redis->bzpopmin(['bzpopmin_foo'], 0);
}

public function argumentsProvider(): array
{
return [
'with one key' => [
[['key1'], 1],
['key1', 1]
],
'with multiple keys' => [
[['key1', 'key2', 'key3'], 1],
['key1', 'key2', 'key3', 1]
],
];
}

public function responsesProvider(): array
{
return [
'null-element array' => [
[null],
[null]
],
'three-element array' => [
['key', 'member', 'score'],
['key' => ['member' => 'score']]
],
];
}
}
23 changes: 19 additions & 4 deletions tests/Predis/Command/Traits/KeysTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,20 @@ public function getId()
/**
* @dataProvider argumentsProvider
* @param int $offset
* @param bool $withNumkeys
* @param array $actualArguments
* @param array $expectedArguments
* @return void
*/
public function testReturnsCorrectArguments(int $offset, array $actualArguments, array $expectedArguments): void
{
public function testReturnsCorrectArguments(
int $offset,
bool $withNumkeys,
array $actualArguments,
array $expectedArguments
): void {
$this->testClass::$keysArgumentPositionOffset = $offset;

$this->testClass->setArguments($actualArguments);
$this->testClass->setArguments($actualArguments, $withNumkeys);

$this->assertSame($expectedArguments, $this->testClass->getArguments());
}
Expand All @@ -63,24 +68,34 @@ public function argumentsProvider(): array
return [
'keys argument first and there is arguments after' => [
0,
true,
[['key1', 'key2'], 'second argument', 'third argument'],
[2, 'key1', 'key2', 'second argument', 'third argument']
],
'keys argument last and there is arguments before' => [
2,
true,
['first argument', 'second argument', ['key1', 'key2']],
['first argument', 'second argument', 2, 'key1', 'key2']
],
'keys argument not the first and not the last' => [
1,
true,
['first argument', ['key1', 'key2'], 'third argument'],
['first argument', 2, 'key1', 'key2', 'third argument']
],
'keys argument the only argument' => [
0,
true,
[['key1', 'key2']],
[2, 'key1', 'key2']
]
],
'without numkeys modifier' => [
0,
false,
[['key1', 'key2']],
['key1', 'key2'],
],
];
}

Expand Down