diff --git a/CHANGELOG.md b/CHANGELOG.md index 66f3acf0..255bfbd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added - Add experimental support for vector sets commands (#1550) - Added support for `XACK` command (#1555) +- Added support for `XCLAIM` command (#1557) ### Changed - Handle and retry `LOADING` errors from Sentinel replicas (#1536) diff --git a/src/ClientContextInterface.php b/src/ClientContextInterface.php index 7102d8d7..cf1a95b5 100644 --- a/src/ClientContextInterface.php +++ b/src/ClientContextInterface.php @@ -285,6 +285,7 @@ use Predis\Command\Redis\VADD; * @method $this tsrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null) * @method $this tsrevrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null) * @method $this xack(string $key, string $group, string ...$id) + * @method $this xclaim(string $key, string $group, string $consumer, int $minIdleTime, string|array $ids, ?int $idle = null, ?int $time = null, ?int $retryCount = null, bool $force = false, bool $justId = false, ?string $lastId = null) * @method $this zadd($key, array $membersAndScoresDictionary) * @method $this zcard($key) * @method $this zcount($key, $min, $max) diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 21c32b8e..c4aa83d1 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -298,6 +298,7 @@ use Predis\Response\Status; * @method int xack(string $key, string $group, string ...$id) * @method string xadd(string $key, array $dictionary, string $id = '*', array $options = null) * @method array xautoclaim(string $key, string $group, string $consumer, int $minIdleTime, string $start, ?int $count = null, bool $justId = false) + * @method array xclaim(string $key, string $group, string $consumer, int $minIdleTime, string|array $ids, ?int $idle = null, ?int $time = null, ?int $retryCount = null, bool $force = false, bool $justId = false, ?string $lastId = null) * @method int xdel(string $key, string ...$id) * @method int xlen(string $key) * @method array xrevrange(string $key, string $end, string $start, ?int $count = null) diff --git a/src/Command/Redis/XCLAIM.php b/src/Command/Redis/XCLAIM.php new file mode 100644 index 00000000..d31b9005 --- /dev/null +++ b/src/Command/Redis/XCLAIM.php @@ -0,0 +1,89 @@ +parseResponse($data); + } + + public function prefixKeys($prefix) + { + $this->applyPrefixForFirstArgument($prefix); + } +} diff --git a/tests/Predis/Command/Redis/XCLAIM_Test.php b/tests/Predis/Command/Redis/XCLAIM_Test.php new file mode 100644 index 00000000..62900e37 --- /dev/null +++ b/tests/Predis/Command/Redis/XCLAIM_Test.php @@ -0,0 +1,179 @@ +getCommand(); + $command->setArguments($actualArguments); + + $this->assertSame($expectedArguments, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse(): void + { + $command = $this->getCommand(); + + $raw = [['1-1', ['key1', 'val1']], ['2-1', ['key2', 'val2']]]; + $expected = ['1-1' => ['key1' => 'val1'], '2-1' => ['key2' => 'val2']]; + $this->assertSame($expected, $command->parseResponse($raw)); + $this->assertSame($expected, $command->parseResp3Response($raw)); + + // JUSTID format + $raw = ['1-1', '2-1']; + $expected = ['1-1', '2-1']; + $this->assertSame($expected, $command->parseResponse($raw)); + $this->assertSame($expected, $command->parseResp3Response($raw)); + } + + /** + * @group disconnected + */ + public function testPrefixKeys(): void + { + $arguments = ['stream', 'group', 'consumer', 0, 'id1']; + $expected = ['prefix:stream', 'group', 'consumer', 0, 'id1']; + + $command = $this->getCommandWithArgumentsArray($arguments); + $command->prefixKeys('prefix:'); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testClaim(): void + { + $redis = $this->getClient(); + $this->testClaimWithClient($redis); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testClaimResp3(): void + { + $redis = $this->getResp3Client(); + $this->testClaimWithClient($redis); + } + + private function testClaimWithClient(ClientInterface $redis): void + { + $redis->xadd('stream', ['key0' => 'val0'], '0-1'); + $redis->xadd('stream', ['key1' => 'val1'], '1-1'); + $redis->xadd('stream', ['key2' => 'val2'], '2-1'); + $redis->xadd('stream', ['key3' => 'val3'], '3-1'); + + $redis->xgroup->create('stream', 'group', '0'); + + $redis->xreadgroup('group', 'consumer1', 4, null, false, 'stream', '>'); + + // Claim one + $claimed = $redis->xclaim('stream', 'group', 'consumer2', 0, '0-1'); + $this->assertSame(['0-1' => ['key0' => 'val0']], $claimed); + + // Claim many + $claimed = $redis->xclaim('stream', 'group', 'consumer2', 0, ['1-1', '2-1']); + $this->assertSame(['1-1' => ['key1' => 'val1'], '2-1' => ['key2' => 'val2']], $claimed); + + // Claim deleted + $redis->xdel('stream', '1-1'); + $claimed = $redis->xclaim('stream', 'group', 'consumer3', 0, ['0-1', '1-1', '2-1'], null, null, null, false, true); + $this->assertSame(['0-1', '2-1'], $claimed); + + // Claim with all options + $redis->xdel('stream', '1-1'); + $claimed = $redis->xclaim('stream', 'group', 'consumer3', 0, '3-1', 10, 100, 5, true, true, '3-1'); + $this->assertSame(['3-1'], $claimed); + + // Claim unknown + $claimed = $redis->xclaim('stream', 'group', 'consumer3', 0, ['4-1']); + $this->assertSame([], $claimed); + } + + public function argumentsProvider(): array + { + return [ + 'with default arguments' => [ + ['stream', 'group', 'consumer', 0, 'id1'], + ['stream', 'group', 'consumer', 0, 'id1'], + ], + 'with array ids' => [ + ['stream', 'group', 'consumer', 0, ['id1', 'id2']], + ['stream', 'group', 'consumer', 0, 'id1', 'id2'], + ], + 'with IDLE modifier' => [ + ['stream', 'group', 'consumer', 0, ['id1', 'id2'], 10], + ['stream', 'group', 'consumer', 0, 'id1', 'id2', 'IDLE', 10], + ], + 'with TIME modifier' => [ + ['stream', 'group', 'consumer', 0, ['id1', 'id2'], null, 12345], + ['stream', 'group', 'consumer', 0, 'id1', 'id2', 'TIME', 12345], + ], + 'with RETRYCOUNT modifier' => [ + ['stream', 'group', 'consumer', 0, ['id1', 'id2'], null, null, 5], + ['stream', 'group', 'consumer', 0, 'id1', 'id2', 'RETRYCOUNT', 5], + ], + 'with FORCE modifier' => [ + ['stream', 'group', 'consumer', 0, ['id1', 'id2'], null, null, null, true], + ['stream', 'group', 'consumer', 0, 'id1', 'id2', 'FORCE'], + ], + 'with JUSTID modifier' => [ + ['stream', 'group', 'consumer', 0, ['id1', 'id2'], null, null, null, false, true], + ['stream', 'group', 'consumer', 0, 'id1', 'id2', 'JUSTID'], + ], + 'with LASTID modifier' => [ + ['stream', 'group', 'consumer', 0, ['id1', 'id2'], null, null, null, false, false, '1-1'], + ['stream', 'group', 'consumer', 0, 'id1', 'id2', 'LASTID', '1-1'], + ], + 'with all arguments' => [ + ['stream', 'group', 'consumer', 100, ['id1', 'id2'], 10, 12345, 5, true, true, '1-1'], + ['stream', 'group', 'consumer', 100, 'id1', 'id2', 'IDLE', 10, 'TIME', 12345, 'RETRYCOUNT', 5, 'FORCE', 'JUSTID', 'LASTID', '1-1'], + ], + ]; + } +}