From 422990b631087e059f3fefff1c7283c8ee5849aa Mon Sep 17 00:00:00 2001 From: Vladyslav Vildanov <117659936+vladvildanov@users.noreply.github.com> Date: Wed, 22 Jul 2026 10:01:21 +0300 Subject: [PATCH] Added support for LMOVEM and BLMOVEM commands (#1709) * Added support for LMOVEM and BLMOVEM commands * Removed background testing * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/tests.yml | 2 +- CHANGELOG.md | 1 + src/ClientContextInterface.php | 2 + src/ClientInterface.php | 2 + src/Cluster/ClusterStrategy.php | 25 ++ src/Command/Redis/BLMOVEM.php | 38 +++ src/Command/Redis/LMOVEM.php | 100 +++++++ tests/Predis/Cluster/PredisStrategyTest.php | 32 +++ tests/Predis/Cluster/RedisStrategyTest.php | 32 +++ tests/Predis/Command/Redis/BLMOVEM_Test.php | 271 ++++++++++++++++++ tests/Predis/Command/Redis/LMOVEM_Test.php | 271 ++++++++++++++++++ tests/Predis/Command/Redis/SDIFFCARD_Test.php | 8 +- .../Predis/Command/Redis/SUNIONCARD_Test.php | 10 +- .../Command/Redis/Search/FTAGGREGATE_Test.php | 6 +- .../Command/Redis/Search/FTALIASLIST_Test.php | 8 +- .../Redis/TimeSeries/TSMRANGE_Test.php | 6 +- .../Redis/TimeSeries/TSMREVRANGE_Test.php | 4 +- .../Redis/TimeSeries/TSQUERYLABELS_Test.php | 8 +- .../Command/Redis/TimeSeries/TSREAD_Test.php | 16 +- 19 files changed, 808 insertions(+), 34 deletions(-) create mode 100644 src/Command/Redis/BLMOVEM.php create mode 100644 src/Command/Redis/LMOVEM.php create mode 100644 tests/Predis/Command/Redis/BLMOVEM_Test.php create mode 100644 tests/Predis/Command/Redis/LMOVEM_Test.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0f09788a..0ad2b8bc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -46,7 +46,7 @@ jobs: run: | # Mapping of original redis versions to client test containers declare -A redis_clients_version_mapping=( - ["8.10"]="unstable-29454887387-debian" + ["8.10"]="8.10-rc2" ["8.8"]="8.8.0" ["8.6"]="8.6.1" ["8.4"]="8.4.0" diff --git a/CHANGELOG.md b/CHANGELOG.md index d89cf158..04db4139 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Added support for `EXCLUDEEMPTY` argument for `TS.MRANGE` and `TS.MREVRANGE` commands - Added explicit testing for FT.SEARCH timeout policies - Added support for `TS.QUERYLABELS` command +- Added support for `LMOVEM` and `BLMOVEM` commands - Added support for `FT.ALIASLIST` command ### Fixed diff --git a/src/ClientContextInterface.php b/src/ClientContextInterface.php index 7fb82efe..66dce61c 100644 --- a/src/ClientContextInterface.php +++ b/src/ClientContextInterface.php @@ -237,6 +237,7 @@ use Predis\Command\Redis\VADD; * @method $this jsontoggle(string $key, string $path) * @method $this jsontype(string $key, string $path = '$') * @method $this blmove(string $source, string $destination, string $where, string $to, int $timeout) + * @method $this blmovem(string $source, string $destination, string $from, string $to, int|float $timeout, ?string $quantifier = null, ?int $count = null, ?string $ordering = null) * @method $this blpop(array|string $keys, $timeout) * @method $this brpop(array|string $keys, $timeout) * @method $this brpoplpush($source, $destination, $timeout) @@ -245,6 +246,7 @@ use Predis\Command\Redis\VADD; * @method $this linsert($key, $whence, $pivot, $value) * @method $this llen($key) * @method $this lmove(string $source, string $destination, string $where, string $to) + * @method $this lmovem(string $source, string $destination, string $from, string $to, ?string $quantifier = null, ?int $count = null, ?string $ordering = null) * @method $this lmpop(array $keys, string $modifier = 'left', int $count = 1) * @method $this lpop($key) * @method $this lpush($key, array $values) diff --git a/src/ClientInterface.php b/src/ClientInterface.php index f8cfb4a5..30b48883 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -248,6 +248,7 @@ use Predis\Response\Status; * @method array jsontoggle(string $key, string $path) * @method array jsontype(string $key, string $path = '$') * @method string blmove(string $source, string $destination, string $where, string $to, int $timeout) + * @method array|null blmovem(string $source, string $destination, string $from, string $to, int|float $timeout, ?string $quantifier = null, ?int $count = null, ?string $ordering = null) * @method array|null blpop(array|string $keys, int|float $timeout) * @method array|null brpop(array|string $keys, int|float $timeout) * @method string|null brpoplpush(string $source, string $destination, int|float $timeout) @@ -256,6 +257,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 lmovem(string $source, string $destination, string $from, string $to, ?string $quantifier = null, ?int $count = null, ?string $ordering = null) * @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) diff --git a/src/Cluster/ClusterStrategy.php b/src/Cluster/ClusterStrategy.php index e58599c1..a7234d67 100644 --- a/src/Cluster/ClusterStrategy.php +++ b/src/Cluster/ClusterStrategy.php @@ -86,9 +86,11 @@ abstract class ClusterStrategy implements StrategyInterface 'LINSERT' => $getKeyFromFirstArgument, 'LINDEX' => $getKeyFromFirstArgument, 'LLEN' => $getKeyFromFirstArgument, + 'LMOVEM' => [$this, 'getKeyFromFirstTwoKeys'], 'LPOP' => $getKeyFromFirstArgument, 'RPOP' => $getKeyFromFirstArgument, 'RPOPLPUSH' => $getKeyFromAllArguments, + 'BLMOVEM' => [$this, 'getKeyFromFirstTwoKeys'], 'BLPOP' => [$this, 'getKeyFromBlockingListCommands'], 'BRPOP' => [$this, 'getKeyFromBlockingListCommands'], 'BRPOPLPUSH' => [$this, 'getKeyFromBlockingListCommands'], @@ -360,6 +362,29 @@ abstract class ClusterStrategy implements StrategyInterface return $firstKey; } + /** + * Extracts the key from commands where the first two arguments are keys, + * followed by non-key arguments (e.g. LMOVEM). + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromFirstTwoKeys(CommandInterface $command) + { + $arguments = $command->getArguments(); + + if (!isset($arguments[1])) { + return $arguments[0] ?? null; + } + + if (!$this->checkSameSlotForKeys(array_slice($arguments, 0, 2))) { + return null; + } + + return $arguments[0]; + } + /** * Extracts the key from BLPOP and BRPOP commands. * diff --git a/src/Command/Redis/BLMOVEM.php b/src/Command/Redis/BLMOVEM.php new file mode 100644 index 00000000..8c30b0a4 --- /dev/null +++ b/src/Command/Redis/BLMOVEM.php @@ -0,0 +1,38 @@ +getQuantifierOffset(); + + if (count($arguments) <= $offset) { + parent::setArguments($arguments); + + return; + } + + $processed = array_slice($arguments, 0, $offset); + + $quantifier = strtoupper($arguments[$offset]); + + if (!in_array($quantifier, [self::COUNT, self::EXACTLY], true)) { + throw new UnexpectedValueException('Quantifier argument accepts only: COUNT, EXACTLY values'); + } + + if (!isset($arguments[$offset + 1])) { + throw new UnexpectedValueException("{$quantifier} quantifier requires a count argument"); + } + + if (!isset($arguments[$offset + 2])) { + throw new UnexpectedValueException("{$quantifier} quantifier requires an ordering argument"); + } + + $ordering = strtoupper($arguments[$offset + 2]); + + if (!in_array($ordering, [self::OBO, self::BULK], true)) { + throw new UnexpectedValueException('Ordering argument accepts only: OBO, BULK values'); + } + + array_push($processed, $quantifier, $arguments[$offset + 1], $ordering); + + parent::setArguments($processed); + } + + public function prefixKeys($prefix) + { + if ($arguments = $this->getArguments()) { + $arguments[0] = $prefix . $arguments[0]; + + if (isset($arguments[1])) { + $arguments[1] = $prefix . $arguments[1]; + } + + $this->setRawArguments($arguments); + } + } +} diff --git a/tests/Predis/Cluster/PredisStrategyTest.php b/tests/Predis/Cluster/PredisStrategyTest.php index 8cabe1c0..db7a2af8 100644 --- a/tests/Predis/Cluster/PredisStrategyTest.php +++ b/tests/Predis/Cluster/PredisStrategyTest.php @@ -162,6 +162,36 @@ class PredisStrategyTest extends PredisTestCase } } + /** + * @group disconnected + */ + public function testKeysForFirstTwoKeysCommands(): void + { + $strategy = $this->getClusterStrategy(); + $commands = $this->getCommandFactory(); + $arguments = ['{key}:source', '{key}:destination', 'LEFT', 'RIGHT']; + + foreach ($this->getExpectedCommands('keys-first-two') as $commandID) { + $command = $commands->create($commandID, $arguments); + $this->assertNotNull($strategy->getSlot($command), $commandID); + } + } + + /** + * @group disconnected + */ + public function testReturnsNullOnFirstTwoKeysCommandsWithDifferentSlots(): void + { + $strategy = $this->getClusterStrategy(); + $commands = $this->getCommandFactory(); + $arguments = ['key:source', 'key:destination', 'LEFT', 'RIGHT']; + + foreach ($this->getExpectedCommands('keys-first-two') as $commandID) { + $command = $commands->create($commandID, $arguments); + $this->assertNull($strategy->getSlot($command), $commandID); + } + } + /** * @group disconnected */ @@ -413,9 +443,11 @@ class PredisStrategyTest extends PredisTestCase 'LINSERT' => 'keys-first', 'LINDEX' => 'keys-first', 'LLEN' => 'keys-first', + 'LMOVEM' => 'keys-first-two', 'LPOP' => 'keys-first', 'RPOP' => 'keys-first', 'RPOPLPUSH' => 'keys-all', + 'BLMOVEM' => 'keys-first-two', 'BLPOP' => 'keys-blockinglist', 'BRPOP' => 'keys-blockinglist', 'BRPOPLPUSH' => 'keys-blockinglist', diff --git a/tests/Predis/Cluster/RedisStrategyTest.php b/tests/Predis/Cluster/RedisStrategyTest.php index 7b323361..13013bd2 100644 --- a/tests/Predis/Cluster/RedisStrategyTest.php +++ b/tests/Predis/Cluster/RedisStrategyTest.php @@ -205,6 +205,36 @@ class RedisStrategyTest extends PredisTestCase } } + /** + * @group disconnected + */ + public function testKeysForFirstTwoKeysCommands(): void + { + $strategy = $this->getClusterStrategy(); + $commands = $this->getCommandFactory(); + $arguments = ['{key}:source', '{key}:destination', 'LEFT', 'RIGHT']; + + foreach ($this->getExpectedCommands('keys-first-two') as $commandID) { + $command = $commands->create($commandID, $arguments); + $this->assertNotNull($strategy->getSlot($command), $commandID); + } + } + + /** + * @group disconnected + */ + public function testReturnsNullOnFirstTwoKeysCommandsWithDifferentSlots(): void + { + $strategy = $this->getClusterStrategy(); + $commands = $this->getCommandFactory(); + $arguments = ['key:source', 'key:destination', 'LEFT', 'RIGHT']; + + foreach ($this->getExpectedCommands('keys-first-two') as $commandID) { + $command = $commands->create($commandID, $arguments); + $this->assertNull($strategy->getSlot($command), $commandID); + } + } + /** * @group disconnected */ @@ -436,9 +466,11 @@ class RedisStrategyTest extends PredisTestCase 'LINSERT' => 'keys-first', 'LINDEX' => 'keys-first', 'LLEN' => 'keys-first', + 'LMOVEM' => 'keys-first-two', 'LPOP' => 'keys-first', 'RPOP' => 'keys-first', 'RPOPLPUSH' => 'keys-all', + 'BLMOVEM' => 'keys-first-two', 'BLPOP' => 'keys-blockinglist', 'BRPOP' => 'keys-blockinglist', 'BRPOPLPUSH' => 'keys-blockinglist', diff --git a/tests/Predis/Command/Redis/BLMOVEM_Test.php b/tests/Predis/Command/Redis/BLMOVEM_Test.php new file mode 100644 index 00000000..023892d0 --- /dev/null +++ b/tests/Predis/Command/Redis/BLMOVEM_Test.php @@ -0,0 +1,271 @@ +getCommand(); + $command->setArguments($actualArguments); + + $this->assertSameValues($expectedArguments, $command->getArguments()); + } + + /** + * @group disconnected + * @dataProvider invalidArgumentsProvider + */ + public function testSetArgumentsThrowsExceptionOnInvalidArguments( + array $arguments, + string $expectedExceptionMessage + ): void { + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + + $this->getCommand()->setArguments($arguments); + } + + /** + * @group disconnected + */ + public function testPrefixKeys(): void + { + /** @var PrefixableCommand $command */ + $command = $this->getCommand(); + $actualArguments = ['source', 'destination', 'LEFT', 'RIGHT', 0.1, 'COUNT', 3, 'OBO']; + $prefix = 'prefix:'; + $expectedArguments = ['prefix:source', 'prefix:destination', 'LEFT', 'RIGHT', 0.1, 'COUNT', 3, 'OBO']; + + $command->setArguments($actualArguments); + $command->prefixKeys($prefix); + + $this->assertSame($expectedArguments, $command->getArguments()); + } + + /** + * @group connected + * @dataProvider listsProvider + * @param array $sourceList + * @param array $destinationList + * @param array $commandArguments + * @param array|null $expectedResponse + * @param array $expectedSourceList + * @param array $expectedDestinationList + * @return void + * @requiresRedisVersion >= 8.9.0 + */ + public function testMovesElementsBetweenLists( + array $sourceList, + array $destinationList, + array $commandArguments, + ?array $expectedResponse, + array $expectedSourceList, + array $expectedDestinationList + ): void { + $redis = $this->getClient(); + + if ($sourceList) { + $redis->rpush('source', $sourceList); + } + + if ($destinationList) { + $redis->rpush('destination', $destinationList); + } + + $this->assertSame($expectedResponse, $redis->blmovem('source', 'destination', ...$commandArguments)); + $this->assertSame($expectedSourceList, $redis->lrange('source', 0, -1)); + $this->assertSame($expectedDestinationList, $redis->lrange('destination', 0, -1)); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 8.9.0 + */ + public function testMovesElementsBetweenListsResp3(): void + { + $redis = $this->getResp3Client(); + + $redis->rpush('source', ['1', '2', '3', '4', '5']); + $redis->rpush('destination', ['6', '7', '8', '9', '10']); + + $this->assertSame( + ['3', '2', '1'], + $redis->blmovem('source', 'destination', 'LEFT', 'LEFT', 0.1, 'COUNT', 3, 'OBO') + ); + $this->assertSame(['4', '5'], $redis->lrange('source', 0, -1)); + $this->assertSame( + ['3', '2', '1', '6', '7', '8', '9', '10'], + $redis->lrange('destination', 0, -1) + ); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 8.9.0 + */ + public function testThrowsExceptionOnWrongType(): void + { + $redis = $this->getClient(); + + $this->expectException(ServerException::class); + $this->expectExceptionMessage('Operation against a key holding the wrong kind of value'); + + $redis->set('source', 'foo'); + $redis->blmovem('source', 'destination', 'LEFT', 'RIGHT', 0.1); + } + + public function argumentsProvider(): array + { + return [ + 'with required arguments only' => [ + ['source', 'destination', 'LEFT', 'RIGHT', 0.1], + ['source', 'destination', 'LEFT', 'RIGHT', 0.1], + ], + 'with COUNT quantifier' => [ + ['source', 'destination', 'LEFT', 'LEFT', 0, 'COUNT', 3, 'OBO'], + ['source', 'destination', 'LEFT', 'LEFT', 0, 'COUNT', 3, 'OBO'], + ], + 'with EXACTLY quantifier' => [ + ['source', 'destination', 'RIGHT', 'RIGHT', 1.5, 'EXACTLY', 2, 'BULK'], + ['source', 'destination', 'RIGHT', 'RIGHT', 1.5, 'EXACTLY', 2, 'BULK'], + ], + 'with lowercase quantifier and ordering' => [ + ['source', 'destination', 'LEFT', 'RIGHT', 0.1, 'count', 3, 'bulk'], + ['source', 'destination', 'LEFT', 'RIGHT', 0.1, 'COUNT', 3, 'BULK'], + ], + ]; + } + + public function invalidArgumentsProvider(): array + { + return [ + 'with invalid quantifier' => [ + ['source', 'destination', 'LEFT', 'RIGHT', 0.1, 'WRONG', 3, 'OBO'], + 'Quantifier argument accepts only: COUNT, EXACTLY values', + ], + 'with missing count' => [ + ['source', 'destination', 'LEFT', 'RIGHT', 0.1, 'COUNT'], + 'COUNT quantifier requires a count argument', + ], + 'with missing ordering' => [ + ['source', 'destination', 'LEFT', 'RIGHT', 0.1, 'EXACTLY', 2], + 'EXACTLY quantifier requires an ordering argument', + ], + 'with invalid ordering' => [ + ['source', 'destination', 'LEFT', 'RIGHT', 0.1, 'COUNT', 3, 'WRONG'], + 'Ordering argument accepts only: OBO, BULK values', + ], + ]; + } + + public function listsProvider(): array + { + return [ + 'moves single element without quantifier' => [ + ['1', '2', '3', '4', '5'], + [], + ['LEFT', 'RIGHT', 0.1], + ['1'], + ['2', '3', '4', '5'], + ['1'], + ], + 'with COUNT and OBO - reversed block order' => [ + ['1', '2', '3', '4', '5'], + ['6', '7', '8', '9', '10'], + ['LEFT', 'LEFT', 0.1, 'COUNT', 3, 'OBO'], + ['3', '2', '1'], + ['4', '5'], + ['3', '2', '1', '6', '7', '8', '9', '10'], + ], + 'with COUNT and BULK - preserved relative order' => [ + ['1', '2', '3', '4', '5'], + ['6', '7', '8', '9', '10'], + ['LEFT', 'LEFT', 0.1, 'COUNT', 3, 'BULK'], + ['1', '2', '3'], + ['4', '5'], + ['1', '2', '3', '6', '7', '8', '9', '10'], + ], + 'with mixed directions - RIGHT to RIGHT' => [ + ['1', '2', '3', '4', '5'], + ['6', '7', '8', '9', '10'], + ['RIGHT', 'RIGHT', 0.1, 'COUNT', 3, 'BULK'], + ['3', '4', '5'], + ['1', '2'], + ['6', '7', '8', '9', '10', '3', '4', '5'], + ], + 'with COUNT greater than source length - moves fewer immediately' => [ + ['1', '2'], + [], + ['LEFT', 'RIGHT', 0.1, 'COUNT', 5, 'BULK'], + ['1', '2'], + [], + ['1', '2'], + ], + 'with EXACTLY and enough elements' => [ + ['john', 'doe'], + [], + ['LEFT', 'RIGHT', 0.1, 'EXACTLY', 2, 'BULK'], + ['john', 'doe'], + [], + ['john', 'doe'], + ], + 'with EXACTLY and too few elements - times out moving nothing' => [ + ['john'], + [], + ['LEFT', 'RIGHT', 0.1, 'EXACTLY', 2, 'BULK'], + null, + ['john'], + [], + ], + 'with empty source - times out moving nothing' => [ + [], + ['6', '7'], + ['LEFT', 'RIGHT', 0.1, 'COUNT', 2, 'BULK'], + null, + [], + ['6', '7'], + ], + ]; + } +} diff --git a/tests/Predis/Command/Redis/LMOVEM_Test.php b/tests/Predis/Command/Redis/LMOVEM_Test.php new file mode 100644 index 00000000..fc8a5dab --- /dev/null +++ b/tests/Predis/Command/Redis/LMOVEM_Test.php @@ -0,0 +1,271 @@ +getCommand(); + $command->setArguments($actualArguments); + + $this->assertSameValues($expectedArguments, $command->getArguments()); + } + + /** + * @group disconnected + * @dataProvider invalidArgumentsProvider + */ + public function testSetArgumentsThrowsExceptionOnInvalidArguments( + array $arguments, + string $expectedExceptionMessage + ): void { + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + + $this->getCommand()->setArguments($arguments); + } + + /** + * @group disconnected + */ + public function testPrefixKeys(): void + { + /** @var PrefixableCommand $command */ + $command = $this->getCommand(); + $actualArguments = ['source', 'destination', 'LEFT', 'RIGHT', 'COUNT', 3, 'OBO']; + $prefix = 'prefix:'; + $expectedArguments = ['prefix:source', 'prefix:destination', 'LEFT', 'RIGHT', 'COUNT', 3, 'OBO']; + + $command->setArguments($actualArguments); + $command->prefixKeys($prefix); + + $this->assertSame($expectedArguments, $command->getArguments()); + } + + /** + * @group connected + * @dataProvider listsProvider + * @param array $sourceList + * @param array $destinationList + * @param array $commandArguments + * @param array|null $expectedResponse + * @param array $expectedSourceList + * @param array $expectedDestinationList + * @return void + * @requiresRedisVersion >= 8.9.0 + */ + public function testMovesElementsBetweenLists( + array $sourceList, + array $destinationList, + array $commandArguments, + ?array $expectedResponse, + array $expectedSourceList, + array $expectedDestinationList + ): void { + $redis = $this->getClient(); + + if ($sourceList) { + $redis->rpush('source', $sourceList); + } + + if ($destinationList) { + $redis->rpush('destination', $destinationList); + } + + $this->assertSame($expectedResponse, $redis->lmovem('source', 'destination', ...$commandArguments)); + $this->assertSame($expectedSourceList, $redis->lrange('source', 0, -1)); + $this->assertSame($expectedDestinationList, $redis->lrange('destination', 0, -1)); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 8.9.0 + */ + public function testMovesElementsBetweenListsResp3(): void + { + $redis = $this->getResp3Client(); + + $redis->rpush('source', ['1', '2', '3', '4', '5']); + $redis->rpush('destination', ['6', '7', '8', '9', '10']); + + $this->assertSame( + ['3', '2', '1'], + $redis->lmovem('source', 'destination', 'LEFT', 'LEFT', 'COUNT', 3, 'OBO') + ); + $this->assertSame(['4', '5'], $redis->lrange('source', 0, -1)); + $this->assertSame( + ['3', '2', '1', '6', '7', '8', '9', '10'], + $redis->lrange('destination', 0, -1) + ); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 8.9.0 + */ + public function testThrowsExceptionOnWrongType(): void + { + $redis = $this->getClient(); + + $this->expectException(ServerException::class); + $this->expectExceptionMessage('Operation against a key holding the wrong kind of value'); + + $redis->set('source', 'foo'); + $redis->lmovem('source', 'destination', 'LEFT', 'RIGHT'); + } + + public function argumentsProvider(): array + { + return [ + 'with required arguments only' => [ + ['source', 'destination', 'LEFT', 'RIGHT'], + ['source', 'destination', 'LEFT', 'RIGHT'], + ], + 'with COUNT quantifier' => [ + ['source', 'destination', 'LEFT', 'LEFT', 'COUNT', 3, 'OBO'], + ['source', 'destination', 'LEFT', 'LEFT', 'COUNT', 3, 'OBO'], + ], + 'with EXACTLY quantifier' => [ + ['source', 'destination', 'RIGHT', 'RIGHT', 'EXACTLY', 2, 'BULK'], + ['source', 'destination', 'RIGHT', 'RIGHT', 'EXACTLY', 2, 'BULK'], + ], + 'with lowercase quantifier and ordering' => [ + ['source', 'destination', 'LEFT', 'RIGHT', 'count', 3, 'bulk'], + ['source', 'destination', 'LEFT', 'RIGHT', 'COUNT', 3, 'BULK'], + ], + ]; + } + + public function invalidArgumentsProvider(): array + { + return [ + 'with invalid quantifier' => [ + ['source', 'destination', 'LEFT', 'RIGHT', 'WRONG', 3, 'OBO'], + 'Quantifier argument accepts only: COUNT, EXACTLY values', + ], + 'with missing count' => [ + ['source', 'destination', 'LEFT', 'RIGHT', 'COUNT'], + 'COUNT quantifier requires a count argument', + ], + 'with missing ordering' => [ + ['source', 'destination', 'LEFT', 'RIGHT', 'EXACTLY', 2], + 'EXACTLY quantifier requires an ordering argument', + ], + 'with invalid ordering' => [ + ['source', 'destination', 'LEFT', 'RIGHT', 'COUNT', 3, 'WRONG'], + 'Ordering argument accepts only: OBO, BULK values', + ], + ]; + } + + public function listsProvider(): array + { + return [ + 'moves single element without quantifier' => [ + ['1', '2', '3', '4', '5'], + [], + ['LEFT', 'RIGHT'], + ['1'], + ['2', '3', '4', '5'], + ['1'], + ], + 'with COUNT and OBO - reversed block order' => [ + ['1', '2', '3', '4', '5'], + ['6', '7', '8', '9', '10'], + ['LEFT', 'LEFT', 'COUNT', 3, 'OBO'], + ['3', '2', '1'], + ['4', '5'], + ['3', '2', '1', '6', '7', '8', '9', '10'], + ], + 'with COUNT and BULK - preserved relative order' => [ + ['1', '2', '3', '4', '5'], + ['6', '7', '8', '9', '10'], + ['LEFT', 'LEFT', 'COUNT', 3, 'BULK'], + ['1', '2', '3'], + ['4', '5'], + ['1', '2', '3', '6', '7', '8', '9', '10'], + ], + 'with mixed directions - RIGHT to RIGHT' => [ + ['1', '2', '3', '4', '5'], + ['6', '7', '8', '9', '10'], + ['RIGHT', 'RIGHT', 'COUNT', 3, 'BULK'], + ['3', '4', '5'], + ['1', '2'], + ['6', '7', '8', '9', '10', '3', '4', '5'], + ], + 'with COUNT greater than source length - moves fewer' => [ + ['1', '2'], + [], + ['LEFT', 'RIGHT', 'COUNT', 5, 'BULK'], + ['1', '2'], + [], + ['1', '2'], + ], + 'with EXACTLY and enough elements' => [ + ['john', 'doe'], + [], + ['LEFT', 'RIGHT', 'EXACTLY', 2, 'BULK'], + ['john', 'doe'], + [], + ['john', 'doe'], + ], + 'with EXACTLY and too few elements - moves nothing' => [ + ['john'], + [], + ['LEFT', 'RIGHT', 'EXACTLY', 2, 'BULK'], + null, + ['john'], + [], + ], + 'with empty source - moves nothing' => [ + [], + ['6', '7'], + ['LEFT', 'RIGHT', 'COUNT', 2, 'BULK'], + null, + [], + ['6', '7'], + ], + ]; + } +} diff --git a/tests/Predis/Command/Redis/SDIFFCARD_Test.php b/tests/Predis/Command/Redis/SDIFFCARD_Test.php index 4cee3005..23556050 100644 --- a/tests/Predis/Command/Redis/SDIFFCARD_Test.php +++ b/tests/Predis/Command/Redis/SDIFFCARD_Test.php @@ -65,7 +65,7 @@ class SDIFFCARD_Test extends PredisCommandTestCase * @param int $limit * @param int $expectedCardinality * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testReturnsCorrectCardinalityOfGivenSetDifference( array $sets, @@ -85,7 +85,7 @@ class SDIFFCARD_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testReturnsCorrectCardinalityOfGivenSetDifferenceResp3(): void { @@ -103,7 +103,7 @@ class SDIFFCARD_Test extends PredisCommandTestCase * @param array $arguments * @param string $expectedExceptionMessage * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testThrowsExceptionOnUnexpectedValuesGiven( array $arguments, @@ -120,7 +120,7 @@ class SDIFFCARD_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testThrowsExceptionOnWrongType(): void { diff --git a/tests/Predis/Command/Redis/SUNIONCARD_Test.php b/tests/Predis/Command/Redis/SUNIONCARD_Test.php index a221902e..aba24ae1 100644 --- a/tests/Predis/Command/Redis/SUNIONCARD_Test.php +++ b/tests/Predis/Command/Redis/SUNIONCARD_Test.php @@ -66,7 +66,7 @@ class SUNIONCARD_Test extends PredisCommandTestCase * @param int $limit * @param int $expectedCardinality * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testReturnsCorrectCardinalityOfGivenSetUnion( array $sets, @@ -87,7 +87,7 @@ class SUNIONCARD_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testApproxReturnsSameResultAsExactMatchingOnSmallSets(): void { @@ -105,7 +105,7 @@ class SUNIONCARD_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testReturnsCorrectCardinalityOfGivenSetUnionResp3(): void { @@ -123,7 +123,7 @@ class SUNIONCARD_Test extends PredisCommandTestCase * @param array $arguments * @param string $expectedExceptionMessage * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testThrowsExceptionOnUnexpectedValuesGiven( array $arguments, @@ -140,7 +140,7 @@ class SUNIONCARD_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testThrowsExceptionOnWrongType(): void { diff --git a/tests/Predis/Command/Redis/Search/FTAGGREGATE_Test.php b/tests/Predis/Command/Redis/Search/FTAGGREGATE_Test.php index 84a45698..6536409b 100644 --- a/tests/Predis/Command/Redis/Search/FTAGGREGATE_Test.php +++ b/tests/Predis/Command/Redis/Search/FTAGGREGATE_Test.php @@ -179,7 +179,7 @@ class FTAGGREGATE_Test extends PredisCommandTestCase * @group connected * @group relay-resp3 * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testAggregatesWithCollectReducer(): void { @@ -223,7 +223,7 @@ class FTAGGREGATE_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testAggregatesWithCollectReducerResp3(): void { @@ -271,7 +271,7 @@ class FTAGGREGATE_Test extends PredisCommandTestCase * @group connected * @group relay-resp3 * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testCollectReducerProjectsAllFields(): void { diff --git a/tests/Predis/Command/Redis/Search/FTALIASLIST_Test.php b/tests/Predis/Command/Redis/Search/FTALIASLIST_Test.php index aaad54d2..f3fe3b52 100644 --- a/tests/Predis/Command/Redis/Search/FTALIASLIST_Test.php +++ b/tests/Predis/Command/Redis/Search/FTALIASLIST_Test.php @@ -66,7 +66,7 @@ class FTALIASLIST_Test extends PredisCommandTestCase * @group connected * @group relay-resp3 * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testReturnsAliasesAssociatedWithGivenIndex(): void { @@ -83,7 +83,7 @@ class FTALIASLIST_Test extends PredisCommandTestCase * @group connected * @group relay-resp3 * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testReturnsEmptyListForIndexWithoutAliases(): void { @@ -97,7 +97,7 @@ class FTALIASLIST_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testReturnsAliasesAssociatedWithGivenIndexResp3(): void { @@ -114,7 +114,7 @@ class FTALIASLIST_Test extends PredisCommandTestCase * @group connected * @group relay-resp3 * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testThrowsExceptionOnNonExistingIndex(): void { diff --git a/tests/Predis/Command/Redis/TimeSeries/TSMRANGE_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSMRANGE_Test.php index a8a57922..85f393f9 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSMRANGE_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSMRANGE_Test.php @@ -305,7 +305,7 @@ class TSMRANGE_Test extends PredisCommandTestCase * @group connected * @group relay-resp3 * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testQueryRangeExcludesEmptySeriesWithExcludeEmptyModifier(): void { @@ -348,7 +348,7 @@ class TSMRANGE_Test extends PredisCommandTestCase * @group connected * @group relay-resp3 * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testQueryRangeExcludesEmptySeriesWithExcludeEmptyAndAggregation(): void { @@ -380,7 +380,7 @@ class TSMRANGE_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testQueryRangeExcludesEmptySeriesWithExcludeEmptyModifierResp3(): void { diff --git a/tests/Predis/Command/Redis/TimeSeries/TSMREVRANGE_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSMREVRANGE_Test.php index e16c6d07..dfdbb167 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSMREVRANGE_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSMREVRANGE_Test.php @@ -305,7 +305,7 @@ class TSMREVRANGE_Test extends PredisCommandTestCase * @group connected * @group relay-resp3 * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testQueryRangeExcludesEmptySeriesWithExcludeEmptyModifier(): void { @@ -344,7 +344,7 @@ class TSMREVRANGE_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testQueryRangeExcludesEmptySeriesWithExcludeEmptyModifierResp3(): void { diff --git a/tests/Predis/Command/Redis/TimeSeries/TSQUERYLABELS_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSQUERYLABELS_Test.php index 24653b48..8bee91e6 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSQUERYLABELS_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSQUERYLABELS_Test.php @@ -53,7 +53,7 @@ class TSQUERYLABELS_Test extends PredisCommandTestCase * @group connected * @group relay-resp3 * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testQueryReturnsLabelNamesMatchingGivenFilterExpression(): void { @@ -79,7 +79,7 @@ class TSQUERYLABELS_Test extends PredisCommandTestCase * @group connected * @group relay-resp3 * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testQueryReturnsLabelValuesMatchingGivenFilterExpression(): void { @@ -104,7 +104,7 @@ class TSQUERYLABELS_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testQueryReturnsLabelNamesMatchingGivenFilterExpressionResp3(): void { @@ -129,7 +129,7 @@ class TSQUERYLABELS_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testQueryReturnsLabelValuesMatchingGivenFilterExpressionResp3(): void { diff --git a/tests/Predis/Command/Redis/TimeSeries/TSREAD_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSREAD_Test.php index 024d267e..0d599c1e 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSREAD_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSREAD_Test.php @@ -63,7 +63,7 @@ class TSREAD_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testReadsAllSamplesAtOnce(): void { @@ -83,7 +83,7 @@ class TSREAD_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testReadsSamplesUsingCursorPagination(): void { @@ -113,7 +113,7 @@ class TSREAD_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testReadsSamplesUsingSpecialTimestampCursors(): void { @@ -137,7 +137,7 @@ class TSREAD_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testReadsMissingKeyReturnsEmptyListWithoutError(): void { @@ -149,7 +149,7 @@ class TSREAD_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testBlocksForGivenTimeoutWhenNotEnoughSamplesAvailable(): void { @@ -172,7 +172,7 @@ class TSREAD_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testReturnsImmediatelyWhenMinCountSamplesAlreadyAvailable(): void { @@ -192,7 +192,7 @@ class TSREAD_Test extends PredisCommandTestCase /** * @group connected * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testReadsAllSamplesAtOnceResp3(): void { @@ -214,7 +214,7 @@ class TSREAD_Test extends PredisCommandTestCase * @param ReadArguments $arguments * @param string $expectedExceptionMessage * @return void - * @requiresRedisVersion >= 8.10.0 + * @requiresRedisVersion >= 8.9.0 */ public function testThrowsExceptionOnCountConstraintViolations( ReadArguments $arguments,