From 946c4b7bd718cad74a5cb0f012bc9e83609e2e38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20Kr=C3=BCss?= Date: Tue, 9 May 2023 10:39:00 -0700 Subject: [PATCH] [v2.x] Relay support (#1263) --- .gitattributes | 1 + .github/workflows/stack.yml | 8 +- .github/workflows/tests.yml | 15 +- CHANGELOG.md | 3 +- FAQ.md | 96 ++--- README.md | 27 +- composer.json | 3 + examples/pubsub_consumer.php | 6 +- examples/relay_compression.php | 51 +++ examples/relay_connection.php | 55 +++ examples/relay_events.php | 61 ++++ examples/relay_pubsub_consumer.php | 59 +++ phpunit.relay.xml | 42 +++ phpunit.xml.dist | 12 +- src/Client.php | 58 ++- src/Command/Redis/ACL.php | 24 ++ src/Command/Redis/COMMAND.php | 11 + src/Command/Redis/CONFIG.php | 4 + src/Command/Redis/HGETALL.php | 4 + src/Command/Redis/HRANDFIELD.php | 19 + src/Command/Redis/LCS.php | 4 + src/Command/Redis/TDigest/TDIGESTBYRANK.php | 27 ++ .../Redis/TDigest/TDIGESTBYREVRANK.php | 27 ++ src/Command/Redis/TDigest/TDIGESTCDF.php | 27 ++ src/Command/Redis/TDigest/TDIGESTMAX.php | 21 ++ src/Command/Redis/TDigest/TDIGESTMIN.php | 21 ++ src/Command/Redis/TDigest/TDIGESTQUANTILE.php | 27 ++ .../Redis/TDigest/TDIGESTTRIMMED_MEAN.php | 21 ++ src/Command/Redis/TYPE.php | 22 ++ src/Command/Redis/ZPOPMAX.php | 6 +- src/Command/Redis/ZPOPMIN.php | 6 +- src/Command/Redis/ZRANGE.php | 6 +- src/Command/Traits/With/WithScores.php | 4 +- src/Configuration/Option/Connections.php | 8 + src/Connection/ParametersInterface.php | 7 +- src/Connection/RelayConnection.php | 337 ++++++++++++++++++ src/Connection/RelayMethods.php | 136 +++++++ src/Pipeline/Pipeline.php | 2 +- src/Pipeline/RelayAtomic.php | 69 ++++ src/Pipeline/RelayPipeline.php | 75 ++++ src/PubSub/AbstractConsumer.php | 4 +- src/PubSub/Consumer.php | 10 +- src/PubSub/RelayConsumer.php | 114 ++++++ src/Transaction/MultiExec.php | 44 ++- tests/PHPUnit/PredisConnectionTestCase.php | 10 + tests/PHPUnit/PredisTestCase.php | 7 +- tests/Predis/Command/Redis/BZMPOP_Test.php | 2 +- tests/Predis/Command/Redis/BZPOPMAX_Test.php | 2 +- tests/Predis/Command/Redis/BZPOPMIN_Test.php | 2 +- tests/Predis/Command/Redis/CLIENT_Test.php | 1 + tests/Predis/Command/Redis/COMMAND_Test.php | 4 + tests/Predis/Command/Redis/DISCARD_Test.php | 19 +- tests/Predis/Command/Redis/EXEC_Test.php | 2 +- tests/Predis/Command/Redis/EXPIREAT_Test.php | 2 +- tests/Predis/Command/Redis/EXPIRE_Test.php | 2 +- tests/Predis/Command/Redis/FCALL_Test.php | 3 + tests/Predis/Command/Redis/FUNCTIONS_Test.php | 6 + tests/Predis/Command/Redis/GEOADD_Test.php | 2 +- tests/Predis/Command/Redis/GETBIT_Test.php | 6 +- tests/Predis/Command/Redis/HMGET_Test.php | 24 ++ tests/Predis/Command/Redis/MIGRATE_Test.php | 13 + tests/Predis/Command/Redis/MONITOR_Test.php | 1 + tests/Predis/Command/Redis/MOVE_Test.php | 1 - tests/Predis/Command/Redis/MULTI_Test.php | 21 ++ tests/Predis/Command/Redis/PING_Test.php | 16 + .../Predis/Command/Redis/PSUBSCRIBE_Test.php | 1 + tests/Predis/Command/Redis/PUBLISH_Test.php | 1 + .../Command/Redis/PUNSUBSCRIBE_Test.php | 1 + tests/Predis/Command/Redis/QUIT_Test.php | 1 + tests/Predis/Command/Redis/SELECT_Test.php | 1 + tests/Predis/Command/Redis/SUBSCRIBE_Test.php | 1 + .../Command/Redis/Search/FTAGGREGATE_Test.php | 4 + .../Command/Redis/Search/FTCURSOR_Test.php | 4 + .../Command/Redis/Search/FTEXPLAIN_Test.php | 4 + .../Command/Redis/Search/FTINFO_Test.php | 3 + .../Redis/Search/FTSPELLCHECK_Test.php | 2 +- .../Command/Redis/Search/FTSUGGET_Test.php | 6 +- .../Command/Redis/Search/FTSUGLEN_Test.php | 4 + .../Redis/TDigest/TDIGESTBYRANK_Test.php | 4 +- .../Redis/TDigest/TDIGESTBYREVRANK_Test.php | 4 +- .../Command/Redis/TDigest/TDIGESTCDF_Test.php | 4 +- .../Command/Redis/TDigest/TDIGESTMAX_Test.php | 4 +- .../Redis/TDigest/TDIGESTMERGE_Test.php | 10 +- .../Command/Redis/TDigest/TDIGESTMIN_Test.php | 4 +- .../Redis/TDigest/TDIGESTQUANTILE_Test.php | 4 +- .../Redis/TDigest/TDIGESTRESET_Test.php | 4 +- .../TDigest/TDIGESTTRIMMED_MEAN_Test.php | 4 +- .../Command/Redis/TimeSeries/TSADD_Test.php | 4 + .../Command/Redis/TimeSeries/TSALTER_Test.php | 4 + .../Redis/TimeSeries/TSCREATERULE_Test.php | 4 + .../Redis/TimeSeries/TSCREATE_Test.php | 4 + .../Redis/TimeSeries/TSDECRBY_Test.php | 4 + .../Redis/TimeSeries/TSDELETERULE_Test.php | 4 + .../Command/Redis/TimeSeries/TSDEL_Test.php | 4 + .../Command/Redis/TimeSeries/TSGET_Test.php | 4 + .../Redis/TimeSeries/TSINCRBY_Test.php | 4 + .../Command/Redis/TimeSeries/TSINFO_Test.php | 4 + .../Command/Redis/TimeSeries/TSMADD_Test.php | 4 + .../Command/Redis/TimeSeries/TSMGET_Test.php | 4 + .../Redis/TimeSeries/TSMRANGE_Test.php | 4 + .../Redis/TimeSeries/TSMREVRANGE_Test.php | 4 + .../Redis/TimeSeries/TSQUERYINDEX_Test.php | 4 + .../Command/Redis/TimeSeries/TSRANGE_Test.php | 4 + .../Redis/TimeSeries/TSREVRANGE_Test.php | 4 + .../Command/Redis/TopK/TOPKINFO_Test.php | 2 +- .../Command/Redis/TopK/TOPKRESERVE_Test.php | 2 +- .../Predis/Command/Redis/UNSUBSCRIBE_Test.php | 1 + tests/Predis/Command/Redis/UNWATCH_Test.php | 14 + tests/Predis/Command/Redis/WATCH_Test.php | 1 + tests/Predis/Command/Redis/ZADD_Test.php | 8 +- tests/Predis/Command/Redis/ZINCRBY_Test.php | 8 +- .../Predis/Command/Redis/ZINTERSTORE_Test.php | 2 +- tests/Predis/Command/Redis/ZINTER_Test.php | 2 +- tests/Predis/Command/Redis/ZMPOP_Test.php | 2 +- tests/Predis/Command/Redis/ZMSCORE_Test.php | 2 +- tests/Predis/Command/Redis/ZPOPMAX_Test.php | 9 +- tests/Predis/Command/Redis/ZPOPMIN_Test.php | 9 +- .../Command/Redis/ZRANGEBYSCORE_Test.php | 6 +- tests/Predis/Command/Redis/ZRANGE_Test.php | 4 +- .../Command/Redis/ZREVRANGEBYSCORE_Test.php | 6 +- tests/Predis/Command/Redis/ZREVRANGE_Test.php | 4 +- tests/Predis/Command/Redis/ZSCORE_Test.php | 6 +- .../Predis/Command/Redis/ZUNIONSTORE_Test.php | 2 +- tests/Predis/Command/Redis/ZUNION_Test.php | 2 +- .../Configuration/Option/ConnectionsTest.php | 3 +- .../Connection/Cluster/RedisClusterTest.php | 1 + tests/Predis/Connection/ParametersTest.php | 17 + .../Predis/Connection/RelayConnectionTest.php | 234 ++++++++++++ tests/Predis/Transaction/MultiExecTest.php | 20 ++ tests/README.md | 2 +- 130 files changed, 2037 insertions(+), 205 deletions(-) create mode 100644 examples/relay_compression.php create mode 100644 examples/relay_connection.php create mode 100644 examples/relay_events.php create mode 100644 examples/relay_pubsub_consumer.php create mode 100644 phpunit.relay.xml create mode 100644 src/Connection/RelayConnection.php create mode 100644 src/Connection/RelayMethods.php create mode 100644 src/Pipeline/RelayAtomic.php create mode 100644 src/Pipeline/RelayPipeline.php create mode 100644 src/PubSub/RelayConsumer.php create mode 100644 tests/Predis/Connection/RelayConnectionTest.php diff --git a/.gitattributes b/.gitattributes index 24d268ad..226cb6ab 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13,6 +13,7 @@ /CONTRIBUTING.md export-ignore linguist-documentation /FAQ.md export-ignore linguist-documentation /VERSION export-ignore +/phpunit.relay.xml export-ignore /phpunit.xml.dist export-ignore /phpstan.dist.neon export-ignore /phpstan-tests.dist.neon export-ignore diff --git a/.github/workflows/stack.yml b/.github/workflows/stack.yml index df25cac9..c5a7ad60 100644 --- a/.github/workflows/stack.yml +++ b/.github/workflows/stack.yml @@ -41,6 +41,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} + extensions: relay - name: Get Composer cache directory id: composer-cache @@ -58,5 +59,8 @@ jobs: PHP_VERSION: ${{ matrix.php }} run: composer install --ansi --no-progress --prefer-dist - - name: Run PHPUnit tests - run: vendor/bin/phpunit + - name: Run tests + run: vendor/bin/phpunit --group realm-stack + + - name: Run tests using Relay + run: vendor/bin/phpunit --group realm-stack -c phpunit.relay.xml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 647a0029..b5259142 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ concurrency: jobs: predis: - name: PHP ${{ matrix.php }} Redis ${{ matrix.redis }} + name: PHP ${{ matrix.php }} (Redis ${{ matrix.redis }}) runs-on: ubuntu-latest strategy: @@ -50,6 +50,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} + extensions: relay coverage: ${{ (matrix.php == '8.1' && matrix.redis == '7') && 'xdebug' || 'none' }} - name: Install Composer dependencies @@ -58,13 +59,17 @@ jobs: dependency-versions: highest composer-options: ${{ matrix.php == '8.0' && '--ignore-platform-reqs' || '' }} - - name: Run PHPUnit tests + - name: Run tests if: ${{ matrix.php != '8.1' || matrix.redis != '7' }} - run: vendor/bin/phpunit --verbose --exclude-group realm-stack + run: vendor/bin/phpunit - - name: Run PHPUnit tests with coverage + - name: Run tests with coverage if: ${{ matrix.php == '8.1' && matrix.redis == '7' }} - run: vendor/bin/phpunit --verbose --exclude-group realm-stack --coverage-clover build/logs/clover.xml --coverage-filter ./src + run: vendor/bin/phpunit --coverage-clover build/logs/clover.xml --coverage-filter ./src + + - name: Run tests using Relay + if: ${{ matrix.redis >= '6' }} + run: vendor/bin/phpunit -c phpunit.relay.xml - name: Send coverage to Coveralls env: diff --git a/CHANGELOG.md b/CHANGELOG.md index ceb2660a..f89c35f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,13 @@ ## Unreleased ### Added -- Added support for `ACL SETUSER, GETUSER, DRYRUN` commands +- Added support for [Relay](https://github.com/predis/predis/wiki/Using-Relay) (#1263) - Added support for `FCALL_RO` command - Added support for `Redis JSON` module - Added support for `Redis Bloom` module - Added support for `Redis Search` module - Added support for `Redis TimeSeries` module +- Added support for `ACL SETUSER, GETUSER, DRYRUN` commands ### Fixed - Fixed prefixes for `XTRIM` and `XREVRANGE` commands diff --git a/FAQ.md b/FAQ.md index ed96cf31..65d34a0b 100644 --- a/FAQ.md +++ b/FAQ.md @@ -1,7 +1,6 @@ -# Some frequently asked questions about Predis # -________________________________________________ +# Frequently asked questions about Predis # -### What is the point of Predis? ### +## What is the point of Predis? ## The main point of Predis is about offering a highly customizable and extensible client for Redis, that can be easily extended by developers while still being reasonably fast. With Predis you can @@ -13,12 +12,12 @@ a great asset since it allows developers to add new and still missing features o the standard behaviour of the library without the need to break dependencies in production code (at least to some degree). -### Does Predis support UNIX domain sockets and persistent connections? ### +## Does Predis support UNIX domain sockets and persistent connections? ## Yes. Obviously persistent connections actually work only when using PHP configured as a persistent process reused by the web server (see [PHP-FPM](http://php-fpm.org)). -### Does Predis support SSL-encrypted connections? ### +## Does Predis support SSL-encrypted connections? ## Yes. Encrypted connections are mostly useful when connecting to Redis instances exposed by various cloud hosting providers without the need to configure an SSL proxy, but you should also take into @@ -26,16 +25,21 @@ account the general performances degradation especially during the connect() ope handshake must be performed to secure the connection. Persistent SSL-encrypted connections may help in that respect, but they are supported only when running on PHP >= 7.0.0. -### Does Predis support transparent (de)serialization of values? ### +## Does Predis support transparent (de)serialization of values? ## -No and it will not ever do that by default. The reason behind this decision is that serialization is -usually something that developers prefer to customize depending on their needs and can not be easily -generalized when using Redis because of the many possible access patterns for your data. This does -not mean that it is impossible to have such a feature since you can leverage the extensibility of -this library to define your own serialization-aware commands. You can find more details about how to -do that [on this issue](http://github.com/predis/predis/issues/29#issuecomment-1202624). +When using [Relay](https://github.com/cachewerk/relay) as the underlying client, several +serialization and compression algorithms are supported. This slightly increases CPU usage, +but significantly reduces bytes sent over the network and Redis memory usage. -### How can I force Predis to connect to Redis before sending any command? ### +Without Relay, Predis will not serialize data and will never do that by default. The reason +behind this decision is that serialization is usually something that developers prefer to +customize depending on their needs and can not be easilygeneralized when using Redis because +of the many possible access patterns for your data. This does not mean that it is impossible +to have such a feature since you can leverage the extensibility of this library to define +your own serialization-aware commands. You can find more details about how to do that +[on this issue](http://github.com/predis/predis/issues/29#issuecomment-1202624). + +## How can I force Predis to connect to Redis before sending any command? ## Explicitly connecting to Redis is usually not needed since the client initializes connections lazily only when they are needed. Admittedly, this behavior can be inconvenient in certain scenarios when @@ -55,7 +59,7 @@ try { $client->info(); ``` -### How Predis abstracts Redis commands? ### +## How Predis abstracts Redis commands? ## The approach used to implement Redis commands is quite simple: by default each command follows the same signature as defined on the [Redis documentation](http://redis.io/commands) which makes things @@ -75,12 +79,20 @@ $client->hmset('my:hash', ['field1'=>'value1', 'field2'=>'value2']); // single n An exception to this rule is [`SORT`](http://redis.io/commands/sort) for which modifiers are passed [using a named array](tests/Predis/Command/KeySortTest.php#L54-L75). +## When should I use Relay? ## -# Speaking about performances... # -_________________________________________________ +If you care about performance, __always__. [Relay](https://github.com/cachewerk/relay) is free to use. +## When should I use PhpRedis? ### -### Predis is a pure-PHP implementation: it can not be fast enough! ### +Predis is fast enough when Redis is located on the same machine as PHP, more on that later. + +[PhpRedis](https://github.com/phpredis/phpredis) (and Relay) perform significantly better when +network I/O is involved, due to their ability to compress data by ~75%. Fewer bytes and received +sent over the network [means faster operations](https://akalongman.medium.com/phpredis-vs-predis-comparison-on-real-production-data-a819b48cbadb), +and potentially cost savings when network traffic isn't free (e.g. AWS Elasticache Inter-AZ transfer costs). + +## Predis is a pure-PHP implementation: it can not be fast enough! ## It really depends, but most of the times the answer is: _yes, it is fast enough_. I will give you a couple of easy numbers with a simple test that uses a single client and is executed by PHP 5.5.6 @@ -92,7 +104,7 @@ against a local instance of Redis 2.8 that runs under Ubuntu 13.10 on a Intel Q6 0.130 seconds to fetch 30000 keys using _KEYS *_. ``` -How does it compare with [__phpredis__](http://github.com/nicolasff/phpredis), a nice C extension +How does it compare with [__PhpRedis__](http://github.com/phpredis/phpredis), a nice C extension providing an efficient client for Redis? ``` @@ -101,7 +113,7 @@ providing an efficient client for Redis? 0.035 seconds to fetch 30000 keys using "KEYS *"". ``` -Wow __phpredis__ seems much faster! Well, we are comparing a C extension with a pure-PHP library so +Wow __PhpRedis__ seems much faster! Well, we are comparing a C extension with a pure-PHP library so lower numbers are quite expected but there is a fundamental flaw in them: is this really how you are going to use Redis in your application? Are you really going to send thousands of commands using a for-loop on each page request using a single client instance? If so... well I guess you are probably @@ -119,7 +131,7 @@ Using Predis: 3200 GET/sec while retrieving the very same values 0.132 seconds to fetch 30000 keys using "KEYS *". -Using phpredis: +Using PhpRedis: 3500 SET/sec using 12 bytes for both key and value 3500 GET/sec while retrieving the very same values 0.045 seconds to fetch 30000 keys using "KEYS *". @@ -131,47 +143,3 @@ that we are measuring the overhead of client libraries implementations and the e round-trip times, so we are not really measuring how fast Redis is. Redis shines best with thousands of concurrent clients doing requests! Also, actual performances should be measured according to how your application will use Redis. - -### I am convinced, but performances for multi-bulk responses are still worse ### - -Fair enough, but there is an option available if you need even more speed and consists on installing -__[phpiredis](http://github.com/nrk/phpiredis)__ (note the additional _i_ in the name) and let the -client use it. __phpiredis__ is another C extension that wraps __hiredis__ (the official C client -library for Redis) with a thin layer exposing its features to PHP. You can then choose between two -different connection classes: - - - `Predis\Connection\PhpiredisStreamConnection` (using native PHP streams). - - `Predis\Connection\PhpiredisSocketConnection` (requires `ext-socket`). - -You will now get the benefits of a faster protocol serializer and parser just by adding a couple of -lines of code: - -```php -$client = new Predis\Client('tcp://127.0.0.1', array( - 'connections' => array( - 'tcp' => 'Predis\Connection\PhpiredisStreamConnection', - 'unix' => 'Predis\Connection\PhpiredisSocketConnection', - ), -)); -``` - -Dead simple. Nothing changes in the way you use the library in your application. So how fast is it -our basic benchmark script now? There are not much improvements for inline or short bulk responses -like the ones returned by `SET` and `GET`, but the speed for parsing multi-bulk responses is now on -par with phpredis: - -``` -Fatching 30000 keys with _KEYS *_ using Predis paired with phpiredis:: - -0.035 seconds from a local Redis instance -0.047 seconds from a remote Redis instance -``` - -### If I need an extension to get better performances, why not using phpredis? ### - -Good question. Generically speaking if you need absolute uber-speed using Redis on the localhost and -you do not care about abstractions built around some Redis features such as MULTI / EXEC, or if you -do not need any kind of extensibility or guaranteed backwards compatibility with different versions -of Redis (Predis currently supports from 1.2 up to 2.8 and the current development version), then -using __phpredis__ makes absolutely sense. Otherwise, Predis is perfect for the job and by adding -__phpiredis__ you can get a nice speed bump almost for free. diff --git a/README.md b/README.md index 913af580..d7375470 100644 --- a/README.md +++ b/README.md @@ -396,6 +396,16 @@ $response = $client->lpushrand('random_values', $seed = mt_rand()); ### Customizable connection backends ### +Predis can use different connection backends to connect to Redis. The builtin Relay integration +leverages the [Relay](https://github.com/cachewerk/relay) extension for PHP for major performance +gains, by caching a partial replica of the Redis dataset in PHP shared runtime memory. + +```php +$client = new Predis\Client('tcp://127.0.0.1', [ + 'connections' => 'relay', +]); +``` + Developers can create their own connection classes to support whole new network backends, extend existing classes or provide completely different implementations. Connection classes must implement `Predis\Connection\NodeConnectionInterface` or extend `Predis\Connection\AbstractConnection`: @@ -439,23 +449,6 @@ be disabled. See [the tests README](tests/README.md) for more details about test Predis uses GitHub Actions for continuous integration and the history for past and current builds can be found [on its actions page](https://github.com/predis/predis/actions). - -## Other ## - - -### Project related links ### - -- [Source code](https://github.com/predis/predis) -- [Wiki](https://github.com/predis/predis/wiki) -- [Issue tracker](https://github.com/predis/predis/issues) - - -### Author ### - -- [Till Krüss](https://till.im) ([Twitter](http://twitter.com/tillkruss)) -- [Daniele Alessandri](mailto:suppakilla@gmail.com) ([twitter](http://twitter.com/JoL1hAHN)) - - ### License ### The code for Predis is distributed under the terms of the MIT license (see [LICENSE](LICENSE)). diff --git a/composer.json b/composer.json index 7968991c..f842e16a 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,9 @@ "phpstan/phpstan": "^1.9", "phpunit/phpunit": "^8.0 || ~9.4.4" }, + "suggest": { + "ext-relay": "Faster connection with in-memory caching (>=0.6.2)" + }, "scripts": { "phpstan": "phpstan analyse", "style": "php-cs-fixer fix --diff --dry-run", diff --git a/examples/pubsub_consumer.php b/examples/pubsub_consumer.php index c593067e..61cc9fcd 100644 --- a/examples/pubsub_consumer.php +++ b/examples/pubsub_consumer.php @@ -24,10 +24,10 @@ $pubsub = $client->pubSubLoop(); // Subscribe to your channels $pubsub->subscribe('control_channel', 'notifications'); -// Start processing the pubsup messages. Open a terminal and use redis-cli +// Start processing the pubsub messages. Open a terminal and use redis-cli // to push messages to the channels. Examples: -// ./redis-cli PUBLISH notifications "this is a test" -// ./redis-cli PUBLISH control_channel quit_loop +// redis-cli PUBLISH notifications "this is a test" +// redis-cli PUBLISH control_channel quit_loop foreach ($pubsub as $message) { switch ($message->kind) { case 'subscribe': diff --git a/examples/relay_compression.php b/examples/relay_compression.php new file mode 100644 index 00000000..eb10a3d9 --- /dev/null +++ b/examples/relay_compression.php @@ -0,0 +1,51 @@ + 'igbinary', + 'compression' => 'lz4', +]; + +$client = new Predis\Client($single_server + $options, [ + 'connections' => 'relay', +]); + +$quote = (object) [ + 'author' => 'Jean-Luc Picard', + 'text' => 'I look forward to your report Mr. Broccoli.', +]; + +// Serialize object and apply LZ4 compression, then write key to Redis +$client->set('quote', $client->pack($quote)); + +// NOTE: In Predis v3.x serialization and compression will happen +// automatically without the need to call `pack()` and `unpack()` + +// Retrieve raw binary value from Redis +$raw = $client->get('quote'); + +// Decompress and unserialize binary value +$data = $client->unpack($raw); + +var_dump($quote == $data); // true + +var_dump($data); + +/* +object(stdClass)#11 (2) { + ["author"]=>string(15) "Jean-Luc Picard" + ["text"]=>string(43) "I look forward to your report Mr. Broccoli." +} +*/ diff --git a/examples/relay_connection.php b/examples/relay_connection.php new file mode 100644 index 00000000..f54e8fa3 --- /dev/null +++ b/examples/relay_connection.php @@ -0,0 +1,55 @@ + 1.0, + 'read_write_timeout' => 1.0, + + // Relay specific options + 'cache' => true, + // 'compression' => 'lz4', + // 'serializer' => 'igbinary', +]; + +$client = new Predis\Client($single_server + $options, [ + 'connections' => 'relay', +]); + +// Write key to Redis +$client->set('torpedo', mt_rand()); + +// Retrieve key from Redis +$client->get('torpedo'); + +// Retrieve key from Relay (without talking to Redis) +// This key is now available to all PHP workers in this FPM pool +$client->get('torpedo'); + +// For debugging only: +var_export( + $client->getConnection()->getClient()->_getKeys() +); + +/* +array ( + 'torpedo' => array ( + 0 => array ( + 'type' => 'string', + 'local-len' => 10, + 'remote-len' => 10, + 'size' => 10, + ), + ), +) +*/ diff --git a/examples/relay_events.php b/examples/relay_events.php new file mode 100644 index 00000000..cd37b0f3 --- /dev/null +++ b/examples/relay_events.php @@ -0,0 +1,61 @@ + 'relay', +]); + +/** @var Predis\Connection\RelayConnection $relay */ +$relay = $client->getConnection(); + +// establish connection +$client->ping(); + +// register `FLUSH*` callback +$relay->onFlushed( + static function (Relay\Event $event) use (&$key) { + echo 'Redis was flushed, unsetting $key...' . PHP_EOL; + $key = null; + } +); + +// register `INVALIDATE` callback +$relay->onInvalidated( + static function (Relay\Event $event) use (&$key) { + if ($event->key === 'library') { + echo "The `{$event->key}` key was invalidated, unsetting \$key..." . PHP_EOL; + $key = null; + } + } +); + +// Write key to Redis +$client->set('library', mt_rand()); + +// Retrieve key once from Redis, then cached in Relay and $key +$key = $client->get('library'); + +while (true) { + echo '$key is: ' . var_export($key, true) . PHP_EOL; + + // To trigger our event callbacks, we need to either interact with Relay: + $client->get(mt_rand()); + + // ... or alternatively dispatch events directly on Relay: + $relay->dispatchEvents(); + + sleep(1); +} diff --git a/examples/relay_pubsub_consumer.php b/examples/relay_pubsub_consumer.php new file mode 100644 index 00000000..43affa77 --- /dev/null +++ b/examples/relay_pubsub_consumer.php @@ -0,0 +1,59 @@ + 0], + ['connections' => 'relay'] +); + +// Initialize a new pubsub consumer. +$pubsub = $client->pubSubLoop(); + +// When using Relay you cannot use foreach-loops to iterate +// over messages instead use a callback function +$poorMansKafka = function ($message, $client) { + switch ($message->kind) { + case 'subscribe': + echo "Subscribed to {$message->channel}", PHP_EOL; + break; + + case 'message': + case 'pmessage': + if ($message->channel == 'control_channel') { + if ($message->payload == 'quit_loop') { + echo 'Aborting pubsub loop...', PHP_EOL; + $client->unsubscribe(); + } else { + echo "Received an unrecognized command: {$message->payload}.", PHP_EOL; + } + } else { + echo "Received the message from `{$message->channel}` channel:", + PHP_EOL, " {$message->payload}", PHP_EOL, PHP_EOL; + } + } +}; + +// Subscribe to your channels and start processing the messages. +$pubsub->subscribe('control_channel', 'notifications', $poorMansKafka); + +// Open a terminal and use redis-cli to push messages to the channels. Examples: +// redis-cli PUBLISH notifications "this is a test" +// redis-cli PUBLISH control_channel quit_loop + +// When using Relay, there is no need to unset the pubsub consumer instance when you are done + +// Say goodbye :-) +$version = redis_version($client->info()); +echo "Goodbye from Redis $version!", PHP_EOL; diff --git a/phpunit.relay.xml b/phpunit.relay.xml new file mode 100644 index 00000000..731f29c2 --- /dev/null +++ b/phpunit.relay.xml @@ -0,0 +1,42 @@ + + + + + + tests/Predis/ + + + + + + relay-incompatible + realm-webdis + realm-stack + ext-curl + ext-phpiredis + + + + + + src/ + + + + + + + + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index dff6900b..d47afa8d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -28,9 +28,11 @@ - ext-phpiredis - ext-curl realm-webdis + realm-stack + ext-relay + ext-curl + ext-phpiredis @@ -45,13 +47,9 @@ - - - - - + diff --git a/src/Client.php b/src/Client.php index 91c126f4..6e89678a 100644 --- a/src/Client.php +++ b/src/Client.php @@ -25,9 +25,15 @@ use Predis\Configuration\OptionsInterface; use Predis\Connection\ConnectionInterface; use Predis\Connection\Parameters; use Predis\Connection\ParametersInterface; +use Predis\Connection\RelayConnection; use Predis\Monitor\Consumer as MonitorConsumer; +use Predis\Pipeline\Atomic; +use Predis\Pipeline\FireAndForget; use Predis\Pipeline\Pipeline; +use Predis\Pipeline\RelayAtomic; +use Predis\Pipeline\RelayPipeline; use Predis\PubSub\Consumer as PubSubConsumer; +use Predis\PubSub\RelayConsumer as RelayPubSubConsumer; use Predis\Response\ErrorInterface as ErrorResponseInterface; use Predis\Response\ResponseInterface; use Predis\Response\ServerException; @@ -262,6 +268,32 @@ class Client implements ClientInterface, IteratorAggregate return $this->connection; } + /** + * Applies the configured serializer and compression to given value. + * + * @param mixed $value + * @return string + */ + public function pack($value) + { + return $this->connection instanceof RelayConnection + ? $this->connection->pack($value) + : $value; + } + + /** + * Deserializes and decompresses to given value. + * + * @param mixed $value + * @return string + */ + public function unpack($value) + { + return $this->connection instanceof RelayConnection + ? $this->connection->unpack($value) + : $value; + } + /** * Executes a command without filtering its arguments, parsing the response, * applying any prefix to keys or throwing exceptions on Redis errors even @@ -435,19 +467,29 @@ class Client implements ClientInterface, IteratorAggregate /** * Actual pipeline context initializer method. * - * @param array $options Options for the context. - * @param mixed $callable Optional callable used to execute the context. + * @param array|null $options Options for the context. + * @param mixed $callable Optional callable used to execute the context. * * @return Pipeline|array */ protected function createPipeline(array $options = null, $callable = null) { if (isset($options['atomic']) && $options['atomic']) { - $class = 'Predis\Pipeline\Atomic'; + $class = Atomic::class; } elseif (isset($options['fire-and-forget']) && $options['fire-and-forget']) { - $class = 'Predis\Pipeline\FireAndForget'; + $class = FireAndForget::class; } else { - $class = 'Predis\Pipeline\Pipeline'; + $class = Pipeline::class; + } + + if ($this->connection instanceof RelayConnection) { + if (isset($options['atomic']) && $options['atomic']) { + $class = RelayAtomic::class; + } elseif (isset($options['fire-and-forget']) && $options['fire-and-forget']) { + throw new NotSupportedException('The "relay" extension does not support fire-and-forget pipelines.'); + } else { + $class = RelayPipeline::class; + } } /* @@ -517,7 +559,11 @@ class Client implements ClientInterface, IteratorAggregate */ protected function createPubSub(array $options = null, $callable = null) { - $pubsub = new PubSubConsumer($this, $options); + if ($this->connection instanceof RelayConnection) { + $pubsub = new RelayPubSubConsumer($this, $options); + } else { + $pubsub = new PubSubConsumer($this, $options); + } if (!isset($callable)) { return $pubsub; diff --git a/src/Command/Redis/ACL.php b/src/Command/Redis/ACL.php index 4cfbdb25..e8999ea2 100644 --- a/src/Command/Redis/ACL.php +++ b/src/Command/Redis/ACL.php @@ -26,4 +26,28 @@ class ACL extends RedisCommand { return 'ACL'; } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (!is_array($data)) { + return $data; + } + + if ($data === array_values($data)) { + return $data; + } + + // flatten Relay (RESP3) maps + $return = []; + + array_walk($data, function ($value, $key) use (&$return) { + $return[] = $key; + $return[] = $value; + }); + + return $return; + } } diff --git a/src/Command/Redis/COMMAND.php b/src/Command/Redis/COMMAND.php index a215f47e..385d54ee 100644 --- a/src/Command/Redis/COMMAND.php +++ b/src/Command/Redis/COMMAND.php @@ -26,4 +26,15 @@ class COMMAND extends BaseCommand { return 'COMMAND'; } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + // Relay (RESP3) uses maps and it might be good + // to make the return value a breaking change + + return $data; + } } diff --git a/src/Command/Redis/CONFIG.php b/src/Command/Redis/CONFIG.php index 60d8271c..c60ac2dc 100644 --- a/src/Command/Redis/CONFIG.php +++ b/src/Command/Redis/CONFIG.php @@ -36,6 +36,10 @@ class CONFIG extends RedisCommand public function parseResponse($data) { if (is_array($data)) { + if ($data !== array_values($data)) { + return $data; // Relay + } + $result = []; for ($i = 0; $i < count($data); ++$i) { diff --git a/src/Command/Redis/HGETALL.php b/src/Command/Redis/HGETALL.php index 5db3eded..c5f566ba 100644 --- a/src/Command/Redis/HGETALL.php +++ b/src/Command/Redis/HGETALL.php @@ -32,6 +32,10 @@ class HGETALL extends RedisCommand */ public function parseResponse($data) { + if ($data !== array_values($data)) { + return $data; // Relay + } + $result = []; for ($i = 0; $i < count($data); ++$i) { diff --git a/src/Command/Redis/HRANDFIELD.php b/src/Command/Redis/HRANDFIELD.php index 0d0fb75d..62ce7dbe 100644 --- a/src/Command/Redis/HRANDFIELD.php +++ b/src/Command/Redis/HRANDFIELD.php @@ -31,4 +31,23 @@ class HRANDFIELD extends RedisCommand { return 'HRANDFIELD'; } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (!is_array($data)) { + return $data; + } + + // flatten Relay (RESP3) maps + $return = []; + + array_walk_recursive($data, function ($value) use (&$return) { + $return[] = $value; + }); + + return $return; + } } diff --git a/src/Command/Redis/LCS.php b/src/Command/Redis/LCS.php index e3f91697..e8663f45 100644 --- a/src/Command/Redis/LCS.php +++ b/src/Command/Redis/LCS.php @@ -57,6 +57,10 @@ class LCS extends RedisCommand public function parseResponse($data) { if (is_array($data)) { + if ($data !== array_values($data)) { + return $data; // Relay + } + return [$data[0] => $data[1], $data[2] => $data[3]]; } diff --git a/src/Command/Redis/TDigest/TDIGESTBYRANK.php b/src/Command/Redis/TDigest/TDIGESTBYRANK.php index a8b8d482..8fba75ec 100644 --- a/src/Command/Redis/TDigest/TDIGESTBYRANK.php +++ b/src/Command/Redis/TDigest/TDIGESTBYRANK.php @@ -25,4 +25,31 @@ class TDIGESTBYRANK extends RedisCommand { return 'TDIGEST.BYRANK'; } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (!is_array($data)) { + return $data; + } + + // convert Relay (RESP3) constants to strings + return array_map(function ($value) { + if (is_string($value) || !is_float($value)) { + return $value; + } + + if (is_nan($value)) { + return 'nan'; + } + + switch ($value) { + case INF: return 'inf'; + case -INF: return '-inf'; + default: return $value; + } + }, $data); + } } diff --git a/src/Command/Redis/TDigest/TDIGESTBYREVRANK.php b/src/Command/Redis/TDigest/TDIGESTBYREVRANK.php index 62322836..979270cc 100644 --- a/src/Command/Redis/TDigest/TDIGESTBYREVRANK.php +++ b/src/Command/Redis/TDigest/TDIGESTBYREVRANK.php @@ -25,4 +25,31 @@ class TDIGESTBYREVRANK extends RedisCommand { return 'TDIGEST.BYREVRANK'; } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (!is_array($data)) { + return $data; + } + + // convert Relay (RESP3) constants to strings + return array_map(function ($value) { + if (is_string($value) || !is_float($value)) { + return $value; + } + + if (is_nan($value)) { + return 'nan'; + } + + switch ($value) { + case INF: return 'inf'; + case -INF: return '-inf'; + default: return $value; + } + }, $data); + } } diff --git a/src/Command/Redis/TDigest/TDIGESTCDF.php b/src/Command/Redis/TDigest/TDIGESTCDF.php index 42aaefb4..3f58f9a7 100644 --- a/src/Command/Redis/TDigest/TDIGESTCDF.php +++ b/src/Command/Redis/TDigest/TDIGESTCDF.php @@ -27,4 +27,31 @@ class TDIGESTCDF extends RedisCommand { return 'TDIGEST.CDF'; } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (!is_array($data)) { + return $data; + } + + // convert Relay (RESP3) constants to strings + return array_map(function ($value) { + if (is_string($value) || !is_float($value)) { + return $value; + } + + if (is_nan($value)) { + return 'nan'; + } + + switch ($value) { + case INF: return 'inf'; + case -INF: return '-inf'; + default: return $value; + } + }, $data); + } } diff --git a/src/Command/Redis/TDigest/TDIGESTMAX.php b/src/Command/Redis/TDigest/TDIGESTMAX.php index 5dc86410..6441f24c 100644 --- a/src/Command/Redis/TDigest/TDIGESTMAX.php +++ b/src/Command/Redis/TDigest/TDIGESTMAX.php @@ -25,4 +25,25 @@ class TDIGESTMAX extends RedisCommand { return 'TDIGEST.MAX'; } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (is_string($data) || !is_float($data)) { + return $data; + } + + // convert Relay (RESP3) constants to strings + if (is_nan($data)) { + return 'nan'; + } + + switch ($data) { + case INF: return 'inf'; + case -INF: return '-inf'; + default: return $data; + } + } } diff --git a/src/Command/Redis/TDigest/TDIGESTMIN.php b/src/Command/Redis/TDigest/TDIGESTMIN.php index d997c841..472f07da 100644 --- a/src/Command/Redis/TDigest/TDIGESTMIN.php +++ b/src/Command/Redis/TDigest/TDIGESTMIN.php @@ -25,4 +25,25 @@ class TDIGESTMIN extends RedisCommand { return 'TDIGEST.MIN'; } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (is_string($data) || !is_float($data)) { + return $data; + } + + // convert Relay (RESP3) constants to strings + if (is_nan($data)) { + return 'nan'; + } + + switch ($data) { + case INF: return 'inf'; + case -INF: return '-inf'; + default: return $data; + } + } } diff --git a/src/Command/Redis/TDigest/TDIGESTQUANTILE.php b/src/Command/Redis/TDigest/TDIGESTQUANTILE.php index 7de94ffe..001ec0e3 100644 --- a/src/Command/Redis/TDigest/TDIGESTQUANTILE.php +++ b/src/Command/Redis/TDigest/TDIGESTQUANTILE.php @@ -25,4 +25,31 @@ class TDIGESTQUANTILE extends RedisCommand { return 'TDIGEST.QUANTILE'; } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (!is_array($data)) { + return $data; + } + + // convert Relay (RESP3) constants to strings + return array_map(function ($value) { + if (is_string($value) || !is_float($value)) { + return $value; + } + + if (is_nan($value)) { + return 'nan'; + } + + switch ($value) { + case INF: return 'inf'; + case -INF: return '-inf'; + default: return $value; + } + }, $data); + } } diff --git a/src/Command/Redis/TDigest/TDIGESTTRIMMED_MEAN.php b/src/Command/Redis/TDigest/TDIGESTTRIMMED_MEAN.php index 6658a5e9..2ccd3d34 100644 --- a/src/Command/Redis/TDigest/TDIGESTTRIMMED_MEAN.php +++ b/src/Command/Redis/TDigest/TDIGESTTRIMMED_MEAN.php @@ -26,4 +26,25 @@ class TDIGESTTRIMMED_MEAN extends RedisCommand { return 'TDIGEST.TRIMMED_MEAN'; } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (is_string($data) || !is_float($data)) { + return $data; + } + + // convert Relay (RESP3) constants to strings + if (is_nan($data)) { + return 'nan'; + } + + switch ($data) { + case INF: return 'inf'; + case -INF: return '-inf'; + default: return $data; + } + } } diff --git a/src/Command/Redis/TYPE.php b/src/Command/Redis/TYPE.php index dea01f28..dece6377 100644 --- a/src/Command/Redis/TYPE.php +++ b/src/Command/Redis/TYPE.php @@ -26,4 +26,26 @@ class TYPE extends RedisCommand { return 'TYPE'; } + + /** + * {@inheritdoc} + */ + public function parseResponse($data) + { + if (is_string($data)) { + return $data; + } + + // Relay types + switch ($data) { + case 0: return 'none'; + case 1: return 'string'; + case 2: return 'set'; + case 3: return 'list'; + case 4: return 'zset'; + case 5: return 'hash'; + case 6: return 'stream'; + default: return $data; + } + } } diff --git a/src/Command/Redis/ZPOPMAX.php b/src/Command/Redis/ZPOPMAX.php index 9f9c7c30..1ebf45b0 100644 --- a/src/Command/Redis/ZPOPMAX.php +++ b/src/Command/Redis/ZPOPMAX.php @@ -35,7 +35,11 @@ class ZPOPMAX extends RedisCommand $result = []; for ($i = 0; $i < count($data); ++$i) { - $result[$data[$i]] = $data[++$i]; + if (is_array($data[$i])) { + $result[$data[$i][0]] = $data[$i][1]; // Relay + } else { + $result[$data[$i]] = $data[++$i]; + } } return $result; diff --git a/src/Command/Redis/ZPOPMIN.php b/src/Command/Redis/ZPOPMIN.php index a58ece3e..5f561c8e 100644 --- a/src/Command/Redis/ZPOPMIN.php +++ b/src/Command/Redis/ZPOPMIN.php @@ -35,7 +35,11 @@ class ZPOPMIN extends RedisCommand $result = []; for ($i = 0; $i < count($data); ++$i) { - $result[$data[$i]] = $data[++$i]; + if (is_array($data[$i])) { + $result[$data[$i][0]] = $data[$i][1]; // Relay + } else { + $result[$data[$i]] = $data[++$i]; + } } return $result; diff --git a/src/Command/Redis/ZRANGE.php b/src/Command/Redis/ZRANGE.php index c06ee5da..2024ad4e 100644 --- a/src/Command/Redis/ZRANGE.php +++ b/src/Command/Redis/ZRANGE.php @@ -94,7 +94,11 @@ class ZRANGE extends RedisCommand $result = []; for ($i = 0; $i < count($data); ++$i) { - $result[$data[$i]] = $data[++$i]; + if (is_array($data[$i])) { + $result[$data[$i][0]] = $data[$i][1]; // Relay + } else { + $result[$data[$i]] = $data[++$i]; + } } return $result; diff --git a/src/Command/Traits/With/WithScores.php b/src/Command/Traits/With/WithScores.php index e98b4ea2..bc81d36c 100644 --- a/src/Command/Traits/With/WithScores.php +++ b/src/Command/Traits/With/WithScores.php @@ -53,7 +53,9 @@ trait WithScores $result = []; for ($i = 0, $iMax = count($data); $i < $iMax; ++$i) { - if ($data[$i + 1] ?? false) { + if (is_array($data[$i])) { + $result[$data[$i][0]] = $data[$i][1]; // Relay + } elseif (array_key_exists($i + 1, $data)) { $result[$data[$i]] = $data[++$i]; } } diff --git a/src/Configuration/Option/Connections.php b/src/Configuration/Option/Connections.php index 89bfb8f0..e37de4ca 100644 --- a/src/Configuration/Option/Connections.php +++ b/src/Configuration/Option/Connections.php @@ -19,6 +19,7 @@ use Predis\Connection\Factory; use Predis\Connection\FactoryInterface; use Predis\Connection\PhpiredisSocketConnection; use Predis\Connection\PhpiredisStreamConnection; +use Predis\Connection\RelayConnection; /** * Configures a new connection factory instance. @@ -89,6 +90,7 @@ class Connections implements OptionInterface * - "phpiredis-stream" maps tcp, redis, unix to PhpiredisStreamConnection * - "phpiredis-socket" maps tcp, redis, unix to PhpiredisSocketConnection * - "phpiredis" is an alias of "phpiredis-stream" + * - "relay" maps tcp, redis, unix, tls, rediss to RelayConnection * * @param OptionsInterface $options Client options * @param string $value Descriptive string identifying the desired configuration @@ -116,6 +118,12 @@ class Connections implements OptionInterface $factory->define('unix', PhpiredisSocketConnection::class); break; + case 'relay': + $factory->define('tcp', RelayConnection::class); + $factory->define('redis', RelayConnection::class); + $factory->define('unix', RelayConnection::class); + break; + case 'default': return $factory; diff --git a/src/Connection/ParametersInterface.php b/src/Connection/ParametersInterface.php index 25c7e1ae..37ed97f7 100644 --- a/src/Connection/ParametersInterface.php +++ b/src/Connection/ParametersInterface.php @@ -26,11 +26,14 @@ namespace Predis\Connection; * @property string $alias Alias for the connection. * @property float $timeout Timeout for the connect() operation. * @property float $read_write_timeout Timeout for read() and write() operations. - * @property bool $async_connect Performs the connect() operation asynchronously. - * @property bool $tcp_nodelay Toggles the Nagle's algorithm for coalescing. * @property bool $persistent Leaves the connection open after a GC collection. * @property string $password Password to access Redis (see the AUTH command). * @property string $database Database index (see the SELECT command). + * @property bool $async_connect Performs the connect() operation asynchronously. + * @property bool $tcp_nodelay Toggles the Nagle's algorithm for coalescing. + * @property bool $cache (Relay only) Whether to use in-memory caching. + * @property string $serializer (Relay only) Serializer used for data serialization. + * @property string $compression (Relay only) Algorithm used for data compression. */ interface ParametersInterface { diff --git a/src/Connection/RelayConnection.php b/src/Connection/RelayConnection.php new file mode 100644 index 00000000..4ff674f5 --- /dev/null +++ b/src/Connection/RelayConnection.php @@ -0,0 +1,337 @@ +assertExtensions(); + + $this->parameters = $this->assertParameters($parameters); + $this->client = $this->createClient(); + } + + /** + * {@inheritdoc} + */ + public function isConnected() + { + return $this->client->isConnected(); + } + + /** + * {@inheritdoc} + */ + public function disconnect() + { + if ($this->client->isConnected()) { + $this->client->close(); + } + } + + /** + * Checks if the Relay extension is loaded in PHP. + */ + private function assertExtensions() + { + if (!extension_loaded('relay')) { + throw new NotSupportedException( + 'The "relay" extension is required by this connection backend.' + ); + } + } + + /** + * {@inheritdoc} + */ + protected function assertParameters(ParametersInterface $parameters) + { + if (!in_array($parameters->scheme, ['tcp', 'tls', 'unix', 'redis', 'rediss'])) { + throw new InvalidArgumentException("Invalid scheme: '{$parameters->scheme}'."); + } + + if (!in_array($parameters->serializer, [null, 'php', 'igbinary', 'msgpack', 'json'])) { + throw new InvalidArgumentException("Invalid serializer: '{$parameters->serializer}'."); + } + + if (!in_array($parameters->compression, [null, 'lzf', 'lz4', 'zstd'])) { + throw new InvalidArgumentException("Invalid compression algorithm: '{$parameters->compression}'."); + } + + return $parameters; + } + + /** + * Creates a new instance of the client. + * + * @return \Relay\Relay + */ + private function createClient() + { + $client = new Relay(); + + // throw when errors occur and return `null` for non-existent keys + $client->setOption(Relay::OPT_PHPREDIS_COMPATIBILITY, false); + + // use reply literals + $client->setOption(Relay::OPT_REPLY_LITERAL, true); + + // disable Relay's command/connection retry + $client->setOption(Relay::OPT_MAX_RETRIES, 0); + + // whether to use in-memory caching + $client->setOption(Relay::OPT_USE_CACHE, $this->parameters->cache ?? true); + + // set data serializer + $client->setOption(Relay::OPT_SERIALIZER, constant(sprintf( + '%s::SERIALIZER_%s', + Relay::class, + strtoupper($this->parameters->serializer ?? 'none') + ))); + + // set data compression algorithm + $client->setOption(Relay::OPT_COMPRESSION, constant(sprintf( + '%s::COMPRESSION_%s', + Relay::class, + strtoupper($this->parameters->compression ?? 'none') + ))); + + return $client; + } + + /** + * Returns the underlying client. + * + * @return \Relay\Relay + */ + public function getClient() + { + return $this->client; + } + + /** + * {@inheritdoc} + */ + protected function getIdentifier() + { + return $this->client->endpointId(); + } + + /** + * {@inheritdoc} + */ + protected function createStreamSocket(ParametersInterface $parameters, $address, $flags) + { + $timeout = isset($parameters->timeout) ? (float) $parameters->timeout : 5.0; + + $retry_interval = 0; + $read_timeout = 5.0; + + if (isset($parameters->read_write_timeout)) { + $read_timeout = (float) $parameters->read_write_timeout; + $read_timeout = $read_timeout > 0 ? $read_timeout : 0; + } + + try { + $this->client->connect( + $parameters->path ?? $parameters->host, + isset($parameters->path) ? 0 : $parameters->port, + $timeout, + null, + $retry_interval, + $read_timeout + ); + } catch (RelayException $ex) { + $this->onConnectionError($ex->getMessage(), $ex->getCode()); + } + + return $this->client; + } + + /** + * {@inheritdoc} + */ + public function executeCommand(CommandInterface $command) + { + if (!$this->client->isConnected()) { + $this->getResource(); + } + + try { + $name = $command->getId(); + + // When using compression or a serializer, we'll need a dedicated + // handler for `Predis\Command\RawCommand` calls, currently both + // parameters are unsupported until a future Relay release + return in_array($name, $this->atypicalCommands) + ? $this->client->{$name}(...$command->getArguments()) + : $this->client->rawCommand($name, ...$command->getArguments()); + } catch (RelayException $ex) { + throw $this->onCommandError($ex, $command); + } + } + + /** + * {@inheritdoc} + */ + public function onCommandError(RelayException $exception, CommandInterface $command) + { + $code = $exception->getCode(); + $message = $exception->getMessage(); + + if (strpos($message, 'RELAY_ERR_IO')) { + return new ConnectionException($this, $message, $code, $exception); + } + + if (strpos($message, 'RELAY_ERR_REDIS')) { + return new ServerException($message, $code, $exception); + } + + if (strpos($message, 'RELAY_ERR_WRONGTYPE') && strpos($message, "Got reply-type 'status'")) { + $message = 'Operation against a key holding the wrong kind of value'; + } + + return new ClientException($message, $code, $exception); + } + + /** + * Applies the configured serializer and compression to given value. + * + * @param mixed $value + * @return string + */ + public function pack($value) + { + return $this->client->_pack($value); + } + + /** + * Deserializes and decompresses to given value. + * + * @param mixed $value + * @return string + */ + public function unpack($value) + { + return $this->client->_unpack($value); + } + + /** + * {@inheritdoc} + */ + public function writeRequest(CommandInterface $command) + { + throw new NotSupportedException('The "relay" extension does not support writing requests.'); + } + + /** + * {@inheritdoc} + */ + public function readResponse(CommandInterface $command) + { + throw new NotSupportedException('The "relay" extension does not support reading responses.'); + } + + /** + * {@inheritdoc} + */ + public function __destruct() + { + $this->disconnect(); + } + + /** + * {@inheritdoc} + */ + public function __wakeup() + { + $this->assertExtensions(); + $this->client = $this->createClient(); + } +} diff --git a/src/Connection/RelayMethods.php b/src/Connection/RelayMethods.php new file mode 100644 index 00000000..0e460a6e --- /dev/null +++ b/src/Connection/RelayMethods.php @@ -0,0 +1,136 @@ +client->onFlushed($callback); + } + + /** + * Registers a new `invalidated` event listener. + * + * @param callable $callback + * @param string $pattern + * @return bool + */ + public function onInvalidated(?callable $callback, ?string $pattern = null) + { + return $this->client->onInvalidated($callback, $pattern); + } + + /** + * Dispatches all pending events. + * + * @return int|false + */ + public function dispatchEvents() + { + return $this->client->dispatchEvents(); + } + + /** + * Adds ignore pattern(s). Matching keys will not be cached in memory. + * + * @param string $pattern,... + * @return int + */ + public function addIgnorePatterns(string ...$pattern) + { + return $this->client->addIgnorePatterns(...$pattern); + } + + /** + * Adds allow pattern(s). Only matching keys will be cached in memory. + * + * @param string $pattern,... + * @return int + */ + public function addAllowPatterns(string ...$pattern) + { + return $this->client->addAllowPatterns(...$pattern); + } + + /** + * Returns the connection's endpoint identifier. + * + * @return string|false + */ + public function endpointId() + { + return $this->client->endpointId(); + } + + /** + * Returns a unique representation of the underlying socket connection identifier. + * + * @return string|false + */ + public function socketId() + { + return $this->client->socketId(); + } + + /** + * Returns information about the license. + * + * @return array + */ + public function license() + { + return $this->client->license(); + } + + /** + * Returns statistics about Relay. + * + * @return array> + */ + public function stats() + { + return $this->client->stats(); + } + + /** + * Returns the number of bytes allocated, or `0` in client-only mode. + * + * @return int + */ + public function maxMemory() + { + return $this->client->maxMemory(); + } + + /** + * Flushes Relay's in-memory cache of all databases. + * When given an endpoint, only that connection will be flushed. + * When given an endpoint and database index, only that database + * for that connection will be flushed. + * + * @param ?string $endpointId + * @param ?int $db + * @return bool + */ + public function flushMemory(?string $endpointId = null, int $db = null) + { + return $this->client->flushMemory($endpointId, $db); + } +} diff --git a/src/Pipeline/Pipeline.php b/src/Pipeline/Pipeline.php index 3e1011f2..1f67d0b9 100644 --- a/src/Pipeline/Pipeline.php +++ b/src/Pipeline/Pipeline.php @@ -33,7 +33,7 @@ use SplQueue; */ class Pipeline implements ClientContextInterface { - private $client; + protected $client; private $pipeline; private $responses = []; diff --git a/src/Pipeline/RelayAtomic.php b/src/Pipeline/RelayAtomic.php new file mode 100644 index 00000000..c36e1086 --- /dev/null +++ b/src/Pipeline/RelayAtomic.php @@ -0,0 +1,69 @@ +getClient(); + + $throw = $this->client->getOptions()->exceptions; + + try { + $transaction = $client->multi(); + + foreach ($commands as $command) { + $name = $command->getId(); + + in_array($name, $connection->atypicalCommands) + ? $transaction->{$name}(...$command->getArguments()) + : $transaction->rawCommand($name, ...$command->getArguments()); + } + + $responses = $transaction->exec(); + + if (!is_array($responses)) { + return $responses; + } + + foreach ($responses as $key => $response) { + if ($response instanceof RelayException) { + if ($throw) { + throw $response; + } + + $responses[$key] = new Error($response->getMessage()); + } + } + + return $responses; + } catch (RelayException $ex) { + if ($client->getMode() !== $client::ATOMIC) { + $client->discard(); + } + + throw new ServerException($ex->getMessage(), $ex->getCode(), $ex); + } + } +} diff --git a/src/Pipeline/RelayPipeline.php b/src/Pipeline/RelayPipeline.php new file mode 100644 index 00000000..5f36a0aa --- /dev/null +++ b/src/Pipeline/RelayPipeline.php @@ -0,0 +1,75 @@ +getClient(); + + $throw = $this->client->getOptions()->exceptions; + + try { + $pipeline = $client->pipeline(); + + foreach ($commands as $command) { + $name = $command->getId(); + + in_array($name, $connection->atypicalCommands) + ? $pipeline->{$name}(...$command->getArguments()) + : $pipeline->rawCommand($name, ...$command->getArguments()); + } + + $responses = $pipeline->exec(); + + if (!is_array($responses)) { + return $responses; + } + + foreach ($responses as $key => $response) { + if ($response instanceof RelayException) { + if ($throw) { + throw $response; + } + + $responses[$key] = new Error($response->getMessage()); + } + } + + return $responses; + } catch (RelayException $ex) { + if ($client->getMode() !== $client::ATOMIC) { + $client->discard(); + } + + throw new ServerException($ex->getMessage(), $ex->getCode(), $ex); + } + } +} diff --git a/src/PubSub/AbstractConsumer.php b/src/PubSub/AbstractConsumer.php index 7c77e331..653517a0 100644 --- a/src/PubSub/AbstractConsumer.php +++ b/src/PubSub/AbstractConsumer.php @@ -32,8 +32,8 @@ abstract class AbstractConsumer implements Iterator public const STATUS_SUBSCRIBED = 2; // 0b0010 public const STATUS_PSUBSCRIBED = 4; // 0b0100 - private $position = null; - private $statusFlags = self::STATUS_VALID; + protected $position = null; + protected $statusFlags = self::STATUS_VALID; /** * Automatically stops the consumer when the garbage collector kicks in. diff --git a/src/PubSub/Consumer.php b/src/PubSub/Consumer.php index 253c33fd..b5267323 100644 --- a/src/PubSub/Consumer.php +++ b/src/PubSub/Consumer.php @@ -19,12 +19,12 @@ use Predis\Connection\Cluster\ClusterInterface; use Predis\NotSupportedException; /** - * PUB/SUB consumer abstraction. + * PUB/SUB consumer. */ class Consumer extends AbstractConsumer { - private $client; - private $options; + protected $client; + protected $options; /** * @param ClientInterface $client Client instance used by the consumer. @@ -59,7 +59,7 @@ class Consumer extends AbstractConsumer * * @throws NotSupportedException */ - private function checkCapabilities(ClientInterface $client) + protected function checkCapabilities(ClientInterface $client) { if ($client->getConnection() instanceof ClusterInterface) { throw new NotSupportedException( @@ -81,7 +81,7 @@ class Consumer extends AbstractConsumer * * @param string $subscribeAction Type of subscription. */ - private function genericSubscribeInit($subscribeAction) + protected function genericSubscribeInit($subscribeAction) { if (isset($this->options[$subscribeAction])) { $this->$subscribeAction($this->options[$subscribeAction]); diff --git a/src/PubSub/RelayConsumer.php b/src/PubSub/RelayConsumer.php new file mode 100644 index 00000000..2af67b84 --- /dev/null +++ b/src/PubSub/RelayConsumer.php @@ -0,0 +1,114 @@ +statusFlags |= self::STATUS_SUBSCRIBED; + + $command = $this->client->createCommand('subscribe', [ + $channels, + function ($relay, $channel, $message) use ($callback) { + $callback((object) [ + 'kind' => is_null($message) ? self::SUBSCRIBE : self::MESSAGE, + 'channel' => $channel, + 'payload' => $message, + ], $relay); + }, + ]); + + $this->client->getConnection()->executeCommand($command); + + $this->invalidate(); + } + + /** + * Subscribes to the specified channels using a pattern. + * + * @param string ...$pattern One or more channel name patterns. + * @param callable $callback The message callback. + */ + public function psubscribe(...$pattern) // @phpstan-ignore-line + { + $patterns = func_get_args(); + $callback = array_pop($patterns); + + $this->statusFlags |= self::STATUS_PSUBSCRIBED; + + $command = $this->client->createCommand('psubscribe', [ + $patterns, + function ($relay, $pattern, $channel, $message) use ($callback) { + $callback((object) [ + 'kind' => is_null($message) ? self::PSUBSCRIBE : self::PMESSAGE, + 'pattern' => $pattern, + 'channel' => $channel, + 'payload' => $message, + ], $relay); + }, + ]); + + $this->client->getConnection()->executeCommand($command); + + $this->invalidate(); + } + + /** + * {@inheritDoc} + */ + protected function genericSubscribeInit($subscribeAction) + { + if (isset($this->options[$subscribeAction])) { + throw new NotSupportedException('Relay does not support Pub/Sub constructor options.'); + } + } + + /** + * {@inheritDoc} + */ + public function ping($payload = null) + { + throw new NotSupportedException('Relay does not support PING in Pub/Sub.'); + } + + /** + * {@inheritDoc} + */ + public function stop($drop = false) + { + return false; + } + + /** + * {@inheritDoc} + */ + public function __destruct() + { + // NOOP + } +} diff --git a/src/Transaction/MultiExec.php b/src/Transaction/MultiExec.php index 1f99d2a9..32ac1e12 100644 --- a/src/Transaction/MultiExec.php +++ b/src/Transaction/MultiExec.php @@ -20,11 +20,15 @@ use Predis\ClientInterface; use Predis\Command\CommandInterface; use Predis\CommunicationException; use Predis\Connection\Cluster\ClusterInterface; +use Predis\Connection\RelayConnection; use Predis\NotSupportedException; use Predis\Protocol\ProtocolException; +use Predis\Response\Error; use Predis\Response\ErrorInterface as ErrorResponseInterface; use Predis\Response\ServerException; use Predis\Response\Status as StatusResponse; +use Relay\Exception as RelayException; +use Relay\Relay; use SplQueue; /** @@ -175,9 +179,25 @@ class MultiExec implements ClientContextInterface */ protected function call($commandID, array $arguments = []) { - $response = $this->client->executeCommand( - $this->client->createCommand($commandID, $arguments) - ); + try { + $response = $this->client->executeCommand( + $this->client->createCommand($commandID, $arguments) + ); + } catch (ServerException $exception) { + if (!$this->client->getConnection() instanceof RelayConnection) { + throw $exception; + } + + if (strcasecmp($commandID, 'EXEC') != 0) { + throw $exception; + } + + if (!strpos($exception->getMessage(), 'RELAY_ERR_REDIS')) { + throw $exception; + } + + return null; + } if ($response instanceof ErrorResponseInterface) { throw new ServerException($response->getMessage()); @@ -207,6 +227,8 @@ class MultiExec implements ClientContextInterface if ($response instanceof StatusResponse && $response == 'QUEUED') { $this->commands->enqueue($command); + } elseif ($response instanceof Relay) { + $this->commands->enqueue($command); } elseif ($response instanceof ErrorResponseInterface) { throw new AbortedMultiExecException($this, $response->getMessage()); } else { @@ -375,7 +397,9 @@ class MultiExec implements ClientContextInterface $execResponse = $this->call('EXEC'); - if ($execResponse === null) { + // The additional `false` check is needed for Relay, + // let's hope it won't break anything + if ($execResponse === null || $execResponse === false) { if ($attempts === 0) { throw new AbortedMultiExecException( $this, 'The current transaction has been aborted by the server.' @@ -401,10 +425,20 @@ class MultiExec implements ClientContextInterface for ($i = 0; $i < $size; ++$i) { $cmdResponse = $execResponse[$i]; - if ($cmdResponse instanceof ErrorResponseInterface && $this->exceptions) { + if ($this->exceptions && $cmdResponse instanceof ErrorResponseInterface) { throw new ServerException($cmdResponse->getMessage()); } + if ($cmdResponse instanceof RelayException) { + if ($this->exceptions) { + throw new ServerException($cmdResponse->getMessage(), $cmdResponse->getCode(), $cmdResponse); + } + + $commands->dequeue(); + $response[$i] = new Error($cmdResponse->getMessage()); + continue; + } + $response[$i] = $commands->dequeue()->parseResponse($cmdResponse); } diff --git a/tests/PHPUnit/PredisConnectionTestCase.php b/tests/PHPUnit/PredisConnectionTestCase.php index 89e9419e..d5c7f30d 100644 --- a/tests/PHPUnit/PredisConnectionTestCase.php +++ b/tests/PHPUnit/PredisConnectionTestCase.php @@ -167,6 +167,7 @@ abstract class PredisConnectionTestCase extends PredisTestCase /** * @group connected + * @group relay-incompatible */ public function testDisconnectForcesDisconnection(): void { @@ -193,6 +194,7 @@ abstract class PredisConnectionTestCase extends PredisTestCase /** * @group connected + * @group relay-incompatible */ public function testGetResourceForcesConnection(): void { @@ -273,6 +275,7 @@ abstract class PredisConnectionTestCase extends PredisTestCase /** * @group connected + * @group relay-incompatible */ public function testWritesCommandToServer(): void { @@ -294,6 +297,7 @@ abstract class PredisConnectionTestCase extends PredisTestCase /** * @group connected + * @group relay-incompatible */ public function testReadsCommandFromServer(): void { @@ -316,6 +320,7 @@ abstract class PredisConnectionTestCase extends PredisTestCase /** * @group connected + * @group relay-incompatible */ public function testIsAbleToWriteMultipleCommandsAndReadThemBackForPipelining(): void { @@ -382,6 +387,7 @@ abstract class PredisConnectionTestCase extends PredisTestCase /** * @group connected + * @group relay-incompatible */ public function testReadsStatusResponses(): void { @@ -402,6 +408,7 @@ abstract class PredisConnectionTestCase extends PredisTestCase /** * @group connected + * @group relay-incompatible */ public function testReadsBulkResponses(): void { @@ -419,6 +426,7 @@ abstract class PredisConnectionTestCase extends PredisTestCase /** * @group connected + * @group relay-incompatible */ public function testReadsIntegerResponses(): void { @@ -433,6 +441,7 @@ abstract class PredisConnectionTestCase extends PredisTestCase /** * @group connected + * @group relay-incompatible */ public function testReadsErrorResponsesAsResponseErrorObjects(): void { @@ -450,6 +459,7 @@ abstract class PredisConnectionTestCase extends PredisTestCase /** * @group connected + * @group relay-incompatible */ public function testReadsMultibulkResponsesAsArrays(): void { diff --git a/tests/PHPUnit/PredisTestCase.php b/tests/PHPUnit/PredisTestCase.php index d4156b10..d334f59a 100644 --- a/tests/PHPUnit/PredisTestCase.php +++ b/tests/PHPUnit/PredisTestCase.php @@ -232,10 +232,9 @@ abstract class PredisTestCase extends \PHPUnit\Framework\TestCase ); $options = array_merge( - [ - 'commands' => $this->getCommandFactory(), - ], - $options ?: [] + ['commands' => $this->getCommandFactory()], + $options ?: [], + getenv('USE_RELAY') ? ['connections' => 'relay'] : [] ); $client = new Client($parameters, $options); diff --git a/tests/Predis/Command/Redis/BZMPOP_Test.php b/tests/Predis/Command/Redis/BZMPOP_Test.php index 8e4da84f..27f11046 100644 --- a/tests/Predis/Command/Redis/BZMPOP_Test.php +++ b/tests/Predis/Command/Redis/BZMPOP_Test.php @@ -81,7 +81,7 @@ class BZMPOP_Test extends PredisCommandTestCase $redis->zadd($key, ...$sortedSetDictionary); $actualResponse = $redis->bzmpop($timeout, [$key], $modifier, $count); - $this->assertSame($expectedResponse, $actualResponse); + $this->assertEquals($expectedResponse, $actualResponse); $this->assertSame($expectedModifiedSortedSet, $redis->zrange($key, 0, -1)); } diff --git a/tests/Predis/Command/Redis/BZPOPMAX_Test.php b/tests/Predis/Command/Redis/BZPOPMAX_Test.php index a83149fb..d4d7b9aa 100644 --- a/tests/Predis/Command/Redis/BZPOPMAX_Test.php +++ b/tests/Predis/Command/Redis/BZPOPMAX_Test.php @@ -47,7 +47,7 @@ class BZPOPMAX_Test extends PredisCommandTestCase $redis->zadd('test-bzpopmax', ...$sortedSetDictionary); - $this->assertSame($expectedResponse, $redis->bzpopmax(['empty sorted set', 'test-bzpopmax'], 0)); + $this->assertEquals($expectedResponse, $redis->bzpopmax(['empty sorted set', 'test-bzpopmax'], 0)); $this->assertSame($expectedModifiedSortedSet, $redis->zrange('test-bzpopmax', 0, -1)); } diff --git a/tests/Predis/Command/Redis/BZPOPMIN_Test.php b/tests/Predis/Command/Redis/BZPOPMIN_Test.php index e24c26c2..115506c8 100644 --- a/tests/Predis/Command/Redis/BZPOPMIN_Test.php +++ b/tests/Predis/Command/Redis/BZPOPMIN_Test.php @@ -47,7 +47,7 @@ class BZPOPMIN_Test extends PredisCommandTestCase $redis->zadd('test-bzpopmin', ...$sortedSetDictionary); - $this->assertSame($expectedResponse, $redis->bzpopmin(['empty sorted set', 'test-bzpopmin'], 0)); + $this->assertEquals($expectedResponse, $redis->bzpopmin(['empty sorted set', 'test-bzpopmin'], 0)); $this->assertSame($expectedModifiedSortedSet, $redis->zrange('test-bzpopmin', 0, -1)); } diff --git a/tests/Predis/Command/Redis/CLIENT_Test.php b/tests/Predis/Command/Redis/CLIENT_Test.php index e1ed3c79..41fe4176 100644 --- a/tests/Predis/Command/Redis/CLIENT_Test.php +++ b/tests/Predis/Command/Redis/CLIENT_Test.php @@ -145,6 +145,7 @@ BUFFER; /** * @group connected + * @group relay-incompatible * @requiresRedisVersion >= 2.6.9 */ public function testGetsNameOfConnection(): void diff --git a/tests/Predis/Command/Redis/COMMAND_Test.php b/tests/Predis/Command/Redis/COMMAND_Test.php index 988661d4..47233988 100644 --- a/tests/Predis/Command/Redis/COMMAND_Test.php +++ b/tests/Predis/Command/Redis/COMMAND_Test.php @@ -97,7 +97,11 @@ class COMMAND_Test extends PredisCommandTestCase /** * @group connected + * @group relay-incompatible + * @group relay-fixme * @requiresRedisVersion >= 2.8.13 + * + * Relay uses RESP3 maps, the `Predis\Command\Redis\COMMAND` needs a converter. */ public function testReturnsCommandInfoOnExistingCommand(): void { diff --git a/tests/Predis/Command/Redis/DISCARD_Test.php b/tests/Predis/Command/Redis/DISCARD_Test.php index 843fe58a..b54c92c3 100644 --- a/tests/Predis/Command/Redis/DISCARD_Test.php +++ b/tests/Predis/Command/Redis/DISCARD_Test.php @@ -55,6 +55,7 @@ class DISCARD_Test extends PredisCommandTestCase /** * @group connected + * @group relay-incompatible * @requiresRedisVersion >= 2.0.0 */ public function testAbortsTransactionAndRestoresNormalFlow(): void @@ -68,6 +69,22 @@ class DISCARD_Test extends PredisCommandTestCase $this->assertSame(0, $redis->exists('foo')); } + /** + * @group connected + * @group ext-relay + */ + public function testAbortsTransactionAndRestoresNormalFlowUsingRelay(): void + { + $redis = $this->getClient(); + $relay = $redis->getConnection()->getClient(); + + $redis->multi(); + + $this->assertSame($relay, $redis->set('foo', 'bar')); + $this->assertTrue($redis->discard()); + $this->assertSame(0, $redis->exists('foo')); + } + /** * @group connected * @requiresRedisVersion >= 2.0.0 @@ -75,7 +92,7 @@ class DISCARD_Test extends PredisCommandTestCase public function testThrowsExceptionWhenCallingOutsideTransaction(): void { $this->expectException('Predis\Response\ServerException'); - $this->expectExceptionMessage('ERR DISCARD without MULTI'); + $this->expectExceptionMessage('DISCARD without MULTI'); $redis = $this->getClient(); diff --git a/tests/Predis/Command/Redis/EXEC_Test.php b/tests/Predis/Command/Redis/EXEC_Test.php index 0d2a8270..5595db60 100644 --- a/tests/Predis/Command/Redis/EXEC_Test.php +++ b/tests/Predis/Command/Redis/EXEC_Test.php @@ -105,7 +105,7 @@ class EXEC_Test extends PredisCommandTestCase public function testThrowsExceptionWhenCallingOutsideTransaction(): void { $this->expectException('Predis\Response\ServerException'); - $this->expectExceptionMessage('ERR EXEC without MULTI'); + $this->expectExceptionMessage('EXEC without MULTI'); $redis = $this->getClient(); diff --git a/tests/Predis/Command/Redis/EXPIREAT_Test.php b/tests/Predis/Command/Redis/EXPIREAT_Test.php index 78490200..5ef1a051 100644 --- a/tests/Predis/Command/Redis/EXPIREAT_Test.php +++ b/tests/Predis/Command/Redis/EXPIREAT_Test.php @@ -93,8 +93,8 @@ class EXPIREAT_Test extends PredisCommandTestCase /** * @medium * @group connected - * @dataProvider keysProvider * @group slow + * @dataProvider keysProvider * @param array $firstKeyArguments * @param array $secondKeyArguments * @param array $positivePathArguments diff --git a/tests/Predis/Command/Redis/EXPIRE_Test.php b/tests/Predis/Command/Redis/EXPIRE_Test.php index cfe0584d..105898b8 100644 --- a/tests/Predis/Command/Redis/EXPIRE_Test.php +++ b/tests/Predis/Command/Redis/EXPIRE_Test.php @@ -90,8 +90,8 @@ class EXPIRE_Test extends PredisCommandTestCase /** * @medium * @group connected - * @dataProvider keysProvider * @group slow + * @dataProvider keysProvider * @param array $firstKeyArguments * @param array $secondKeyArguments * @param array $positivePathArguments diff --git a/tests/Predis/Command/Redis/FCALL_Test.php b/tests/Predis/Command/Redis/FCALL_Test.php index 516032af..af852518 100644 --- a/tests/Predis/Command/Redis/FCALL_Test.php +++ b/tests/Predis/Command/Redis/FCALL_Test.php @@ -17,6 +17,7 @@ use Predis\Response\ServerException; /** * @group commands * @group realm-scripting + * @requiresRedisVersion >= 7.0.0 */ class FCALL_Test extends PredisCommandTestCase { @@ -71,6 +72,7 @@ class FCALL_Test extends PredisCommandTestCase $expectedResponse ): void { $redis = $this->getClient(); + $redis->executeRaw(['FUNCTION', 'FLUSH']); $this->assertSame('mylib', $redis->function->load($function)); @@ -87,6 +89,7 @@ class FCALL_Test extends PredisCommandTestCase public function testThrowsExceptionOnNonExistingFunctionGiven(): void { $redis = $this->getClient(); + $redis->executeRaw(['FUNCTION', 'FLUSH']); $this->expectException(ServerException::class); $this->expectExceptionMessage('ERR Function not found'); diff --git a/tests/Predis/Command/Redis/FUNCTIONS_Test.php b/tests/Predis/Command/Redis/FUNCTIONS_Test.php index 3bf838cd..49fa20ad 100644 --- a/tests/Predis/Command/Redis/FUNCTIONS_Test.php +++ b/tests/Predis/Command/Redis/FUNCTIONS_Test.php @@ -17,6 +17,7 @@ use Predis\Response\ServerException; /** * @group commands * @group realm-scripting + * @requiresRedisVersion >= 7.0.0 */ class FUNCTIONS_Test extends PredisCommandTestCase { @@ -85,6 +86,7 @@ class FUNCTIONS_Test extends PredisCommandTestCase public function testLoadFunctionAddFunctionIntoGivenLibrary(): void { $redis = $this->getClient(); + $redis->executeRaw(['FUNCTION', 'FLUSH']); $actualResponse = $redis->function->load( "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" @@ -103,6 +105,7 @@ class FUNCTIONS_Test extends PredisCommandTestCase public function testLoadFunctionOverridesExistingFunctionWithReplaceArgumentGiven(): void { $redis = $this->getClient(); + $redis->executeRaw(['FUNCTION', 'FLUSH']); $actualResponse = $redis->function->load( "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" @@ -129,6 +132,7 @@ class FUNCTIONS_Test extends PredisCommandTestCase public function testLoadFunctionThrowsErrorOnAlreadyExistingLibraryGiven(): void { $redis = $this->getClient(); + $redis->executeRaw(['FUNCTION', 'FLUSH']); $actualResponse = $redis->function->load( "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" @@ -156,6 +160,7 @@ class FUNCTIONS_Test extends PredisCommandTestCase public function testDeleteFunctionRemovesAlreadyExistingLibrary(): void { $redis = $this->getClient(); + $redis->executeRaw(['FUNCTION', 'FLUSH']); $actualResponse = $redis->function->load( "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" @@ -173,6 +178,7 @@ class FUNCTIONS_Test extends PredisCommandTestCase public function testDeleteFunctionThrowsErrorOnNonExistingLibrary(): void { $redis = $this->getClient(); + $redis->executeRaw(['FUNCTION', 'FLUSH']); $this->expectException(ServerException::class); $this->expectExceptionMessage('ERR Library not found'); diff --git a/tests/Predis/Command/Redis/GEOADD_Test.php b/tests/Predis/Command/Redis/GEOADD_Test.php index dc54fd55..417f4805 100644 --- a/tests/Predis/Command/Redis/GEOADD_Test.php +++ b/tests/Predis/Command/Redis/GEOADD_Test.php @@ -88,7 +88,7 @@ class GEOADD_Test extends PredisCommandTestCase $redis = $this->getClient(); $redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo'); - $this->assertSame(['Palermo' => '3479099956230698'], $redis->zrange('Sicily', 0, -1, 'WITHSCORES')); + $this->assertEquals(['Palermo' => '3479099956230698'], $redis->zrange('Sicily', 0, -1, 'WITHSCORES')); } /** diff --git a/tests/Predis/Command/Redis/GETBIT_Test.php b/tests/Predis/Command/Redis/GETBIT_Test.php index c38bfbb1..7df11f26 100644 --- a/tests/Predis/Command/Redis/GETBIT_Test.php +++ b/tests/Predis/Command/Redis/GETBIT_Test.php @@ -80,8 +80,7 @@ class GETBIT_Test extends PredisCommandTestCase */ public function testThrowsExceptionOnNegativeOffset(): void { - $this->expectException('Predis\Response\ServerException'); - $this->expectExceptionMessage('ERR bit offset is not an integer or out of range'); + $this->expectExceptionMessage('bit offset is not an integer or out of range'); $redis = $this->getClient(); @@ -95,8 +94,7 @@ class GETBIT_Test extends PredisCommandTestCase */ public function testThrowsExceptionOnInvalidOffset(): void { - $this->expectException('Predis\Response\ServerException'); - $this->expectExceptionMessage('ERR bit offset is not an integer or out of range'); + $this->expectExceptionMessage('bit offset is not an integer or out of range'); $redis = $this->getClient(); diff --git a/tests/Predis/Command/Redis/HMGET_Test.php b/tests/Predis/Command/Redis/HMGET_Test.php index 989e94e5..573dfbb5 100644 --- a/tests/Predis/Command/Redis/HMGET_Test.php +++ b/tests/Predis/Command/Redis/HMGET_Test.php @@ -86,7 +86,31 @@ class HMGET_Test extends PredisCommandTestCase $redis->hmset('metavars', 'foo', 'bar', 'hoge', 'piyo', 'lol', 'wut'); $this->assertSame(['bar', 'piyo', null], $redis->hmget('metavars', 'foo', 'hoge', 'unknown')); + } + + /** + * @group connected + * @requiresRedisVersion >= 2.0.0 + */ + public function testReturnsDuplicateValues(): void + { + $redis = $this->getClient(); + + $redis->hmset('metavars', 'foo', 'bar', 'hoge', 'piyo', 'lol', 'wut'); + $this->assertSame(['bar', 'bar'], $redis->hmget('metavars', 'foo', 'foo')); + } + + /** + * @group connected + * @requiresRedisVersion >= 2.0.0 + */ + public function testReturnsNullValues(): void + { + $redis = $this->getClient(); + + $redis->hmset('metavars', 'foo', 'bar', 'hoge', 'piyo', 'lol', 'wut'); + $this->assertSame([null, null], $redis->hmget('metavars', 'unknown', 'unknown')); $this->assertSame([null, null], $redis->hmget('unknown', 'foo', 'hoge')); } diff --git a/tests/Predis/Command/Redis/MIGRATE_Test.php b/tests/Predis/Command/Redis/MIGRATE_Test.php index 6d4b27d6..bf789cf3 100644 --- a/tests/Predis/Command/Redis/MIGRATE_Test.php +++ b/tests/Predis/Command/Redis/MIGRATE_Test.php @@ -88,6 +88,7 @@ class MIGRATE_Test extends PredisCommandTestCase /** * @group connected + * @group relay-incompatible * @requiresRedisVersion >= 2.6.0 */ public function testReturnsStatusNOKEYOnNonExistingKey(): void @@ -98,6 +99,18 @@ class MIGRATE_Test extends PredisCommandTestCase $this->assertInstanceOf('Predis\Response\Status', $response); } + /** + * @group connected + * @group ext-relay + * @requiresRedisVersion >= 2.6.0 + */ + public function testReturnsStatusNOKEYOnNonExistingKeyUsingRelay(): void + { + $redis = $this->getClient(); + + $this->assertEquals('NOKEY', $redis->migrate('169.254.10.10', 16379, 'foo', 15, 1)); + } + /** * @group connected * @requiresRedisVersion >= 2.6.0 diff --git a/tests/Predis/Command/Redis/MONITOR_Test.php b/tests/Predis/Command/Redis/MONITOR_Test.php index 21f6ea4b..6a82705f 100644 --- a/tests/Predis/Command/Redis/MONITOR_Test.php +++ b/tests/Predis/Command/Redis/MONITOR_Test.php @@ -56,6 +56,7 @@ class MONITOR_Test extends PredisCommandTestCase /** * @group connected + * @group relay-incompatible */ public function testReturnsStatusResponseAndReadsEventsFromTheConnection(): void { diff --git a/tests/Predis/Command/Redis/MOVE_Test.php b/tests/Predis/Command/Redis/MOVE_Test.php index 6c9966f6..27c7e1fd 100644 --- a/tests/Predis/Command/Redis/MOVE_Test.php +++ b/tests/Predis/Command/Redis/MOVE_Test.php @@ -61,7 +61,6 @@ class MOVE_Test extends PredisCommandTestCase /** * @group connected - * * @todo Should be improved, this test fails when REDIS_SERVER_DBNUM is 0. */ public function testMovesKeysToDifferentDatabases(): void diff --git a/tests/Predis/Command/Redis/MULTI_Test.php b/tests/Predis/Command/Redis/MULTI_Test.php index 9992149d..e4d5b4e3 100644 --- a/tests/Predis/Command/Redis/MULTI_Test.php +++ b/tests/Predis/Command/Redis/MULTI_Test.php @@ -55,6 +55,7 @@ class MULTI_Test extends PredisCommandTestCase /** * @group connected + * @group relay-incompatible */ public function testInitializesNewTransaction(): void { @@ -67,6 +68,24 @@ class MULTI_Test extends PredisCommandTestCase /** * @group connected + * @group ext-relay + */ + public function testInitializesNewTransactionUsingRelay(): void + { + $redis = $this->getClient(); + $relay = $redis->getConnection()->getClient(); + + $this->assertSame($relay, $redis->multi()); + $this->assertSame($relay, $redis->echo('tx1')); + $this->assertSame($relay, $redis->echo('tx2')); + + $relay->discard(); + } + + /** + * @group connected + * @group relay-incompatible + * @group relay-fixme */ public function testActuallyReturnsResponseObjectAbstraction(): void { @@ -79,6 +98,8 @@ class MULTI_Test extends PredisCommandTestCase /** * @group connected + * @group relay-incompatible + * @group relay-fixme */ public function testThrowsExceptionWhenCallingMultiInsideTransaction(): void { diff --git a/tests/Predis/Command/Redis/PING_Test.php b/tests/Predis/Command/Redis/PING_Test.php index 731597b0..54b15fab 100644 --- a/tests/Predis/Command/Redis/PING_Test.php +++ b/tests/Predis/Command/Redis/PING_Test.php @@ -58,6 +58,7 @@ class PING_Test extends PredisCommandTestCase /** * @group connected + * @group relay-incompatible */ public function testAlwaysReturnsStatusResponse(): void { @@ -67,4 +68,19 @@ class PING_Test extends PredisCommandTestCase $this->assertInstanceOf('Predis\Response\Status', $response); $this->assertEquals('PONG', $response); } + + /** + * @group connected + * @group ext-relay + */ + public function testAlwaysReturnsResponseUsingRelay(): void + { + $redis = $this->getClient(); + + $response = $redis->ping(); + $this->assertEquals('PONG', $response); + + $response = $redis->ping('HELLO'); + $this->assertSame('HELLO', $response); + } } diff --git a/tests/Predis/Command/Redis/PSUBSCRIBE_Test.php b/tests/Predis/Command/Redis/PSUBSCRIBE_Test.php index 92e99b44..47ec3c36 100644 --- a/tests/Predis/Command/Redis/PSUBSCRIBE_Test.php +++ b/tests/Predis/Command/Redis/PSUBSCRIBE_Test.php @@ -15,6 +15,7 @@ namespace Predis\Command\Redis; /** * @group commands * @group realm-pubsub + * @group relay-incompatible */ class PSUBSCRIBE_Test extends PredisCommandTestCase { diff --git a/tests/Predis/Command/Redis/PUBLISH_Test.php b/tests/Predis/Command/Redis/PUBLISH_Test.php index 8d42ec85..bd810e36 100644 --- a/tests/Predis/Command/Redis/PUBLISH_Test.php +++ b/tests/Predis/Command/Redis/PUBLISH_Test.php @@ -58,6 +58,7 @@ class PUBLISH_Test extends PredisCommandTestCase /** * @group connected + * @group relay-incompatible * @requiresRedisVersion >= 2.0.0 */ public function testPublishesMessagesToChannel(): void diff --git a/tests/Predis/Command/Redis/PUNSUBSCRIBE_Test.php b/tests/Predis/Command/Redis/PUNSUBSCRIBE_Test.php index c3804bb6..0149526b 100644 --- a/tests/Predis/Command/Redis/PUNSUBSCRIBE_Test.php +++ b/tests/Predis/Command/Redis/PUNSUBSCRIBE_Test.php @@ -15,6 +15,7 @@ namespace Predis\Command\Redis; /** * @group commands * @group realm-pubsub + * @group relay-incompatible */ class PUNSUBSCRIBE_Test extends PredisCommandTestCase { diff --git a/tests/Predis/Command/Redis/QUIT_Test.php b/tests/Predis/Command/Redis/QUIT_Test.php index 12cd82f2..94848412 100644 --- a/tests/Predis/Command/Redis/QUIT_Test.php +++ b/tests/Predis/Command/Redis/QUIT_Test.php @@ -58,6 +58,7 @@ class QUIT_Test extends PredisCommandTestCase /** * @group connected + * @group relay-incompatible */ public function testReturnsStatusResponseWhenClosingConnection(): void { diff --git a/tests/Predis/Command/Redis/SELECT_Test.php b/tests/Predis/Command/Redis/SELECT_Test.php index 30d6bea8..ecaf4edc 100644 --- a/tests/Predis/Command/Redis/SELECT_Test.php +++ b/tests/Predis/Command/Redis/SELECT_Test.php @@ -84,6 +84,7 @@ class SELECT_Test extends PredisCommandTestCase /** * @group connected + * @group relay-incompatible */ public function testThrowsExceptionOnUnexpectedDatabaseName(): void { diff --git a/tests/Predis/Command/Redis/SUBSCRIBE_Test.php b/tests/Predis/Command/Redis/SUBSCRIBE_Test.php index 4ed3600b..92b19b2d 100644 --- a/tests/Predis/Command/Redis/SUBSCRIBE_Test.php +++ b/tests/Predis/Command/Redis/SUBSCRIBE_Test.php @@ -15,6 +15,7 @@ namespace Predis\Command\Redis; /** * @group commands * @group realm-pubsub + * @group relay-incompatible */ class SUBSCRIBE_Test extends PredisCommandTestCase { diff --git a/tests/Predis/Command/Redis/Search/FTAGGREGATE_Test.php b/tests/Predis/Command/Redis/Search/FTAGGREGATE_Test.php index 190c37b6..5518709e 100644 --- a/tests/Predis/Command/Redis/Search/FTAGGREGATE_Test.php +++ b/tests/Predis/Command/Redis/Search/FTAGGREGATE_Test.php @@ -20,6 +20,10 @@ use Predis\Command\Argument\Search\SchemaFields\TextField; use Predis\Command\Redis\PredisCommandTestCase; use Predis\Response\ServerException; +/** + * @group commands + * @group realm-stack + */ class FTAGGREGATE_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/Search/FTCURSOR_Test.php b/tests/Predis/Command/Redis/Search/FTCURSOR_Test.php index 637c0be4..a814eaac 100644 --- a/tests/Predis/Command/Redis/Search/FTCURSOR_Test.php +++ b/tests/Predis/Command/Redis/Search/FTCURSOR_Test.php @@ -21,6 +21,10 @@ use Predis\Command\Argument\Search\SchemaFields\TextField; use Predis\Command\Redis\PredisCommandTestCase; use Predis\Response\ServerException; +/** + * @group commands + * @group realm-stack + */ class FTCURSOR_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/Search/FTEXPLAIN_Test.php b/tests/Predis/Command/Redis/Search/FTEXPLAIN_Test.php index 11eeae12..8d6dcead 100644 --- a/tests/Predis/Command/Redis/Search/FTEXPLAIN_Test.php +++ b/tests/Predis/Command/Redis/Search/FTEXPLAIN_Test.php @@ -17,6 +17,10 @@ use Predis\Command\Argument\Search\SchemaFields\TextField; use Predis\Command\Redis\PredisCommandTestCase; use Predis\Response\ServerException; +/** + * @group commands + * @group realm-stack + */ class FTEXPLAIN_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/Search/FTINFO_Test.php b/tests/Predis/Command/Redis/Search/FTINFO_Test.php index 276be34e..6915e006 100644 --- a/tests/Predis/Command/Redis/Search/FTINFO_Test.php +++ b/tests/Predis/Command/Redis/Search/FTINFO_Test.php @@ -63,8 +63,11 @@ class FTINFO_Test extends PredisCommandTestCase /** * @group connected + * @group relay-incompatible * @return void * @requiresRediSearchVersion >= 1.0.0 + * + * Prior to Redis 7.2 `-nan` is messing with Relay/hiredis. */ public function testInfoReturnsInformationAboutGivenIndex(): void { diff --git a/tests/Predis/Command/Redis/Search/FTSPELLCHECK_Test.php b/tests/Predis/Command/Redis/Search/FTSPELLCHECK_Test.php index 4bb77a12..16ee1f70 100644 --- a/tests/Predis/Command/Redis/Search/FTSPELLCHECK_Test.php +++ b/tests/Predis/Command/Redis/Search/FTSPELLCHECK_Test.php @@ -83,7 +83,7 @@ class FTSPELLCHECK_Test extends PredisCommandTestCase (new SpellcheckArguments())->distance(2)->terms('dict') ); - $this->assertSame($expectedResponse, $actualResponse); + $this->assertEquals($expectedResponse, $actualResponse); } /** diff --git a/tests/Predis/Command/Redis/Search/FTSUGGET_Test.php b/tests/Predis/Command/Redis/Search/FTSUGGET_Test.php index da05f156..3239d53d 100644 --- a/tests/Predis/Command/Redis/Search/FTSUGGET_Test.php +++ b/tests/Predis/Command/Redis/Search/FTSUGGET_Test.php @@ -16,6 +16,10 @@ use Predis\Command\Argument\Search\SugAddArguments; use Predis\Command\Argument\Search\SugGetArguments; use Predis\Command\Redis\PredisCommandTestCase; +/** + * @group commands + * @group realm-stack + */ class FTSUGGET_Test extends PredisCommandTestCase { /** @@ -74,7 +78,7 @@ class FTSUGGET_Test extends PredisCommandTestCase $actualResponse = $redis->ftsugget(...$getArguments); - $this->assertSame($expectedResponse, $actualResponse); + $this->assertEquals($expectedResponse, $actualResponse); } /** diff --git a/tests/Predis/Command/Redis/Search/FTSUGLEN_Test.php b/tests/Predis/Command/Redis/Search/FTSUGLEN_Test.php index 9d06cb8f..214ed246 100644 --- a/tests/Predis/Command/Redis/Search/FTSUGLEN_Test.php +++ b/tests/Predis/Command/Redis/Search/FTSUGLEN_Test.php @@ -14,6 +14,10 @@ namespace Predis\Command\Redis\Search; use Predis\Command\Redis\PredisCommandTestCase; +/** + * @group commands + * @group realm-stack + */ class FTSUGLEN_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTBYRANK_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTBYRANK_Test.php index 47d9841a..4aadeb79 100644 --- a/tests/Predis/Command/Redis/TDigest/TDIGESTBYRANK_Test.php +++ b/tests/Predis/Command/Redis/TDigest/TDIGESTBYRANK_Test.php @@ -76,8 +76,8 @@ class TDIGESTBYRANK_Test extends PredisCommandTestCase $actualResponse = $redis->tdigestbyrank('key', 0, 1, 2, 3, 4, 5, 6); - $this->assertSame($expectedResponse, $actualResponse); - $this->assertSame(['nan', 'nan'], $redis->tdigestbyrank('empty_key', 0, 1)); + $this->assertEquals($expectedResponse, $actualResponse); + $this->assertEquals(['nan', 'nan'], $redis->tdigestbyrank('empty_key', 0, 1)); } /** diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTBYREVRANK_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTBYREVRANK_Test.php index ee3c3040..7fc8a3a1 100644 --- a/tests/Predis/Command/Redis/TDigest/TDIGESTBYREVRANK_Test.php +++ b/tests/Predis/Command/Redis/TDigest/TDIGESTBYREVRANK_Test.php @@ -76,8 +76,8 @@ class TDIGESTBYREVRANK_Test extends PredisCommandTestCase $actualResponse = $redis->tdigestbyrevrank('key', 0, 1, 2, 3, 4, 5, 6); - $this->assertSame($expectedResponse, $actualResponse); - $this->assertSame(['nan', 'nan'], $redis->tdigestbyrevrank('empty_key', 0, 1)); + $this->assertEquals($expectedResponse, $actualResponse); + $this->assertEquals(['nan', 'nan'], $redis->tdigestbyrevrank('empty_key', 0, 1)); } /** diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTCDF_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTCDF_Test.php index 5a844523..2b23e6ae 100644 --- a/tests/Predis/Command/Redis/TDigest/TDIGESTCDF_Test.php +++ b/tests/Predis/Command/Redis/TDigest/TDIGESTCDF_Test.php @@ -76,8 +76,8 @@ class TDIGESTCDF_Test extends PredisCommandTestCase $actualResponse = $redis->tdigestcdf('key', 0, 1, 2, 3, 4); - $this->assertSame($expectedResponse, $actualResponse); - $this->assertSame(['nan', 'nan'], $redis->tdigestcdf('empty_key', 0, 1)); + $this->assertEquals($expectedResponse, $actualResponse); + $this->assertEquals(['nan', 'nan'], $redis->tdigestcdf('empty_key', 0, 1)); } /** diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTMAX_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTMAX_Test.php index f3c206ae..62b0a7a8 100644 --- a/tests/Predis/Command/Redis/TDigest/TDIGESTMAX_Test.php +++ b/tests/Predis/Command/Redis/TDigest/TDIGESTMAX_Test.php @@ -75,8 +75,8 @@ class TDIGESTMAX_Test extends PredisCommandTestCase $actualResponse = $redis->tdigestmax('key'); - $this->assertSame('5', $actualResponse); - $this->assertSame('nan', $redis->tdigestmax('empty_key')); + $this->assertEquals('5', $actualResponse); + $this->assertEquals('nan', $redis->tdigestmax('empty_key')); } /** diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTMERGE_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTMERGE_Test.php index a6575498..b4d73071 100644 --- a/tests/Predis/Command/Redis/TDigest/TDIGESTMERGE_Test.php +++ b/tests/Predis/Command/Redis/TDigest/TDIGESTMERGE_Test.php @@ -84,7 +84,7 @@ class TDIGESTMERGE_Test extends PredisCommandTestCase $this->assertEquals('OK', $actualResponse); $this->assertSame($expectedCompression, $info['Compression']); - $this->assertSame( + $this->assertEquals( $expectedMergedSketchValues, $redis->tdigestbyrank('destination-key', 0, 1, 2, 3, 4) ); @@ -110,7 +110,7 @@ class TDIGESTMERGE_Test extends PredisCommandTestCase $this->assertEquals('OK', $actualResponse); $this->assertSame(1000, $info['Compression']); - $this->assertSame( + $this->assertEquals( ['1', '2', '3', '4', 'inf'], $redis->tdigestbyrank('destination-key', 0, 1, 2, 3, 4) ); @@ -133,7 +133,7 @@ class TDIGESTMERGE_Test extends PredisCommandTestCase $redis->tdigestadd('source-key2', 3, 4); $redis->tdigestadd('destination-key', 5, 6, 7, 8); - $this->assertSame( + $this->assertEquals( ['5', '6', '7', '8', 'inf'], $redis->tdigestbyrank('destination-key', 0, 1, 2, 3, 4) ); @@ -148,7 +148,7 @@ class TDIGESTMERGE_Test extends PredisCommandTestCase $this->assertEquals('OK', $actualResponse); $this->assertSame(100, $info['Compression']); - $this->assertSame( + $this->assertEquals( ['1', '2', '3', '4', 'inf'], $redis->tdigestbyrank('destination-key', 0, 1, 2, 3, 4) ); @@ -176,7 +176,7 @@ class TDIGESTMERGE_Test extends PredisCommandTestCase $this->assertEquals('OK', $actualResponse); $this->assertSame(100, $info['Compression']); - $this->assertSame( + $this->assertEquals( ['1', '2', '3', '4', '5', '6', '7', '8', 'inf'], $redis->tdigestbyrank('destination-key', 0, 1, 2, 3, 4, 5, 6, 7, 8) ); diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTMIN_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTMIN_Test.php index cab0cf19..c7062995 100644 --- a/tests/Predis/Command/Redis/TDigest/TDIGESTMIN_Test.php +++ b/tests/Predis/Command/Redis/TDigest/TDIGESTMIN_Test.php @@ -75,8 +75,8 @@ class TDIGESTMIN_Test extends PredisCommandTestCase $actualResponse = $redis->tdigestmin('key'); - $this->assertSame('1', $actualResponse); - $this->assertSame('nan', $redis->tdigestmin('empty_key')); + $this->assertEquals('1', $actualResponse); + $this->assertEquals('nan', $redis->tdigestmin('empty_key')); } /** diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTQUANTILE_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTQUANTILE_Test.php index 2e76ab91..b43cc533 100644 --- a/tests/Predis/Command/Redis/TDigest/TDIGESTQUANTILE_Test.php +++ b/tests/Predis/Command/Redis/TDigest/TDIGESTQUANTILE_Test.php @@ -74,10 +74,10 @@ class TDIGESTQUANTILE_Test extends PredisCommandTestCase $quantileResponse = $redis->tdigestquantile('key', 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0); $this->assertEquals('OK', $addResponse); - $this->assertSame(['1', '2', '3', '3', '4', '4', '4', '5', '5', '5', '5'], $quantileResponse); + $this->assertEquals(['1', '2', '3', '3', '4', '4', '4', '5', '5', '5', '5'], $quantileResponse); $redis->tdigestcreate('empty_key'); - $this->assertSame(['nan', 'nan'], $redis->tdigestquantile('empty_key', 0.0, 0.1)); + $this->assertEquals(['nan', 'nan'], $redis->tdigestquantile('empty_key', 0.0, 0.1)); } /** diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTRESET_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTRESET_Test.php index 1a2a0ca1..bc3ebe25 100644 --- a/tests/Predis/Command/Redis/TDigest/TDIGESTRESET_Test.php +++ b/tests/Predis/Command/Redis/TDigest/TDIGESTRESET_Test.php @@ -71,7 +71,7 @@ class TDIGESTRESET_Test extends PredisCommandTestCase $redis->tdigestcreate('key', 500); $redis->tdigestadd('key', 1, 2, 2, 3, 3, 3); - $this->assertSame( + $this->assertEquals( ['1', '2', '2', '3', '3', '3'], $redis->tdigestbyrank('key', 0, 1, 2, 3, 4, 5) ); @@ -81,7 +81,7 @@ class TDIGESTRESET_Test extends PredisCommandTestCase $this->assertEquals('OK', $actualResponse); $this->assertSame(500, $info['Compression']); - $this->assertSame( + $this->assertEquals( ['nan', 'nan', 'nan', 'nan', 'nan', 'nan'], $redis->tdigestbyrank('key', 0, 1, 2, 3, 4, 5) ); diff --git a/tests/Predis/Command/Redis/TDigest/TDIGESTTRIMMED_MEAN_Test.php b/tests/Predis/Command/Redis/TDigest/TDIGESTTRIMMED_MEAN_Test.php index 4d7127de..15490426 100644 --- a/tests/Predis/Command/Redis/TDigest/TDIGESTTRIMMED_MEAN_Test.php +++ b/tests/Predis/Command/Redis/TDigest/TDIGESTTRIMMED_MEAN_Test.php @@ -82,7 +82,7 @@ class TDIGESTTRIMMED_MEAN_Test extends PredisCommandTestCase $actualResponse = $redis->tdigesttrimmed_mean(...$trimmedMeanArguments); - $this->assertSame($expectedResponse, $actualResponse); + $this->assertEquals($expectedResponse, $actualResponse); } /** @@ -97,7 +97,7 @@ class TDIGESTTRIMMED_MEAN_Test extends PredisCommandTestCase $redis->tdigestcreate('key'); $actualResponse = $redis->tdigesttrimmed_mean('key', 0, 1); - $this->assertSame('nan', $actualResponse); + $this->assertEquals('nan', $actualResponse); } /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSADD_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSADD_Test.php index c4db6892..a969e88e 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSADD_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSADD_Test.php @@ -17,6 +17,10 @@ use Predis\Command\Argument\TimeSeries\CommonArguments; use Predis\Command\Argument\TimeSeries\CreateArguments; use Predis\Command\Redis\PredisCommandTestCase; +/** + * @group commands + * @group realm-stack + */ class TSADD_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSALTER_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSALTER_Test.php index e8c5db63..01f6fd3e 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSALTER_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSALTER_Test.php @@ -18,6 +18,10 @@ use Predis\Command\Argument\TimeSeries\CreateArguments; use Predis\Command\Redis\PredisCommandTestCase; use Predis\Response\ServerException; +/** + * @group commands + * @group realm-stack + */ class TSALTER_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSCREATERULE_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSCREATERULE_Test.php index d3eab4e6..25a448f2 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSCREATERULE_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSCREATERULE_Test.php @@ -16,6 +16,10 @@ use Predis\Command\Argument\TimeSeries\CreateArguments; use Predis\Command\Redis\PredisCommandTestCase; use Predis\Response\ServerException; +/** + * @group commands + * @group realm-stack + */ class TSCREATERULE_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSCREATE_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSCREATE_Test.php index 62409d42..ef7c66ce 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSCREATE_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSCREATE_Test.php @@ -17,6 +17,10 @@ use Predis\Command\Argument\TimeSeries\CreateArguments; use Predis\Command\Redis\PredisCommandTestCase; use Predis\Response\ServerException; +/** + * @group commands + * @group realm-stack + */ class TSCREATE_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSDECRBY_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSDECRBY_Test.php index b99888a7..b2d7e210 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSDECRBY_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSDECRBY_Test.php @@ -19,6 +19,10 @@ use Predis\Command\Argument\TimeSeries\DecrByArguments; use Predis\Command\Redis\PredisCommandTestCase; use Predis\Response\ServerException; +/** + * @group commands + * @group realm-stack + */ class TSDECRBY_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSDELETERULE_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSDELETERULE_Test.php index 8b83840d..ac581894 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSDELETERULE_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSDELETERULE_Test.php @@ -16,6 +16,10 @@ use Predis\Command\Argument\TimeSeries\CreateArguments; use Predis\Command\Redis\PredisCommandTestCase; use Predis\Response\ServerException; +/** + * @group commands + * @group realm-stack + */ class TSDELETERULE_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSDEL_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSDEL_Test.php index 671f44ce..a9506eab 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSDEL_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSDEL_Test.php @@ -17,6 +17,10 @@ use Predis\Command\Argument\TimeSeries\CreateArguments; use Predis\Command\Redis\PredisCommandTestCase; use Predis\Response\ServerException; +/** + * @group commands + * @group realm-stack + */ class TSDEL_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSGET_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSGET_Test.php index 099a9f28..4ab298df 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSGET_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSGET_Test.php @@ -18,6 +18,10 @@ use Predis\Command\Argument\TimeSeries\GetArguments; use Predis\Command\Redis\PredisCommandTestCase; use Predis\Response\ServerException; +/** + * @group commands + * @group realm-stack + */ class TSGET_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSINCRBY_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSINCRBY_Test.php index 95add6c9..fbfceaca 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSINCRBY_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSINCRBY_Test.php @@ -19,6 +19,10 @@ use Predis\Command\Argument\TimeSeries\IncrByArguments; use Predis\Command\Redis\PredisCommandTestCase; use Predis\Response\ServerException; +/** + * @group commands + * @group realm-stack + */ class TSINCRBY_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSINFO_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSINFO_Test.php index 1720cbc4..20566d53 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSINFO_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSINFO_Test.php @@ -17,6 +17,10 @@ use Predis\Command\Argument\TimeSeries\CreateArguments; use Predis\Command\Argument\TimeSeries\InfoArguments; use Predis\Command\Redis\PredisCommandTestCase; +/** + * @group commands + * @group realm-stack + */ class TSINFO_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSMADD_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSMADD_Test.php index 649139b5..a9f8a11a 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSMADD_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSMADD_Test.php @@ -17,6 +17,10 @@ use Predis\Command\Argument\TimeSeries\CreateArguments; use Predis\Command\Redis\PredisCommandTestCase; use Predis\Response\ServerException; +/** + * @group commands + * @group realm-stack + */ class TSMADD_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSMGET_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSMGET_Test.php index 6b7acc47..74d5b981 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSMGET_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSMGET_Test.php @@ -17,6 +17,10 @@ use Predis\Command\Argument\TimeSeries\CreateArguments; use Predis\Command\Argument\TimeSeries\MGetArguments; use Predis\Command\Redis\PredisCommandTestCase; +/** + * @group commands + * @group realm-stack + */ class TSMGET_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSMRANGE_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSMRANGE_Test.php index 25788b5e..cd7c126e 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSMRANGE_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSMRANGE_Test.php @@ -16,6 +16,10 @@ use Predis\Command\Argument\TimeSeries\CreateArguments; use Predis\Command\Argument\TimeSeries\MRangeArguments; use Predis\Command\Redis\PredisCommandTestCase; +/** + * @group commands + * @group realm-stack + */ class TSMRANGE_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSMREVRANGE_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSMREVRANGE_Test.php index 214a4476..8d5a9adb 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSMREVRANGE_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSMREVRANGE_Test.php @@ -16,6 +16,10 @@ use Predis\Command\Argument\TimeSeries\CreateArguments; use Predis\Command\Argument\TimeSeries\MRangeArguments; use Predis\Command\Redis\PredisCommandTestCase; +/** + * @group commands + * @group realm-stack + */ class TSMREVRANGE_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSQUERYINDEX_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSQUERYINDEX_Test.php index 927875c9..0630300c 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSQUERYINDEX_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSQUERYINDEX_Test.php @@ -16,6 +16,10 @@ use Predis\Command\Argument\TimeSeries\CreateArguments; use Predis\Command\Redis\PredisCommandTestCase; use Predis\Response\ServerException; +/** + * @group commands + * @group realm-stack + */ class TSQUERYINDEX_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSRANGE_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSRANGE_Test.php index ae47a21f..b4c7b0df 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSRANGE_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSRANGE_Test.php @@ -17,6 +17,10 @@ use Predis\Command\Argument\TimeSeries\RangeArguments; use Predis\Command\Redis\PredisCommandTestCase; use Predis\Response\ServerException; +/** + * @group commands + * @group realm-stack + */ class TSRANGE_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TimeSeries/TSREVRANGE_Test.php b/tests/Predis/Command/Redis/TimeSeries/TSREVRANGE_Test.php index b607f608..1615f5c6 100644 --- a/tests/Predis/Command/Redis/TimeSeries/TSREVRANGE_Test.php +++ b/tests/Predis/Command/Redis/TimeSeries/TSREVRANGE_Test.php @@ -17,6 +17,10 @@ use Predis\Command\Argument\TimeSeries\RangeArguments; use Predis\Command\Redis\PredisCommandTestCase; use Predis\Response\ServerException; +/** + * @group commands + * @group realm-stack + */ class TSREVRANGE_Test extends PredisCommandTestCase { /** diff --git a/tests/Predis/Command/Redis/TopK/TOPKINFO_Test.php b/tests/Predis/Command/Redis/TopK/TOPKINFO_Test.php index 2b83eb06..2a0ca4c9 100644 --- a/tests/Predis/Command/Redis/TopK/TOPKINFO_Test.php +++ b/tests/Predis/Command/Redis/TopK/TOPKINFO_Test.php @@ -73,7 +73,7 @@ class TOPKINFO_Test extends PredisCommandTestCase $redis->topkreserve('key', 50); - $this->assertSame( + $this->assertEquals( ['k' => 50, 'width' => 8, 'depth' => 7, 'decay' => '0.90000000000000002'], $redis->topkinfo('key') ); diff --git a/tests/Predis/Command/Redis/TopK/TOPKRESERVE_Test.php b/tests/Predis/Command/Redis/TopK/TOPKRESERVE_Test.php index 66ae6739..08a978ee 100644 --- a/tests/Predis/Command/Redis/TopK/TOPKRESERVE_Test.php +++ b/tests/Predis/Command/Redis/TopK/TOPKRESERVE_Test.php @@ -77,7 +77,7 @@ class TOPKRESERVE_Test extends PredisCommandTestCase $actualInfoResponse = $redis->topkinfo($key); $this->assertEquals('OK', $actualResponse); - $this->assertSame($expectedInfoResponse, $actualInfoResponse); + $this->assertEquals($expectedInfoResponse, $actualInfoResponse); } /** diff --git a/tests/Predis/Command/Redis/UNSUBSCRIBE_Test.php b/tests/Predis/Command/Redis/UNSUBSCRIBE_Test.php index 418aa63e..9aedf18b 100644 --- a/tests/Predis/Command/Redis/UNSUBSCRIBE_Test.php +++ b/tests/Predis/Command/Redis/UNSUBSCRIBE_Test.php @@ -15,6 +15,7 @@ namespace Predis\Command\Redis; /** * @group commands * @group realm-pubsub + * @group relay-incompatible */ class UNSUBSCRIBE_Test extends PredisCommandTestCase { diff --git a/tests/Predis/Command/Redis/UNWATCH_Test.php b/tests/Predis/Command/Redis/UNWATCH_Test.php index 556d9340..efa9cc8d 100644 --- a/tests/Predis/Command/Redis/UNWATCH_Test.php +++ b/tests/Predis/Command/Redis/UNWATCH_Test.php @@ -75,6 +75,7 @@ class UNWATCH_Test extends PredisCommandTestCase /** * @group connected + * @group relay-incompatible * @requiresRedisVersion >= 2.2.0 */ public function testCanBeCalledInsideTransaction(): void @@ -84,4 +85,17 @@ class UNWATCH_Test extends PredisCommandTestCase $redis->multi(); $this->assertInstanceOf('Predis\Response\Status', $redis->unwatch()); } + + /** + * @group connected + * @group ext-relay + * @requiresRedisVersion >= 2.2.0 + */ + public function testCanBeCalledInsideTransactionUsingRelay(): void + { + $redis = $this->getClient(); + + $redis->multi(); + $this->assertInstanceOf('Relay\Relay', $redis->unwatch()); + } } diff --git a/tests/Predis/Command/Redis/WATCH_Test.php b/tests/Predis/Command/Redis/WATCH_Test.php index c963aade..fd2a4234 100644 --- a/tests/Predis/Command/Redis/WATCH_Test.php +++ b/tests/Predis/Command/Redis/WATCH_Test.php @@ -15,6 +15,7 @@ namespace Predis\Command\Redis; /** * @group commands * @group realm-transaction + * @group relay-incompatible */ class WATCH_Test extends PredisCommandTestCase { diff --git a/tests/Predis/Command/Redis/ZADD_Test.php b/tests/Predis/Command/Redis/ZADD_Test.php index bee4e21d..9f4827af 100644 --- a/tests/Predis/Command/Redis/ZADD_Test.php +++ b/tests/Predis/Command/Redis/ZADD_Test.php @@ -154,10 +154,10 @@ class ZADD_Test extends PredisCommandTestCase { $redis = $this->getClient(); - $this->assertSame('1', $redis->zadd('letters', 'INCR', 1, 'a')); - $this->assertSame('0', $redis->zadd('letters', 'INCR', -1, 'a')); - $this->assertSame('0.5', $redis->zadd('letters', 'INCR', 0.5, 'a')); - $this->assertSame('-10', $redis->zadd('letters', 'INCR', -10.5, 'a')); + $this->assertEquals('1', $redis->zadd('letters', 'INCR', 1, 'a')); + $this->assertEquals('0', $redis->zadd('letters', 'INCR', -1, 'a')); + $this->assertEquals('0.5', $redis->zadd('letters', 'INCR', 0.5, 'a')); + $this->assertEquals('-10', $redis->zadd('letters', 'INCR', -10.5, 'a')); } /** diff --git a/tests/Predis/Command/Redis/ZINCRBY_Test.php b/tests/Predis/Command/Redis/ZINCRBY_Test.php index 121eb35a..74f1cd8b 100644 --- a/tests/Predis/Command/Redis/ZINCRBY_Test.php +++ b/tests/Predis/Command/Redis/ZINCRBY_Test.php @@ -63,10 +63,10 @@ class ZINCRBY_Test extends PredisCommandTestCase { $redis = $this->getClient(); - $this->assertSame('1', $redis->zincrby('letters', 1, 'member')); - $this->assertSame('0', $redis->zincrby('letters', -1, 'member')); - $this->assertSame('0.5', $redis->zincrby('letters', 0.5, 'member')); - $this->assertSame('-10', $redis->zincrby('letters', -10.5, 'member')); + $this->assertEquals('1', $redis->zincrby('letters', 1, 'member')); + $this->assertEquals('0', $redis->zincrby('letters', -1, 'member')); + $this->assertEquals('0.5', $redis->zincrby('letters', 0.5, 'member')); + $this->assertEquals('-10', $redis->zincrby('letters', -10.5, 'member')); } /** diff --git a/tests/Predis/Command/Redis/ZINTERSTORE_Test.php b/tests/Predis/Command/Redis/ZINTERSTORE_Test.php index 758a5eb4..2f52e73f 100644 --- a/tests/Predis/Command/Redis/ZINTERSTORE_Test.php +++ b/tests/Predis/Command/Redis/ZINTERSTORE_Test.php @@ -92,7 +92,7 @@ class ZINTERSTORE_Test extends PredisCommandTestCase ); $this->assertSame($expectedResponse, $actualResponse); - $this->assertSame( + $this->assertEquals( $expectedResultSortedSet, $redis->zrange($destination, 0, -1, ['withscores' => true]) ); diff --git a/tests/Predis/Command/Redis/ZINTER_Test.php b/tests/Predis/Command/Redis/ZINTER_Test.php index 297d4387..9390cd5a 100644 --- a/tests/Predis/Command/Redis/ZINTER_Test.php +++ b/tests/Predis/Command/Redis/ZINTER_Test.php @@ -107,7 +107,7 @@ class ZINTER_Test extends PredisCommandTestCase $withScores ); - $this->assertSame($expectedResponse, $actualResponse); + $this->assertEquals($expectedResponse, $actualResponse); } /** diff --git a/tests/Predis/Command/Redis/ZMPOP_Test.php b/tests/Predis/Command/Redis/ZMPOP_Test.php index b57adba3..6d52005f 100644 --- a/tests/Predis/Command/Redis/ZMPOP_Test.php +++ b/tests/Predis/Command/Redis/ZMPOP_Test.php @@ -79,7 +79,7 @@ class ZMPOP_Test extends PredisCommandTestCase $redis->zadd($key, ...$sortedSetDictionary); $actualResponse = $redis->zmpop([$key], $modifier, $count); - $this->assertSame($expectedResponse, $actualResponse); + $this->assertEquals($expectedResponse, $actualResponse); $this->assertSame($expectedModifiedSortedSet, $redis->zrange($key, 0, -1)); } diff --git a/tests/Predis/Command/Redis/ZMSCORE_Test.php b/tests/Predis/Command/Redis/ZMSCORE_Test.php index 19f62c7a..db1d0941 100644 --- a/tests/Predis/Command/Redis/ZMSCORE_Test.php +++ b/tests/Predis/Command/Redis/ZMSCORE_Test.php @@ -78,7 +78,7 @@ class ZMSCORE_Test extends PredisCommandTestCase $redis->zadd($key, ...$membersDictionary); - $this->assertSame($expectedResponse, $redis->zmscore($key, ...$members)); + $this->assertEquals($expectedResponse, $redis->zmscore($key, ...$members)); $this->assertNull($redis->zmscore($key, $notExpectedMember)[0]); } diff --git a/tests/Predis/Command/Redis/ZPOPMAX_Test.php b/tests/Predis/Command/Redis/ZPOPMAX_Test.php index 5939c153..dad4dde4 100644 --- a/tests/Predis/Command/Redis/ZPOPMAX_Test.php +++ b/tests/Predis/Command/Redis/ZPOPMAX_Test.php @@ -66,9 +66,8 @@ class ZPOPMAX_Test extends PredisCommandTestCase } /** - * @requiresRedisVersion >= 5.0.0 - * * @group connected + * @requiresRedisVersion >= 5.0.0 */ public function testReturnsElements(): void { @@ -79,9 +78,9 @@ class ZPOPMAX_Test extends PredisCommandTestCase $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f'); - $this->assertSame(['f' => '30'], $redis->zpopmax('letters')); - $this->assertSame(['e' => '20', 'd' => '20', 'c' => '10'], $redis->zpopmax('letters', 3)); - $this->assertSame(['b' => '0', 'a' => '-10'], $redis->zpopmax('letters', 3)); + $this->assertEquals(['f' => '30'], $redis->zpopmax('letters')); + $this->assertEquals(['e' => '20', 'd' => '20', 'c' => '10'], $redis->zpopmax('letters', 3)); + $this->assertEquals(['b' => '0', 'a' => '-10'], $redis->zpopmax('letters', 3)); } /** diff --git a/tests/Predis/Command/Redis/ZPOPMIN_Test.php b/tests/Predis/Command/Redis/ZPOPMIN_Test.php index a15abade..43849803 100644 --- a/tests/Predis/Command/Redis/ZPOPMIN_Test.php +++ b/tests/Predis/Command/Redis/ZPOPMIN_Test.php @@ -66,9 +66,8 @@ class ZPOPMIN_Test extends PredisCommandTestCase } /** - * @requiresRedisVersion >= 5.0.0 - * * @group connected + * @requiresRedisVersion >= 5.0.0 */ public function testReturnsElements(): void { @@ -79,9 +78,9 @@ class ZPOPMIN_Test extends PredisCommandTestCase $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f'); - $this->assertSame(['a' => '-10'], $redis->zpopmin('letters')); - $this->assertSame(['b' => '0', 'c' => '10', 'd' => '20'], $redis->zpopmin('letters', 3)); - $this->assertSame(['e' => '20', 'f' => '30'], $redis->zpopmin('letters', 3)); + $this->assertEquals(['a' => '-10'], $redis->zpopmin('letters')); + $this->assertEquals(['b' => '0', 'c' => '10', 'd' => '20'], $redis->zpopmin('letters', 3)); + $this->assertEquals(['e' => '20', 'f' => '30'], $redis->zpopmin('letters', 3)); } /** diff --git a/tests/Predis/Command/Redis/ZRANGEBYSCORE_Test.php b/tests/Predis/Command/Redis/ZRANGEBYSCORE_Test.php index 8d370062..b933a6ec 100644 --- a/tests/Predis/Command/Redis/ZRANGEBYSCORE_Test.php +++ b/tests/Predis/Command/Redis/ZRANGEBYSCORE_Test.php @@ -180,8 +180,8 @@ class ZRANGEBYSCORE_Test extends PredisCommandTestCase $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f'); $expected = ['c' => '10', 'd' => '20', 'e' => '20']; - $this->assertSame($expected, $redis->zrangebyscore('letters', 10, 20, 'withscores')); - $this->assertSame($expected, $redis->zrangebyscore('letters', 10, 20, ['withscores' => true])); + $this->assertEquals($expected, $redis->zrangebyscore('letters', 10, 20, 'withscores')); + $this->assertEquals($expected, $redis->zrangebyscore('letters', 10, 20, ['withscores' => true])); } /** @@ -210,7 +210,7 @@ class ZRANGEBYSCORE_Test extends PredisCommandTestCase $options = ['limit' => [1, 2], 'withscores' => true]; $expected = ['d' => '20', 'e' => '20']; - $this->assertSame($expected, $redis->zrangebyscore('letters', 10, 20, $options)); + $this->assertEquals($expected, $redis->zrangebyscore('letters', 10, 20, $options)); } /** diff --git a/tests/Predis/Command/Redis/ZRANGE_Test.php b/tests/Predis/Command/Redis/ZRANGE_Test.php index 9c63ca3e..3e58516a 100644 --- a/tests/Predis/Command/Redis/ZRANGE_Test.php +++ b/tests/Predis/Command/Redis/ZRANGE_Test.php @@ -137,8 +137,8 @@ class ZRANGE_Test extends PredisCommandTestCase $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f'); $expected = ['c' => '10', 'd' => '20', 'e' => '20']; - $this->assertSame($expected, $redis->zrange('letters', 2, 4, 'withscores')); - $this->assertSame($expected, $redis->zrange('letters', 2, 4, ['withscores' => true])); + $this->assertEquals($expected, $redis->zrange('letters', 2, 4, 'withscores')); + $this->assertEquals($expected, $redis->zrange('letters', 2, 4, ['withscores' => true])); } /** diff --git a/tests/Predis/Command/Redis/ZREVRANGEBYSCORE_Test.php b/tests/Predis/Command/Redis/ZREVRANGEBYSCORE_Test.php index f8a2a319..e1545432 100644 --- a/tests/Predis/Command/Redis/ZREVRANGEBYSCORE_Test.php +++ b/tests/Predis/Command/Redis/ZREVRANGEBYSCORE_Test.php @@ -184,8 +184,8 @@ class ZREVRANGEBYSCORE_Test extends PredisCommandTestCase $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f'); $expected = ['e' => '20', 'd' => '20', 'c' => '10']; - $this->assertSame($expected, $redis->zrevrangebyscore('letters', 20, 10, 'withscores')); - $this->assertSame($expected, $redis->zrevrangebyscore('letters', 20, 10, ['withscores' => true])); + $this->assertEquals($expected, $redis->zrevrangebyscore('letters', 20, 10, 'withscores')); + $this->assertEquals($expected, $redis->zrevrangebyscore('letters', 20, 10, ['withscores' => true])); } /** @@ -216,7 +216,7 @@ class ZREVRANGEBYSCORE_Test extends PredisCommandTestCase $options = ['limit' => [1, 2], 'withscores' => true]; $expected = ['d' => '20', 'c' => '10']; - $this->assertSame($expected, $redis->zrevrangebyscore('letters', 20, 10, $options)); + $this->assertEquals($expected, $redis->zrevrangebyscore('letters', 20, 10, $options)); } /** diff --git a/tests/Predis/Command/Redis/ZREVRANGE_Test.php b/tests/Predis/Command/Redis/ZREVRANGE_Test.php index 8ae88d78..ac2e228e 100644 --- a/tests/Predis/Command/Redis/ZREVRANGE_Test.php +++ b/tests/Predis/Command/Redis/ZREVRANGE_Test.php @@ -137,8 +137,8 @@ class ZREVRANGE_Test extends PredisCommandTestCase $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f'); $expected = ['d' => '20', 'c' => '10', 'b' => '0']; - $this->assertSame($expected, $redis->zrevrange('letters', 2, 4, 'withscores')); - $this->assertSame($expected, $redis->zrevrange('letters', 2, 4, ['withscores' => true])); + $this->assertEquals($expected, $redis->zrevrange('letters', 2, 4, 'withscores')); + $this->assertEquals($expected, $redis->zrevrange('letters', 2, 4, ['withscores' => true])); } /** diff --git a/tests/Predis/Command/Redis/ZSCORE_Test.php b/tests/Predis/Command/Redis/ZSCORE_Test.php index dc50820c..680788d5 100644 --- a/tests/Predis/Command/Redis/ZSCORE_Test.php +++ b/tests/Predis/Command/Redis/ZSCORE_Test.php @@ -65,9 +65,9 @@ class ZSCORE_Test extends PredisCommandTestCase $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f'); - $this->assertSame('-10', $redis->zscore('letters', 'a')); - $this->assertSame('0', $redis->zscore('letters', 'b')); - $this->assertSame('20', $redis->zscore('letters', 'e')); + $this->assertEquals('-10', $redis->zscore('letters', 'a')); + $this->assertEquals('0', $redis->zscore('letters', 'b')); + $this->assertEquals('20', $redis->zscore('letters', 'e')); $this->assertNull($redis->zscore('unknown', 'a')); } diff --git a/tests/Predis/Command/Redis/ZUNIONSTORE_Test.php b/tests/Predis/Command/Redis/ZUNIONSTORE_Test.php index 72c8724a..61405d83 100644 --- a/tests/Predis/Command/Redis/ZUNIONSTORE_Test.php +++ b/tests/Predis/Command/Redis/ZUNIONSTORE_Test.php @@ -92,7 +92,7 @@ class ZUNIONSTORE_Test extends PredisCommandTestCase ); $this->assertSame($expectedResponse, $actualResponse); - $this->assertSame( + $this->assertEquals( $expectedResultSortedSet, $redis->zrange($destination, 0, -1, ['withscores' => true]) ); diff --git a/tests/Predis/Command/Redis/ZUNION_Test.php b/tests/Predis/Command/Redis/ZUNION_Test.php index eef3941a..2e1d3ed4 100644 --- a/tests/Predis/Command/Redis/ZUNION_Test.php +++ b/tests/Predis/Command/Redis/ZUNION_Test.php @@ -81,7 +81,7 @@ class ZUNION_Test extends PredisCommandTestCase $withScores ); - $this->assertSame($expectedResponse, $actualResponse); + $this->assertEquals($expectedResponse, $actualResponse); } /** diff --git a/tests/Predis/Configuration/Option/ConnectionsTest.php b/tests/Predis/Configuration/Option/ConnectionsTest.php index fc46308b..346049b0 100644 --- a/tests/Predis/Configuration/Option/ConnectionsTest.php +++ b/tests/Predis/Configuration/Option/ConnectionsTest.php @@ -66,7 +66,7 @@ class ConnectionsTest extends PredisTestCase * @group disconnected * @dataProvider provideSupportedStringValuesForOption */ - public function testAcceptsStringToConfigurePhpiredisStreamBackend($value, $classFQCN) + public function testAcceptsStringToConfigureRelayBackend($value, $classFQCN) { $options = $this->getMockBuilder('Predis\Configuration\OptionsInterface')->getMock(); @@ -232,6 +232,7 @@ class ConnectionsTest extends PredisTestCase ['phpiredis-stream', 'Predis\Connection\PhpiredisStreamConnection'], ['phpiredis-socket', 'Predis\Connection\PhpiredisSocketConnection'], ['phpiredis', 'Predis\Connection\PhpiredisStreamConnection'], + ['relay', \Predis\Connection\RelayConnection::class], ]; } } diff --git a/tests/Predis/Connection/Cluster/RedisClusterTest.php b/tests/Predis/Connection/Cluster/RedisClusterTest.php index 7cf28a66..cf021b2c 100644 --- a/tests/Predis/Connection/Cluster/RedisClusterTest.php +++ b/tests/Predis/Connection/Cluster/RedisClusterTest.php @@ -737,6 +737,7 @@ class RedisClusterTest extends PredisTestCase /** * @group disconnected + * @group slow */ public function testThrowsClientExceptionWhenExecutingCommandWithEmptyPool(): void { diff --git a/tests/Predis/Connection/ParametersTest.php b/tests/Predis/Connection/ParametersTest.php index 700ec4a4..eaab5baf 100644 --- a/tests/Predis/Connection/ParametersTest.php +++ b/tests/Predis/Connection/ParametersTest.php @@ -386,6 +386,23 @@ class ParametersTest extends PredisTestCase $this->assertSame($uri, (string) $parameters); } + /** + * @group disconnected + */ + public function testSettingRelayOptions(): void + { + $uri = 'tcp://10.10.10.10?serializer=igbinary&compression=lz4'; + + $expected = [ + 'scheme' => 'tcp', + 'host' => '10.10.10.10', + 'serializer' => 'igbinary', + 'compression' => 'lz4', + ]; + + $this->assertSame($expected, Parameters::parse($uri)); + } + // ******************************************************************** // // ---- HELPER METHODS ------------------------------------------------ // // ******************************************************************** // diff --git a/tests/Predis/Connection/RelayConnectionTest.php b/tests/Predis/Connection/RelayConnectionTest.php new file mode 100644 index 00000000..11bc72e3 --- /dev/null +++ b/tests/Predis/Connection/RelayConnectionTest.php @@ -0,0 +1,234 @@ +expectException('Predis\Connection\ConnectionException'); + $this->expectExceptionMessage('`SELECT` failed: ERR invalid DB index [tcp://127.0.0.1:6379]'); + + $cmdSelect = RawCommand::create('SELECT', '1000'); + + /** @var NodeConnectionInterface|MockObject */ + $connection = $this + ->getMockBuilder($this->getConnectionClass()) + ->onlyMethods(['executeCommand', 'createResource']) + ->setConstructorArgs([new Parameters()]) + ->getMock(); + $connection + ->method('executeCommand') + ->with($cmdSelect) + ->willReturn( + new ErrorResponse('ERR invalid DB index') + ); + + $connection->method('createResource'); + + $connection->addConnectCommand($cmdSelect); + $connection->connect(); + } + + // ******************************************************************** // + // ---- INTEGRATION TESTS --------------------------------------------- // + // ******************************************************************** // + + /** + * @group connected + */ + public function testGetResourceForcesConnection(): void + { + $connection = $this->createConnection(); + + $this->assertFalse($connection->isConnected()); + $connection->getResource(); + $this->assertTrue($connection->isConnected()); + } + + /** + * @group connected + * @group slow + * @requires PHP 5.4 + */ + public function testThrowsExceptionOnReadWriteTimeout(): void + { + $this->expectException('Predis\Connection\ConnectionException'); + + $connection = $this->createConnectionWithParams([ + 'read_write_timeout' => 0.5, + ], true); + + $connection->executeCommand( + $this->getCommandFactory()->create('brpop', ['foo', 3]) + ); + } + + /** + * @medium + * @group connected + * @group relay-incompatible + */ + public function testThrowsExceptionOnProtocolDesynchronizationErrors(): void + { + $this->expectException('Predis\Protocol\ProtocolException'); + + $connection = $this->createConnection(); + $stream = $connection->getResource(); + + $connection->writeRequest($this->getCommandFactory()->create('ping')); + stream_socket_recvfrom($stream, 1); + + $connection->read(); + } + + /** + * @group connected + * @group relay-incompatible + * @requires PHP 5.4 + */ + public function testPersistentParameterWithFalseLikeValues(): void + { + $connection1 = $this->createConnectionWithParams(['persistent' => 0]); + $this->assertNonPersistentConnection($connection1); + + $connection2 = $this->createConnectionWithParams(['persistent' => false]); + $this->assertNonPersistentConnection($connection2); + + $connection3 = $this->createConnectionWithParams(['persistent' => '0']); + $this->assertNonPersistentConnection($connection3); + + $connection4 = $this->createConnectionWithParams(['persistent' => 'false']); + $this->assertNonPersistentConnection($connection4); + } + + /** + * @group connected + * @group relay-incompatible + * @requires PHP 5.4 + */ + public function testPersistentParameterWithTrueLikeValues(): void + { + $connection1 = $this->createConnectionWithParams(['persistent' => 1]); + $this->assertPersistentConnection($connection1); + + $connection2 = $this->createConnectionWithParams(['persistent' => true]); + $this->assertPersistentConnection($connection2); + + $connection3 = $this->createConnectionWithParams(['persistent' => '1']); + $this->assertPersistentConnection($connection3); + + $connection4 = $this->createConnectionWithParams(['persistent' => 'true']); + $this->assertPersistentConnection($connection4); + + $connection1->disconnect(); + } + + /** + * @group connected + * @group relay-incompatible + * @requires PHP 5.4 + */ + public function testPersistentConnectionsToSameNodeShareResource(): void + { + $connection1 = $this->createConnectionWithParams(['persistent' => true]); + $connection2 = $this->createConnectionWithParams(['persistent' => true]); + + $this->assertPersistentConnection($connection1); + $this->assertPersistentConnection($connection2); + + $this->assertSame($connection1->getResource(), $connection2->getResource()); + + $connection1->disconnect(); + } + + /** + * @group connected + * @group relay-incompatible + * @requires PHP 5.4 + */ + public function testPersistentConnectionsToSameNodeDoNotShareResourceUsingDifferentPersistentID(): void + { + $connection1 = $this->createConnectionWithParams(['persistent' => 'conn1']); + $connection2 = $this->createConnectionWithParams(['persistent' => 'conn2']); + + $this->assertPersistentConnection($connection1); + $this->assertPersistentConnection($connection2); + + $this->assertNotSame($connection1->getResource(), $connection2->getResource()); + } + + /** + * @group connected + * @group relay-incompatible + */ + public function testTcpNodelayParameterSetsContextFlagWhenTrue() + { + $connection = $this->createConnectionWithParams(['tcp_nodelay' => true]); + $options = stream_context_get_options($connection->getResource()); + + $this->assertIsArray($options); + $this->assertArrayHasKey('socket', $options); + $this->assertArrayHasKey('tcp_nodelay', $options['socket']); + $this->assertTrue($options['socket']['tcp_nodelay']); + } + + /** + * @group connected + * @group relay-incompatible + */ + public function testTcpNodelayParameterDoesNotSetContextFlagWhenFalse() + { + $connection = $this->createConnectionWithParams(['tcp_nodelay' => false]); + $options = stream_context_get_options($connection->getResource()); + + $this->assertIsArray($options); + $this->assertArrayHasKey('socket', $options); + $this->assertArrayHasKey('tcp_nodelay', $options['socket']); + $this->assertFalse($options['socket']['tcp_nodelay']); + } + + /** + * @group connected + * @group relay-incompatible + */ + public function testTcpDelayContextFlagIsNotSetByDefault() + { + $connection = $this->createConnectionWithParams([]); + $options = stream_context_get_options($connection->getResource()); + + $this->assertIsArray($options); + $this->assertArrayHasKey('socket', $options); + $this->assertArrayHasKey('tcp_nodelay', $options['socket']); + $this->assertFalse($options['socket']['tcp_nodelay']); + } +} diff --git a/tests/Predis/Transaction/MultiExecTest.php b/tests/Predis/Transaction/MultiExecTest.php index e42d0251..c250961c 100644 --- a/tests/Predis/Transaction/MultiExecTest.php +++ b/tests/Predis/Transaction/MultiExecTest.php @@ -723,6 +723,7 @@ class MultiExecTest extends PredisTestCase /** * @group connected + * @group relay-incompatible */ public function testIntegrationReturnsErrorObjectOnRedisErrorInBlock(): void { @@ -739,6 +740,25 @@ class MultiExecTest extends PredisTestCase $this->assertSame('foobar', $responses[2]); } + /** + * @group connected + * @group ext-relay + */ + public function testIntegrationReturnsErrorObjectOnRedisErrorInBlockWhenUsingRelay(): void + { + $client = $this->getClient([], ['exceptions' => false]); + + $responses = $client->transaction(function (MultiExec $tx) { + $tx->set('foo', 'bar'); + $tx->lpush('foo', 'bar'); + $tx->echo('foobar'); + }); + + $this->assertSame('OK', $responses[0]); + $this->assertInstanceOf('Predis\Response\Error', $responses[1]); + $this->assertSame('foobar', $responses[2]); + } + /** * @group connected * @requiresRedisVersion >= 2.0.0 diff --git a/tests/README.md b/tests/README.md index 5d2e46a1..34189925 100644 --- a/tests/README.md +++ b/tests/README.md @@ -27,7 +27,7 @@ line test runner. Please note that due to a bug in PHPUnit, older versions ignor option when the group is excluded in the XML configuration file. More details about this issue are available on [PHPUnit's bug tracker](http://github.com/sebastianbergmann/phpunit/issues/320). -Certain groups of tests requiring native extensions, such as `ext-curl` or `ext-phpiredis`, are +Certain groups of tests requiring native extensions, such as `ext-relay`, are disabled by default in the configuration file. To enable these groups of tests you should remove them from the exclusion list in `phpunit.xml`.