Compare commits

...

22 Commits

Author SHA1 Message Date
Till Krüss ff59f74581 bump version 2022-10-11 09:52:29 -07:00
Mehmet Tolga Avcioglu 1a0eb7ee85 sentinel default to no password when replica authentication is used (#807)
* set default nopass if password is not provided

* remove debug output

* added tests for noauth sentinel

* Update SentinelReplication.php

Co-authored-by: Mehmet Tolga Avcioglu <mehmet.avcioglu@pusula.net.tr>
Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
2022-10-06 08:14:47 -07:00
Tobias Bengtsson bb6c1a7b6d Add partial support for Stream commands (#804)
Adds support for the following commands not related to groups/consumers:
XADD, XDEL, XLEN, XRANGE, XREVRANGE, XTRIM.
2022-09-27 09:39:17 -07:00
streamingsystems 42c33a406f Check if socket is resource before reading (#802) 2022-09-18 15:01:02 -07:00
Stefano Borghi 7ad74217c2 check resource before writing, w/ test (#800) 2022-09-16 17:03:13 -07:00
Alexandru Pătrănescu 72d7675563 improve PHP 8.2 support (#797)
* use static::class . '::<method>' instead of 'static::method'

* extract concatenation so it can be improved easier at a later point

one improvement at some point could be to replace static::class . '::<method>' with static::<method>(...) in PHP 8.1+

Co-authored-by: Alexandru Patranescu <alexandru.patranescu@payu.com>
2022-09-08 11:25:37 -07:00
Till Krüss 8b5fa92856 mention php version [no ci] 2022-09-06 07:34:14 -07:00
Till Krüss 076a62e3d3 bump version to 2.0.2 2022-09-06 07:32:44 -07:00
Dries Vints 85dc1752b7 PHP 8.2 Support (#796) 2022-09-06 07:13:46 -07:00
Till Krüss 7683215023 bump tag to v2.0.1 2022-09-04 14:57:36 -07:00
Till Krüss dd4ddf6504 Update CHANGELOG.md 2022-08-03 11:53:59 -07:00
Till Krüss 6468e6cc2e Update CHANGELOG.md 2022-08-03 11:51:22 -07:00
digital2real 12f6d30502 Add retry logic to support temporary redis cluster failure (#788)
* Add retry logic to support temporary redis cluster failure

* formatting

* Use exponential backoff retries.
$minRetryAfter cant be changed if needed with RedisCluster::setMinRetryAfter

* fix formating

* (hotfix) formaing + import import  \Predis\Response\Error and \Predis\Connection\ConnectionException

* Update RedisCluster.php

* formatting

* tweaks

* Update RedisCluster.php

* Update RedisCluster.php

* Update RedisCluster.php

* Update unit tests regarding renaming of "retryInterval"

* spacing

Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
2022-08-03 11:49:56 -07:00
Linc a602fc7425 Fix sentinel reconnect issue on long-running PHP processes (#784)
* Fix no sentinel server available for autodiscovery on Swoole

* Fix errors in PHPUnit 9.5

the reason: https://github.com/sebastianbergmann/phpunit/commit/68582043e149039cfa3596b42ed35753dcf54fb2?fbclid=IwAR1qy1jFNFeJ3hUt5k7XAflqyCTUTWZhEqVSeizkybVHUEqXjBZ1gJM739U

* add restart sentinel test
2022-07-12 09:44:28 -07:00
John Paul E. Balandan, CPA 4ec74cc5fe Declare $parameters property of Predis\Connection\Parameters (#781) 2022-06-29 07:58:14 -07:00
BrightQi 78fd4cb998 bugfix for srem type to accept array #779 (#780)
* bugfix for srem  type to accept array #779

* add test to test SREM accept members as array type #779
2022-06-28 08:23:35 -07:00
Tomáš Fedor 2a8dd6a7f1 Add array as possible return type to spop (#774)
* Add array as possible return type to spop

* Update ClientInterface.php

Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
2022-06-14 19:59:10 -07:00
Jayin Taung 7d7b3416a8 Add example for installing predis by Composer (#772)
* Add example for installing predis by Composer

* Update README.md

Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
2022-06-13 07:54:46 -07:00
Till Krüss 99c253733d tweak changelog format 2022-06-08 06:14:56 -07:00
Till Krüss 69bb8fa082 bump version to v2.0.0 stable 2022-06-08 06:11:35 -07:00
Josias Montag e5221fa13b Allow PubSub / MultiExec with Replication 2022-05-28 08:18:01 -07:00
Till Krüss 15244c9642 cleanup readme 2022-05-26 12:01:30 -07:00
33 changed files with 1956 additions and 321 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1']
php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
redis-versions: ['3', '4', '5', '6', '7']
steps:
+26 -2
View File
@@ -1,5 +1,23 @@
v2.0.0-beta.1 (2022-05-26)
================================================================================
## Changelog
## v2.0.3 (2022-10-11)
- Improved PHP 8.2 support
- Call `is_resource()` before reading/writing
- Added partial support for Redis Stream commands
- Fixed Sentinel authentication issue
## v2.0.2 (2022-09-06)
- Fixed PHP 8.2 deprecation notice: Use of "static" in callables
## v2.0.1 (2022-09-04)
- Added retry interval to `RedisCluster` with a default of `10ms`
- Avoid PHP 8.2 warning in `Connection\Parameters`
- Fixed Sentinel reconnect issue in long-running PHP processes
## v2.0.0 (2022-06-08)
- Dropped support for PHP 7.1 and older
@@ -106,6 +124,8 @@ v2.0.0-beta.1 (2022-05-26)
class now passes the second argument as an integer value `0` as its default
value instead of `null`.
- Support Pub/Sub and Pipelines when using replication
- The class `Predis\Transaction\AbortedMultiExecException` now uses the correct
default types for the `$code` (integer) parameter.
@@ -120,3 +140,7 @@ v2.0.0-beta.1 (2022-05-26)
- __FIX__: the value returned from `getArgument()` in `parseResponse()`method,
part of `Predis\Command\Redis\SENTINEL` class, is checked to not pass `null`
to `strtolower()` function.
## v2.0.0-beta.1 (2022-05-26)
Same as v2.0.0
+5 -10
View File
@@ -8,21 +8,12 @@
A flexible and feature-complete [Redis](http://redis.io) client for PHP 7.2 and newer.
__ATTENTION:__ you are on the README file of an unstable branch of Predis specifically meant for the
development of future releases. This means that the code on this branch is potentially unstable, and
breaking change may happen without any prior notice. Do not use it in production environments or use
it at your own risk!
Predis does not require any additional C extension by default, but it can be optionally paired with
[phpiredis](https://github.com/nrk/phpiredis) to lower the overhead of the serialization and parsing
of the [Redis RESP Protocol](http://redis.io/topics/protocol).
More details about this project can be found on the [frequently asked questions](FAQ.md).
## Main features ##
- Support for Redis from __2.0__ to __6.0__.
- Support for Redis from __3.0__ to __7.0__.
- Support for clustering using client-side sharding and pluggable keyspace distributors.
- Support for [redis-cluster](http://redis.io/topics/cluster-tutorial) (Redis >= 3.0).
- Support for master-slave replication setups and [redis-sentinel](http://redis.io/topics/sentinel).
@@ -44,6 +35,10 @@ This library can be found on [Packagist](http://packagist.org/packages/predis/pr
management of projects dependencies using [Composer](http://packagist.org/about-composer).
Compressed archives of each release are [available on GitHub](https://github.com/predis/predis/releases).
```shell
composer require predis/predis
```
### Loading the library ###
+1 -1
View File
@@ -1 +1 @@
2.0.0-beta.1
2.0.3
+1 -1
View File
@@ -40,7 +40,7 @@ use Predis\Transaction\MultiExec as MultiExecTransaction;
*/
class Client implements ClientInterface, \IteratorAggregate
{
const VERSION = '2.0.0-beta.1';
const VERSION = '2.0.3';
/** @var OptionsInterface */
private $options;
+157 -151
View File
@@ -25,157 +25,163 @@ use Predis\Response\Status;
* and more friendly interface to ease programming which is described in the
* following list of methods:
*
* @method int del(string[]|string $keyOrKeys, string ...$keys = null)
* @method string|null dump(string $key)
* @method int exists(string $key)
* @method int expire(string $key, int $seconds)
* @method int expireat(string $key, int $timestamp)
* @method array keys(string $pattern)
* @method int move(string $key, int $db)
* @method mixed object($subcommand, string $key)
* @method int persist(string $key)
* @method int pexpire(string $key, int $milliseconds)
* @method int pexpireat(string $key, int $timestamp)
* @method int pttl(string $key)
* @method string|null randomkey()
* @method mixed rename(string $key, string $target)
* @method int renamenx(string $key, string $target)
* @method array scan($cursor, array $options = null)
* @method array sort(string $key, array $options = null)
* @method int ttl(string $key)
* @method mixed type(string $key)
* @method int append(string $key, $value)
* @method int bitcount(string $key, $start = null, $end = null)
* @method int bitop($operation, $destkey, $key)
* @method array|null bitfield(string $key, $subcommand, ...$subcommandArg)
* @method int bitpos(string $key, $bit, $start = null, $end = null)
* @method int decr(string $key)
* @method int decrby(string $key, int $decrement)
* @method string|null get(string $key)
* @method int getbit(string $key, $offset)
* @method string getrange(string $key, $start, $end)
* @method string|null getset(string $key, $value)
* @method int incr(string $key)
* @method int incrby(string $key, int $increment)
* @method string incrbyfloat(string $key, int|float $increment)
* @method array mget(string[]|string $keyOrKeys, string ...$keys = null)
* @method mixed mset(array $dictionary)
* @method int msetnx(array $dictionary)
* @method Status psetex(string $key, $milliseconds, $value)
* @method Status set(string $key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
* @method int setbit(string $key, $offset, $value)
* @method Status setex(string $key, $seconds, $value)
* @method int setnx(string $key, $value)
* @method int setrange(string $key, $offset, $value)
* @method int strlen(string $key)
* @method int hdel(string $key, array $fields)
* @method int hexists(string $key, string $field)
* @method string|null hget(string $key, string $field)
* @method array hgetall(string $key)
* @method int hincrby(string $key, string $field, int $increment)
* @method string hincrbyfloat(string $key, string $field, int|float $increment)
* @method array hkeys(string $key)
* @method int hlen(string $key)
* @method array hmget(string $key, array $fields)
* @method mixed hmset(string $key, array $dictionary)
* @method array hscan(string $key, $cursor, array $options = null)
* @method int hset(string $key, string $field, string $value)
* @method int hsetnx(string $key, string $field, string $value)
* @method array hvals(string $key)
* @method int hstrlen(string $key, string $field)
* @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)
* @method string|null lindex(string $key, int $index)
* @method int linsert(string $key, $whence, $pivot, $value)
* @method int llen(string $key)
* @method string|null lpop(string $key)
* @method int lpush(string $key, array $values)
* @method int lpushx(string $key, array $values)
* @method string[] lrange(string $key, int $start, int $stop)
* @method int lrem(string $key, int $count, string $value)
* @method mixed lset(string $key, int $index, string $value)
* @method mixed ltrim(string $key, int $start, int $stop)
* @method string|null rpop(string $key)
* @method string|null rpoplpush(string $source, string $destination)
* @method int rpush(string $key, array $values)
* @method int rpushx(string $key, array $values)
* @method int sadd(string $key, array $members)
* @method int scard(string $key)
* @method string[] sdiff(array|string $keys)
* @method int sdiffstore(string $destination, array|string $keys)
* @method string[] sinter(array|string $keys)
* @method int sinterstore(string $destination, array|string $keys)
* @method int sismember(string $key, string $member)
* @method string[] smembers(string $key)
* @method int smove(string $source, string $destination, string $member)
* @method string|null spop(string $key, int $count = null)
* @method string|null srandmember(string $key, int $count = null)
* @method int srem(string $key, string $member)
* @method array sscan(string $key, int $cursor, array $options = null)
* @method string[] sunion(array|string $keys)
* @method int sunionstore(string $destination, array|string $keys)
* @method int touch(string[]|string $keyOrKeys, string ...$keys = null)
* @method int zadd(string $key, array $membersAndScoresDictionary)
* @method int zcard(string $key)
* @method string zcount(string $key, int|string $min, int|string $max)
* @method string zincrby(string $key, int $increment, string $member)
* @method int zinterstore(string $destination, array|string $keys, array $options = null)
* @method array zpopmin(string $key, int $count = 1)
* @method array zpopmax(string $key, int $count = 1)
* @method array zrange(string $key, int|string $start, int|string $stop, array $options = null)
* @method array zrangebyscore(string $key, int|string $min, int|string $max, array $options = null)
* @method int|null zrank(string $key, string $member)
* @method int zrem(string $key, string ...$member)
* @method int zremrangebyrank(string $key, int|string $start, int|string $stop)
* @method int zremrangebyscore(string $key, int|string $min, int|string $max)
* @method array zrevrange(string $key, int|string $start, int|string $stop, array $options = null)
* @method array zrevrangebyscore(string $key, int|string $max, int|string $min, array $options = null)
* @method int|null zrevrank(string $key, string $member)
* @method int zunionstore(string $destination, array|string $keys, array $options = null)
* @method string|null zscore(string $key, string $member)
* @method array zscan(string $key, int $cursor, array $options = null)
* @method array zrangebylex(string $key, string $start, string $stop, array $options = null)
* @method array zrevrangebylex(string $key, string $start, string $stop, array $options = null)
* @method int zremrangebylex(string $key, string $min, string $max)
* @method int zlexcount(string $key, string $min, string $max)
* @method int pfadd(string $key, array $elements)
* @method mixed pfmerge(string $destinationKey, array|string $sourceKeys)
* @method int pfcount(string[]|string $keyOrKeys, string ...$keys = null)
* @method mixed pubsub($subcommand, $argument)
* @method int publish($channel, $message)
* @method mixed discard()
* @method array|null exec()
* @method mixed multi()
* @method mixed unwatch()
* @method mixed watch(string $key)
* @method mixed eval(string $script, int $numkeys, string ...$keyOrArg = null)
* @method mixed evalsha(string $script, int $numkeys, string ...$keyOrArg = null)
* @method mixed script($subcommand, $argument = null)
* @method mixed auth(string $password)
* @method string echo(string $message)
* @method mixed ping(string $message = null)
* @method mixed select(int $database)
* @method mixed bgrewriteaof()
* @method mixed bgsave()
* @method mixed client($subcommand, $argument = null)
* @method mixed config($subcommand, $argument = null)
* @method int dbsize()
* @method mixed flushall()
* @method mixed flushdb()
* @method array info($section = null)
* @method int lastsave()
* @method mixed save()
* @method mixed slaveof(string $host, int $port)
* @method mixed slowlog($subcommand, $argument = null)
* @method array time()
* @method array command()
* @method int geoadd(string $key, $longitude, $latitude, $member)
* @method array geohash(string $key, array $members)
* @method array geopos(string $key, array $members)
* @method string|null geodist(string $key, $member1, $member2, $unit = null)
* @method array georadius(string $key, $longitude, $latitude, $radius, $unit, array $options = null)
* @method array georadiusbymember(string $key, $member, $radius, $unit, array $options = null)
* @method int del(string[]|string $keyOrKeys, string ...$keys = null)
* @method string|null dump(string $key)
* @method int exists(string $key)
* @method int expire(string $key, int $seconds)
* @method int expireat(string $key, int $timestamp)
* @method array keys(string $pattern)
* @method int move(string $key, int $db)
* @method mixed object($subcommand, string $key)
* @method int persist(string $key)
* @method int pexpire(string $key, int $milliseconds)
* @method int pexpireat(string $key, int $timestamp)
* @method int pttl(string $key)
* @method string|null randomkey()
* @method mixed rename(string $key, string $target)
* @method int renamenx(string $key, string $target)
* @method array scan($cursor, array $options = null)
* @method array sort(string $key, array $options = null)
* @method int ttl(string $key)
* @method mixed type(string $key)
* @method int append(string $key, $value)
* @method int bitcount(string $key, $start = null, $end = null)
* @method int bitop($operation, $destkey, $key)
* @method array|null bitfield(string $key, $subcommand, ...$subcommandArg)
* @method int bitpos(string $key, $bit, $start = null, $end = null)
* @method int decr(string $key)
* @method int decrby(string $key, int $decrement)
* @method string|null get(string $key)
* @method int getbit(string $key, $offset)
* @method string getrange(string $key, $start, $end)
* @method string|null getset(string $key, $value)
* @method int incr(string $key)
* @method int incrby(string $key, int $increment)
* @method string incrbyfloat(string $key, int|float $increment)
* @method array mget(string[]|string $keyOrKeys, string ...$keys = null)
* @method mixed mset(array $dictionary)
* @method int msetnx(array $dictionary)
* @method Status psetex(string $key, $milliseconds, $value)
* @method Status set(string $key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
* @method int setbit(string $key, $offset, $value)
* @method Status setex(string $key, $seconds, $value)
* @method int setnx(string $key, $value)
* @method int setrange(string $key, $offset, $value)
* @method int strlen(string $key)
* @method int hdel(string $key, array $fields)
* @method int hexists(string $key, string $field)
* @method string|null hget(string $key, string $field)
* @method array hgetall(string $key)
* @method int hincrby(string $key, string $field, int $increment)
* @method string hincrbyfloat(string $key, string $field, int|float $increment)
* @method array hkeys(string $key)
* @method int hlen(string $key)
* @method array hmget(string $key, array $fields)
* @method mixed hmset(string $key, array $dictionary)
* @method array hscan(string $key, $cursor, array $options = null)
* @method int hset(string $key, string $field, string $value)
* @method int hsetnx(string $key, string $field, string $value)
* @method array hvals(string $key)
* @method int hstrlen(string $key, string $field)
* @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)
* @method string|null lindex(string $key, int $index)
* @method int linsert(string $key, $whence, $pivot, $value)
* @method int llen(string $key)
* @method string|null lpop(string $key)
* @method int lpush(string $key, array $values)
* @method int lpushx(string $key, array $values)
* @method string[] lrange(string $key, int $start, int $stop)
* @method int lrem(string $key, int $count, string $value)
* @method mixed lset(string $key, int $index, string $value)
* @method mixed ltrim(string $key, int $start, int $stop)
* @method string|null rpop(string $key)
* @method string|null rpoplpush(string $source, string $destination)
* @method int rpush(string $key, array $values)
* @method int rpushx(string $key, array $values)
* @method int sadd(string $key, array $members)
* @method int scard(string $key)
* @method string[] sdiff(array|string $keys)
* @method int sdiffstore(string $destination, array|string $keys)
* @method string[] sinter(array|string $keys)
* @method int sinterstore(string $destination, array|string $keys)
* @method int sismember(string $key, string $member)
* @method string[] smembers(string $key)
* @method int smove(string $source, string $destination, string $member)
* @method string|array|null spop(string $key, int $count = null)
* @method string|null srandmember(string $key, int $count = null)
* @method int srem(string $key, array|string $member)
* @method array sscan(string $key, int $cursor, array $options = null)
* @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)
* @method string zincrby(string $key, int $increment, string $member)
* @method int zinterstore(string $destination, array|string $keys, array $options = null)
* @method array zpopmin(string $key, int $count = 1)
* @method array zpopmax(string $key, int $count = 1)
* @method array zrange(string $key, int|string $start, int|string $stop, array $options = null)
* @method array zrangebyscore(string $key, int|string $min, int|string $max, array $options = null)
* @method int|null zrank(string $key, string $member)
* @method int zrem(string $key, string ...$member)
* @method int zremrangebyrank(string $key, int|string $start, int|string $stop)
* @method int zremrangebyscore(string $key, int|string $min, int|string $max)
* @method array zrevrange(string $key, int|string $start, int|string $stop, array $options = null)
* @method array zrevrangebyscore(string $key, int|string $max, int|string $min, array $options = null)
* @method int|null zrevrank(string $key, string $member)
* @method int zunionstore(string $destination, array|string $keys, array $options = null)
* @method string|null zscore(string $key, string $member)
* @method array zscan(string $key, int $cursor, array $options = null)
* @method array zrangebylex(string $key, string $start, string $stop, array $options = null)
* @method array zrevrangebylex(string $key, string $start, string $stop, array $options = null)
* @method int zremrangebylex(string $key, string $min, string $max)
* @method int zlexcount(string $key, string $min, string $max)
* @method int pfadd(string $key, array $elements)
* @method mixed pfmerge(string $destinationKey, array|string $sourceKeys)
* @method int pfcount(string[]|string $keyOrKeys, string ...$keys = null)
* @method mixed pubsub($subcommand, $argument)
* @method int publish($channel, $message)
* @method mixed discard()
* @method array|null exec()
* @method mixed multi()
* @method mixed unwatch()
* @method mixed watch(string $key)
* @method mixed eval(string $script, int $numkeys, string ...$keyOrArg = null)
* @method mixed evalsha(string $script, int $numkeys, string ...$keyOrArg = null)
* @method mixed script($subcommand, $argument = null)
* @method mixed auth(string $password)
* @method string echo(string $message)
* @method mixed ping(string $message = null)
* @method mixed select(int $database)
* @method mixed bgrewriteaof()
* @method mixed bgsave()
* @method mixed client($subcommand, $argument = null)
* @method mixed config($subcommand, $argument = null)
* @method int dbsize()
* @method mixed flushall()
* @method mixed flushdb()
* @method array info($section = null)
* @method int lastsave()
* @method mixed save()
* @method mixed slaveof(string $host, int $port)
* @method mixed slowlog($subcommand, $argument = null)
* @method array time()
* @method array command()
* @method int geoadd(string $key, $longitude, $latitude, $member)
* @method array geohash(string $key, array $members)
* @method array geopos(string $key, array $members)
* @method string|null geodist(string $key, $member1, $member2, $unit = null)
* @method array georadius(string $key, $longitude, $latitude, $radius, $unit, array $options = null)
* @method array georadiusbymember(string $key, $member, $radius, $unit, array $options = null)
*
* @author Daniele Alessandri <suppakilla@gmail.com>
*/
+143 -131
View File
@@ -31,142 +31,154 @@ class KeyPrefixProcessor implements ProcessorInterface
public function __construct($prefix)
{
$this->prefix = $prefix;
$this->commands = array(
$prefixFirst = static::class . '::first';
$prefixAll = static::class . '::all';
$prefixInterleaved = static::class . '::interleaved';
$prefixSkipFirst = static::class . '::skipFirst';
$prefixSkipLast = static::class . '::skipLast';
$prefixSort = static::class . '::sort';
$prefixEvalKeys = static::class . '::evalKeys';
$prefixZsetStore = static::class . '::zsetStore';
$prefixMigrate = static::class . '::migrate';
$prefixGeoradius = static::class . '::georadius';
$this->commands = [
/* ---------------- Redis 1.2 ---------------- */
'EXISTS' => 'static::all',
'DEL' => 'static::all',
'TYPE' => 'static::first',
'KEYS' => 'static::first',
'RENAME' => 'static::all',
'RENAMENX' => 'static::all',
'EXPIRE' => 'static::first',
'EXPIREAT' => 'static::first',
'TTL' => 'static::first',
'MOVE' => 'static::first',
'SORT' => 'static::sort',
'DUMP' => 'static::first',
'RESTORE' => 'static::first',
'SET' => 'static::first',
'SETNX' => 'static::first',
'MSET' => 'static::interleaved',
'MSETNX' => 'static::interleaved',
'GET' => 'static::first',
'MGET' => 'static::all',
'GETSET' => 'static::first',
'INCR' => 'static::first',
'INCRBY' => 'static::first',
'DECR' => 'static::first',
'DECRBY' => 'static::first',
'RPUSH' => 'static::first',
'LPUSH' => 'static::first',
'LLEN' => 'static::first',
'LRANGE' => 'static::first',
'LTRIM' => 'static::first',
'LINDEX' => 'static::first',
'LSET' => 'static::first',
'LREM' => 'static::first',
'LPOP' => 'static::first',
'RPOP' => 'static::first',
'RPOPLPUSH' => 'static::all',
'SADD' => 'static::first',
'SREM' => 'static::first',
'SPOP' => 'static::first',
'SMOVE' => 'static::skipLast',
'SCARD' => 'static::first',
'SISMEMBER' => 'static::first',
'SINTER' => 'static::all',
'SINTERSTORE' => 'static::all',
'SUNION' => 'static::all',
'SUNIONSTORE' => 'static::all',
'SDIFF' => 'static::all',
'SDIFFSTORE' => 'static::all',
'SMEMBERS' => 'static::first',
'SRANDMEMBER' => 'static::first',
'ZADD' => 'static::first',
'ZINCRBY' => 'static::first',
'ZREM' => 'static::first',
'ZRANGE' => 'static::first',
'ZREVRANGE' => 'static::first',
'ZRANGEBYSCORE' => 'static::first',
'ZCARD' => 'static::first',
'ZSCORE' => 'static::first',
'ZREMRANGEBYSCORE' => 'static::first',
'EXISTS' => $prefixAll,
'DEL' => $prefixAll,
'TYPE' => $prefixFirst,
'KEYS' => $prefixFirst,
'RENAME' => $prefixAll,
'RENAMENX' => $prefixAll,
'EXPIRE' => $prefixFirst,
'EXPIREAT' => $prefixFirst,
'TTL' => $prefixFirst,
'MOVE' => $prefixFirst,
'SORT' => $prefixSort,
'DUMP' => $prefixFirst,
'RESTORE' => $prefixFirst,
'SET' => $prefixFirst,
'SETNX' => $prefixFirst,
'MSET' => $prefixInterleaved,
'MSETNX' => $prefixInterleaved,
'GET' => $prefixFirst,
'MGET' => $prefixAll,
'GETSET' => $prefixFirst,
'INCR' => $prefixFirst,
'INCRBY' => $prefixFirst,
'DECR' => $prefixFirst,
'DECRBY' => $prefixFirst,
'RPUSH' => $prefixFirst,
'LPUSH' => $prefixFirst,
'LLEN' => $prefixFirst,
'LRANGE' => $prefixFirst,
'LTRIM' => $prefixFirst,
'LINDEX' => $prefixFirst,
'LSET' => $prefixFirst,
'LREM' => $prefixFirst,
'LPOP' => $prefixFirst,
'RPOP' => $prefixFirst,
'RPOPLPUSH' => $prefixAll,
'SADD' => $prefixFirst,
'SREM' => $prefixFirst,
'SPOP' => $prefixFirst,
'SMOVE' => $prefixSkipLast,
'SCARD' => $prefixFirst,
'SISMEMBER' => $prefixFirst,
'SINTER' => $prefixAll,
'SINTERSTORE' => $prefixAll,
'SUNION' => $prefixAll,
'SUNIONSTORE' => $prefixAll,
'SDIFF' => $prefixAll,
'SDIFFSTORE' => $prefixAll,
'SMEMBERS' => $prefixFirst,
'SRANDMEMBER' => $prefixFirst,
'ZADD' => $prefixFirst,
'ZINCRBY' => $prefixFirst,
'ZREM' => $prefixFirst,
'ZRANGE' => $prefixFirst,
'ZREVRANGE' => $prefixFirst,
'ZRANGEBYSCORE' => $prefixFirst,
'ZCARD' => $prefixFirst,
'ZSCORE' => $prefixFirst,
'ZREMRANGEBYSCORE' => $prefixFirst,
/* ---------------- Redis 2.0 ---------------- */
'SETEX' => 'static::first',
'APPEND' => 'static::first',
'SUBSTR' => 'static::first',
'BLPOP' => 'static::skipLast',
'BRPOP' => 'static::skipLast',
'ZUNIONSTORE' => 'static::zsetStore',
'ZINTERSTORE' => 'static::zsetStore',
'ZCOUNT' => 'static::first',
'ZRANK' => 'static::first',
'ZREVRANK' => 'static::first',
'ZREMRANGEBYRANK' => 'static::first',
'HSET' => 'static::first',
'HSETNX' => 'static::first',
'HMSET' => 'static::first',
'HINCRBY' => 'static::first',
'HGET' => 'static::first',
'HMGET' => 'static::first',
'HDEL' => 'static::first',
'HEXISTS' => 'static::first',
'HLEN' => 'static::first',
'HKEYS' => 'static::first',
'HVALS' => 'static::first',
'HGETALL' => 'static::first',
'SUBSCRIBE' => 'static::all',
'UNSUBSCRIBE' => 'static::all',
'PSUBSCRIBE' => 'static::all',
'PUNSUBSCRIBE' => 'static::all',
'PUBLISH' => 'static::first',
'SETEX' => $prefixFirst,
'APPEND' => $prefixFirst,
'SUBSTR' => $prefixFirst,
'BLPOP' => $prefixSkipLast,
'BRPOP' => $prefixSkipLast,
'ZUNIONSTORE' => $prefixZsetStore,
'ZINTERSTORE' => $prefixZsetStore,
'ZCOUNT' => $prefixFirst,
'ZRANK' => $prefixFirst,
'ZREVRANK' => $prefixFirst,
'ZREMRANGEBYRANK' => $prefixFirst,
'HSET' => $prefixFirst,
'HSETNX' => $prefixFirst,
'HMSET' => $prefixFirst,
'HINCRBY' => $prefixFirst,
'HGET' => $prefixFirst,
'HMGET' => $prefixFirst,
'HDEL' => $prefixFirst,
'HEXISTS' => $prefixFirst,
'HLEN' => $prefixFirst,
'HKEYS' => $prefixFirst,
'HVALS' => $prefixFirst,
'HGETALL' => $prefixFirst,
'SUBSCRIBE' => $prefixAll,
'UNSUBSCRIBE' => $prefixAll,
'PSUBSCRIBE' => $prefixAll,
'PUNSUBSCRIBE' => $prefixAll,
'PUBLISH' => $prefixFirst,
/* ---------------- Redis 2.2 ---------------- */
'PERSIST' => 'static::first',
'STRLEN' => 'static::first',
'SETRANGE' => 'static::first',
'GETRANGE' => 'static::first',
'SETBIT' => 'static::first',
'GETBIT' => 'static::first',
'RPUSHX' => 'static::first',
'LPUSHX' => 'static::first',
'LINSERT' => 'static::first',
'BRPOPLPUSH' => 'static::skipLast',
'ZREVRANGEBYSCORE' => 'static::first',
'WATCH' => 'static::all',
'PERSIST' => $prefixFirst,
'STRLEN' => $prefixFirst,
'SETRANGE' => $prefixFirst,
'GETRANGE' => $prefixFirst,
'SETBIT' => $prefixFirst,
'GETBIT' => $prefixFirst,
'RPUSHX' => $prefixFirst,
'LPUSHX' => $prefixFirst,
'LINSERT' => $prefixFirst,
'BRPOPLPUSH' => $prefixSkipLast,
'ZREVRANGEBYSCORE' => $prefixFirst,
'WATCH' => $prefixAll,
/* ---------------- Redis 2.6 ---------------- */
'PTTL' => 'static::first',
'PEXPIRE' => 'static::first',
'PEXPIREAT' => 'static::first',
'PSETEX' => 'static::first',
'INCRBYFLOAT' => 'static::first',
'BITOP' => 'static::skipFirst',
'BITCOUNT' => 'static::first',
'HINCRBYFLOAT' => 'static::first',
'EVAL' => 'static::evalKeys',
'EVALSHA' => 'static::evalKeys',
'MIGRATE' => 'static::migrate',
'PTTL' => $prefixFirst,
'PEXPIRE' => $prefixFirst,
'PEXPIREAT' => $prefixFirst,
'PSETEX' => $prefixFirst,
'INCRBYFLOAT' => $prefixFirst,
'BITOP' => $prefixSkipFirst,
'BITCOUNT' => $prefixFirst,
'HINCRBYFLOAT' => $prefixFirst,
'EVAL' => $prefixEvalKeys,
'EVALSHA' => $prefixEvalKeys,
'MIGRATE' => $prefixMigrate,
/* ---------------- Redis 2.8 ---------------- */
'SSCAN' => 'static::first',
'ZSCAN' => 'static::first',
'HSCAN' => 'static::first',
'PFADD' => 'static::first',
'PFCOUNT' => 'static::all',
'PFMERGE' => 'static::all',
'ZLEXCOUNT' => 'static::first',
'ZRANGEBYLEX' => 'static::first',
'ZREMRANGEBYLEX' => 'static::first',
'ZREVRANGEBYLEX' => 'static::first',
'BITPOS' => 'static::first',
'SSCAN' => $prefixFirst,
'ZSCAN' => $prefixFirst,
'HSCAN' => $prefixFirst,
'PFADD' => $prefixFirst,
'PFCOUNT' => $prefixAll,
'PFMERGE' => $prefixAll,
'ZLEXCOUNT' => $prefixFirst,
'ZRANGEBYLEX' => $prefixFirst,
'ZREMRANGEBYLEX' => $prefixFirst,
'ZREVRANGEBYLEX' => $prefixFirst,
'BITPOS' => $prefixFirst,
/* ---------------- Redis 3.2 ---------------- */
'HSTRLEN' => 'static::first',
'BITFIELD' => 'static::first',
'GEOADD' => 'static::first',
'GEOHASH' => 'static::first',
'GEOPOS' => 'static::first',
'GEODIST' => 'static::first',
'GEORADIUS' => 'static::georadius',
'GEORADIUSBYMEMBER' => 'static::georadius',
);
'HSTRLEN' => $prefixFirst,
'BITFIELD' => $prefixFirst,
'GEOADD' => $prefixFirst,
'GEOHASH' => $prefixFirst,
'GEOPOS' => $prefixFirst,
'GEODIST' => $prefixFirst,
'GEORADIUS' => $prefixGeoradius,
'GEORADIUSBYMEMBER' => $prefixGeoradius,
];
}
/**
@@ -197,7 +209,7 @@ class KeyPrefixProcessor implements ProcessorInterface
if ($command instanceof PrefixableCommandInterface) {
$command->prefixKeys($this->prefix);
} elseif (isset($this->commands[$commandID = strtoupper($command->getId())])) {
call_user_func($this->commands[$commandID], $command, $this->prefix);
$this->commands[$commandID]($command, $this->prefix);
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* 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 <suppakilla@gmail.com>
*/
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);
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* 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 <suppakilla@gmail.com>
*/
class XDEL extends RedisCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'XDEL';
}
/**
* {@inheritdoc}
*/
public function setArguments(array $arguments)
{
$arguments = self::normalizeVariadic($arguments);
parent::setArguments($arguments);
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* 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 <suppakilla@gmail.com>
*/
class XLEN extends RedisCommand
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'XLEN';
}
}
+63
View File
@@ -0,0 +1,63 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* 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 <suppakilla@gmail.com>
*/
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;
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* 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 <suppakilla@gmail.com>
*/
class XREVRANGE extends XRANGE
{
/**
* {@inheritdoc}
*/
public function getId()
{
return 'XREVRANGE';
}
}
+55
View File
@@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* 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 <suppakilla@gmail.com>
*/
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);
}
}
+50 -7
View File
@@ -22,6 +22,8 @@ use Predis\Connection\FactoryInterface;
use Predis\Connection\NodeConnectionInterface;
use Predis\NotSupportedException;
use Predis\Response\ErrorInterface as ErrorResponseInterface;
use Predis\Response\ServerException;
use Predis\Response\Error as ErrorResponse;
/**
* Abstraction for a Redis-backed cluster of nodes (Redis >= 3.0.0).
@@ -54,6 +56,7 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
private $strategy;
private $connections;
private $retryLimit = 5;
private $retryInterval = 10;
/**
* @param FactoryInterface $connections Optional connection factory.
@@ -82,6 +85,26 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
$this->retryLimit = (int) $retry;
}
/**
* Sets the initial retry interval (milliseconds).
*
* @param int $retryInterval Milliseconds between retries.
*/
public function setRetryInterval($retryInterval)
{
$this->retryInterval = (int) $retryInterval;
}
/**
* Returns the retry interval (milliseconds).
*
* @return int Milliseconds between retries.
*/
public function getRetryInterval()
{
return (int) $this->retryInterval;
}
/**
* {@inheritdoc}
*/
@@ -207,6 +230,7 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
private function queryClusterNodeForSlotMap(NodeConnectionInterface $connection)
{
$retries = 0;
$retryAfter = $this->retryInterval;
$command = RawCommand::create('CLUSTER', 'SLOTS');
RETRY_COMMAND: {
@@ -226,7 +250,10 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
throw new ClientException('No connections left in the pool for `CLUSTER SLOTS`');
}
usleep($retryAfter * 1000);
$retryAfter = $retryAfter * 2;
++$retries;
goto RETRY_COMMAND;
}
}
@@ -482,24 +509,40 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
*/
private function retryCommandOnFailure(CommandInterface $command, $method)
{
$failure = false;
$retries = 0;
$retryAfter = $this->retryInterval;
RETRY_COMMAND: {
try {
$response = $this->getConnectionByCommand($command)->$method($command);
} catch (ConnectionException $exception) {
$connection = $exception->getConnection();
$connection->disconnect();
$this->remove($connection);
if ($response instanceof ErrorResponse) {
$message = $response->getMessage();
if ($failure) {
if (strpos($message, 'CLUSTERDOWN') !== false) {
throw new ServerException($message);
}
}
} catch (\Throwable $exception) {
usleep($retryAfter * 1000);
$retryAfter = $retryAfter * 2;
if ($exception instanceof ConnectionException) {
$connection = $exception->getConnection();
if ($connection) {
$connection->disconnect();
$this->remove($connection);
}
}
if ($retries === $this->retryLimit) {
throw $exception;
} elseif ($this->useClusterSlots) {
$this->askSlotMap();
}
$failure = true;
++$retries;
goto RETRY_COMMAND;
}
+8
View File
@@ -26,6 +26,14 @@ class Parameters implements ParametersInterface
'port' => 6379,
);
/**
* Set of connection paramaters already filtered
* for NULL or 0-length string values.
*
* @var array
*/
protected $parameters;
/**
* @param array $parameters Named array of connection parameters.
*/
@@ -70,6 +70,11 @@ class SentinelReplication implements ReplicationInterface
*/
protected $sentinels = array();
/**
* @var int
*/
protected $sentinelIndex = 0;
/**
* @var NodeConnectionInterface
*/
@@ -264,6 +269,12 @@ class SentinelReplication implements ReplicationInterface
$parameters['database'] = null;
$parameters['username'] = null;
// don't leak password from between configurations
// https://github.com/predis/predis/pull/807/#discussion_r985764770
if (! isset($parameters['password'])) {
$parameters['password'] = null;
}
if (!isset($parameters['timeout'])) {
$parameters['timeout'] = $this->sentinelTimeout;
}
@@ -282,11 +293,13 @@ class SentinelReplication implements ReplicationInterface
public function getSentinelConnection()
{
if (!$this->sentinelConnection) {
if (!$this->sentinels) {
if ($this->sentinelIndex >= count($this->sentinels)) {
$this->sentinelIndex = 0;
throw new \Predis\ClientException('No sentinel server available for autodiscovery.');
}
$sentinel = array_shift($this->sentinels);
$sentinel = $this->sentinels[$this->sentinelIndex];
++$this->sentinelIndex;
$this->sentinelConnection = $this->createSentinelConnection($sentinel);
}
@@ -307,6 +320,7 @@ class SentinelReplication implements ReplicationInterface
);
$this->sentinels = array();
$this->sentinelIndex = 0;
// NOTE: sentinel server does not return itself, so we add it back.
$this->sentinels[] = $sentinel->getParameters()->toArray();
+2 -2
View File
@@ -260,7 +260,7 @@ class StreamConnection extends AbstractConnection
$socket = $this->getResource();
while (($length = strlen($buffer)) > 0) {
$written = @fwrite($socket, $buffer);
$written = is_resource($socket) ? @fwrite($socket, $buffer) : false;
if ($length === $written) {
return;
@@ -304,7 +304,7 @@ class StreamConnection extends AbstractConnection
$bytesLeft = ($size += 2);
do {
$chunk = fread($socket, min($bytesLeft, 4096));
$chunk = is_resource($socket) ? fread($socket, min($bytesLeft, 4096)) : false;
if ($chunk === false || $chunk === '') {
$this->onConnectionError('Error while reading bytes from the server.');
+3 -3
View File
@@ -12,7 +12,7 @@
namespace Predis\Monitor;
use Predis\ClientInterface;
use Predis\Connection\AggregateConnectionInterface;
use Predis\Connection\Cluster\ClusterInterface;
use Predis\NotSupportedException;
/**
@@ -56,9 +56,9 @@ class Consumer implements \Iterator
*/
private function assertClient(ClientInterface $client)
{
if ($client->getConnection() instanceof AggregateConnectionInterface) {
if ($client->getConnection() instanceof ClusterInterface) {
throw new NotSupportedException(
'Cannot initialize a monitor consumer over aggregate connections.'
'Cannot initialize a monitor consumer over cluster connections.'
);
}
+3 -3
View File
@@ -14,7 +14,7 @@ namespace Predis\PubSub;
use Predis\ClientException;
use Predis\ClientInterface;
use Predis\Command\Command;
use Predis\Connection\AggregateConnectionInterface;
use Predis\Connection\Cluster\ClusterInterface;
use Predis\NotSupportedException;
/**
@@ -62,9 +62,9 @@ class Consumer extends AbstractConsumer
*/
private function checkCapabilities(ClientInterface $client)
{
if ($client->getConnection() instanceof AggregateConnectionInterface) {
if ($client->getConnection() instanceof ClusterInterface) {
throw new NotSupportedException(
'Cannot initialize a PUB/SUB consumer over aggregate connections.'
'Cannot initialize a PUB/SUB consumer over cluster connections.'
);
}
+3 -3
View File
@@ -16,7 +16,7 @@ use Predis\ClientException;
use Predis\ClientInterface;
use Predis\Command\CommandInterface;
use Predis\CommunicationException;
use Predis\Connection\AggregateConnectionInterface;
use Predis\Connection\Cluster\ClusterInterface;
use Predis\NotSupportedException;
use Predis\Protocol\ProtocolException;
use Predis\Response\ErrorInterface as ErrorResponseInterface;
@@ -66,9 +66,9 @@ class MultiExec implements ClientContextInterface
*/
private function assertClient(ClientInterface $client)
{
if ($client->getConnection() instanceof AggregateConnectionInterface) {
if ($client->getConnection() instanceof ClusterInterface) {
throw new NotSupportedException(
'Cannot initialize a MULTI/EXEC transaction over aggregate connections.'
'Cannot initialize a MULTI/EXEC transaction over cluster connections.'
);
}
+5 -1
View File
@@ -10,6 +10,7 @@
*/
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Util\Test as TestUtil;
use Predis\Client;
use Predis\Command;
use Predis\Connection;
@@ -315,7 +316,10 @@ abstract class PredisTestCase extends \PHPUnit\Framework\TestCase
*/
protected function getRequiredRedisServerVersion(): ?string
{
$annotations = $this->getAnnotations();
$annotations = TestUtil::parseTestMethodAnnotations(
get_class($this),
$this->getName(false)
);
if (isset($annotations['method']['requiresRedisVersion'], $annotations['method']['group']) &&
!empty($annotations['method']['requiresRedisVersion']) &&
+16
View File
@@ -101,6 +101,22 @@ class SREM_Test extends PredisCommandTestCase
$this->assertSame(0, $redis->srem('digits', 1));
}
/**
* @group connected
* @requiresRedisVersion >= 2.4.0
*/
public function testRemovesMembersInArrayTypeFromSetVariadic(): void
{
$redis = $this->getClient();
$redis->sadd('letters', 'a', 'b', 'c', 'd');
$this->assertSame(2, $redis->srem('letters', ['b', 'd', 'z']));
$this->assertSameValues(array('a', 'c'), $redis->smembers('letters'));
$this->assertSame(0, $redis->srem('digits', [1]));
}
/**
* @group connected
*/
+290
View File
@@ -0,0 +1,290 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* 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']);
}
}
+90
View File
@@ -0,0 +1,90 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* 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');
}
}
+99
View File
@@ -0,0 +1,99 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* 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');
}
}
+159
View File
@@ -0,0 +1,159 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* 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');
}
}
@@ -0,0 +1,159 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* 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');
}
}
+201
View File
@@ -0,0 +1,201 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) Daniele Alessandri <suppakilla@gmail.com>
*
* 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);
}
}
@@ -1301,4 +1301,144 @@ class RedisClusterTest extends PredisTestCase
$this->assertEquals($cluster, $unserialized);
}
/**
* @medium
* @group disconnected
* @group slow
*/
public function testRetryCommandSuccessOnClusterDownErrors()
{
$clusterDownError= new Response\Error("CLUSTERDOWN") ;
$command = Command\RawCommand::create('get', 'node:1001');
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
$connection1->expects($this->exactly(3))
->method('executeCommand')
->with($command)
->will($this->onConsecutiveCalls(
$clusterDownError,
$clusterDownError,
'foobar'));
$cluster = new RedisCluster(new Connection\Factory());
$cluster->useClusterSlots(false);
$cluster->setRetryLimit(2);
$cluster->add($connection1);
$this->assertSame('foobar', $cluster->executeCommand($command));
}
/**
* @medium
* @group disconnected
* @group slow
*/
public function testRetryCommandFailureOnClusterDownErrors()
{
$this->expectException('Predis\Response\ServerException');
$this->expectExceptionMessage('CLUSTERDOWN');
$clusterDownError= new Response\Error("CLUSTERDOWN") ;
$command = Command\RawCommand::create('get', 'node:1001');
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
$connection1->expects($this->exactly(3))
->method('executeCommand')
->with($command)
->will($this->onConsecutiveCalls(
$clusterDownError,
$clusterDownError,
$clusterDownError
));
$cluster = new RedisCluster(new Connection\Factory());
$cluster->useClusterSlots(false);
$cluster->setRetryLimit(2);
$cluster->add($connection1);
$cluster->executeCommand($command);
}
/**
* @medium
* @group disconnected
* @group slow
*/
public function testQueryClusterNodeForSlotMapPauseDurationOnRetry()
{
$slotsmap = array(
array(0, 5460, array('127.0.0.1', 9381), array()),
array(5461, 10922, array('127.0.0.1', 6382), array()),
array(10923, 16383, array('127.0.0.1', 6383), array()),
);
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5460');
$connection1
->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand(
'CLUSTER', array('SLOTS')
))
->willThrowException(
new Connection\ConnectionException($connection1, 'Unknown connection error [127.0.0.1:6381]')
);
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6382?slots=5461-10922');
$connection2
->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand(
'CLUSTER', array('SLOTS')
))
->willThrowException(
new Connection\ConnectionException($connection2, 'Unknown connection error [127.0.0.1:6383]')
);
$connection3 = $this->getMockConnection('tcp://127.0.0.1:6383?slots=10923-16383');
$connection3
->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand(
'CLUSTER', array('SLOTS')
))
->willReturn($slotsmap);
$factory = $this->getMockBuilder('Predis\Connection\FactoryInterface')->getMock();
$factory
->expects($this->never())
->method('create');
// TODO: I'm not sure about mocking a protected method, but it'll do for now
/** @var Connection\Cluster\RedisCluster|MockObject */
$cluster = $this->getMockBuilder('Predis\Connection\Cluster\RedisCluster')
->onlyMethods(array('getRandomConnection'))
->setConstructorArgs(array($factory))
->getMock();
$cluster
->expects($this->exactly(3))
->method('getRandomConnection')
->willReturnOnConsecutiveCalls($connection1, $connection2, $connection3);
$cluster->add($connection1);
$cluster->add($connection2);
$cluster->add($connection3);
$cluster->setRetryInterval(2000);
$startTime = time() ;
$cluster->askSlotMap();
$endTime = time();
$totalTime=$endTime-$startTime;
$t1 = $cluster->getRetryInterval() ;
$t2 = $t1 * 2;
$expectedTime = ($t1 + $t2 )/1000 ; // expected time for 2 retries (fail 1=wait 2s, fail 2=wait 4s , OK)
$this->AssertEqualsWithDelta($expectedTime, $totalTime, 1, "Unexpected execution time") ;
$this->assertCount(16384, $cluster->getSlotMap());
}
}
@@ -62,6 +62,7 @@ class SentinelReplicationTest extends PredisTestCase
$this->assertArrayNotHasKey('database', $parameters, 'Parameter `database` was expected to not exist in connection parameters');
$this->assertArrayNotHasKey('username', $parameters, 'Parameter `username` was expected to not exist in connection parameters');
$this->assertArrayNotHasKey('password', $parameters, 'Parameter `password` was expected to not exist in connection parameters');
}
/**
@@ -117,6 +118,30 @@ class SentinelReplicationTest extends PredisTestCase
$this->assertNotNull($parameters->database);
}
/**
* @group disconnected
*/
public function testConnectionParametersInstanceForSentinelConnectionIsNotModifiedEmptyPassword(): void
{
$sentinel1 = Connection\Parameters::create('tcp://127.0.0.1:5381?role=sentinel&database=1&password=');
$sentinel2 = Connection\Parameters::create('tcp://127.0.0.1:5381?role=sentinel&database=1');
$replication1 = $this->getReplicationConnection('svc', array($sentinel1));
$replication2 = $this->getReplicationConnection('svc', array($sentinel2));
$parameters1 = $replication1->getSentinelConnection()->getParameters();
$parameters2 = $replication2->getSentinelConnection()->getParameters();
$this->assertSame($sentinel1, $parameters1);
$this->assertSame($sentinel2, $parameters2);
$this->assertNull($parameters1->password);
$this->assertNull($parameters2->password);
$this->assertNotNull($parameters1->database);
$this->assertNotNull($parameters2->database);
}
/**
* @group disconnected
*/
@@ -1447,6 +1472,52 @@ class SentinelReplicationTest extends PredisTestCase
$this->assertEquals($strategy, $unserialized->getReplicationStrategy());
}
/**
* @group disconnected
*/
public function testMethodGetSentinelConnectionAfterSentinelRestart(): void
{
$sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?role=sentinel&alias=sentinel1');
$sentinel1
->expects($this->exactly(2))
->method('executeCommand')
->with($this->isRedisCommand(
'SENTINEL', array('sentinels', 'svc')
))
->willReturnOnConsecutiveCalls(
$this->throwException(new Connection\ConnectionException($sentinel1, 'Unknown connection error [127.0.0.1:5381]')),
array(
array(
'name', '127.0.0.1:5382',
'ip', '127.0.0.1',
'port', '5382',
'runid', 'f53b52d281be5cdd4873700c94846af8dbe47209',
'flags', 'sentinel',
)
)
);
$sentinel2 = $this->getMockSentinelConnection('tcp://127.0.0.1:5382?role=sentinel&alias=sentinel2');
$sentinel2
->expects($this->once())
->method('executeCommand')
->with($this->isRedisCommand(
'SENTINEL', array('sentinels', 'svc')
))
->willThrowException(
new Connection\ConnectionException($sentinel2, 'Unknown connection error [127.0.0.1:5382]')
);
$replication = $this->getReplicationConnection('svc', array($sentinel1, $sentinel2));
try {
$replication->updateSentinels();
} catch (\Predis\ClientException $exception){
$this->assertEquals('No sentinel server available for autodiscovery.', $exception->getMessage());
}
$replication->updateSentinels();
}
// ******************************************************************** //
// ---- HELPER METHODS ------------------------------------------------ //
// ******************************************************************** //
@@ -57,6 +57,29 @@ class StreamConnectionTest extends PredisConnectionTestCase
$connection->connect();
}
/**
* @group disconnected
*/
public function testDoesntThrowErrorOnInvalidResource(): void
{
$this->expectException('Predis\Connection\ConnectionException');
$cmdSelect = RawCommand::create('SELECT', '1000');
$invalidResource = null;
/** @var NodeConnectionInterface|MockObject */
$connection = $this
->getMockBuilder($this->getConnectionClass())
->onlyMethods(array('getResource'))
->setConstructorArgs(array(new Parameters()))
->getMock();
$connection
->method('getResource')
->willReturn($invalidResource);
$connection->writeRequest($cmdSelect);
}
// ******************************************************************** //
// ---- INTEGRATION TESTS --------------------------------------------- //
// ******************************************************************** //
+2 -2
View File
@@ -46,9 +46,9 @@ class ConsumerTest extends PredisTestCase
public function testMonitorConsumerDoesNotWorkOnClusters(): void
{
$this->expectException('Predis\NotSupportedException');
$this->expectExceptionMessage('Cannot initialize a monitor consumer over aggregate connections');
$this->expectExceptionMessage('Cannot initialize a monitor consumer over cluster connections');
$cluster = $this->getMockBuilder('Predis\Connection\AggregateConnectionInterface')->getMock();
$cluster = $this->getMockBuilder('Predis\Connection\Cluster\ClusterInterface')->getMock();
$client = new Client($cluster);
new MonitorConsumer($client);
+1 -1
View File
@@ -45,7 +45,7 @@ class ConsumerTest extends PredisTestCase
public function testPubSubConsumerDoesNotWorkOnClusters(): void
{
$this->expectException('Predis\NotSupportedException');
$this->expectExceptionMessage('Cannot initialize a PUB/SUB consumer over aggregate connections');
$this->expectExceptionMessage('Cannot initialize a PUB/SUB consumer over cluster connections');
$cluster = $this->getMockBuilder('Predis\Connection\Cluster\ClusterInterface')->getMock();
$client = new Client($cluster);