From d1acca607e03b3fb3ec91380c6bf46200dda6ce1 Mon Sep 17 00:00:00 2001 From: Vladyslav Vildanov <117659936+vladvildanov@users.noreply.github.com> Date: Mon, 16 Jan 2023 18:45:04 +0200 Subject: [PATCH] Added support for LMPOP command (#1013) Co-authored-by: Vladyslav Vildanov --- src/ClientContextInterface.php | 1 + src/ClientInterface.php | 1 + src/Command/Redis/LMPOP.php | 51 ++++++ src/Command/Traits/LeftRight.php | 49 ++++++ tests/Predis/Command/Redis/LMPOP_Test.php | 151 ++++++++++++++++++ tests/Predis/Command/Traits/LeftRightTest.php | 90 +++++++++++ 6 files changed, 343 insertions(+) create mode 100644 src/Command/Redis/LMPOP.php create mode 100644 src/Command/Traits/LeftRight.php create mode 100644 tests/Predis/Command/Redis/LMPOP_Test.php create mode 100644 tests/Predis/Command/Traits/LeftRightTest.php diff --git a/src/ClientContextInterface.php b/src/ClientContextInterface.php index c350814f..49a341ec 100644 --- a/src/ClientContextInterface.php +++ b/src/ClientContextInterface.php @@ -93,6 +93,7 @@ use Predis\Command\CommandInterface; * @method $this linsert($key, $whence, $pivot, $value) * @method $this llen($key) * @method $this lmove(string $source, string $destination, string $where, string $to) + * @method $this lmpop(array $keys, string $modifier = 'left', int $count = 1) * @method $this lpop($key) * @method $this lpush($key, array $values) * @method $this lpushx($key, array $values) diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 4e98251c..39339477 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -102,6 +102,7 @@ use Predis\Response\Status; * @method int linsert(string $key, $whence, $pivot, $value) * @method int llen(string $key) * @method string lmove(string $source, string $destination, string $where, string $to) + * @method array|null lmpop(array $keys, string $modifier = 'left', int $count = 1) * @method string|null lpop(string $key) * @method int lpush(string $key, array $values) * @method int lpushx(string $key, array $values) diff --git a/src/Command/Redis/LMPOP.php b/src/Command/Redis/LMPOP.php new file mode 100644 index 00000000..a6f79c74 --- /dev/null +++ b/src/Command/Redis/LMPOP.php @@ -0,0 +1,51 @@ +setCount($arguments); + $arguments = $this->getArguments(); + + $this->setLeftRight($arguments); + $arguments = $this->getArguments(); + + $this->setKeys($arguments); + $this->filterArguments(); + } + + public function parseResponse($data) + { + if (null !== $data) { + return [$data[0] => $data[1]]; + } + + return null; + } +} diff --git a/src/Command/Traits/LeftRight.php b/src/Command/Traits/LeftRight.php new file mode 100644 index 00000000..10b303a5 --- /dev/null +++ b/src/Command/Traits/LeftRight.php @@ -0,0 +1,49 @@ + 'LEFT', + 'right' => 'RIGHT', + ]; + + public function setArguments(array $arguments) + { + $argumentsLength = count($arguments); + + if (static::$leftRightArgumentPositionOffset >= $argumentsLength) { + $arguments[] = 'LEFT'; + parent::setArguments($arguments); + return; + } + + $argument = $arguments[static::$leftRightArgumentPositionOffset]; + + if (is_string($argument) && in_array(strtoupper($argument), self::$leftRightEnum, true)) { + $argument = self::$leftRightEnum[$argument]; + } else { + $enumValues = implode(', ', array_keys(self::$leftRightEnum)); + throw new UnexpectedValueException("Left/Right argument accepts only: {$enumValues} values"); + } + + $argumentsBefore = array_slice($arguments, 0, static::$leftRightArgumentPositionOffset); + $argumentsAfter = array_slice($arguments, static::$leftRightArgumentPositionOffset + 1); + + parent::setArguments(array_merge( + $argumentsBefore, + [$argument], + $argumentsAfter + )); + } +} diff --git a/tests/Predis/Command/Redis/LMPOP_Test.php b/tests/Predis/Command/Redis/LMPOP_Test.php new file mode 100644 index 00000000..2ed2f50d --- /dev/null +++ b/tests/Predis/Command/Redis/LMPOP_Test.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Redis; + +use Predis\Response\ServerException; + +/** + * @group commands + * @group realm-list + */ +class LMPOP_Test extends PredisCommandTestCase +{ + /** + * {@inheritdoc} + */ + protected function getExpectedCommand(): string + { + return LMPOP::class; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedId(): string + { + return 'LMPOP'; + } + + /** + * @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 + */ + public function testParseResponse(): void + { + $raw = ['key', ['elem1', 'elem2']]; + $expected = ['key' => ['elem1', 'elem2']]; + + $command = $this->getCommand(); + + $this->assertSame($expected, $command->parseResponse($raw)); + } + + /** + * @group connected + * @dataProvider listProvider + * @param array $listArguments + * @param string $key + * @param string $modifier + * @param int $count + * @param array|null $expectedResponse + * @param array $expectedModifiedList + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testPopElementsFromGivenList( + array $listArguments, + string $key, + string $modifier, + int $count, + ?array $expectedResponse, + array $expectedModifiedList + ): void { + $redis = $this->getClient(); + + $redis->lpush(...$listArguments); + $actualResponse = $redis->lmpop(['key1', $key], $modifier, $count); + + $this->assertSame($expectedResponse, $actualResponse); + $this->assertSame($expectedModifiedList, $redis->lrange($key, 0, -1)); + } + + public function argumentsProvider(): array + { + return [ + 'with default arguments' => [ + [['key']], + [1, 'key', 'LEFT'] + ], + 'with LEFT/RIGHT argument' => [ + [['key'], 'right'], + [1, 'key', 'RIGHT'] + ], + 'with COUNT argument' => [ + [['key'], 'left', 2], + [1, 'key', 'LEFT', 'COUNT', 2] + ], + 'with all arguments' => [ + [['key1', 'key2'], 'right', 2], + [2, 'key1', 'key2', 'RIGHT', 'COUNT', 2] + ] + ]; + } + + public function listProvider(): array + { + return [ + 'pops single element - left' => [ + ['key', 'elem1', 'elem2', 'elem3'], + 'key', + 'left', + 1, + ['key' => ['elem3']], + ['elem2', 'elem1'] + ], + 'pops single element - right' => [ + ['key', 'elem1', 'elem2', 'elem3'], + 'key', + 'right', + 1, + ['key' => ['elem1']], + ['elem3', 'elem2'] + ], + 'pops multiple elements' => [ + ['key', 'elem1', 'elem2', 'elem3'], + 'key', + 'right', + 2, + ['key' => ['elem1', 'elem2']], + ['elem3'], + ], + 'with empty list' => [ + ['key', 'elem1', 'elem2', 'elem3'], + 'key2', + 'right', + 2, + null, + [], + ], + ]; + } +} diff --git a/tests/Predis/Command/Traits/LeftRightTest.php b/tests/Predis/Command/Traits/LeftRightTest.php new file mode 100644 index 00000000..0b8b70e4 --- /dev/null +++ b/tests/Predis/Command/Traits/LeftRightTest.php @@ -0,0 +1,90 @@ +testClass = new class extends RedisCommand { + use LeftRight; + + public static $leftRightArgumentPositionOffset = 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::$leftRightArgumentPositionOffset = $offset; + + $this->testClass->setArguments($actualArguments); + + $this->assertSame($expectedArguments, $this->testClass->getArguments()); + } + + /** + * @dataProvider unexpectedValuesProvider + * @param array $actualArguments + * @return void + */ + public function testThrowsExceptionOnUnexpectedValueGiven(array $actualArguments): void + { + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Left/Right argument accepts only: left, right values'); + + $this->testClass->setArguments($actualArguments); + } + + public function argumentsProvider(): array + { + return [ + 'with wrong offset' => [2, ['argument1'], ['argument1', 'LEFT']], + 'left/right argument first and there is arguments after' => [ + 0, + ['left', 'second argument', 'third argument'], + ['LEFT', 'second argument', 'third argument'] + ], + 'left/right argument last and there is arguments before' => [ + 2, + ['first argument', 'second argument', 'right'], + ['first argument', 'second argument', 'RIGHT'] + ], + 'left/right argument not the first and not the last' => [ + 1, + ['first argument', 'left', 'third argument'], + ['first argument', 'LEFT','third argument'] + ], + 'aggregate argument the only argument' => [ + 0, + ['right'], + ['RIGHT'] + ] + ]; + } + + public function unexpectedValuesProvider(): array + { + return [ + 'with non-string argument' => [[1]], + 'with non enum value' => [['wrong']], + ]; + } +}