From 1b8a3cd28c3f7cb088c7e2d132c8460e303a5b32 Mon Sep 17 00:00:00 2001 From: Vladyslav Vildanov <117659936+vladvildanov@users.noreply.github.com> Date: Wed, 1 Feb 2023 12:04:18 +0200 Subject: [PATCH] Extended BloomFilters support by implementing CF.INSERT command (#1083) * Added support for CF.INSERT command * Override argument position offset value to default one * Codestyle fixes --------- Co-authored-by: Vladyslav Vildanov --- src/ClientContextInterface.php | 1 + src/ClientInterface.php | 1 + src/Command/Redis/BloomFilter/BFINSERT.php | 10 +- src/Command/Redis/CuckooFilter/CFINSERT.php | 52 +++++ src/Command/Traits/BloomFilters/NoCreate.php | 49 +++++ .../Redis/CuckooFilter/CFINSERT_Test.php | 182 ++++++++++++++++++ .../Traits/BloomFilters/NoCreateTest.php | 93 +++++++++ 7 files changed, 385 insertions(+), 3 deletions(-) create mode 100644 src/Command/Redis/CuckooFilter/CFINSERT.php create mode 100644 src/Command/Traits/BloomFilters/NoCreate.php create mode 100644 tests/Predis/Command/Redis/CuckooFilter/CFINSERT_Test.php create mode 100644 tests/Predis/Command/Traits/BloomFilters/NoCreateTest.php diff --git a/src/ClientContextInterface.php b/src/ClientContextInterface.php index c8678ad7..088050bd 100644 --- a/src/ClientContextInterface.php +++ b/src/ClientContextInterface.php @@ -66,6 +66,7 @@ use Predis\Command\CommandInterface; * @method $this cfexists(string $key, $item) * @method $this cfmexists(string $key, ...$item) * @method $this cfinfo(string $key) + * @method $this cfinsert(string $key, int $capacity = -1, bool $noCreate = false, string ...$item) * @method $this decr($key) * @method $this decrby($key, $decrement) * @method $this failover(?To $to = null, bool $abort = false, int $timeout = -1) diff --git a/src/ClientInterface.php b/src/ClientInterface.php index c96f5f50..b1a19c54 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -75,6 +75,7 @@ use Predis\Response\Status; * @method int cfexists(string $key, $item) * @method int cfmexists(string $key, ...$item) * @method array cfinfo(string $key) + * @method array cfinsert(string $key, int $capacity = -1, bool $noCreate = false, string ...$item) * @method int decr(string $key) * @method int decrby(string $key, int $decrement) * @method Status failover(?To $to = null, bool $abort = false, int $timeout = -1) diff --git a/src/Command/Redis/BloomFilter/BFINSERT.php b/src/Command/Redis/BloomFilter/BFINSERT.php index 48f2bc54..50e23eea 100644 --- a/src/Command/Redis/BloomFilter/BFINSERT.php +++ b/src/Command/Redis/BloomFilter/BFINSERT.php @@ -17,6 +17,7 @@ use Predis\Command\Traits\BloomFilters\Capacity; use Predis\Command\Traits\BloomFilters\Error; use Predis\Command\Traits\BloomFilters\Expansion; use Predis\Command\Traits\BloomFilters\Items; +use Predis\Command\Traits\BloomFilters\NoCreate; class BFINSERT extends RedisCommand { @@ -32,10 +33,14 @@ class BFINSERT extends RedisCommand use Items { Items::setArguments as setItems; } + use NoCreate { + NoCreate::setArguments as setNoCreate; + } protected static $capacityArgumentPositionOffset = 1; protected static $errorArgumentPositionOffset = 2; protected static $expansionArgumentPositionOffset = 3; + protected static $noCreateArgumentPositionOffset = 4; protected static $itemsArgumentPositionOffset = 6; public function getId() @@ -45,9 +50,8 @@ class BFINSERT extends RedisCommand public function setArguments(array $arguments) { - if (array_key_exists(4, $arguments) && $arguments[4]) { - $arguments[4] = 'NOCREATE'; - } + $this->setNoCreate($arguments); + $arguments = $this->getArguments(); if (array_key_exists(5, $arguments) && $arguments[5]) { $arguments[5] = 'NONSCALING'; diff --git a/src/Command/Redis/CuckooFilter/CFINSERT.php b/src/Command/Redis/CuckooFilter/CFINSERT.php new file mode 100644 index 00000000..3a49a38d --- /dev/null +++ b/src/Command/Redis/CuckooFilter/CFINSERT.php @@ -0,0 +1,52 @@ +setNoCreate($arguments); + $arguments = $this->getArguments(); + + $this->setItems($arguments); + $arguments = $this->getArguments(); + + $this->setCapacity($arguments); + $this->filterArguments(); + } +} diff --git a/src/Command/Traits/BloomFilters/NoCreate.php b/src/Command/Traits/BloomFilters/NoCreate.php new file mode 100644 index 00000000..7fc084ec --- /dev/null +++ b/src/Command/Traits/BloomFilters/NoCreate.php @@ -0,0 +1,49 @@ += $argumentsLength + || false === $arguments[static::$noCreateArgumentPositionOffset] + ) { + parent::setArguments($arguments); + + return; + } + + $argument = $arguments[static::$noCreateArgumentPositionOffset]; + + if (true === $argument) { + $argument = 'NOCREATE'; + } else { + throw new UnexpectedValueException('Wrong NOCREATE argument type'); + } + + $argumentsBefore = array_slice($arguments, 0, static::$noCreateArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$noCreateArgumentPositionOffset + 1); + + parent::setArguments(array_merge($argumentsBefore, [$argument], $argumentsAfter)); + } +} diff --git a/tests/Predis/Command/Redis/CuckooFilter/CFINSERT_Test.php b/tests/Predis/Command/Redis/CuckooFilter/CFINSERT_Test.php new file mode 100644 index 00000000..14af24ed --- /dev/null +++ b/tests/Predis/Command/Redis/CuckooFilter/CFINSERT_Test.php @@ -0,0 +1,182 @@ +getCommand(); + $command->setArguments($actualArguments); + + $this->assertSameValues($expectedArguments, $command->getArguments()); + } + + /** + * @group connected + * @dataProvider filtersProvider + * @param array $filterArguments + * @param string $key + * @param int $expectedCapacity + * @param array $expectedResponse + * @return void + * @requiresRedisBfVersion >= 1.0.0 + */ + public function testInsertItemsIntoGivenCuckooFilter( + array $filterArguments, + string $key, + int $expectedCapacity, + array $expectedResponse + ): void { + $redis = $this->getClient(); + + $actualResponse = $redis->cfinsert(...$filterArguments); + $info = $redis->cfinfo($key); + + $this->assertSame($expectedResponse, $actualResponse); + $this->assertSame($expectedCapacity, $info['Size']); + } + + /** + * @group connected + * @return void + * @requiresRedisBfVersion >= 1.0.0 + */ + public function testInsertIgnoresCapacityModifierOnAlreadyExistingFilter(): void + { + $redis = $this->getClient(); + + $redis->cfadd('filter', 'item'); + + $actualResponse = $redis->cfinsert('filter', 500, false, 'item1'); + $info = $redis->cfinfo('filter'); + + $this->assertSame([1], $actualResponse); + $this->assertSame(1080, $info['Size']); + } + + /** + * @group connected + * @return void + * @requiresRedisBfVersion >= 1.0.0 + */ + public function testInsertThrowsErrorOnInsertingIntoNonExistingFilterWithNoCreateModifier(): void + { + $redis = $this->getClient(); + + $this->expectException(ServerException::class); + $this->expectExceptionMessage('ERR not found'); + + $redis->cfinsert('key', -1, true, 'item'); + } + + /** + * @group connected + * @return void + * @requiresRedisBfVersion >= 1.0.0 + */ + public function testInsertIntoAlreadyExistingFilterWithNoCreateModifier(): void + { + $redis = $this->getClient(); + + $redis->cfadd('filter', 'item'); + + $actualResponse = $redis->cfinsert('filter', -1, true, 'item1'); + $this->assertSame([1], $actualResponse); + } + + /** + * @group connected + * @return void + * @requiresRedisBfVersion >= 1.0.0 + */ + public function testThrowsExceptionOnUnexpectedValueGiven(): void + { + $redis = $this->getClient(); + + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Wrong NOCREATE argument type'); + + $redis->cfinsert('key', -1, 'wrong', 'item'); + } + + public function argumentsProvider(): array + { + return [ + 'with default arguments' => [ + ['key', -1, false, 'item1'], + ['key', 'ITEMS', 'item1'], + ], + 'with CAPACITY modifier' => [ + ['key', 500, false, 'item1'], + ['key', 'CAPACITY', 500, 'ITEMS', 'item1'], + ], + 'with NOCREATE modifier' => [ + ['key', -1, true, 'item1'], + ['key', 'NOCREATE', 'ITEMS', 'item1'], + ], + 'with all arguments' => [ + ['key', 500, true, 'item1', 'item2'], + ['key', 'CAPACITY', 500, 'NOCREATE', 'ITEMS', 'item1', 'item2'], + ], + ]; + } + + public function filtersProvider(): array + { + return [ + 'with default arguments' => [ + ['key', -1, false, 'item1'], + 'key', + 1080, + [1], + ], + 'with modified CAPACITY' => [ + ['key', 500, false, 'item1'], + 'key', + 568, + [1], + ], + 'with multiple items' => [ + ['key', -1, false, 'item1', 'item2'], + 'key', + 1080, + [1, 1], + ], + ]; + } +} diff --git a/tests/Predis/Command/Traits/BloomFilters/NoCreateTest.php b/tests/Predis/Command/Traits/BloomFilters/NoCreateTest.php new file mode 100644 index 00000000..e3fb3c45 --- /dev/null +++ b/tests/Predis/Command/Traits/BloomFilters/NoCreateTest.php @@ -0,0 +1,93 @@ +testClass = new class() extends RedisCommand { + use NoCreate; + + public static $noCreateArgumentPositionOffset = 0; + + public function getId() + { + return 'test'; + } + }; + } + + /** + * @dataProvider argumentsProvider + * @param int $offset + * @param array $actualArguments + * @param array $expectedArguments + * @return void + */ + public function testReturnsCorrectArguments(int $offset, array $actualArguments, array $expectedArguments): void + { + $this->testClass::$noCreateArgumentPositionOffset = $offset; + + $this->testClass->setArguments($actualArguments); + + $this->assertSame($expectedArguments, $this->testClass->getArguments()); + } + + /** + * @return void + */ + public function testThrowsExceptionOnUnexpectedValue(): void + { + $this->testClass::$noCreateArgumentPositionOffset = 0; + + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Wrong NOCREATE argument type'); + + $this->testClass->setArguments(['test']); + } + + public function argumentsProvider(): array + { + return [ + 'NOCREATE false argument' => [ + 0, + [false, 'second argument', 'third argument'], + [false, 'second argument', 'third argument'], + ], + 'NOCREATE argument first and there is arguments after' => [ + 0, + [true, 'second argument', 'third argument'], + ['NOCREATE', 'second argument', 'third argument'], + ], + 'NOCREATE argument last and there is arguments before' => [ + 2, + ['first argument', 'second argument', true], + ['first argument', 'second argument', 'NOCREATE'], + ], + 'NOCREATE argument not the first and not the last' => [ + 1, + ['first argument', true, 'third argument'], + ['first argument', 'NOCREATE', 'third argument'], + ], + ]; + } +}