diff --git a/src/ClientContextInterface.php b/src/ClientContextInterface.php index d39760e2..8600a2fd 100644 --- a/src/ClientContextInterface.php +++ b/src/ClientContextInterface.php @@ -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) diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 3b0cc0d4..31c731ea 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -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) diff --git a/src/Command/Redis/ZINTERCARD.php b/src/Command/Redis/ZINTERCARD.php new file mode 100644 index 00000000..4a6fa6d6 --- /dev/null +++ b/src/Command/Redis/ZINTERCARD.php @@ -0,0 +1,39 @@ +setLimit($arguments); + $arguments = $this->getArguments(); + + $this->setKeys($arguments); + } +} diff --git a/src/Command/Traits/Limit.php b/src/Command/Traits/Limit.php index 5d8db73c..42fbdc92 100644 --- a/src/Command/Traits/Limit.php +++ b/src/Command/Traits/Limit.php @@ -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)); } } diff --git a/tests/Predis/Command/Redis/ZINTERCARD_Test.php b/tests/Predis/Command/Redis/ZINTERCARD_Test.php new file mode 100644 index 00000000..ab379198 --- /dev/null +++ b/tests/Predis/Command/Redis/ZINTERCARD_Test.php @@ -0,0 +1,180 @@ +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' + ] + ]; + } +} diff --git a/tests/Predis/Command/Traits/LimitTest.php b/tests/Predis/Command/Traits/LimitTest.php index c40d7d12..4ce98d8f 100644 --- a/tests/Predis/Command/Traits/LimitTest.php +++ b/tests/Predis/Command/Traits/LimitTest.php @@ -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], + ], ]; } }