Compare commits

...

11 Commits

Author SHA1 Message Date
vladvildanov ebdd22ff61 Added this PR to changelog 2025-03-20 15:01:28 +02:00
vladvildanov 25b33f50ae Merge branch 'vv-added-missing-types' of github.com:predis/predis into vv-added-missing-types 2025-03-20 15:00:36 +02:00
vladvildanov 3864a7b125 Updated CHANGELOG.md 2025-03-20 15:00:21 +02:00
Vladyslav Vildanov 6f4ac2a012 Merge branch 'v2.x' into vv-added-missing-types 2025-03-20 14:45:42 +02:00
vladvildanov 72f6baef26 Added missing types for commands API 2025-03-20 14:44:36 +02:00
messikiller 7bf1f6eb29 optimize redis cluster slotmap with compact slot range object (#1493)
Co-authored-by: heqiming <heqiming@kingsoft.com>
Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
Co-authored-by: Vladyslav Vildanov <117659936+vladvildanov@users.noreply.github.com>
2025-03-19 09:24:53 -07:00
Vladyslav Vildanov 067b051900 Added missing FT._LIST and BITFIELD_RO commands (#1521)
* Added missing FT._LIST and BITFIELD_RO commands

* Codestyle fixes
2025-03-19 09:57:52 +02:00
Till Krüss df4a318b80 remove performance rambling from FAQ 2025-03-18 12:50:00 -07:00
Vladyslav Vildanov a904a3a273 Backport #1517 (#1518)
* Backport #1517

* Added versions restrictions

* Removed Relay for cluster with coverage

* Added phpcov package
2025-03-05 19:29:38 +02:00
Vladyslav Vildanov 8b240d15a5 Backport #1512 (#1515)
* Backport #1512
* Removed outdated test
2025-02-25 09:12:10 -08:00
Vladyslav Vildanov 02e2a7e1cc Added test coverage for compatibility with Redis 8.0 (#1513)
* Added testing with 8.0

* Removed old service

* Moved checkout stage above image

* Changed service name

* Override config options for Redis < 7

* Exported to github env

* Print major version output

* Added double quotes

* Test fixes

* Merge cluster and standalone infrastructure

* Removed separate cluster job and merge together with standalone

* Merge stack tests into main test job

* Fixed annotation condition

* tweaks

* Moved docker-compose.yml to root folder

* Added compose file to expected

* fixed in v0.10.1

* move docker file

* Added -all profile in docker-compose

* Removed redis official image, reduce test matrix, moved docker-compose.yml

* Revert docker-compose moving

* Added test coverage to verify compatibility with 8.0

* Mark tests as relay-incompatible

* Codestyle fix

* Marked as relay-incompatible

---------

Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
2025-02-24 09:22:45 -08:00
47 changed files with 2005 additions and 232 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ services:
- standalone
redis-cluster:
image: ${REDIS_CLUSTER_IMAGE_NAME:-redislabs/client-libs-test:7.4.2}
image: ${REDIS_IMAGE_NAME:-redislabs/client-libs-test:7.4.2}
container_name: redis-cluster
environment:
- REDIS_CLUSTER=yes
+63 -18
View File
@@ -44,8 +44,16 @@ jobs:
["6.2"]="6.2.17"
)
# Mapping of redis version to stack version
declare -A redis_stack_version_mapping=(
["7.4"]="rs-7.4.0-v3"
["7.2"]="rs-7.2.0-v15"
["6.2"]="rs-6.2.6-v19"
)
if [[ -v redis_clients_version_mapping[${{ matrix.redis }}] ]]; then
echo "REDIS_IMAGE_NAME=redislabs/client-libs-test:${redis_clients_version_mapping[${{ matrix.redis }}]}" >> $GITHUB_ENV
echo "REDIS_STACK_IMAGE_NAME=redislabs/client-libs-test:${redis_stack_version_mapping[${{ matrix.redis }}]}" >> $GITHUB_ENV
echo "DOCKER_SERVICE=redis-clients" >> $GITHUB_ENV
redis_major_version=$(echo "${{ matrix.redis }}" | grep -oP '^\d+')
@@ -53,7 +61,11 @@ jobs:
# Some configuration options available since Redis > 7
if (( redis_major_version < 7 )); then
echo "REDIS_EXTRA_ARGS="--tls-auth-clients optional --save ''"" >> $GITHUB_ENV
else
# Since 8.0 modules are bundled with core
echo "REDIS_STACK_SERVER_PORT=6379" >> $GITHUB_ENV
fi
else
echo "REDIS_IMAGE_NAME=redis:${{ matrix.redis }}" >> $GITHUB_ENV
echo "DOCKER_SERVICE=redis-official" >> $GITHUB_ENV
@@ -69,15 +81,17 @@ jobs:
services: ${{ env.DOCKER_SERVICE }}
- name: Start Redis stack image
id: stack_infra
uses: hoverkraft-tech/compose-action@v2.0.1
if: ${{ matrix.redis == '7.4' }}
if: ${{ matrix.redis >= '6.2' && matrix.redis < '8.0' }}
with:
compose-file: .github/docker-compose.yml
services: redis-stack
- name: Start Redis cluster image
id: cluster_infra
uses: hoverkraft-tech/compose-action@v2.0.1
if: ${{ matrix.redis == '7.4' }}
if: ${{ matrix.redis > '4.0' }}
with:
compose-file: .github/docker-compose.yml
services: redis-cluster
@@ -87,7 +101,7 @@ jobs:
with:
php-version: ${{ matrix.php }}
extensions: relay
coverage: ${{ (matrix.php == '8.1' && matrix.redis == '7.4') && 'xdebug' || 'none' }}
coverage: ${{ (matrix.php == '8.4' && matrix.redis == '7.4') && 'xdebug' || 'none' }}
- name: Install Composer dependencies
uses: ramsey/composer-install@v2
@@ -96,43 +110,74 @@ jobs:
composer-options: ${{ matrix.php == '8.0' && '--ignore-platform-reqs' || '' }}
- name: Run tests
if: ${{ matrix.php != '8.1' || matrix.redis != '7.4' }}
if: ${{ matrix.php != '8.4' || matrix.redis != '7.4' }}
run: vendor/bin/phpunit
- name: Run tests with coverage
if: ${{ matrix.php == '8.1' && matrix.redis == '7.4' }}
run: vendor/bin/phpunit --coverage-clover build/logs/clover-default.xml --coverage-filter ./src
if: ${{ matrix.php == '8.4' && matrix.redis == '7.4' }}
run: vendor/bin/phpunit --coverage-php build/cov/coverage-predis.cov --coverage-filter ./src
- name: Run tests using Relay
if: ${{ matrix.php != '8.4' && matrix.redis >= '6.2' }}
run: vendor/bin/phpunit -c phpunit.relay.xml
- name: Run tests using Relay with coverage
if: ${{ matrix.php == '8.1' && matrix.redis == '7.4' }}
run: vendor/bin/phpunit -c phpunit.relay.xml --coverage-clover build/logs/clover-relay.xml --coverage-filter ./src
if: ${{ matrix.php == '8.4' && matrix.redis == '7.4' }}
run: vendor/bin/phpunit -c phpunit.relay.xml --coverage-php build/cov/coverage-relay.cov --coverage-filter ./src
- name: Run stack tests
if: ${{ matrix.redis == '7.4' }}
if: ${{ (matrix.php != '8.4' || matrix.redis != '7.4') && matrix.redis >= '6.2' }}
run: vendor/bin/phpunit --group realm-stack
- name: Run stack tests with coverage
if: ${{ matrix.php == '8.4' && matrix.redis == '7.4' && steps.stack_infra.conclusion == 'success' }}
run: vendor/bin/phpunit --group realm-stack --coverage-php build/cov/coverage-stack.cov --coverage-filter ./src
env:
REDIS_STACK_SERVER_PORT: 6479
- name: Run stack tests using Relay
if: ${{ matrix.redis == '7.4' }}
if: ${{ matrix.php != '8.4' && matrix.redis == '7.4' }}
run: vendor/bin/phpunit --group realm-stack -c phpunit.relay.xml
- name: Run stack tests using Relay with coverage
if: ${{ matrix.php == '8.4' && matrix.redis == '7.4' }}
run: vendor/bin/phpunit --group realm-stack -c phpunit.relay.xml --coverage-php build/cov/coverage-stack-relay.cov --coverage-filter ./src
env:
REDIS_STACK_SERVER_PORT: 6479
- name: Run tests against cluster
if: ${{ matrix.redis == '7.4' }}
if: ${{ (matrix.php != '8.4' || matrix.redis != '7.4') && steps.cluster_infra.conclusion == 'success' }}
run: |
vendor/bin/phpunit --group cluster
- name: Run tests against cluster with coverage
if: ${{ matrix.php == '8.4' && matrix.redis == '7.4' && steps.cluster_infra.conclusion == 'success' }}
run: |
vendor/bin/phpunit --group cluster --coverage-php build/cov/coverage-cluster.cov --coverage-filter ./src
- name: Run tests against cluster using Relay
if: ${{ matrix.redis == '7.4' }}
if: ${{ matrix.php != '8.4' && matrix.redis == '7.4' }}
run: |
vendor/bin/phpunit -c phpunit.relay.xml --group cluster
- name: Merge coverage reports
if: ${{ matrix.php == '8.4' && matrix.redis == '7.4' }}
run: php vendor/bin/phpcov merge --clover build/logs/clover.xml build/cov
- name: Send coverage to Coveralls
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
if: ${{ env.COVERALLS_REPO_TOKEN && matrix.php == '8.1' && matrix.redis == '7.4' }}
run: |
wget "https://github.com/php-coveralls/php-coveralls/releases/download/v2.5.3/php-coveralls.phar"
php ./php-coveralls.phar -v
uses: coverallsapp/github-action@v2
if: ${{ matrix.php == '8.4' && matrix.redis == '7.4' }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel: true
finish:
name: Finish Coverall
needs: predis
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@v2
with:
parallel-finished: true
+15 -1
View File
@@ -2,7 +2,21 @@
## Unreleased
### Fixed
- Fixed PHP 8.4 compatibility with `stream_context_set_option()`
- Fixed PHP 8.4 compatibility with `stream_context_set_option()` (#1503)
- Prevent named arguments runtime failure (#1509)
- Mark GEOSEARCH as read-only to ensure execution on replica (#1481)
- Fix eval_ro cluster support (#1449)
### Changed
- Use parallel on PHP-CS-Fixer (#1489)
- Update watch command accepting string and string[] (#1476)
- Optimize redis cluster slotmap with compact slot range object (#1493)
### Added
- Added testing with 8.0 (#1510)
- Added test coverage for compatibility with Redis 8.0 (#1513)
- Added missing FT._LIST and BITFIELD_RO commands (#1521)
- Added missing types for commands API (#1522)
## v2.3.0 (2024-11-21)
### Added
+9 -57
View File
@@ -81,65 +81,17 @@ An exception to this rule is [`SORT`](http://redis.io/commands/sort) for which m
## When should I use Relay? ##
If you care about performance, __always__. [Relay](https://github.com/cachewerk/relay) is free to use.
If you care about performance, __always__. [Relay][relay] is free to use.
## When should I use PhpRedis? ###
Predis is fast enough when Redis is located on the same machine as PHP, more on that later.
Predis is fast enough when Redis is located on the same machine as PHP.
[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).
[PhpRedis][phpredis] and [Relay][relay] perform significantly better when network I/O is involved,
due to its ability to compress data by ~75%. Fewer bytes and received sent over the network
[means faster operations][performance], 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
against a local instance of Redis 2.8 that runs under Ubuntu 13.10 on a Intel Q6600:
```
21000 SET/sec using 12 bytes for both key and value.
21000 GET/sec while retrieving the very same values.
0.130 seconds to fetch 30000 keys using _KEYS *_.
```
How does it compare with [__PhpRedis__](http://github.com/phpredis/phpredis), a nice C extension
providing an efficient client for Redis?
```
30100 SET/sec using 12 bytes for both key and value
29400 GET/sec while retrieving the very same values
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
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
doing something wrong. Also, if you need to `SET` or `GET` multiple keys you should definitely use
commands such as `MSET` and `MGET`. You can also use pipelining to get more performances when this
technique can be used.
There is one more thing: we have tested the overhead of Predis by connecting on a localhost instance
of Redis but how these numbers change when we hit the physical network by connecting to remote Redis
instances?
```
Using Predis:
3200 SET/sec using 12 bytes for both key and value
3200 GET/sec while retrieving the very same values
0.132 seconds to fetch 30000 keys using "KEYS *".
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 *".
```
There you go, you get almost the same average numbers and the reason is simple: network latency is a
real performance killer and you cannot do (almost) anything about that. As a disclaimer, remember
that we are measuring the overhead of client libraries implementations and the effects of network
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.
[phpredis]: https://github.com/phpredis/phpredis
[relay]: [https://github.com/phpredis/phpredis](https://github.com/cachewerk/relay)
[performance]: https://akalongman.medium.com/phpredis-vs-predis-comparison-on-real-production-data-a819b48cbadb
+2 -1
View File
@@ -27,7 +27,8 @@
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.3",
"phpstan/phpstan": "^1.9",
"phpunit/phpunit": "^8.0 || ^9.4"
"phpunit/phpunit": "^8.0 || ^9.4",
"phpunit/phpcov": "^6.0 || ^8.0"
},
"suggest": {
"ext-relay": "Faster connection with in-memory caching (>=0.6.2)"
+1 -1
View File
@@ -38,9 +38,9 @@
<php>
<const name="REDIS_SERVER_HOST" value="127.0.0.1" />
<const name="REDIS_SERVER_PORT" value="6379" />
<const name="REDIS_STACK_SERVER_PORT" value="6479" />
<const name="REDIS_SERVER_DBNUM" value="0" />
<env name="USE_RELAY" value="true" />
<env name="REDIS_STACK_SERVER_PORT" value="6479" />
<!-- Redis Cluster -->
<!-- Only master nodes endpoints included -->
+1 -1
View File
@@ -50,9 +50,9 @@
<php>
<const name="REDIS_SERVER_HOST" value="127.0.0.1" />
<const name="REDIS_SERVER_PORT" value="6379" />
<const name="REDIS_STACK_SERVER_PORT" value="6479" />
<const name="REDIS_SERVER_DBNUM" value="0" />
<env name="USE_RELAY" value="false" />
<env name="REDIS_STACK_SERVER_PORT" value="6479" />
<!-- Redis Cluster -->
<!-- Only master nodes endpoints included -->
+50 -48
View File
@@ -57,7 +57,7 @@ use Predis\Command\Redis\Container\Search\FTCURSOR;
* @method $this expiretime(string $key)
* @method $this keys($pattern)
* @method $this move($key, $db)
* @method $this object($subcommand, $key)
* @method $this object(string $subcommand, string $key)
* @method $this persist($key)
* @method $this pexpire($key, $milliseconds)
* @method $this pexpireat($key, $timestamp)
@@ -65,36 +65,37 @@ use Predis\Command\Redis\Container\Search\FTCURSOR;
* @method $this randomkey()
* @method $this rename($key, $target)
* @method $this renamenx($key, $target)
* @method $this scan($cursor, ?array $options = null)
* @method $this scan(int|string $cursor, ?array $options = null)
* @method $this sort($key, ?array $options = null)
* @method $this sort_ro(string $key, ?string $byPattern = null, ?LimitOffsetCount $limit = null, array $getPatterns = [], ?string $sorting = null, bool $alpha = false)
* @method $this ttl($key)
* @method $this type($key)
* @method $this append($key, $value)
* @method $this bfadd(string $key, $item)
* @method $this bfexists(string $key, $item)
* @method $this bfadd(string $key, string $item)
* @method $this bfexists(string $key, string $item)
* @method $this bfinfo(string $key, string $modifier = '')
* @method $this bfinsert(string $key, int $capacity = -1, float $error = -1, int $expansion = -1, bool $noCreate = false, bool $nonScaling = false, string ...$item)
* @method $this bfloadchunk(string $key, int $iterator, $data)
* @method $this bfmadd(string $key, ...$item)
* @method $this bfmexists(string $key, ...$item)
* @method $this bfmadd(string $key, string ...$item)
* @method $this bfmexists(string $key, string ...$item)
* @method $this bfreserve(string $key, float $errorRate, int $capacity, int $expansion = -1, bool $nonScaling = false)
* @method $this bfscandump(string $key, int $iterator)
* @method $this bitcount(string $key, $start = null, $end = null, string $index = 'byte')
* @method $this bitop($operation, $destkey, $key)
* @method $this bitfield($key, $subcommand, ...$subcommandArg)
* @method $this bitpos($key, $bit, $start = null, $end = null, string $index = 'byte')
* @method $this bitop(string $operation, string $destkey, string $key)
* @method $this bitfield($key, string $subcommand, ...$subcommandArg)
* @method $this bitfield_ro(string $key, ?array $encodingOffsetMap = null)
* @method $this bitpos(string $key, int $bit, ?int $start = null, ?int $end = null, string $index = 'byte')
* @method $this blmpop(int $timeout, array $keys, string $modifier = 'left', int $count = 1)
* @method $this bzpopmax(array $keys, int $timeout)
* @method $this bzpopmin(array $keys, int $timeout)
* @method $this bzmpop(int $timeout, array $keys, string $modifier = 'min', int $count = 1)
* @method $this cfadd(string $key, $item)
* @method $this cfaddnx(string $key, $item)
* @method $this cfcount(string $key, $item)
* @method $this cfdel(string $key, $item)
* @method $this cfexists(string $key, $item)
* @method $this cfadd(string $key, string $item)
* @method $this cfaddnx(string $key, string $item)
* @method $this cfcount(string $key, string $item)
* @method $this cfdel(string $key, string $item)
* @method $this cfexists(string $key, string $item)
* @method $this cfloadchunk(string $key, int $iterator, $data)
* @method $this cfmexists(string $key, ...$item)
* @method $this cfmexists(string $key, string ...$item)
* @method $this cfinfo(string $key)
* @method $this cfinsert(string $key, int $capacity = -1, bool $noCreate = false, string ...$item)
* @method $this cfinsertnx(string $key, int $capacity = -1, bool $noCreate = false, string ...$item)
@@ -111,14 +112,15 @@ use Predis\Command\Redis\Container\Search\FTCURSOR;
* @method $this failover(?To $to = null, bool $abort = false, int $timeout = -1)
* @method $this fcall(string $function, array $keys, ...$args)
* @method $this fcall_ro(string $function, array $keys, ...$args)
* @method $this ft_list()
* @method $this ftaggregate(string $index, string $query, ?AggregateArguments $arguments = null)
* @method $this ftaliasadd(string $alias, string $index)
* @method $this ftaliasdel(string $alias)
* @method $this ftaliasupdate(string $alias, string $index)
* @method $this ftalter(string $index, FieldInterface[] $schema, ?AlterArguments $arguments = null)
* @method $this ftcreate(string $index, FieldInterface[] $schema, ?CreateArguments $arguments = null)
* @method $this ftdictadd(string $dict, ...$term)
* @method $this ftdictdel(string $dict, ...$term)
* @method $this ftdictadd(string $dict, string ...$term)
* @method $this ftdictdel(string $dict, string ...$term)
* @method $this ftdictdump(string $dict)
* @method $this ftdropindex(string $index, ?DropArguments $arguments = null)
* @method $this ftexplain(string $index, string $query, ?ExplainArguments $arguments = null)
@@ -134,23 +136,23 @@ use Predis\Command\Redis\Container\Search\FTCURSOR;
* @method $this ftsynupdate(string $index, string $synonymGroupId, ?SynUpdateArguments $arguments = null, string ...$terms)
* @method $this fttagvals(string $index, string $fieldName)
* @method $this get($key)
* @method $this getbit($key, $offset)
* @method $this getex(string $key, $modifier = '', $value = false)
* @method $this getrange($key, $start, $end)
* @method $this getbit(string $key, int $offset)
* @method $this getex(string $key, string $modifier = '', string|int $value = false)
* @method $this getrange(string $key, int $start, int $end)
* @method $this getdel(string $key)
* @method $this getset($key, $value)
* @method $this getset(string $key, string|int|float $value)
* @method $this incr($key)
* @method $this incrby($key, $increment)
* @method $this incrbyfloat($key, $increment)
* @method $this mget(array $keys)
* @method $this mset(array $dictionary)
* @method $this msetnx(array $dictionary)
* @method $this psetex($key, $milliseconds, $value)
* @method $this set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
* @method $this setbit($key, $offset, $value)
* @method $this setex($key, $seconds, $value)
* @method $this setnx($key, $value)
* @method $this setrange($key, $offset, $value)
* @method $this psetex(string $key, int $milliseconds, string|int|float $value)
* @method $this set(string $key, string|int|float $value, ?string $expireResolution = null, ?int $expireTTL = null, ?string $flag = null)
* @method $this setbit(string $key, int $offset, string|int|float $value)
* @method $this setex(string $key, int $seconds, string|int|float $value)
* @method $this setnx(string $key, string|int|float $value)
* @method $this setrange(string $key, int $offset, string|int|float $value)
* @method $this strlen($key)
* @method $this hdel($key, array $fields)
* @method $this hexists($key, $field)
@@ -170,14 +172,14 @@ use Predis\Command\Redis\Container\Search\FTCURSOR;
* @method $this hmget($key, array $fields)
* @method $this hmset($key, array $dictionary)
* @method $this hrandfield(string $key, int $count = 1, bool $withValues = false)
* @method $this hscan($key, $cursor, ?array $options = null)
* @method $this hscan(string $key, int|string $cursor, ?array $options = null)
* @method $this hset($key, $field, $value)
* @method $this hsetnx($key, $field, $value)
* @method $this httl(string $key, array $fields)
* @method $this hpttl(string $key, array $fields)
* @method $this hvals($key)
* @method $this hstrlen($key, $field)
* @method $this jsonarrappend(string $key, string $path = '$', ...$value)
* @method $this jsonarrappend(string $key, string $path = '$', string ...$value)
* @method $this jsonarrindex(string $key, string $path, string $value, int $start = 0, int $stop = 0)
* @method $this jsonarrinsert(string $key, string $path, int $index, string ...$value)
* @method $this jsonarrlen(string $key, string $path = '$')
@@ -205,7 +207,7 @@ use Predis\Command\Redis\Container\Search\FTCURSOR;
* @method $this brpoplpush($source, $destination, $timeout)
* @method $this lcs(string $key1, string $key2, bool $len = false, bool $idx = false, int $minMatchLen = 0, bool $withMatchLen = false)
* @method $this lindex($key, $index)
* @method $this linsert($key, $whence, $pivot, $value)
* @method $this linsert(string $key, string $whence, string|int|float $pivot, string|int|float $value)
* @method $this llen($key)
* @method $this lmove(string $source, string $destination, string $where, string $to)
* @method $this lmpop(array $keys, string $modifier = 'left', int $count = 1)
@@ -251,11 +253,11 @@ use Predis\Command\Redis\Container\Search\FTCURSOR;
* @method $this tdigestreset(string $key)
* @method $this tdigestrevrank(string $key, float ...$value)
* @method $this tdigesttrimmed_mean(string $key, float $lowCutQuantile, float $highCutQuantile)
* @method $this topkadd(string $key, ...$items)
* @method $this topkincrby(string $key, ...$itemIncrement)
* @method $this topkadd(string $key, string ...$items)
* @method $this topkincrby(string $key, string|int|float ...$itemIncrement)
* @method $this topkinfo(string $key)
* @method $this topklist(string $key, bool $withCount = false)
* @method $this topkquery(string $key, ...$items)
* @method $this topkquery(string $key, string ...$items)
* @method $this topkreserve(string $key, int $topK, int $width = 8, int $depth = 7, float $decay = 0.9)
* @method $this tsadd(string $key, int $timestamp, float $value, ?AddArguments $arguments = null)
* @method $this tsalter(string $key, ?TSAlterArguments $arguments = null)
@@ -269,11 +271,11 @@ use Predis\Command\Redis\Container\Search\FTCURSOR;
* @method $this tsinfo(string $key, ?InfoArguments $arguments = null)
* @method $this tsmadd(mixed ...$keyTimestampValue)
* @method $this tsmget(MGetArguments $arguments, string ...$filterExpression)
* @method $this tsmrange($fromTimestamp, $toTimestamp, MRangeArguments $arguments)
* @method $this tsmrevrange($fromTimestamp, $toTimestamp, MRangeArguments $arguments)
* @method $this tsmrange(string|int|float $fromTimestamp, string|int|float $toTimestamp, MRangeArguments $arguments)
* @method $this tsmrevrange(string|int|float $fromTimestamp, string|int|float $toTimestamp, MRangeArguments $arguments)
* @method $this tsqueryindex(string ...$filterExpression)
* @method $this tsrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
* @method $this tsrevrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
* @method $this tsrange(string $key, string|int|float $fromTimestamp, string|int|float $toTimestamp, ?RangeArguments $arguments = null)
* @method $this tsrevrange(string $key, string|int|float $fromTimestamp, string|int|float $toTimestamp, ?RangeArguments $arguments = null)
* @method $this zadd($key, array $membersAndScoresDictionary)
* @method $this zcard($key)
* @method $this zcount($key, $min, $max)
@@ -308,8 +310,8 @@ use Predis\Command\Redis\Container\Search\FTCURSOR;
* @method $this pfadd($key, array $elements)
* @method $this pfmerge($destinationKey, array|string $sourceKeys)
* @method $this pfcount(array|string $keys)
* @method $this pubsub($subcommand, $argument)
* @method $this publish($channel, $message)
* @method $this pubsub(string $subcommand, string|int|float $argument)
* @method $this publish(string $channel, string|int|float $message)
* @method $this discard()
* @method $this exec()
* @method $this multi()
@@ -320,7 +322,7 @@ use Predis\Command\Redis\Container\Search\FTCURSOR;
* @method $this eval_ro(string $script, array $keys, ...$argument)
* @method $this evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
* @method $this evalsha_ro(string $sha1, array $keys, ...$argument)
* @method $this script($subcommand, $argument = null)
* @method $this script(string $subcommand, $argument = null)
* @method $this shutdown(?bool $noSave = null, bool $now = false, bool $force = false, bool $abort = false)
* @method $this auth($password)
* @method $this echo($message)
@@ -328,24 +330,24 @@ use Predis\Command\Redis\Container\Search\FTCURSOR;
* @method $this select($database)
* @method $this bgrewriteaof()
* @method $this bgsave()
* @method $this client($subcommand, $argument = null)
* @method $this config($subcommand, $argument = null)
* @method $this client(string $subcommand, $argument = null)
* @method $this config(string $subcommand, $argument = null)
* @method $this dbsize()
* @method $this flushall()
* @method $this flushdb()
* @method $this info($section = null)
* @method $this info(?string $section = null)
* @method $this lastsave()
* @method $this save()
* @method $this slaveof($host, $port)
* @method $this slowlog($subcommand, $argument = null)
* @method $this slowlog(string $subcommand, $argument = null)
* @method $this time()
* @method $this command()
* @method $this geoadd($key, $longitude, $latitude, $member)
* @method $this geoadd(string $key, string|float $longitude, string|float $latitude, string $member)
* @method $this geohash($key, array $members)
* @method $this geopos($key, array $members)
* @method $this geodist($key, $member1, $member2, $unit = null)
* @method $this georadius($key, $longitude, $latitude, $radius, $unit, ?array $options = null)
* @method $this georadiusbymember($key, $member, $radius, $unit, ?array $options = null)
* @method $this geodist(string $key, string $member1, string $member2, string $unit = null)
* @method $this georadius(string $key, string|float $longitude, string|float $latitude, int $radius, string $unit, ?array $options = null)
* @method $this georadiusbymember(string $key, string $member, int $radius, string $unit, ?array $options = null)
* @method $this geosearch(string $key, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $withCoord = false, bool $withDist = false, bool $withHash = false)
* @method $this geosearchstore(string $destination, string $source, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $storeDist = false)
*
+50 -48
View File
@@ -66,7 +66,7 @@ use Predis\Response\Status;
* @method int expiretime(string $key)
* @method array keys(string $pattern)
* @method int move(string $key, int $db)
* @method mixed object($subcommand, string $key)
* @method mixed object(string $subcommand, string $key)
* @method int persist(string $key)
* @method int pexpire(string $key, int $milliseconds)
* @method int pexpireat(string $key, int $timestamp)
@@ -74,36 +74,37 @@ use Predis\Response\Status;
* @method string|null randomkey()
* @method mixed rename(string $key, string $target)
* @method int renamenx(string $key, string $target)
* @method array scan($cursor, ?array $options = null)
* @method array scan(int|string $cursor, ?array $options = null)
* @method array sort(string $key, ?array $options = null)
* @method array sort_ro(string $key, ?string $byPattern = null, ?LimitOffsetCount $limit = null, array $getPatterns = [], ?string $sorting = null, bool $alpha = false)
* @method int ttl(string $key)
* @method mixed type(string $key)
* @method int append(string $key, $value)
* @method int bfadd(string $key, $item)
* @method int bfexists(string $key, $item)
* @method int bfadd(string $key, string $item)
* @method int bfexists(string $key, string $item)
* @method array bfinfo(string $key, string $modifier = '')
* @method array bfinsert(string $key, int $capacity = -1, float $error = -1, int $expansion = -1, bool $noCreate = false, bool $nonScaling = false, string ...$item)
* @method Status bfloadchunk(string $key, int $iterator, $data)
* @method array bfmadd(string $key, ...$item)
* @method array bfmexists(string $key, ...$item)
* @method array bfmadd(string $key, string ...$item)
* @method array bfmexists(string $key, string ...$item)
* @method Status bfreserve(string $key, float $errorRate, int $capacity, int $expansion = -1, bool $nonScaling = false)
* @method array bfscandump(string $key, int $iterator)
* @method int bitcount(string $key, $start = null, $end = null, string $index = 'byte')
* @method int bitop($operation, $destkey, $key)
* @method array|null bitfield(string $key, $subcommand, ...$subcommandArg)
* @method int bitpos(string $key, $bit, $start = null, $end = null, string $index = 'byte')
* @method int bitop(string $operation, string $destkey, string $key)
* @method array|null bitfield(string $key, string $subcommand, ...$subcommandArg)
* @method array|null bitfield_ro(string $key, ?array $encodingOffsetMap = null)
* @method int bitpos(string $key, int $bit, ?int $start = null, ?int $end = null, string $index = 'byte')
* @method array blmpop(int $timeout, array $keys, string $modifier = 'left', int $count = 1)
* @method array bzpopmax(array $keys, int $timeout)
* @method array bzpopmin(array $keys, int $timeout)
* @method array bzmpop(int $timeout, array $keys, string $modifier = 'min', int $count = 1)
* @method int cfadd(string $key, $item)
* @method int cfaddnx(string $key, $item)
* @method int cfcount(string $key, $item)
* @method int cfdel(string $key, $item)
* @method int cfexists(string $key, $item)
* @method int cfadd(string $key, string $item)
* @method int cfaddnx(string $key, string $item)
* @method int cfcount(string $key, string $item)
* @method int cfdel(string $key, string $item)
* @method int cfexists(string $key, string $item)
* @method Status cfloadchunk(string $key, int $iterator, $data)
* @method int cfmexists(string $key, ...$item)
* @method int cfmexists(string $key, string ...$item)
* @method array cfinfo(string $key)
* @method array cfinsert(string $key, int $capacity = -1, bool $noCreate = false, string ...$item)
* @method array cfinsertnx(string $key, int $capacity = -1, bool $noCreate = false, string ...$item)
@@ -120,14 +121,15 @@ use Predis\Response\Status;
* @method Status failover(?To $to = null, bool $abort = false, int $timeout = -1)
* @method mixed fcall(string $function, array $keys, ...$args)
* @method mixed fcall_ro(string $function, array $keys, ...$args)
* @method array ft_list()
* @method array ftaggregate(string $index, string $query, ?AggregateArguments $arguments = null)
* @method Status ftaliasadd(string $alias, string $index)
* @method Status ftaliasdel(string $alias)
* @method Status ftaliasupdate(string $alias, string $index)
* @method Status ftalter(string $index, FieldInterface[] $schema, ?AlterArguments $arguments = null)
* @method Status ftcreate(string $index, FieldInterface[] $schema, ?CreateArguments $arguments = null)
* @method int ftdictadd(string $dict, ...$term)
* @method int ftdictdel(string $dict, ...$term)
* @method int ftdictadd(string $dict, string ...$term)
* @method int ftdictdel(string $dict, string ...$term)
* @method array ftdictdump(string $dict)
* @method Status ftdropindex(string $index, ?DropArguments $arguments = null)
* @method string ftexplain(string $index, string $query, ?ExplainArguments $arguments = null)
@@ -143,23 +145,23 @@ use Predis\Response\Status;
* @method Status ftsynupdate(string $index, string $synonymGroupId, ?SynUpdateArguments $arguments = null, string ...$terms)
* @method array fttagvals(string $index, string $fieldName)
* @method string|null get(string $key)
* @method int getbit(string $key, $offset)
* @method int|null getex(string $key, $modifier = '', $value = false)
* @method string getrange(string $key, $start, $end)
* @method int getbit(string $key, int $offset)
* @method int|null getex(string $key, string $modifier = '', int|string $value = false)
* @method string getrange(string $key, int $start, int $end)
* @method string getdel(string $key)
* @method string|null getset(string $key, $value)
* @method string|null getset(string $key, string|int|float $value)
* @method int incr(string $key)
* @method int incrby(string $key, int $increment)
* @method string incrbyfloat(string $key, int|float $increment)
* @method array mget(string[]|string $keyOrKeys, string ...$keys = null)
* @method mixed mset(array $dictionary)
* @method int msetnx(array $dictionary)
* @method Status psetex(string $key, $milliseconds, $value)
* @method Status|null set(string $key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
* @method int setbit(string $key, $offset, $value)
* @method Status setex(string $key, $seconds, $value)
* @method int setnx(string $key, $value)
* @method int setrange(string $key, $offset, $value)
* @method Status psetex(string $key, int $milliseconds, string|int|float $value)
* @method Status|null set(string $key, string|int|float $value, ?string $expireResolution = null, ?int $expireTTL = null, ?string $flag = null)
* @method int setbit(string $key, int $offset, string|int|float $value)
* @method Status setex(string $key, int $seconds, string|int|float $value)
* @method int setnx(string $key, string|int|float $value)
* @method int setrange(string $key, int $offset, string|int|float $value)
* @method int strlen(string $key)
* @method int hdel(string $key, array $fields)
* @method int hexists(string $key, string $field)
@@ -179,14 +181,14 @@ use Predis\Response\Status;
* @method array hmget(string $key, array $fields)
* @method mixed hmset(string $key, array $dictionary)
* @method array hrandfield(string $key, int $count = 1, bool $withValues = false)
* @method array hscan(string $key, $cursor, ?array $options = null)
* @method array hscan(string $key, int|string $cursor, ?array $options = null)
* @method int hset(string $key, string $field, string $value)
* @method int hsetnx(string $key, string $field, string $value)
* @method array|null httl(string $key, array $fields)
* @method array|null hpttl(string $key, array $fields)
* @method array hvals(string $key)
* @method int hstrlen(string $key, string $field)
* @method array jsonarrappend(string $key, string $path = '$', ...$value)
* @method array jsonarrappend(string $key, string $path = '$', string ...$value)
* @method array jsonarrindex(string $key, string $path, string $value, int $start = 0, int $stop = 0)
* @method array jsonarrinsert(string $key, string $path, int $index, string ...$value)
* @method array jsonarrlen(string $key, string $path = '$')
@@ -214,7 +216,7 @@ use Predis\Response\Status;
* @method string|null brpoplpush(string $source, string $destination, int|float $timeout)
* @method mixed lcs(string $key1, string $key2, bool $len = false, bool $idx = false, int $minMatchLen = 0, bool $withMatchLen = false)
* @method string|null lindex(string $key, int $index)
* @method int linsert(string $key, $whence, $pivot, $value)
* @method int linsert(string $key, string $whence, string|int|float $pivot, string|int|float $value)
* @method int llen(string $key)
* @method string lmove(string $source, string $destination, string $where, string $to)
* @method array|null lmpop(array $keys, string $modifier = 'left', int $count = 1)
@@ -261,11 +263,11 @@ use Predis\Response\Status;
* @method Status tdigestreset(string $key)
* @method array tdigestrevrank(string $key, float ...$value)
* @method string tdigesttrimmed_mean(string $key, float $lowCutQuantile, float $highCutQuantile)
* @method array topkadd(string $key, ...$items)
* @method array topkincrby(string $key, ...$itemIncrement)
* @method array topkadd(string $key, string ...$items)
* @method array topkincrby(string $key, string|int|float ...$itemIncrement)
* @method array topkinfo(string $key)
* @method array topklist(string $key, bool $withCount = false)
* @method array topkquery(string $key, ...$items)
* @method array topkquery(string $key, string|int|float ...$items)
* @method Status topkreserve(string $key, int $topK, int $width = 8, int $depth = 7, float $decay = 0.9)
* @method int tsadd(string $key, int $timestamp, float $value, ?AddArguments $arguments = null)
* @method Status tsalter(string $key, ?TSAlterArguments $arguments = null)
@@ -279,11 +281,11 @@ use Predis\Response\Status;
* @method array tsinfo(string $key, ?InfoArguments $arguments = null)
* @method array tsmadd(mixed ...$keyTimestampValue)
* @method array tsmget(MGetArguments $arguments, string ...$filterExpression)
* @method array tsmrange($fromTimestamp, $toTimestamp, MRangeArguments $arguments)
* @method array tsmrevrange($fromTimestamp, $toTimestamp, MRangeArguments $arguments)
* @method array tsmrange(string|int|float $fromTimestamp, string|int|float $toTimestamp, MRangeArguments $arguments)
* @method array tsmrevrange(string|int|float $fromTimestamp, string|int|float $toTimestamp, MRangeArguments $arguments)
* @method array tsqueryindex(string ...$filterExpression)
* @method array tsrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
* @method array tsrevrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
* @method array tsrange(string $key, string|int|float $fromTimestamp, string|int|float $toTimestamp, ?RangeArguments $arguments = null)
* @method array tsrevrange(string $key, string|int|float $fromTimestamp, string|int|float $toTimestamp, ?RangeArguments $arguments = null)
* @method string xadd(string $key, array $dictionary, string $id = '*', ?array $options = null)
* @method int xdel(string $key, string ...$id)
* @method int xlen(string $key)
@@ -327,8 +329,8 @@ use Predis\Response\Status;
* @method int pfadd(string $key, array $elements)
* @method mixed pfmerge(string $destinationKey, array|string $sourceKeys)
* @method int pfcount(string[]|string $keyOrKeys, string ...$keys = null)
* @method mixed pubsub($subcommand, $argument)
* @method int publish($channel, $message)
* @method mixed pubsub(string $subcommand, string|int|float $argument)
* @method int publish(string $channel, string|int|float $message)
* @method mixed discard()
* @method array|null exec()
* @method mixed multi()
@@ -339,7 +341,7 @@ use Predis\Response\Status;
* @method mixed eval_ro(string $script, array $keys, ...$argument)
* @method mixed evalsha(string $script, int $numkeys, string ...$keyOrArg = null)
* @method mixed evalsha_ro(string $sha1, array $keys, ...$argument)
* @method mixed script($subcommand, $argument = null)
* @method mixed script(string $subcommand, $argument = null)
* @method Status shutdown(?bool $noSave = null, bool $now = false, bool $force = false, bool $abort = false)
* @method mixed auth(string $password)
* @method string echo(string $message)
@@ -347,24 +349,24 @@ use Predis\Response\Status;
* @method mixed select(int $database)
* @method mixed bgrewriteaof()
* @method mixed bgsave()
* @method mixed client($subcommand, $argument = null)
* @method mixed config($subcommand, $argument = null)
* @method mixed client(string $subcommand, $argument = null)
* @method mixed config(string $subcommand, $argument = null)
* @method int dbsize()
* @method mixed flushall()
* @method mixed flushdb()
* @method array info($section = null)
* @method array info(?string $section = null)
* @method int lastsave()
* @method mixed save()
* @method mixed slaveof(string $host, int $port)
* @method mixed slowlog($subcommand, $argument = null)
* @method mixed slowlog(string $subcommand, $argument = null)
* @method array time()
* @method array command()
* @method int geoadd(string $key, $longitude, $latitude, $member)
* @method int geoadd(string $key, string|float $longitude, string|float $latitude, string $member)
* @method array geohash(string $key, array $members)
* @method array geopos(string $key, array $members)
* @method string|null geodist(string $key, $member1, $member2, $unit = null)
* @method array georadius(string $key, $longitude, $latitude, $radius, $unit, ?array $options = null)
* @method array georadiusbymember(string $key, $member, $radius, $unit, ?array $options = null)
* @method string|null geodist(string $key, string $member1, string $member2, string $unit = null)
* @method array georadius(string $key, string|float $longitude, string|float $latitude, int $radius, string $unit, ?array $options = null)
* @method array georadiusbymember(string $key, string $member, int $radius, string $unit, ?array $options = null)
* @method array geosearch(string $key, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $withCoord = false, bool $withDist = false, bool $withHash = false)
* @method int geosearchstore(string $destination, string $source, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $storeDist = false)
*
+40
View File
@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
/**
* Represents the gap between slot ranges.
*/
class NullSlotRange extends SlotRange
{
public function __construct(int $start, int $end)
{
parent::__construct($start, $end, '');
}
/**
* {@inheritDoc}
*/
public function toArray(): array
{
return [];
}
/**
* {@inheritDoc}
*/
public function count(): int
{
return 0;
}
}
+209
View File
@@ -0,0 +1,209 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
use ArrayAccess;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use OutOfBoundsException;
use Predis\Connection\NodeConnectionInterface;
use ReturnTypeWillChange;
use Traversable;
/**
* Slot map for redis-cluster.
*/
class SimpleSlotMap implements ArrayAccess, IteratorAggregate, Countable
{
private $slots = [];
/**
* Checks if the given slot is valid.
*
* @param int $slot Slot index.
*
* @return bool
*/
public static function isValid($slot)
{
return $slot >= 0x0000 && $slot <= 0x3FFF;
}
/**
* Checks if the given slot range is valid.
*
* @param int $first Initial slot of the range.
* @param int $last Last slot of the range.
*
* @return bool
*/
public static function isValidRange($first, $last)
{
return $first >= 0x0000 && $first <= 0x3FFF && $last >= 0x0000 && $last <= 0x3FFF && $first <= $last;
}
/**
* Resets the slot map.
*/
public function reset()
{
$this->slots = [];
}
/**
* Checks if the slot map is empty.
*
* @return bool
*/
public function isEmpty()
{
return empty($this->slots);
}
/**
* Returns the current slot map as a dictionary of $slot => $node.
*
* The order of the slots in the dictionary is not guaranteed.
*
* @return array
*/
public function toArray()
{
return $this->slots;
}
/**
* Returns the list of unique nodes in the slot map.
*
* @return array
*/
public function getNodes()
{
return array_keys(array_flip($this->slots));
}
/**
* Assigns the specified slot range to a node.
*
* @param int $first Initial slot of the range.
* @param int $last Last slot of the range.
* @param NodeConnectionInterface|string $connection ID or connection instance.
*
* @throws OutOfBoundsException
*/
public function setSlots($first, $last, $connection)
{
if (!static::isValidRange($first, $last)) {
throw new OutOfBoundsException("Invalid slot range $first-$last for `$connection`");
}
$this->slots += array_fill($first, $last - $first + 1, (string) $connection);
}
/**
* Returns the specified slot range.
*
* @param int $first Initial slot of the range.
* @param int $last Last slot of the range.
*
* @return array
*/
public function getSlots($first, $last)
{
if (!static::isValidRange($first, $last)) {
throw new OutOfBoundsException("Invalid slot range $first-$last");
}
return array_intersect_key($this->slots, array_fill($first, $last - $first + 1, null));
}
/**
* Checks if the specified slot is assigned.
*
* @param int $slot Slot index.
*
* @return bool
*/
#[ReturnTypeWillChange]
public function offsetExists($slot)
{
return isset($this->slots[$slot]);
}
/**
* Returns the node assigned to the specified slot.
*
* @param int $slot Slot index.
*
* @return string|null
*/
#[ReturnTypeWillChange]
public function offsetGet($slot)
{
return $this->slots[$slot] ?? null;
}
/**
* Assigns the specified slot to a node.
*
* @param int $slot Slot index.
* @param NodeConnectionInterface|string $connection ID or connection instance.
*
* @return void
*/
#[ReturnTypeWillChange]
public function offsetSet($slot, $connection)
{
if (!static::isValid($slot)) {
throw new OutOfBoundsException("Invalid slot $slot for `$connection`");
}
$this->slots[(int) $slot] = (string) $connection;
}
/**
* Returns the node assigned to the specified slot.
*
* @param int $slot Slot index.
*
* @return void
*/
#[ReturnTypeWillChange]
public function offsetUnset($slot)
{
unset($this->slots[$slot]);
}
/**
* Returns the current number of assigned slots.
*
* @return int
*/
#[ReturnTypeWillChange]
public function count()
{
return count($this->slots);
}
/**
* Returns an iterator over the slot map.
*
* @return Traversable<int, string>
*/
#[ReturnTypeWillChange]
public function getIterator()
{
return new ArrayIterator($this->slots);
}
}
+225 -17
View File
@@ -22,11 +22,16 @@ use ReturnTypeWillChange;
use Traversable;
/**
* Slot map for redis-cluster.
* Compact slot map for redis-cluster.
*/
class SlotMap implements ArrayAccess, IteratorAggregate, Countable
{
private $slots = [];
/**
* Slot ranges list.
*
* @var SlotRange[]
*/
private $slotRanges = [];
/**
* Checks if the given slot is valid.
@@ -37,7 +42,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public static function isValid($slot)
{
return $slot >= 0x0000 && $slot <= 0x3FFF;
return $slot >= 0 && $slot <= SlotRange::MAX_SLOTS;
}
/**
@@ -50,7 +55,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public static function isValidRange($first, $last)
{
return $first >= 0x0000 && $first <= 0x3FFF && $last >= 0x0000 && $last <= 0x3FFF && $first <= $last;
return SlotRange::isValidRange($first, $last);
}
/**
@@ -58,7 +63,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public function reset()
{
$this->slots = [];
$this->slotRanges = [];
}
/**
@@ -68,7 +73,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public function isEmpty()
{
return empty($this->slots);
return empty($this->slotRanges);
}
/**
@@ -80,7 +85,13 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public function toArray()
{
return $this->slots;
return array_reduce(
$this->slotRanges,
function ($carry, $slotRange) {
return $carry + $slotRange->toArray();
},
[]
);
}
/**
@@ -90,7 +101,22 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
*/
public function getNodes()
{
return array_keys(array_flip($this->slots));
return array_unique(array_map(
function ($slotRange) {
return $slotRange->getConnection();
},
$this->slotRanges
));
}
/**
* Returns the list of slot ranges.
*
* @return SlotRange[]
*/
public function getSlotRanges()
{
return $this->slotRanges;
}
/**
@@ -108,7 +134,31 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
throw new OutOfBoundsException("Invalid slot range $first-$last for `$connection`");
}
$this->slots += array_fill($first, $last - $first + 1, (string) $connection);
$targetSlotRange = new SlotRange($first, $last, (string) $connection);
// Get gaps of slot ranges list.
$gaps = $this->getGaps($this->slotRanges);
$results = $this->slotRanges;
foreach ($gaps as $gap) {
if (!$gap->hasIntersectionWith($targetSlotRange)) {
continue;
}
// Get intersection of the gap and target slot range.
$results[] = new SlotRange(
max($gap->getStart(), $targetSlotRange->getStart()),
min($gap->getEnd(), $targetSlotRange->getEnd()),
$targetSlotRange->getConnection()
);
}
$this->sortSlotRanges($results);
$results = $this->compactSlotRanges($results);
$this->slotRanges = $results;
}
/**
@@ -117,7 +167,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
* @param int $first Initial slot of the range.
* @param int $last Last slot of the range.
*
* @return array
* @return array<int, string>
*/
public function getSlots($first, $last)
{
@@ -125,7 +175,28 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
throw new OutOfBoundsException("Invalid slot range $first-$last");
}
return array_intersect_key($this->slots, array_fill($first, $last - $first + 1, null));
$placeHolder = new NullSlotRange($first, $last);
$intersections = [];
foreach ($this->slotRanges as $slotRange) {
if (!$placeHolder->hasIntersectionWith($slotRange)) {
continue;
}
$intersections[] = new SlotRange(
max($placeHolder->getStart(), $slotRange->getStart()),
min($placeHolder->getEnd(), $slotRange->getEnd()),
$slotRange->getConnection()
);
}
return array_reduce(
$intersections,
function ($carry, $slotRange) {
return $carry + $slotRange->toArray();
},
[]
);
}
/**
@@ -138,7 +209,7 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
#[ReturnTypeWillChange]
public function offsetExists($slot)
{
return isset($this->slots[$slot]);
return $this->findRangeBySlot($slot) !== false;
}
/**
@@ -151,7 +222,9 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
#[ReturnTypeWillChange]
public function offsetGet($slot)
{
return $this->slots[$slot] ?? null;
$found = $this->findRangeBySlot($slot);
return $found ? $found->getConnection() : null;
}
/**
@@ -169,7 +242,8 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
throw new OutOfBoundsException("Invalid slot $slot for `$connection`");
}
$this->slots[(int) $slot] = (string) $connection;
$this->offsetUnset($slot);
$this->setSlots($slot, $slot, $connection);
}
/**
@@ -182,7 +256,26 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
#[ReturnTypeWillChange]
public function offsetUnset($slot)
{
unset($this->slots[$slot]);
if (!static::isValid($slot)) {
throw new OutOfBoundsException("Invalid slot $slot");
}
$results = [];
foreach ($this->slotRanges as $slotRange) {
if (!$slotRange->hasSlot($slot)) {
$results[] = $slotRange;
}
if (static::isValidRange($slotRange->getStart(), $slot - 1)) {
$results[] = new SlotRange($slotRange->getStart(), $slot - 1, $slotRange->getConnection());
}
if (static::isValidRange($slot + 1, $slotRange->getEnd())) {
$results[] = new SlotRange($slot + 1, $slotRange->getEnd(), $slotRange->getConnection());
}
}
$this->slotRanges = $results;
}
/**
@@ -193,7 +286,12 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
#[ReturnTypeWillChange]
public function count()
{
return count($this->slots);
return array_sum(array_map(
function ($slotRange) {
return $slotRange->count();
},
$this->slotRanges
));
}
/**
@@ -204,6 +302,116 @@ class SlotMap implements ArrayAccess, IteratorAggregate, Countable
#[ReturnTypeWillChange]
public function getIterator()
{
return new ArrayIterator($this->slots);
return new ArrayIterator($this->toArray());
}
/**
* Find the slot range which contains the specific slot index.
*
* @param int $slot Slot index.
*
* @return SlotRange|false The slot range object or false if not found.
*/
protected function findRangeBySlot(int $slot)
{
foreach ($this->slotRanges as $slotRange) {
if ($slotRange->hasSlot($slot)) {
return $slotRange;
}
}
return false;
}
/**
* Get gaps between sorted slot ranges with NullSlotRange object.
*
* @param SlotRange[] $slotRanges
*
* @return SlotRange[]
*/
protected function getGaps(array $slotRanges)
{
if (empty($slotRanges)) {
return [
new NullSlotRange(0, SlotRange::MAX_SLOTS),
];
}
$gaps = [];
$count = count($slotRanges);
$i = 0;
foreach ($slotRanges as $key => $slotRange) {
$start = $slotRange->getStart();
$end = $slotRange->getEnd();
if (static::isValidRange($i, $start - 1)) {
$gaps[] = new NullSlotRange($i, $start - 1);
}
$i = $end + 1;
if ($key === $count - 1) {
if (static::isValidRange($i, SlotRange::MAX_SLOTS)) {
$gaps[] = new NullSlotRange($i, SlotRange::MAX_SLOTS);
}
}
}
return $gaps;
}
/**
* Sort slot ranges by start index.
*
* @param SlotRange[] $slotRanges
*
* @return void
*/
protected function sortSlotRanges(array &$slotRanges)
{
usort(
$slotRanges,
function (SlotRange $a, SlotRange $b) {
if ($a->getStart() == $b->getStart()) {
return 0;
}
return $a->getStart() < $b->getStart() ? -1 : 1;
}
);
}
/**
* Compact adjacent slot ranges with the same connection.
*
* @param SlotRange[] $slotRanges
*
* @return SlotRange[]
*/
protected function compactSlotRanges(array $slotRanges)
{
if (empty($slotRanges)) {
return [];
}
$compacted = [];
$count = count($slotRanges);
$i = 0;
$carry = $slotRanges[0];
while ($i < $count) {
$next = $slotRanges[$i + 1] ?? null;
if (
!is_null($next)
&& ($carry->getEnd() + 1) === $next->getStart()
&& $carry->getConnection() === $next->getConnection()
) {
$carry = new SlotRange($carry->getStart(), $next->getEnd(), $carry->getConnection());
} else {
$compacted[] = $carry;
$carry = $next;
}
$i++;
}
return array_values($compacted);
}
}
+145
View File
@@ -0,0 +1,145 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
use Countable;
use OutOfBoundsException;
/**
* Represents a range of slots in a Redis cluster.
*/
class SlotRange implements Countable
{
/**
* Maximum number of slots in a Redis cluster is 16384.
*/
public const MAX_SLOTS = 0x3FFF;
/**
* Starting slot of the range.
*
* @var int
*/
protected $start;
/**
* Ending slot of the range.
*
* @var int
*/
protected $end;
/**
* Connection to the server hosting this slot range.
*
* @var string
*/
protected $connection;
public function __construct(int $start, int $end, string $connection)
{
if (!static::isValidRange($start, $end)) {
throw new OutOfBoundsException("Invalid slot range $start-$end for `$connection`");
}
$this->start = $start;
$this->end = $end;
$this->connection = $connection;
}
/**
* Checks if a slot range is valid.
*
* @param int $first
* @param int $last
*
* @return bool
*/
public static function isValidRange($first, $last)
{
return $first >= 0 && $first <= self::MAX_SLOTS && $last >= 0x0000 && $last <= self::MAX_SLOTS && $first <= $last;
}
/**
* Returns the start slot index of this range.
*
* @return int
*/
public function getStart()
{
return $this->start;
}
/**
* Returns the end slot index of this range.
*
* @return int
*/
public function getEnd()
{
return $this->end;
}
/**
* Returns the connection to the server hosting this slot range.
*
* @return string
*/
public function getConnection()
{
return $this->connection;
}
/**
* Checks if the specific slot is contained in this range.
*
* @param int $slot
*
* @return bool
*/
public function hasSlot(int $slot)
{
return $this->start <= $slot && $this->end >= $slot;
}
/**
* Returns an array of connection strings for each slot in this range.
*
* @return string[]
*/
public function toArray(): array
{
return array_fill($this->start, $this->end - $this->start + 1, $this->connection);
}
/**
* Returns the number of slots in this range.
*
* @return int
*/
public function count(): int
{
return $this->end - $this->start + 1;
}
/**
* Checks if this range has an intersection with the given slot range.
*
* @param SlotRange $slotRange
*
* @return bool
*/
public function hasIntersectionWith(SlotRange $slotRange): bool
{
return $this->start <= $slotRange->getEnd() && $this->end >= $slotRange->getStart();
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
class BITFIELD_RO extends RedisCommand
{
/**
* @return string
*/
public function getId()
{
return 'BITFIELD_RO';
}
/**
* @param array $arguments
* @return void
*/
public function setArguments(array $arguments)
{
$processedArguments = [$arguments[0]];
if (array_key_exists(1, $arguments) && is_array($arguments[1])) {
// Convert encoding => offset, into GET, encoding, offset
array_walk($arguments[1], function ($value, $key) use (&$processedArguments) {
array_push($processedArguments, 'GET', $key, $value);
});
}
parent::setArguments($processedArguments);
}
}
+1
View File
@@ -15,6 +15,7 @@ namespace Predis\Command\Redis\Container;
use Predis\Response\Status;
/**
* @method array cat(string $category = null)
* @method Status dryRun(string $username, string $command, ...$arguments)
* @method array getUser(string $username)
* @method Status setUser(string $username, string ...$rules)
+4
View File
@@ -13,8 +13,12 @@
namespace Predis\Command\Redis\Search;
use Predis\Command\Command as RedisCommand;
use Predis\Command\Redis\CONFIG;
/**
* @deprecated FT.CONFIG GET and SET is deprecated since Redis 8.0.
* @see CONFIG if you want to manipulate search configuration
*
* @see https://redis.io/commands/ft.config-get/
* @see https://redis.io/commands/ft.config-set/
*
+26
View File
@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Redis\Search;
use Predis\Command\Command as RedisCommand;
class FT_LIST extends RedisCommand
{
/**
* @return string
*/
public function getId()
{
return 'FT._LIST';
}
}
+5
View File
@@ -165,6 +165,11 @@ class Parameters implements ParametersInterface
}
}
public function __set($parameter, $value)
{
$this->parameters[$parameter] = $value;
}
/**
* {@inheritdoc}
*/
+1
View File
@@ -27,6 +27,7 @@ namespace Predis\Connection;
* @property float $timeout Timeout for the connect() operation.
* @property float $read_write_timeout Timeout for read() and write() operations.
* @property bool $persistent Leaves the connection open after a GC collection.
* @property string $conn_uid Unique identifier of connection, needs to create a multiple persistent connections to the same resource.
* @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.
+21
View File
@@ -35,6 +35,15 @@ use Predis\Response\Status as StatusResponse;
*/
class StreamConnection extends AbstractConnection
{
/**
* @param ParametersInterface $parameters
*/
public function __construct(ParametersInterface $parameters)
{
parent::__construct($parameters);
$this->parameters->conn_uid = spl_object_hash($this);
}
/**
* Disconnects from the server and destroys the underlying resource when the
* garbage collector kicks in only if the connection has not been marked as
@@ -105,6 +114,18 @@ class StreamConnection extends AbstractConnection
$timeout = (isset($parameters->timeout) ? (float) $parameters->timeout : 5.0);
$context = stream_context_create(['socket' => ['tcp_nodelay' => (bool) $parameters->tcp_nodelay]]);
if (
(isset($parameters->persistent) && $parameters->persistent)
&& (isset($parameters->conn_uid) && $parameters->conn_uid)
) {
$conn_uid = '/' . $parameters->conn_uid;
} else {
$conn_uid = '';
}
// Needs to create multiple persistent connections to the same resource
$address = $address . $conn_uid;
if (!$resource = @stream_socket_client($address, $errno, $errstr, $timeout, $flags, $context)) {
$this->onConnectionError(trim($errstr), $errno);
}
+1 -1
View File
@@ -175,7 +175,7 @@ abstract class PredisTestCase extends PHPUnit\Framework\TestCase
return [
'scheme' => 'tcp',
'host' => constant('REDIS_SERVER_HOST'),
'port' => ($this->isStackTest()) ? constant('REDIS_STACK_SERVER_PORT') : constant('REDIS_SERVER_PORT'),
'port' => ($this->isStackTest()) ? getenv('REDIS_STACK_SERVER_PORT') : constant('REDIS_SERVER_PORT'),
'database' => constant('REDIS_SERVER_DBNUM'),
];
}
+38
View File
@@ -1283,6 +1283,44 @@ class ClientTest extends PredisTestCase
$this->assertSame(Client::VERSION, $libVer);
}
/**
* @group connected
*/
public function testClientsCreateDifferentPersistentConnections(): void
{
$client1 = new Client($this->getParameters(['database' => 14, 'persistent' => true]));
$client2 = new Client($this->getParameters(['database' => 15, 'persistent' => true]));
$client1->set('foo', 'bar');
$client2->set('foo', 'baz');
$this->assertSame('bar', $client1->get('foo'));
$this->assertSame('baz', $client2->get('foo'));
}
/**
* @group connected
* @group cluster
* @requiresRedisVersion >= 2.0.0
*/
public function testClusterClientsCreateDifferentPersistentConnections(): void
{
$client1 = new Client(
$this->getDefaultParametersArray(),
['cluster' => 'redis', 'parameters' => ['persistent' => true]]
);
$client2 = new Client(
$this->getDefaultParametersArray(),
['cluster' => 'redis', 'parameters' => ['persistent' => true]]
);
$client1->set('{shard1}foo', 'bar');
$client2->set('{shard2}foo', 'baz');
$this->assertSame('bar', $client1->get('{shard1}foo'));
$this->assertSame('baz', $client2->get('{shard2}foo'));
}
// ******************************************************************** //
// ---- HELPER METHODS ------------------------------------------------ //
// ******************************************************************** //
+473
View File
@@ -0,0 +1,473 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
use Predis\Cluster\SimpleSlotMap as SlotMap;
use PredisTestCase;
class SimpleSlotMapTest extends PredisTestCase
{
/**
* @group disconnected
*/
public function testIsValidReturnsTrueOnValidSlot(): void
{
$this->assertTrue(SlotMap::isValid(0));
$this->assertTrue(SlotMap::isValid(16383));
$this->assertTrue(SlotMap::isValid(5000));
$this->assertTrue(SlotMap::isValid('5000'));
}
/**
* @group disconnected
*/
public function testIsValidReturnsFalseOnInvalidSlot(): void
{
$this->assertFalse(SlotMap::isValid(-1));
$this->assertFalse(SlotMap::isValid(16384));
}
/**
* @group disconnected
*/
public function testIsValidRangeReturnsTrueOnValidSlotRange(): void
{
$this->assertTrue(SlotMap::isValidRange(0, 16383));
$this->assertTrue(SlotMap::isValidRange(2000, 2999));
$this->assertTrue(SlotMap::isValidRange(3000, 3000));
}
/**
* @group disconnected
*/
public function testIsValidRangeReturnsFalseOnInvalidSlotRange(): void
{
$this->assertFalse(SlotMap::isValidRange(0, 16384));
$this->assertFalse(SlotMap::isValidRange(-1, 16383));
$this->assertFalse(SlotMap::isValidRange(-1, 16384));
$this->assertFalse(SlotMap::isValidRange(2999, 2000));
}
/**
* @group disconnected
*/
public function testToArrayReturnsEmptyArrayOnEmptySlotMap(): void
{
$slotmap = new SlotMap();
$this->assertEmpty($slotmap->toArray());
}
/**
* @group disconnected
*/
public function testSetSlotsAssignsSpecifiedNodeToSlotRange(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$slotmap->setSlots(5461, 10922, '127.0.0.1:6380');
$slotmap->setSlots(10923, 16383, '127.0.0.1:6381');
$expectedMap = array_merge(
array_fill(0, 5461, '127.0.0.1:6379'),
array_fill(5461, 5462, '127.0.0.1:6380'),
array_fill(10923, 5461, '127.0.0.1:6381')
);
$this->assertSame($expectedMap, $slotmap->toArray());
}
/**
* @group disconnected
*/
public function testSetSlotsOverwritesSlotRange(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$slotmap->setSlots(1000, 2000, '127.0.0.1:6380');
$expectedMap =
array_fill(0, 5461, '127.0.0.1:6379') +
array_fill(1000, 2000, '127.0.0.1:6380');
$this->assertSame($expectedMap, $slotmap->toArray());
}
/**
* @group disconnected
*/
public function testSetSlotsAssignsSingleSlotWhenFirstAndLastSlotMatch(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(10, 10, '127.0.0.1:6379');
$this->assertSame([10 => '127.0.0.1:6379'], $slotmap->toArray());
}
/**
* @group disconnected
*/
public function testSetSlotsCastsValueToString(): void
{
$slotmap = new SlotMap();
$connection = $this->getMockConnection();
$connection
->expects($this->once())
->method('__toString')
->willReturn('127.0.0.1:6379');
$slotmap->setSlots(10, 10, $connection);
$this->assertSame([10 => '127.0.0.1:6379'], $slotmap->toArray());
}
/**
* @group disconnected
*/
public function testSetSlotsThrowsExceptionOnInvalidSlotRange(): void
{
$this->expectException('OutOfBoundsException');
$this->expectExceptionMessage('Invalid slot range 0-16384 for `127.0.0.1:6379`');
$slotmap = new SlotMap();
$slotmap->setSlots(0, 16384, '127.0.0.1:6379');
}
/**
* @group disconnected
*/
public function testGetSlotsReturnsEmptyArrayOnEmptySlotMap(): void
{
$slotmap = new SlotMap();
$this->assertEmpty($slotmap->getSlots(3, 11));
}
/**
* @group disconnected
*/
public function testGetSlotsReturnsDictionaryOfSlotsWithAssignedNodes(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5, '127.0.0.1:6379');
$slotmap->setSlots(10, 13, '127.0.0.1:6380');
$expectedMap = [
3 => '127.0.0.1:6379',
4 => '127.0.0.1:6379',
5 => '127.0.0.1:6379',
10 => '127.0.0.1:6380',
11 => '127.0.0.1:6380',
];
$this->assertSame($expectedMap, $slotmap->getSlots(3, 11));
}
/**
* @group disconnected
*/
public function testGetSlotsReturnsEmptyArrayOnEmptySlotRange(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5, '127.0.0.1:6379');
$slotmap->setSlots(10, 13, '127.0.0.1:6380');
$this->assertEmpty($slotmap->getSlots(100, 200));
}
/**
* @group disconnected
*/
public function testGetSlotsThrowsExceptionOnInvalidSlotRange(): void
{
$this->expectException('OutOfBoundsException');
$this->expectExceptionMessage('Invalid slot range 0-16384');
$slotmap = new SlotMap();
$slotmap->getSlots(0, 16384);
}
/**
* @group disconnected
*/
public function testIsEmptyReturnsTrueOnEmptySlotMap(): void
{
$slotmap = new SlotMap();
$this->assertTrue($slotmap->isEmpty());
}
/**
* @group disconnected
*/
public function testIsEmptyReturnsFalseOnNonEmptySlotMap(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertFalse($slotmap->isEmpty());
}
/**
* @group disconnected
*/
public function testCountReturnsZeroOnEmptySlotMap(): void
{
$slotmap = new SlotMap();
$this->assertCount(0, $slotmap);
}
/**
* @group disconnected
*/
public function testCountReturnsAssignedSlotsInSlotMap(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertCount(5461, $slotmap);
$slotmap->setSlots(5461, 10922, '127.0.0.1:6380');
$this->assertCount(10923, $slotmap);
$slotmap->setSlots(10923, 16383, '127.0.0.1:6381');
$this->assertCount(16384, $slotmap);
}
/**
* @group disconnected
*/
public function testResetEmptiesSlotMap(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$slotmap->setSlots(5461, 10922, '127.0.0.1:6380');
$slotmap->setSlots(10923, 16383, '127.0.0.1:6381');
$this->assertFalse($slotmap->isEmpty());
$slotmap->reset();
$this->assertTrue($slotmap->isEmpty());
}
/**
* @group disconnected
*/
public function testGetNodesReturnsEmptyArrayOnEmptySlotMap(): void
{
$slotmap = new SlotMap();
$this->assertEmpty($slotmap->getNodes());
}
/**
* @group disconnected
*/
public function testGetNodesReturnsArrayOfNodesInSlotMap(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$slotmap->setSlots(5461, 10922, '127.0.0.1:6380');
$slotmap->setSlots(10923, 16383, '127.0.0.1:6381');
$this->assertSame(['127.0.0.1:6379', '127.0.0.1:6380', '127.0.0.1:6381'], $slotmap->getNodes());
}
/**
* @group disconnected
*/
public function testOffsetExistsReturnsTrueOnAssignedSlot(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertTrue(isset($slotmap[0]));
$this->assertTrue(isset($slotmap[2000]));
}
/**
* @group disconnected
*/
public function testOffsetExistsReturnsFalseOnAssignedSlot(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertFalse(isset($slotmap[6000]));
}
/**
* @group disconnected
*/
public function testOffsetExistsReturnsFalseOnInvalidSlot(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertFalse(isset($slotmap[-100]));
$this->assertFalse(isset($slotmap[16384]));
}
/**
* @group disconnected
*/
public function testOffsetGetReturnsNodeOfAssignedSlot(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$slotmap->setSlots(5461, 10922, '127.0.0.1:6380');
$slotmap->setSlots(10923, 16383, '127.0.0.1:6381');
$this->assertSame('127.0.0.1:6379', $slotmap[0]);
$this->assertSame('127.0.0.1:6380', $slotmap[5461]);
$this->assertSame('127.0.0.1:6381', $slotmap[10923]);
}
/**
* @group disconnected
*/
public function testOffsetGetReturnsNullOnUnassignedSlot(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertNull($slotmap[5461]);
}
/**
* @group disconnected
*/
public function testOffsetGetReturnsNullOnInvalidSlot(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertNull($slotmap[-100]);
$this->assertNull($slotmap[16384]);
}
/**
* @group disconnected
*/
public function testOffsetUnsetRemovesSlotAssignment(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertTrue(isset($slotmap[100]));
unset($slotmap[100]);
$this->assertFalse(isset($slotmap[100]));
}
/**
* @group disconnected
*/
public function testOffsetUnsetDoesNotDoAnythingOnUnassignedSlot(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertFalse(isset($slotmap[5461]));
unset($slotmap[5461]);
$this->assertFalse(isset($slotmap[5461]));
}
/**
* @group disconnected
*/
public function testOffsetSetAssignsNodeToSlot(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$this->assertSame('127.0.0.1:6380', $slotmap[100] = '127.0.0.1:6380');
$this->assertSame('127.0.0.1:6380', $slotmap[100]);
$this->assertNull($slotmap[5461]);
$this->assertSame('127.0.0.1:6380', $slotmap[5461] = '127.0.0.1:6380');
$this->assertSame('127.0.0.1:6380', $slotmap[5461]);
}
/**
* @group disconnected
*/
public function testOffsetSetCastsValueToString(): void
{
$slotmap = new SlotMap();
$connection = $this->getMockConnection();
$connection
->expects($this->once())
->method('__toString')
->willReturn('127.0.0.1:6379');
$this->assertSame($connection, $slotmap[0] = $connection);
$this->assertSame('127.0.0.1:6379', $slotmap[0]);
}
/**
* @group disconnected
*/
public function testOffsetSetThrowsExceptionOnInvalidSlot(): void
{
$this->expectException('OutOfBoundsException');
$this->expectExceptionMessage('Invalid slot 16384 for `127.0.0.1:6379`');
$slotmap = new SlotMap();
$slotmap[16384] = '127.0.0.1:6379';
}
/**
* @group disconnected
*/
public function testGetIteratorReturnsIteratorOverSlotMap(): void
{
$slotmap = new SlotMap();
$slotmap->setSlots(0, 5460, '127.0.0.1:6379');
$slotmap->setSlots(5461, 10922, '127.0.0.1:6380');
$slotmap->setSlots(10923, 16383, '127.0.0.1:6381');
$expectedMap = array_merge(
array_fill(0, 5461, '127.0.0.1:6379'),
array_fill(5461, 5462, '127.0.0.1:6380'),
array_fill(10923, 5461, '127.0.0.1:6381')
);
$this->assertSame($expectedMap, iterator_to_array($slotmap));
}
}
+134
View File
@@ -0,0 +1,134 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Cluster;
use PredisTestCase;
class SlotRangeTest extends PredisTestCase
{
/**
* @group disconnected
*/
public function testMaxSlotsEquals16383()
{
$this->assertEquals(SlotRange::MAX_SLOTS, 16383);
}
/**
* @group disconnected
*/
public function testConstructorThrowExceptionOnInvalidSlotRange(): void
{
$this->expectException('OutOfBoundsException');
$this->expectExceptionMessage('Invalid slot range 600-300 for `c1`');
new SlotRange(600, 300, 'c1');
}
/**
* @group disconnected
*/
public function testIsValidRangeReturnsTrueOnValidSlotRange()
{
$this->assertTrue(SlotRange::isValidRange(0, SlotRange::MAX_SLOTS));
$this->assertTrue(SlotRange::isValidRange(2000, 2999));
$this->assertTrue(SlotRange::isValidRange(3000, 3000));
}
/**
* @group disconnected
*/
public function testIsValidRangeReturnsFalseOnInvalidSlotRange()
{
$this->assertFalse(SlotRange::isValidRange(0, 16384));
$this->assertFalse(SlotRange::isValidRange(-1, 16383));
$this->assertFalse(SlotRange::isValidRange(-1, 16384));
$this->assertFalse(SlotRange::isValidRange(2999, 2000));
}
/**
* @group disconnected
*/
public function testPropertySetters()
{
$range = new SlotRange(2000, 5000, 'c1');
$this->assertEquals(2000, $range->getStart());
$this->assertEquals(5000, $range->getEnd());
$this->assertEquals('c1', $range->getConnection());
}
/**
* @group disconnected
*/
public function testHasSlot()
{
$range = new SlotRange(2000, 4000, 'c1');
$this->assertTrue($range->hasSlot(2000));
$this->assertTrue($range->hasSlot(4000));
$this->assertTrue($range->hasSlot(3000));
$this->assertFalse($range->hasSlot(1000));
$this->assertFalse($range->hasSlot(5000));
}
/**
* @group disconnected
*/
public function testToArray()
{
$range = new SlotRange(2000, 2005, 'c1');
$array = $range->toArray();
$this->assertSame([
2000 => 'c1',
2001 => 'c1',
2002 => 'c1',
2003 => 'c1',
2004 => 'c1',
2005 => 'c1',
], $array);
}
/**
* @group disconnected
*/
public function testCount()
{
$range = new SlotRange(2000, 3000, 'c1');
$this->assertEquals(1001, $range->count());
$this->assertEquals(1001, count($range));
}
/**
* @group disconnected
*/
public function testHasIntersectionWith()
{
$original = new SlotRange(2000, 3000, 'c1');
$this->assertFalse($original->hasIntersectionWith(new SlotRange(1000, 1500, 'c1')));
$this->assertFalse($original->hasIntersectionWith(new SlotRange(3001, 5000, 'c1')));
$this->assertFalse($original->hasIntersectionWith(new SlotRange(1999, 1999, 'c1')));
$this->assertFalse($original->hasIntersectionWith(new SlotRange(3001, 3001, 'c1')));
$this->assertTrue($original->hasIntersectionWith(new SlotRange(1500, 6000, 'c1')));
$this->assertTrue($original->hasIntersectionWith(new SlotRange(2500, 2999, 'c1')));
$this->assertTrue($original->hasIntersectionWith(new SlotRange(2000, 2999, 'c1')));
$this->assertTrue($original->hasIntersectionWith(new SlotRange(2500, 3000, 'c1')));
$this->assertTrue($original->hasIntersectionWith(new SlotRange(1500, 2000, 'c1')));
$this->assertTrue($original->hasIntersectionWith(new SlotRange(3000, 3500, 'c1')));
$this->assertTrue($original->hasIntersectionWith(new SlotRange(2000, 2000, 'c1')));
$this->assertTrue($original->hasIntersectionWith(new SlotRange(3000, 3000, 'c1')));
}
}
+169
View File
@@ -12,6 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\Argument\Search\SchemaFields\TextField;
use Predis\Response\ServerException;
class ACL_Test extends PredisCommandTestCase
@@ -132,6 +133,174 @@ class ACL_Test extends PredisCommandTestCase
}
}
/**
* @group connected
* @return void
* @requiresRedisVersion >= 7.9.0
*/
public function testModuleCategoriesAppearsInListOfAllCategories(): void
{
$redis = $this->getClient();
$allCategories = $redis->acl->cat();
foreach (['bloom', 'cuckoo', 'cms', 'topk', 'tdigest', 'search', 'timeseries', 'json'] as $category) {
$this->assertContains($category, $allCategories);
}
}
/**
* @group connected
* @group relay-incompatible
* @return void
* @requiresRedisVersion >= 7.9.0
*/
public function testSetModuleCommandPrivileges(): void
{
$redis = $this->getClient();
$this->assertEquals(
'OK',
$redis->acl->setUser(
'testUser',
'reset',
'nopass',
'on'
)
);
$this->assertEquals('OK', $redis->auth('testUser', ''));
$this->expectException(ServerException::class);
$redis->ftcreate('test', [new TextField('foo')]);
$this->assertEquals(
'OK',
$redis->acl->setUser(
'testUser',
'+ft.create',
'+ft.search'
)
);
$this->assertEquals('OK', $redis->ftcreate('test', [new TextField('foo')]));
$this->assertEmpty($redis->ftsearch('test', '*'));
$this->expectException(ServerException::class);
$redis->jsonset('test', '$', '{"key":"value"}');
$this->assertEquals(
'OK',
$redis->acl->setUser(
'testUser',
'+json.set'
)
);
$this->assertEquals('OK', $redis->jsonset('test', '$', '{"key":"value"}'));
$this->expectException(ServerException::class);
$redis->bfadd('test', 'value');
$this->assertEquals(
'OK',
$redis->acl->setUser(
'testUser',
'+bf.add'
)
);
$this->assertEquals(1, $redis->bfadd('test', 'value'));
$this->expectException(ServerException::class);
$redis->tsadd('test', time(), 0.01);
$this->assertEquals(
'OK',
$redis->acl->setUser(
'testUser',
'+ts.add'
)
);
$this->assertEquals(1, $redis->tsadd('test', time(), 0.01));
}
/**
* @group connected
* @group relay-incompatible
* @return void
* @requiresRedisVersion >= 7.9.0
*/
public function testSetModuleCategoryPrivileges(): void
{
$redis = $this->getClient();
$this->assertEquals(
'OK',
$redis->acl->setUser(
'testUser',
'reset',
'nopass',
'on'
)
);
$this->assertEquals('OK', $redis->auth('testUser', ''));
$this->expectException(ServerException::class);
$redis->ftcreate('test', [new TextField('foo')]);
$this->assertEquals(
'OK',
$redis->acl->setUser(
'testUser',
'+@search'
)
);
$this->assertEquals('OK', $redis->ftcreate('test', [new TextField('foo')]));
$this->assertEmpty($redis->ftsearch('test', '*'));
$this->expectException(ServerException::class);
$redis->jsonset('test', '$', '{"key":"value"}');
$this->assertEquals(
'OK',
$redis->acl->setUser(
'testUser',
'+@json'
)
);
$this->assertEquals('OK', $redis->jsonset('test', '$', '{"key":"value"}'));
$this->expectException(ServerException::class);
$redis->bfadd('test', 'value');
$this->assertEquals(
'OK',
$redis->acl->setUser(
'testUser',
'+@bloom'
)
);
$this->assertEquals(1, $redis->bfadd('test', 'value'));
$this->expectException(ServerException::class);
$redis->tsadd('test', time(), 0.01);
$this->assertEquals(
'OK',
$redis->acl->setUser(
'testUser',
'+@timeseries'
)
);
$this->assertEquals(1, $redis->tsadd('test', time(), 0.01));
}
/**
* @group connected
* @return void
@@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Redis;
class BITFIELD_RO_Test extends PredisCommandTestCase
{
/**
* {@inheritdoc}
*/
protected function getExpectedCommand(): string
{
return BITFIELD_RO::class;
}
/**
* {@inheritdoc}
*/
protected function getExpectedId(): string
{
return 'BITFIELD_RO';
}
/**
* @group disconnected
* @dataProvider argumentsProvider
*/
public function testFilterArguments(array $actualArguments, array $expectedArguments): void
{
$command = $this->getCommand();
$command->setArguments($actualArguments);
$this->assertSame($expectedArguments, $command->getArguments());
}
/**
* @group connected
* @return void
* @requiresRedisVersion >= 6.2.0
*/
public function testReturnBitsOfSpecificString()
{
$redis = $this->getClient();
$redis->set('foo', 'bar');
$this->assertSame([6, 98], $redis->bitfield_ro('foo', ['u4' => 0, 'i8' => 0]));
}
public function argumentsProvider(): array
{
return [
'with default arguments' => [
['key'],
['key'],
],
'with single encoding-offset entry' => [
['key', ['encoding' => 'offset']],
['key', 'GET', 'encoding', 'offset'],
],
'with multiple encoding-offset entry' => [
['key', ['encoding' => 'offset', 'encoding1' => 'offset1', 'encoding2' => 'offset2']],
['key', 'GET', 'encoding', 'offset', 'GET', 'encoding1', 'offset1', 'GET', 'encoding2', 'offset2'],
],
];
}
}
@@ -12,6 +12,8 @@
namespace Predis\Command\Redis;
use Predis\Response\ServerException;
/**
* @group commands
* @group realm-server
@@ -137,6 +139,48 @@ class CONFIG_Test extends PredisCommandTestCase
$redis->config('SET', 'loglevel', $previous['loglevel']);
}
/**
* @group connected
* @group relay-incompatible
* @requiresRedisVersion >= 7.9.0
*/
public function testOverrideDefaultDialectWithConfigCommand()
{
$redis = $this->getClient();
$default_dialect = (int) $redis
->config('GET', 'search-default-dialect')['search-default-dialect'];
$this->assertEquals('OK', $redis->config('SET', 'search-default-dialect', 2));
$this->assertEquals(2, (int) $redis->ftconfig->get('DEFAULT_DIALECT')[0][1]);
$this->assertEquals(2,
(int) $redis->config('GET', 'search-default-dialect')['search-default-dialect']);
$this->assertEquals(
'OK',
$redis->config('SET', 'search-default-dialect', $default_dialect)
);
}
/**
* @group connected
* @requiresRedisVersion >= 7.9.0
*/
public function testSetGetSearchConfiguration()
{
$redis = $this->getClient();
$this->assertGreaterThan(0,
(int) $redis->config('GET', 'search-timeout')['search-timeout']);
$this->assertGreaterThanOrEqual(0,
(int) $redis->config('GET', 'ts-retention-policy')['ts-retention-policy']);
$this->assertGreaterThanOrEqual(0,
(int) $redis->config('GET', 'bf-error-rate')['bf-error-rate']);
$this->assertGreaterThan(0,
(int) $redis->config('GET', 'cf-initial-size')['cf-initial-size']);
$this->expectException(ServerException::class);
$redis->config('SET', 'search-max-doctablesize', 10000);
}
/**
* @group connected
* @requiresRedisVersion >= 2.0.0
+13
View File
@@ -313,6 +313,19 @@ BUFFER;
$this->assertArrayHasKey('redis_version', $info['Server'] ?? $info);
}
/**
* @group connected
* @requiresRedisVersion >= 7.9.0
*/
public function testExposeSearchInformation(): void
{
$redis = $this->getClient();
$this->assertArrayHasKey('search', $redis->info('modules')['Modules']);
$this->assertNotEmpty($redis->info('search'));
$this->assertArrayHasKey('search', $redis->info('everything')['Modules']);
}
/**
* @group connected
* @requiresRedisVersion < 2.6.0
@@ -124,7 +124,6 @@ class FTAGGREGATE_Test extends PredisCommandTestCase
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('index: no such index');
$redis->ftaggregate('index', 'query');
}
@@ -94,7 +94,6 @@ class FTALIASADD_Test extends PredisCommandTestCase
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('Unknown index name (or name is an alias itself)');
$redis->ftaliasadd('alias', 'index');
}
@@ -109,7 +109,6 @@ class FTALIASUPDATE_Test extends PredisCommandTestCase
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('Unknown index name (or name is an alias itself)');
$redis->ftaliasupdate('alias', 'index');
}
@@ -88,7 +88,6 @@ class FTALTER_Test extends PredisCommandTestCase
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('Unknown index name');
$redis->ftalter('alias', [new TextField('field_name')]);
}
@@ -91,7 +91,7 @@ class FTCONFIG_Test extends PredisCommandTestCase
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion <= 7.3.0
* @requiresRedisVersion <= 7.0.0
*/
public function testSetGivenRediSearchConfigurationParameter(): void
{
@@ -104,7 +104,7 @@ class FTCONFIG_Test extends PredisCommandTestCase
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion <= 7.3.0
* @requiresRedisVersion <= 7.0.0
*/
public function testGetReturnsGivenRediSearchConfigurationParameter(): void
{
@@ -118,7 +118,7 @@ class FTCONFIG_Test extends PredisCommandTestCase
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion <= 7.3.0
* @requiresRedisVersion <= 7.0.0
*/
public function testHelpReturnsGivenRediSearchConfigurationDescription(): void
{
@@ -141,14 +141,13 @@ class FTCONFIG_Test extends PredisCommandTestCase
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion <= 7.3.0
* @requiresRedisVersion <= 7.0.0
*/
public function testSetThrowsExceptionOnNonExistingOption(): void
{
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('Invalid option');
$redis->ftconfig->set('foobar', 'value');
}
@@ -188,7 +188,6 @@ class FTCURSOR_Test extends PredisCommandTestCase
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('Cursor not found');
$redis->ftcursor->read('idx', 21412412);
}
@@ -204,7 +203,6 @@ class FTCURSOR_Test extends PredisCommandTestCase
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('Cursor does not exist');
$redis->ftcursor->del('idx', 21412412);
}
@@ -78,13 +78,13 @@ class FTDICTDUMP_Test extends PredisCommandTestCase
* @group connected
* @return void
* @requiresRediSearchVersion >= 1.4.0
* @requiresRedisVersion <= 7.9.0
*/
public function testThrowsExceptionOnNonExistingDictionary(): void
{
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('could not open dict key');
$redis->ftdictdump('dict');
}
@@ -85,7 +85,6 @@ class FTDROPINDEX_Test extends PredisCommandTestCase
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('Unknown Index name');
$redis->ftdropindex('index');
}
@@ -64,6 +64,7 @@ class FTEXPLAIN_Test extends PredisCommandTestCase
* @group relay-resp3
* @return void
* @requiresRediSearchVersion >= 1.0.0
* @requiresRedisVersion <= 7.9.0
*/
public function testExplainReturnsExecutionPlanForGivenQuery(): void
{
@@ -119,7 +120,6 @@ EOT;
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('index: no such index');
$redis->ftexplain('index', 'query');
}
@@ -88,7 +88,6 @@ class FTPROFILE_Test extends PredisCommandTestCase
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('index: no such index');
$redis->ftprofile('index', (new ProfileArguments())->search()->query('query'));
}
@@ -18,6 +18,7 @@ use Predis\Command\Argument\Search\SchemaFields\GeoShapeField;
use Predis\Command\Argument\Search\SchemaFields\NumericField;
use Predis\Command\Argument\Search\SchemaFields\TagField;
use Predis\Command\Argument\Search\SchemaFields\TextField;
use Predis\Command\Argument\Search\SchemaFields\VectorField;
use Predis\Command\Argument\Search\SearchArguments;
use Predis\Command\Redis\PredisCommandTestCase;
use Predis\Response\ServerException;
@@ -409,6 +410,80 @@ class FTSEARCH_Test extends PredisCommandTestCase
);
}
/**
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion >= 7.9.0
*/
public function testVectorSearchWithInt8Types(): void
{
$redis = $this->getClient();
$this->assertEquals('OK', $redis->ftcreate('test', [
new VectorField(
'v', 'HNSW',
['TYPE', 'INT8', 'DIM', 2, 'DISTANCE_METRIC', 'L2']
),
]));
$this->sleep(0.1);
$a = [1.5, 10];
$b = [123, 100];
$c = [1, 1];
$redis->hset('a', 'v', pack('c*', ...$a));
$redis->hset('b', 'v', pack('c*', ...$b));
$redis->hset('c', 'v', pack('c*', ...$c));
$searchArguments = new SearchArguments();
$searchArguments->params(['vec', pack('c*', ...$a)]);
$searchArguments->dialect(2);
$this->assertEquals(
2,
$redis->ftsearch('test', '*=>[KNN 2 @v $vec]', $searchArguments)[0]
);
}
/**
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion >= 7.9.0
*/
public function testVectorSearchWithUInt8Types(): void
{
$redis = $this->getClient();
$this->assertEquals('OK', $redis->ftcreate('test', [
new VectorField(
'v', 'HNSW',
['TYPE', 'UINT8', 'DIM', 2, 'DISTANCE_METRIC', 'L2']
),
]));
$this->sleep(0.1);
$a = [1.5, 10];
$b = [123, 100];
$c = [1, 1];
$redis->hset('a', 'v', pack('C*', ...$a));
$redis->hset('b', 'v', pack('C*', ...$b));
$redis->hset('c', 'v', pack('C*', ...$c));
$searchArguments = new SearchArguments();
$searchArguments->params(['vec', pack('C*', ...$a)]);
$searchArguments->dialect(2);
$this->assertEquals(
2,
$redis->ftsearch('test', '*=>[KNN 2 @v $vec]', $searchArguments)[0]
);
}
public function argumentsProvider(): array
{
return [
@@ -116,7 +116,6 @@ class FTSPELLCHECK_Test extends PredisCommandTestCase
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('Unknown Index name');
$redis->ftspellcheck(
'index',
@@ -105,11 +105,11 @@ class FTSUGGET_Test extends PredisCommandTestCase
* @return void
* @requiresRediSearchVersion >= 1.0.0
*/
public function testGetReturnsNullOnNonExistingKey(): void
public function testGetReturnsEmptyArrayOnNonExistingKey(): void
{
$redis = $this->getClient();
$this->assertNull($redis->ftsugget('key', 'hel'));
$this->assertEmpty($redis->ftsugget('key', 'hel'));
}
public function argumentsProvider(): array
@@ -94,7 +94,6 @@ class FTSYNDUMP_Test extends PredisCommandTestCase
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('Unknown index name');
$redis->ftsyndump('index');
}
@@ -116,7 +116,6 @@ class FTSYNUPDATE_Test extends PredisCommandTestCase
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('Unknown index name');
$redis->ftsynupdate(
'index',
@@ -104,7 +104,6 @@ class FTTAGVALS_Test extends PredisCommandTestCase
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('Unknown Index name');
$redis->fttagvals('index', 'fieldName');
}
@@ -0,0 +1,60 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2025 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Command\Redis\Search;
use Predis\Command\Argument\Search\SchemaFields\TextField;
use Predis\Command\Redis\PredisCommandTestCase;
class FT_LIST_Test extends PredisCommandTestCase
{
/**
* {@inheritDoc}
*/
protected function getExpectedCommand(): string
{
return FT_LIST::class;
}
/**
* {@inheritDoc}
*/
protected function getExpectedId(): string
{
return 'FT_LIST';
}
/**
* @group disconnected
*/
public function testFilterArguments(): void
{
$this->assertEmpty($this->getCommand()->getArguments());
}
/**
* @group connected
* @return void
* @requiresRediSearchVersion >= 2.0.0
*/
public function testReturnListOfExistingIndices(): void
{
$redis = $this->getClient();
$this->assertEquals('OK', $redis->ftcreate('idx1', [new TextField('text')]));
$this->assertEquals('OK', $redis->ftcreate('idx2', [new TextField('text')]));
$this->sleep(0.1);
$this->assertSameValues(['idx1', 'idx2'], $redis->ft_list());
}
}
@@ -64,6 +64,7 @@ class TSINFO_Test extends PredisCommandTestCase
* @group relay-resp3
* @return void
* @requiresRedisTimeSeriesVersion <= 1.10.13
* @requiresRedisVersion > 6.3.0
*/
public function testReturnsInformationAboutGivenTimeSeries(): void
{
@@ -123,23 +123,6 @@ class StreamConnectionTest extends PredisConnectionTestCase
$connection1->disconnect();
}
/**
* @group connected
* @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
* @requires PHP 5.4