diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 38d0e868..e4bc0d96 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -116,6 +116,12 @@ use Predis\Response\Status; * @method string[] sunion(array|string $keys) * @method int sunionstore(string $destination, array|string $keys) * @method int touch(string[]|string $keyOrKeys, string ...$keys = null) + * @method string xadd(string $key, array $dictionary, string $id = '*', array $options = 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) + * @method array xrange(string $key, string $start, string $end, ?int $count = null) + * @method string xtrim(string $key, array|string $strategy, string $threshold, array $options = null) * @method int zadd(string $key, array $membersAndScoresDictionary) * @method int zcard(string $key) * @method string zcount(string $key, int|string $min, int|string $max) diff --git a/src/Command/Redis/XADD.php b/src/Command/Redis/XADD.php new file mode 100644 index 00000000..b00b5005 --- /dev/null +++ b/src/Command/Redis/XADD.php @@ -0,0 +1,65 @@ + + * + * 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\Command\Command as RedisCommand; + +/** + * @link http://redis.io/commands/xadd + * + * @author Daniele Alessandri + */ +class XADD extends RedisCommand +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'XADD'; + } + + /** + * {@inheritdoc} + */ + public function setArguments(array $arguments) + { + $args = array(); + + $args[] = $arguments[0]; + $options = $arguments[3] ?? []; + + if (isset($options['nomkstream']) && $options['nomkstream']) { + $args[] = 'NOMKSTREAM'; + } + + if (isset($options['trim']) && is_array($options['trim'])) { + array_push($args, ...$options['trim']); + + if (isset($options['limit'])) { + $args[] = 'LIMIT'; + $args[] = $options['limit']; + } + } + + // ID, default to * to let Redis set it + $args[] = $arguments[2] ?? '*'; + if (isset($arguments[1]) && is_array($arguments[1])) { + foreach ($arguments[1] as $key => $val) { + $args[] = $key; + $args[] = $val; + } + } + + parent::setArguments($args); + } +} diff --git a/src/Command/Redis/XDEL.php b/src/Command/Redis/XDEL.php new file mode 100644 index 00000000..f182a041 --- /dev/null +++ b/src/Command/Redis/XDEL.php @@ -0,0 +1,40 @@ + + * + * 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\Command\Command as RedisCommand; + +/** + * @link http://redis.io/commands/xdel + * + * @author Daniele Alessandri + */ +class XDEL extends RedisCommand +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'XDEL'; + } + + /** + * {@inheritdoc} + */ + public function setArguments(array $arguments) + { + $arguments = self::normalizeVariadic($arguments); + + parent::setArguments($arguments); + } +} diff --git a/src/Command/Redis/XLEN.php b/src/Command/Redis/XLEN.php new file mode 100644 index 00000000..d0670cc0 --- /dev/null +++ b/src/Command/Redis/XLEN.php @@ -0,0 +1,30 @@ + + * + * 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\Command\Command as RedisCommand; + +/** + * @link http://redis.io/commands/xlen + * + * @author Daniele Alessandri + */ +class XLEN extends RedisCommand +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'XLEN'; + } +} diff --git a/src/Command/Redis/XRANGE.php b/src/Command/Redis/XRANGE.php new file mode 100644 index 00000000..39296184 --- /dev/null +++ b/src/Command/Redis/XRANGE.php @@ -0,0 +1,63 @@ + + * + * 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\Command\Command as RedisCommand; + +/** + * @link http://redis.io/commands/xrange + * + * @author Daniele Alessandri + */ +class XRANGE extends RedisCommand +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'XRANGE'; + } + + /** + * {@inheritdoc} + */ + public function setArguments(array $arguments) + { + if (count($arguments) === 4) { + $arguments[] = $arguments[3]; + $arguments[3] = 'COUNT'; + } + + parent::setArguments($arguments); + } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + $result = array(); + foreach ($data as $entry) { + $processed = array(); + $count = count($entry[1]); + + for ($i = 0; $i < $count; ++$i) { + $processed[$entry[1][$i]] = $entry[1][++$i]; + } + + $result[$entry[0]] = $processed; + } + + return $result; + } +} diff --git a/src/Command/Redis/XREVRANGE.php b/src/Command/Redis/XREVRANGE.php new file mode 100644 index 00000000..37ac9310 --- /dev/null +++ b/src/Command/Redis/XREVRANGE.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Redis; + +/** + * @link http://redis.io/commands/xrevrange + * + * @author Daniele Alessandri + */ +class XREVRANGE extends XRANGE +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'XREVRANGE'; + } +} diff --git a/src/Command/Redis/XTRIM.php b/src/Command/Redis/XTRIM.php new file mode 100644 index 00000000..e181ba3c --- /dev/null +++ b/src/Command/Redis/XTRIM.php @@ -0,0 +1,55 @@ + + * + * 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\Command\Command as RedisCommand; + +/** + * @link http://redis.io/commands/xtrim + * + * @author Daniele Alessandri + */ +class XTRIM extends RedisCommand +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'XTRIM'; + } + + /** + * {@inheritdoc} + */ + public function setArguments(array $arguments) + { + $args = []; + $options = $arguments[3] ?? []; + + $args[] = $arguments[0]; + // Either e.g. 'MAXLEN' or ['MAXLEN', '~'] + if (is_array($arguments[1])) { + array_push($args, ...$arguments[1]); + } else { + $args[] = $arguments[1]; + } + + $args[] = $arguments[2]; + if (isset($options['limit'])) { + $args[] = 'LIMIT'; + $args[] = $options['limit']; + } + + parent::setArguments($args); + } +} diff --git a/tests/Predis/Command/Redis/XADD_Test.php b/tests/Predis/Command/Redis/XADD_Test.php new file mode 100644 index 00000000..f1620472 --- /dev/null +++ b/tests/Predis/Command/Redis/XADD_Test.php @@ -0,0 +1,290 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Redis; + +/** + * @group commands + * @group realm-stream + */ +class XADD_Test extends PredisCommandTestCase +{ + /** + * @group disconnected + * @dataProvider dataFilterArguments + */ + public function testFilterArguments(array $arguments, array $expected): void + { + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + public function dataFilterArguments(): array + { + $data = []; + $data[] = [ + [ + 'stream', + ['key' => 'val'], + '*', + ['trim' => ['MINID', '~', '0-1'], 'limit' => 5, 'nomkstream' => true] + ], + ['stream', 'NOMKSTREAM', 'MINID', '~', '0-1', 'LIMIT', 5, '*', 'key', 'val'], + ]; + + $data[] = [ + [ + 'stream', + ['key1' => 'val1', 'key2' => 'val2'], + '*', + ['trim' => ['MINID', '~', '0-1'], 'limit' => 5, 'nomkstream' => true] + ], + ['stream', 'NOMKSTREAM', 'MINID', '~', '0-1', 'LIMIT', 5, '*', 'key1', 'val1', 'key2', 'val2'], + ]; + + $data[] = [ + [ + 'stream', + ['key' => 'val'], + '*', + ['trim' => ['MINID', '~', '0-1'], 'limit' => 5] + ], + ['stream', 'MINID', '~', '0-1', 'LIMIT', 5, '*', 'key', 'val'], + ]; + + $data[] = [ + [ + 'stream', + ['key' => 'val'], + '*', + ['trim' => ['MINID', '~', '0-1']] + ], + ['stream', 'MINID', '~', '0-1', '*', 'key', 'val'], + ]; + + $data[] = [ + [ + 'stream', + ['key' => 'val'], + '*', + ['trim' => ['MINID', '0-1']] + ], + ['stream', 'MINID', '0-1', '*', 'key', 'val'], + ]; + + $data[] = [ + ['stream', ['key' => 'val'], '2-3'], + ['stream', '2-3', 'key', 'val'], + ]; + + $data[] = [ + ['stream', ['key' => 'val']], + ['stream', '*', 'key', 'val'], + ]; + + return $data; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedCommand(): string + { + return 'Predis\Command\Redis\XADD'; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedId(): string + { + return 'XADD'; + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testAddsToStreamWithDefaults(): void + { + $redis = $this->getClient(); + + $redis->xadd('stream', ['key' => 'val']); + + $this->assertSame(1, $redis->xlen('stream')); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testAddsToStreamWithSpecificId(): void + { + $redis = $this->getClient(); + $id = time() . '-123'; + + $redis->xadd('stream', ['key' => 'val'], $id); + + $response = $redis->xrange('stream', $id, $id); + $this->assertCount(1, $response); + $this->assertNotNull($response[$id]); + } + + /** + * @group connected + * @requiresRedisVersion >= 6.2.0 + */ + public function testNomkstreamWhenStreamDoesNotExist(): void + { + $redis = $this->getClient(); + + $redis->xadd('new-stream', ['key' => 'val'], '*', ['nomkstream' => true]); + + $this->assertSame(0, $redis->exists('new-stream')); + } + + /** + * @group connected + * @requiresRedisVersion >= 6.2.0 + */ + public function testNomkstreamWhenStreamExists(): void + { + $redis = $this->getClient(); + + $redis->xadd('stream', ['key' => 'val']); + + $redis->xadd('stream', ['key' => 'val'], '*', ['nomkstream' => true]); + + $this->assertSame(1, $redis->exists('stream')); + $this->assertSame(2, $redis->xlen('stream')); + } + + /** + * @group connected + * @requiresRedisVersion >= 6.2.0 + */ + public function testTrimOnMinidExact(): void + { + $redis = $this->getClient(); + + $redis->xadd('stream', ['key' => 'val']); + $id = $redis->xadd('stream', ['key' => 'val']); + + $redis->xadd('stream', ['key' => 'val'], '*', ['trim' => ['MINID', $id]]); + + $this->assertSame(2, $redis->xlen('stream')); + } + + /** + * @group connected + * @requiresRedisVersion >= 6.2.0 + */ + public function testTrimOnMinidInexact(): void + { + $redis = $this->getClient(); + $config = $redis->config('get', 'stream-node-max-entries'); + $oldStreamNodeMaxEntries = (int) array_pop($config); + $redis->config('set', 'stream-node-max-entries', 2); + + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $id = $redis->xadd('stream', ['key' => 'val']); + + $redis->xadd('stream', ['key' => 'val'], '*', ['trim' => ['MINID', '~', $id]]); + + $this->assertSame(3, $redis->xlen('stream')); + $redis->config('set', 'stream-node-max-entries', $oldStreamNodeMaxEntries); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testTrimOnMaxlenExact(): void + { + $redis = $this->getClient(); + + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + + $redis->xadd('stream', ['key' => 'val'], '*', ['trim' => ['MAXLEN', 2]]); + + $this->assertSame(2, $redis->xlen('stream')); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testTrimOnMaxlenInexact(): void + { + $redis = $this->getClient(); + $config = $redis->config('get', 'stream-node-max-entries'); + $oldStreamNodeMaxEntries = (int) array_pop($config); + $redis->config('set', 'stream-node-max-entries', 2); + + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + + $redis->xadd('stream', ['key' => 'val'], '*', ['trim' => ['MAXLEN', '~', 2]]); + + $this->assertSame(3, $redis->xlen('stream')); + $redis->config('set', 'stream-node-max-entries', $oldStreamNodeMaxEntries); + } + + /** + * @group connected + * @requiresRedisVersion >= 6.2.0 + */ + public function testTrimOnMaxlenWithLimit(): void + { + $redis = $this->getClient(); + $config = $redis->config('get', 'stream-node-max-entries'); + $oldStreamNodeMaxEntries = (int) array_pop($config); + $redis->config('set', 'stream-node-max-entries', 2); + + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + + $redis->xadd( + 'stream', + ['key' => 'val'], + '*', + ['trim' => ['MAXLEN', '~', 2], 'limit' => 2] + ); + + $this->assertSame(4, $redis->xlen('stream')); + $redis->config('set', 'stream-node-max-entries', $oldStreamNodeMaxEntries); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testThrowsExceptionOnWrongType(): void + { + $this->expectException('Predis\Response\ServerException'); + $this->expectExceptionMessage('Operation against a key holding the wrong kind of value'); + + $redis = $this->getClient(); + + $redis->set('foo', 'bar'); + $redis->xadd('foo', ['key' => 'val']); + } +} diff --git a/tests/Predis/Command/Redis/XDEL_Test.php b/tests/Predis/Command/Redis/XDEL_Test.php new file mode 100644 index 00000000..0affd4ee --- /dev/null +++ b/tests/Predis/Command/Redis/XDEL_Test.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Redis; + +/** + * @group commands + * @group realm-stream + */ +class XDEL_Test extends PredisCommandTestCase +{ + /** + * {@inheritdoc} + */ + protected function getExpectedCommand(): string + { + return 'Predis\Command\Redis\XDEL'; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedId(): string + { + return 'XDEL'; + } + + /** + * @group disconnected + */ + public function testFilterArguments(): void + { + $arguments = array('stream', 'id1', 'id2', 'id3'); + $expected = array('stream', 'id1', 'id2', 'id3'); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse(): void + { + $this->assertSame(1, $this->getCommand()->parseResponse(1)); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testRemovesSpecifiedMembers(): void + { + $redis = $this->getClient(); + + $redis->xadd('stream', ['key0' => 'val0'], '0-1'); + $redis->xadd('stream', ['key1' => 'val1'], '1-1'); + $redis->xadd('stream', ['key2' => 'val2'], '2-1'); + + $this->assertSame(2, $redis->xdel('stream', '0-1', '2-1', '99-1')); + $this->assertSame(['1-1' => ['key1' => 'val1']], $redis->xrange('stream', '-', '+')); + + $this->assertSame(0, $redis->xdel('stream', '0-1')); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testThrowsExceptionOnWrongType(): void + { + $this->expectException('Predis\Response\ServerException'); + $this->expectExceptionMessage('Operation against a key holding the wrong kind of value'); + + $redis = $this->getClient(); + + $redis->set('foo', 'bar'); + $redis->xdel('foo', 'bar'); + } +} diff --git a/tests/Predis/Command/Redis/XLEN_Test.php b/tests/Predis/Command/Redis/XLEN_Test.php new file mode 100644 index 00000000..ad07efdc --- /dev/null +++ b/tests/Predis/Command/Redis/XLEN_Test.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Redis; + +/** + * @group commands + * @group realm-stream + */ +class XLEN_Test extends PredisCommandTestCase +{ + /** + * {@inheritdoc} + */ + protected function getExpectedCommand(): string + { + return 'Predis\Command\Redis\XLEN'; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedId(): string + { + return 'XLEN'; + } + + /** + * @group disconnected + */ + public function testFilterArguments(): void + { + $arguments = array('key'); + $expected = array('key'); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse(): void + { + $this->assertSame(1, $this->getCommand()->parseResponse(1)); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testReturnsLengthOfList(): void + { + $redis = $this->getClient(); + + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $this->assertSame(2, $redis->xlen('stream')); + + $redis->xadd('stream', ['key' => 'val']); + $this->assertSame(3, $redis->xlen('stream')); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testReturnsZeroLengthOnNonExistingList(): void + { + $redis = $this->getClient(); + + $this->assertSame(0, $redis->llen('stream')); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testThrowsExceptionOnWrongType(): void + { + $this->expectException('Predis\Response\ServerException'); + $this->expectExceptionMessage('Operation against a key holding the wrong kind of value'); + + $redis = $this->getClient(); + + $redis->set('foo', 'bar'); + $redis->xlen('foo'); + } +} diff --git a/tests/Predis/Command/Redis/XRANGE_Test.php b/tests/Predis/Command/Redis/XRANGE_Test.php new file mode 100644 index 00000000..2ee52929 --- /dev/null +++ b/tests/Predis/Command/Redis/XRANGE_Test.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Redis; + +/** + * @group commands + * @group realm-stream + */ +class XRANGE_Test extends PredisCommandTestCase +{ + /** + * {@inheritdoc} + */ + protected function getExpectedCommand(): string + { + return 'Predis\Command\Redis\XRANGE'; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedId(): string + { + return 'XRANGE'; + } + + /** + * @group disconnected + */ + public function testFilterArguments(): void + { + $arguments = array('stream', '0-1', '1-2', 123); + $expected = array('stream', '0-1', '1-2', 'COUNT', 123); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testFilterArgumentsNoCount(): void + { + $arguments = array('stream', '0-1', '1-2'); + $expected = array('stream', '0-1', '1-2'); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse(): void + { + $raw = array(array('0-1', ['key', 'val'])); + $expected = array('0-1' => ['key' => 'val']); + + $command = $this->getCommand(); + + $this->assertSame($expected, $command->parseResponse($raw)); + } + + /** + * @group disconnected + */ + public function testParseResponseMultipleKeys(): void + { + $raw = array(array('0-1', ['key1', 'val1', 'key2', 'val2'])); + $expected = array('0-1' => ['key1' => 'val1', 'key2' => 'val2']); + + $command = $this->getCommand(); + + $this->assertSame($expected, $command->parseResponse($raw)); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testReturnsElementsInRange(): void + { + $redis = $this->getClient(); + + for ($i = 0; $i < 10; $i++) { + $redis->xadd('stream', ['key' . $i => 'val' . $i], $i . '-1'); + } + + $this->assertSame(array(), $redis->xrange('stream', '1-1', '0-1')); + $this->assertSame( + array('0-1' => ['key0' => 'val0']), + $redis->xrange('stream', '0-1', '0-1') + ); + $this->assertSame( + array('0-1' => ['key0' => 'val0'], '1-1' => ['key1' => 'val1']), + $redis->xrange('stream', '0-1', '1-1') + ); + $this->assertSame( + array('0-1' => ['key0' => 'val0'], '1-1' => ['key1' => 'val1']), + $redis->xrange('stream', '-', '1-1') + ); + $this->assertSame( + array('8-1' => ['key8' => 'val8'], '9-1' => ['key9' => 'val9']), + $redis->xrange('stream', '8-1', '+') + ); + $this->assertSame( + array('5-1' => ['key5' => 'val5'], '6-1' => ['key6' => 'val6']), + $redis->xrange('stream', '5-1', '6-1') + ); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testMultipleKeys(): void + { + $redis = $this->getClient(); + + $redis->xadd('stream', ['key1' => 'val1', 'key2' => 'val2'], '0-1'); + $redis->xadd('stream', ['key1' => 'val1', 'key2' => 'val2'], '1-1'); + + $this->assertSame( + array( + '0-1' => ['key1' => 'val1', 'key2' => 'val2'], + '1-1' => ['key1' => 'val1', 'key2' => 'val2'], + ), + $redis->xrange('stream', '-', '+') + ); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testThrowsExceptionOnWrongType(): void + { + $this->expectException('Predis\Response\ServerException'); + $this->expectExceptionMessage('Operation against a key holding the wrong kind of value'); + + $redis = $this->getClient(); + + $redis->set('foo', 'bar'); + $redis->xrange('foo', '0-1', '1-1'); + } +} diff --git a/tests/Predis/Command/Redis/XREVRANGE_Test.php b/tests/Predis/Command/Redis/XREVRANGE_Test.php new file mode 100644 index 00000000..5b83eb83 --- /dev/null +++ b/tests/Predis/Command/Redis/XREVRANGE_Test.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Redis; + +/** + * @group commands + * @group realm-stream + */ +class XREVRANGE_Test extends PredisCommandTestCase +{ + /** + * {@inheritdoc} + */ + protected function getExpectedCommand(): string + { + return 'Predis\Command\Redis\XREVRANGE'; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedId(): string + { + return 'XREVRANGE'; + } + + /** + * @group disconnected + */ + public function testFilterArguments(): void + { + $arguments = array('stream', '1-1', '0-1', 123); + $expected = array('stream', '1-1', '0-1', 'COUNT', 123); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testFilterArgumentsNoCount(): void + { + $arguments = array('stream', '1-1', '0-1'); + $expected = array('stream', '1-1', '0-1'); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse(): void + { + $raw = array(array('0-1', ['key', 'val'])); + $expected = array('0-1' => ['key' => 'val']); + + $command = $this->getCommand(); + + $this->assertSame($expected, $command->parseResponse($raw)); + } + + /** + * @group disconnected + */ + public function testParseResponseMultipleKeys(): void + { + $raw = array(array('0-1', ['key1', 'val1', 'key2', 'val2'])); + $expected = array('0-1' => ['key1' => 'val1', 'key2' => 'val2']); + + $command = $this->getCommand(); + + $this->assertSame($expected, $command->parseResponse($raw)); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testReturnsElementsInRange(): void + { + $redis = $this->getClient(); + + for ($i = 0; $i < 10; $i++) { + $redis->xadd('stream', ['key' . $i => 'val' . $i], $i . '-1'); + } + + $this->assertSame(array(), $redis->xrevrange('stream', '0-1', '1-1')); + $this->assertSame( + array('0-1' => ['key0' => 'val0']), + $redis->xrevrange('stream', '0-1', '0-1') + ); + $this->assertSame( + array('1-1' => ['key1' => 'val1'], '0-1' => ['key0' => 'val0']), + $redis->xrevrange('stream', '1-1', '0-1') + ); + $this->assertSame( + array('1-1' => ['key1' => 'val1'], '0-1' => ['key0' => 'val0']), + $redis->xrevrange('stream', '1-1', '-') + ); + $this->assertSame( + array('9-1' => ['key9' => 'val9'], '8-1' => ['key8' => 'val8']), + $redis->xrevrange('stream', '+', '8-1') + ); + $this->assertSame( + array('6-1' => ['key6' => 'val6'], '5-1' => ['key5' => 'val5']), + $redis->xrevrange('stream', '6-1', '5-1') + ); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testMultipleKeys(): void + { + $redis = $this->getClient(); + + $redis->xadd('stream', ['key1' => 'val1', 'key2' => 'val2'], '0-1'); + $redis->xadd('stream', ['key1' => 'val1', 'key2' => 'val2'], '1-1'); + + $this->assertSame( + array( + '1-1' => ['key1' => 'val1', 'key2' => 'val2'], + '0-1' => ['key1' => 'val1', 'key2' => 'val2'], + ), + $redis->xrevrange('stream', '+', '-') + ); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testThrowsExceptionOnWrongType(): void + { + $this->expectException('Predis\Response\ServerException'); + $this->expectExceptionMessage('Operation against a key holding the wrong kind of value'); + + $redis = $this->getClient(); + + $redis->set('foo', 'bar'); + $redis->xrevrange('foo', '1-1', '0-1'); + } +} diff --git a/tests/Predis/Command/Redis/XTRIM_Test.php b/tests/Predis/Command/Redis/XTRIM_Test.php new file mode 100644 index 00000000..b6b9431d --- /dev/null +++ b/tests/Predis/Command/Redis/XTRIM_Test.php @@ -0,0 +1,201 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Redis; + +/** + * @group commands + * @group realm-stream + */ +class XTRIM_Test extends PredisCommandTestCase +{ + /** + * {@inheritdoc} + */ + protected function getExpectedCommand(): string + { + return 'Predis\Command\Redis\XTRIM'; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedId(): string + { + return 'XTRIM'; + } + + /** + * @group disconnected + * @dataProvider dataFilterArguments + */ + public function testFilterArguments(array $arguments, array $expected): void + { + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + public function dataFilterArguments(): array + { + return [ + [ + ['stream', ['MINID', '~'], '0-1', ['limit' => 10]], + ['stream', 'MINID', '~', '0-1', 'LIMIT', 10], + ], + [ + ['stream', ['MINID'], '0-1', ['limit' => 10]], + ['stream', 'MINID', '0-1', 'LIMIT', 10], + ], + [ + ['stream', 'MINID', '0-1', ['limit' => 10]], + ['stream', 'MINID', '0-1', 'LIMIT', 10], + ], + [ + ['stream', 'MINID', '0-1'], + ['stream', 'MINID', '0-1'], + ], + ]; + } + + /** + * @group disconnected + */ + public function testParseResponse(): void + { + $this->assertSame(1, $this->getCommand()->parseResponse(1)); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testTrimOnMaxlenExact(): void + { + $redis = $this->getClient(); + + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + + $res = $redis->xtrim('stream', 'MAXLEN', 2); + + $this->assertSame(1, $res); + $this->assertSame(2, $redis->xlen('stream')); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testTrimOnMaxlenInexact(): void + { + $redis = $this->getClient(); + $config = $redis->config('get', 'stream-node-max-entries'); + $oldStreamNodeMaxEntries = (int) array_pop($config); + $redis->config('set', 'stream-node-max-entries', 2); + + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + + $res = $redis->xtrim('stream', ['MAXLEN', '~'], 2); + + $this->assertSame(2, $res); + $this->assertSame(3, $redis->xlen('stream')); + $redis->config('set', 'stream-node-max-entries', $oldStreamNodeMaxEntries); + } + + /** + * @group connected + * @requiresRedisVersion >= 6.2.0 + */ + public function testTrimOnMinidExact(): void + { + $redis = $this->getClient(); + + $redis->xadd('stream', ['key' => 'val']); + $id = $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + + $res = $redis->xtrim('stream', 'MINID', $id); + + $this->assertSame(1, $res); + $this->assertSame(2, $redis->xlen('stream')); + } + + /** + * @group connected + * @requiresRedisVersion >= 6.2.0 + */ + public function testTrimOnMinidInexact(): void + { + $redis = $this->getClient(); + $config = $redis->config('get', 'stream-node-max-entries'); + $oldStreamNodeMaxEntries = (int) array_pop($config); + $redis->config('set', 'stream-node-max-entries', 2); + + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $id = $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + + $res = $redis->xtrim('stream', ['MINID', '~'], $id); + + $this->assertSame(2, $res); + $this->assertSame(3, $redis->xlen('stream')); + $redis->config('set', 'stream-node-max-entries', $oldStreamNodeMaxEntries); + } + + /** + * @group connected + * @requiresRedisVersion >= 6.2.0 + */ + public function testTrimOnMaxlenWithLimit(): void + { + $redis = $this->getClient(); + $config = $redis->config('get', 'stream-node-max-entries'); + $oldStreamNodeMaxEntries = (int) array_pop($config); + $redis->config('set', 'stream-node-max-entries', 2); + + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + $redis->xadd('stream', ['key' => 'val']); + + $res = $redis->xtrim('stream', ['MAXLEN', '~'], 2, ['limit' => 2]); + + $this->assertSame(2, $res); + $this->assertSame(4, $redis->xlen('stream')); + $redis->config('set', 'stream-node-max-entries', $oldStreamNodeMaxEntries); + } + + /** + * @group connected + * @requiresRedisVersion >= 5.0.0 + */ + public function testThrowsExceptionOnWrongType(): void + { + $this->expectException('Predis\Response\ServerException'); + $this->expectExceptionMessage('Operation against a key holding the wrong kind of value'); + + $redis = $this->getClient(); + + $redis->set('key', 'foo'); + $redis->xtrim('key', 'MAXLEN', 2); + } +}