Extended Sorted Set support by adding ZINTERCARD command (#861)

* Refactored zinterstore, zunionstore commands and command traits

* Merge conflicts resolve, update cluster strategy test with new arguments

* Updated assertion in case if command executed faster then duration minimal threshold

* Updated Limit trait to handle integer values

* Updated Limit trait to return previous arguments if limit argument isn't set

* Added support for ZINTERCARD command

Co-authored-by: Vladyslav Vildanov <vladyslavvildanov@Vladyslav-Vildanov-MacBook-Pro.local>
This commit is contained in:
Vladyslav Vildanov
2022-12-12 18:38:06 +02:00
committed by GitHub
parent 87ecd7bde2
commit 7c0cc7bdbe
6 changed files with 247 additions and 7 deletions
+1
View File
@@ -114,6 +114,7 @@ use Predis\Command\CommandInterface;
* @method $this zdiff(array $keys, bool $withScores = false)
* @method $this zdiffstore(string $destination, array $keys)
* @method $this zincrby($key, $increment, $member)
* @method $this zintercard(array $keys, int $limit = 0)
* @method $this zinterstore(string $destination, array $keys, int[] $weights = [], string $aggregate = 'sum')
* @method $this zmpop(array $keys, string $modifier = 'min', int $count = 1)
* @method $this zmscore(string $key, string ...$member)
+1
View File
@@ -130,6 +130,7 @@ use Predis\Response\Status;
* @method array zdiff(array $keys, bool $withScores = false)
* @method int zdiffstore(string $destination, array $keys)
* @method string zincrby(string $key, int $increment, string $member)
* @method int zintercard(array $keys, int $limit = 0)
* @method int zinterstore(string $destination, array $keys, int[] $weights = [], string $aggregate = 'sum')
* @method array zmpop(array $keys, string $modifier = 'min', int $count = 1)
* @method array zmscore(string $key, string ...$member)
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
use Predis\Command\Traits\Keys;
use Predis\Command\Traits\Limit;
/**
* @link https://redis.io/commands/zintercard/
*
* This command is similar to ZINTER, but instead of returning the result set,
* it returns just the cardinality of the result.
*/
class ZINTERCARD extends RedisCommand
{
use Keys {
Keys::setArguments as setKeys;
}
use Limit {
Limit::setArguments as setLimit;
}
protected static $keysArgumentPositionOffset = 0;
protected static $limitArgumentPositionOffset = 1;
public function getId()
{
return 'ZINTERCARD';
}
public function setArguments(array $arguments)
{
$this->setLimit($arguments);
$arguments = $this->getArguments();
$this->setKeys($arguments);
}
}
+16 -7
View File
@@ -10,24 +10,33 @@ use UnexpectedValueException;
*/
trait Limit
{
private static $limitModifier = 'LIMIT';
public function setArguments(array $arguments)
{
$argument = $arguments[static::$limitArgumentPositionOffset];
$argumentsLength = count($arguments);
$argumentsBefore = array_slice($arguments, 0, static::$limitArgumentPositionOffset);
if (false === $argument) {
if (
static::$limitArgumentPositionOffset >= $argumentsLength
|| false === $arguments[static::$limitArgumentPositionOffset]
) {
parent::setArguments($argumentsBefore);
return;
}
$argument = $arguments[static::$limitArgumentPositionOffset];
$argumentsAfter = array_slice($arguments, static::$limitArgumentPositionOffset + 1);
if (true === $argument) {
$argument = 'LIMIT';
} else {
parent::setArguments(array_merge($argumentsBefore, [self::$limitModifier], $argumentsAfter));
return;
}
if (!is_int($argument)) {
throw new UnexpectedValueException('Wrong limit argument type');
}
$argumentsAfter = array_slice($arguments, static::$limitArgumentPositionOffset + 1);
parent::setArguments(array_merge($argumentsBefore, [$argument], $argumentsAfter));
parent::setArguments(array_merge($argumentsBefore, [self::$limitModifier], [$argument], $argumentsAfter));
}
}
@@ -0,0 +1,180 @@
<?php
namespace Predis\Command\Redis;
use Predis\Response\ServerException;
use UnexpectedValueException;
class ZINTERCARD_Test extends PredisCommandTestCase
{
/**
* @inheritDoc
*/
protected function getExpectedCommand(): string
{
return ZINTERCARD::class;
}
/**
* @inheritDoc
*/
protected function getExpectedId(): string
{
return 'ZINTERCARD';
}
/**
* @dataProvider argumentsProvider
* @group disconnected
*/
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
* @dataProvider sortedSetsProvider
* @param array $firstSortedSet
* @param array $secondSortedSet
* @param int $limit
* @param int $expectedResponse
* @return void
* @requiresRedisVersion >= 7.0.0
*/
public function testReturnsIntersectionCardinalityOnSortedSets(
array $firstSortedSet,
array $secondSortedSet,
int $limit,
int $expectedResponse
): void {
$redis = $this->getClient();
$redis->zadd('test-zintercard1', ...$firstSortedSet);
$redis->zadd('test-zintercard2', ...$secondSortedSet);
$this->assertSame($expectedResponse, $redis->zintercard(['test-zintercard1', 'test-zintercard2'], $limit));
}
/**
* @group connected
* @return void
* @requiresRedisVersion >= 7.0.0
*/
public function testReturnsIntersectionCardinalityZeroOnEmptySortedSetGiven(): void
{
$redis = $this->getClient();
$sortedSet = [1, 'member1', 2, 'member2', 3, 'member3'];
$redis->zadd('test-zintercard', ...$sortedSet);
$this->assertSame(0, $redis->zintercard(['test-zintercard', 'non-existing-key']));
}
/**
* @group connected
* @requiresRedisVersion >= 7.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('foo', 'bar');
$redis->zintercard(['foo']);
}
/**
* @group connected
* @dataProvider unexpectedValuesProvider
* @param $keys
* @param $limit
* @param string $expectedExceptionMessage
* @return void
* @requiresRedisVersion >= 7.0.0
*/
public function testThrowsExceptionOnUnexpectedValueGiven(
$keys,
$limit,
string $expectedExceptionMessage
): void {
$redis = $this->getClient();
$this->expectException(UnexpectedValueException::class);
$this->expectExceptionMessage($expectedExceptionMessage);
$redis->zintercard($keys, $limit);
}
public function argumentsProvider(): array
{
return [
'with required arguments only' => [
[['key1', 'key2']],
[2, 'key1', 'key2'],
],
'with all arguments' => [
[['key1', 'key2'], 2],
[2, 'key1', 'key2', 'LIMIT', 2],
],
];
}
public function sortedSetsProvider(): array
{
return [
'with full intersection' => [
[1, 'member1', 2, 'member2', 3, 'member3'],
[1, 'member1', 2, 'member2', 3, 'member3'],
0,
3
],
'with partial intersection' => [
[1, 'member1', 2, 'member2', 3, 'member3'],
[1, 'member1', 2, 'member2', 4, 'member4'],
0,
2
],
'with no intersection' => [
[1, 'member1', 2, 'member2', 3, 'member3'],
[4, 'member4', 5, 'member5', 6, 'member6'],
0,
0
],
'with full intersection and limit' => [
[1, 'member1', 2, 'member2', 3, 'member3'],
[1, 'member1', 2, 'member2', 3, 'member3'],
1,
1
]
];
}
public function unexpectedValuesProvider(): array
{
return [
'with wrong type keys argument' => [
'wrong',
0,
'Wrong keys argument type or position offset'
],
'with wrong type limit argument' => [
['key1', 'key'],
[1],
'Wrong limit argument type'
]
];
}
}
+10
View File
@@ -88,6 +88,16 @@ class LimitTest extends PredisTestCase
['first argument', true, 'third argument'],
['first argument', 'LIMIT', 'third argument']
],
'limit argument is integer' => [
0,
[1],
['LIMIT', 1]
],
'limit argument with wrong offset' => [
2,
[1],
[1],
],
];
}
}