Compare commits

..

9 Commits

Author SHA1 Message Date
Till Krüss 1d7f035ec0 update changelog 2026-07-06 07:06:36 -07:00
Kyle f97b473fe1 Fix annotation of cmsincrby (#1698) 2026-07-06 07:05:02 -07:00
dependabot[bot] 7bd5426348 Bump the github-actions group with 3 updates (#1697)
Bumps the github-actions group with 3 updates: [actions/checkout](https://github.com/actions/checkout), [rojopolis/spellcheck-github-actions](https://github.com/rojopolis/spellcheck-github-actions) and [hoverkraft-tech/compose-action](https://github.com/hoverkraft-tech/compose-action).


Updates `actions/checkout` from 6 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

Updates `rojopolis/spellcheck-github-actions` from 0.60.0 to 0.62.0
- [Release notes](https://github.com/rojopolis/spellcheck-github-actions/releases)
- [Changelog](https://github.com/rojopolis/spellcheck-github-actions/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rojopolis/spellcheck-github-actions/compare/0.60.0...0.62.0)

Updates `hoverkraft-tech/compose-action` from 2.6.0 to 3.0.0
- [Release notes](https://github.com/hoverkraft-tech/compose-action/releases)
- [Commits](https://github.com/hoverkraft-tech/compose-action/compare/v2.6.0...v3.0.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: rojopolis/spellcheck-github-actions
  dependency-version: 0.62.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: hoverkraft-tech/compose-action
  dependency-version: 3.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-02 08:48:21 -07:00
Vladyslav Vildanov efb3958fb7 Added support for new TS commands + Indonesian language support integration test (#1695)
* Added support for new TS commands

* Fixed version constraints

* Codestyle fixes

* Remvoed TS.BGET command
2026-06-25 21:11:59 +03:00
Vladyslav Vildanov 09b8ce59dd Fixed Sentinel does not wipe servers on exception caused (#1694) 2026-06-22 10:55:28 -07:00
Vladyslav Vildanov b6b7f0d4e0 Make ZMSCORE command Prefixable and add to a ClusterStrategy (#1692)
* Make ZMSCORE command Prefixable and add to a ClusterStrategy

* Updated CHANGELOG.md

* Codestyle fixes
2026-06-18 17:50:20 +03:00
Copilot a1ede6c5e8 Fixed workflow permissions warnings (#1690) 2026-06-15 10:34:10 -07:00
Copilot d55b3072fa Correct pack/unpack PHPDoc types, clean up UNLINK test typing (#1689) 2026-06-15 10:32:18 -07:00
Till Krüss a43cfd723c Add Dependabot configuration (#1663) 2026-06-15 10:01:29 -07:00
23 changed files with 1025 additions and 30 deletions
+6 -6
View File
@@ -24,7 +24,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Check file permissions
run: test "$(find . -type f -not -path './.git/*' -executable)" = "./bin/create-command-test"
@@ -47,7 +47,7 @@ jobs:
tools: parallel-lint
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Check source code for syntax errors
run: composer exec -- parallel-lint bin/ examples/ src/ tests/
@@ -67,7 +67,7 @@ jobs:
coverage: none
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Validate Composer configuration
run: composer validate --no-interaction --strict
@@ -101,7 +101,7 @@ jobs:
coverage: none
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Check EditorConfig configuration
run: test -f .editorconfig
@@ -130,7 +130,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Check exported files
run: |
@@ -159,7 +159,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
+8 -3
View File
@@ -1,14 +1,19 @@
name: spellcheck
name: Spellcheck
on:
pull_request:
jobs:
check-spelling:
runs-on: ubuntu-latest
permissions:
contents: read
checks: write
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Check Spelling
uses: rojopolis/spellcheck-github-actions@0.60.0
uses: rojopolis/spellcheck-github-actions@0.62.0
with:
config_path: .github/spellcheck-settings.yml
task_name: Markdown
+17 -8
View File
@@ -17,6 +17,10 @@ jobs:
predis:
name: PHP ${{ matrix.php }} (Redis ${{ matrix.redis }})
runs-on: ubuntu-latest
permissions:
contents: read
statuses: write
pull-requests: write
strategy:
fail-fast: false
@@ -30,11 +34,11 @@ jobs:
- '4.0'
- '7.2'
- '7.4'
- '8.0'
- '8.2'
- '8.4'
- '8.6'
- '8.8'
- '8.10'
steps:
@@ -42,11 +46,11 @@ jobs:
run: |
# Mapping of original redis versions to client test containers
declare -A redis_clients_version_mapping=(
["8.10"]="unstable-27987813126-debian"
["8.8"]="8.8.0"
["8.6"]="8.6.1"
["8.4"]="8.4.0"
["8.2"]="8.2.2"
["8.0"]="8.0.2"
["7.4"]="7.4.2"
["7.2"]="7.2.7"
)
@@ -78,16 +82,16 @@ jobs:
fi
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Start Redis standalone image
uses: hoverkraft-tech/compose-action@v2.6.0
uses: hoverkraft-tech/compose-action@v3.0.0
with:
compose-file: .github/docker-compose.yml
services: ${{ env.DOCKER_SERVICE }}
- name: Start Redis unprotected image
uses: hoverkraft-tech/compose-action@v2.6.0
uses: hoverkraft-tech/compose-action@v3.0.0
if: ${{ matrix.redis > '4.0' }}
with:
compose-file: .github/docker-compose.yml
@@ -95,7 +99,7 @@ jobs:
- name: Start Redis stack image
id: stack_infra
uses: hoverkraft-tech/compose-action@v2.6.0
uses: hoverkraft-tech/compose-action@v3.0.0
if: ${{ matrix.redis >= '7.2' && matrix.redis < '8.0' }}
with:
compose-file: .github/docker-compose.yml
@@ -103,7 +107,7 @@ jobs:
- name: Start Redis cluster image
id: cluster_infra
uses: hoverkraft-tech/compose-action@v2.6.0
uses: hoverkraft-tech/compose-action@v3.0.0
if: ${{ matrix.redis > '4.0' }}
with:
compose-file: .github/docker-compose.yml
@@ -111,7 +115,7 @@ jobs:
- name: Start Redis sentinels image
id: sentinel_infra
uses: hoverkraft-tech/compose-action@v2.6.0
uses: hoverkraft-tech/compose-action@v3.0.0
if: ${{ matrix.redis > '4.0' }}
with:
compose-file: .github/docker-compose.yml
@@ -216,6 +220,11 @@ jobs:
needs: predis
if: ${{ always() }}
runs-on: ubuntu-latest
permissions:
contents: read
statuses: write
pull-requests: write
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@v2
+10
View File
@@ -1,4 +1,13 @@
## Changelog
## Unreleased
### Added
- Make ZMSCORE command Prefixable and add to a ClusterStrategy (#1692)
- Added support for new TS commands + Indonesian language support integration test (#1695)
### Fixed
- Fixed Sentinel does not wipe servers on exception caused (#1694)
- Fixed `@method cmsincrby()` annotation
## v3.5.1 (2026-06-11)
### Added
@@ -6,6 +15,7 @@
### Fixed
- Allow `UNLINK` to accept an array of keys (#1687)
- Fixed `Client::(un)pack()` return types
## v3.5.0 (2026-06-02)
### Added
+4 -4
View File
@@ -272,8 +272,8 @@ class Client implements ClientInterface, IteratorAggregate
/**
* Applies the configured serializer and compression to given value.
*
* @param mixed $value
* @return string
* @param mixed $value
* @return mixed
*/
public function pack($value)
{
@@ -285,8 +285,8 @@ class Client implements ClientInterface, IteratorAggregate
/**
* Deserializes and decompresses to given value.
*
* @param mixed $value
* @return string
* @param mixed $value
* @return mixed
*/
public function unpack($value)
{
+4 -1
View File
@@ -37,6 +37,7 @@ use Predis\Command\Argument\TimeSeries\IncrByArguments;
use Predis\Command\Argument\TimeSeries\InfoArguments;
use Predis\Command\Argument\TimeSeries\MGetArguments;
use Predis\Command\Argument\TimeSeries\MRangeArguments;
use Predis\Command\Argument\TimeSeries\NRangeArguments;
use Predis\Command\Argument\TimeSeries\RangeArguments;
use Predis\Command\CommandInterface;
use Predis\Command\Container\ACL;
@@ -127,7 +128,7 @@ use Predis\Command\Redis\VADD;
* @method $this cfinsertnx(string $key, int $capacity = -1, bool $noCreate = false, string ...$item)
* @method $this cfreserve(string $key, int $capacity, int $bucketSize = -1, int $maxIterations = -1, int $expansion = -1)
* @method $this cfscandump(string $key, int $iterator)
* @method $this cmsincrby(string $key, string|int...$itemIncrementDictionary)
* @method $this cmsincrby(string $key, string|int ...$itemIncrementDictionary)
* @method $this cmsinfo(string $key)
* @method $this cmsinitbydim(string $key, int $width, int $depth)
* @method $this cmsinitbyprob(string $key, float $errorRate, float $probability)
@@ -308,6 +309,8 @@ use Predis\Command\Redis\VADD;
* @method $this tsmget(MGetArguments $arguments, string ...$filterExpression)
* @method $this tsmrange($fromTimestamp, $toTimestamp, MRangeArguments $arguments)
* @method $this tsmrevrange($fromTimestamp, $toTimestamp, MRangeArguments $arguments)
* @method $this tsnrange(array $keys, $fromTimestamp, $toTimestamp, ?NRangeArguments $arguments = null)
* @method $this tsnrevrange(array $keys, $fromTimestamp, $toTimestamp, ?NRangeArguments $arguments = null)
* @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)
+3
View File
@@ -37,6 +37,7 @@ use Predis\Command\Argument\TimeSeries\IncrByArguments;
use Predis\Command\Argument\TimeSeries\InfoArguments;
use Predis\Command\Argument\TimeSeries\MGetArguments;
use Predis\Command\Argument\TimeSeries\MRangeArguments;
use Predis\Command\Argument\TimeSeries\NRangeArguments;
use Predis\Command\Argument\TimeSeries\RangeArguments;
use Predis\Command\CommandInterface;
use Predis\Command\Container\ACL;
@@ -320,6 +321,8 @@ use Predis\Response\Status;
* @method array tsmget(MGetArguments $arguments, string ...$filterExpression)
* @method array tsmrange($fromTimestamp, $toTimestamp, MRangeArguments $arguments)
* @method array tsmrevrange($fromTimestamp, $toTimestamp, MRangeArguments $arguments)
* @method array tsnrange(array $keys, $fromTimestamp, $toTimestamp, ?NRangeArguments $arguments = null)
* @method array tsnrevrange(array $keys, $fromTimestamp, $toTimestamp, ?NRangeArguments $arguments = null)
* @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)
+1
View File
@@ -153,6 +153,7 @@ abstract class ClusterStrategy implements StrategyInterface
'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument,
'ZREVRANK' => $getKeyFromFirstArgument,
'ZSCORE' => $getKeyFromFirstArgument,
'ZMSCORE' => $getKeyFromFirstArgument,
'ZUNIONSTORE' => [$this, 'getKeyFromZsetAggregationCommands'],
'ZSCAN' => $getKeyFromFirstArgument,
'ZLEXCOUNT' => $getKeyFromFirstArgument,
@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2026 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\Argument\TimeSeries;
class NRangeArguments extends RangeArguments
{
/**
* Aggregates samples into time buckets.
*
* Unlike TS.RANGE, TS.NRANGE expects one aggregator per queried key, passed
* as individual tokens (e.g. AGGREGATION min max 1000) rather than a single
* comma-separated token. Exactly numkeys aggregators are required.
*
* @param string|array $aggregator Aggregation type, or list of aggregation types. Check class constants.
* @param int $bucketDuration Is duration of each bucket, in milliseconds.
* @param int $align It controls the time bucket timestamps by changing the reference timestamp on which a bucket is defined.
* @param int $bucketTimestamp Controls how bucket timestamps are reported.
* @param bool $empty Is a flag, which, when specified, reports aggregations also for empty buckets.
* @return $this
*/
public function aggregation($aggregator, int $bucketDuration, int $align = 0, int $bucketTimestamp = 0, bool $empty = false): RangeArguments
{
$aggregators = is_array($aggregator) ? $aggregator : explode(',', (string) $aggregator);
if ($align > 0) {
array_push($this->arguments, 'ALIGN', $align);
}
array_push($this->arguments, 'AGGREGATION', ...$aggregators, ...[$bucketDuration]);
if ($bucketTimestamp > 0) {
array_push($this->arguments, 'BUCKETTIMESTAMP', $bucketTimestamp);
}
if (true === $empty) {
$this->arguments[] = 'EMPTY';
}
return $this;
}
}
+61
View File
@@ -0,0 +1,61 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2026 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\TimeSeries;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/ts.nrange/
*
* Query an explicit list of time series keys over a timestamp range in forward
* direction and return a timestamp-major response: [timestamp, [value_for_key_0,
* value_for_key_1, ...]]. The value array preserves the input key order and
* missing values are represented as NaN.
*/
class TSNRANGE extends RedisCommand
{
public function getId()
{
return 'TS.NRANGE';
}
public function setArguments(array $arguments)
{
[$keys, $fromTimestamp, $toTimestamp] = $arguments;
$commandArguments = (!empty($arguments[3])) ? $arguments[3]->toArray() : [];
parent::setArguments(array_merge(
[count($keys)],
$keys,
[$fromTimestamp, $toTimestamp],
$commandArguments
));
}
public function prefixKeys($prefix)
{
$arguments = $this->getArguments();
$keysCount = $arguments[0];
$keys = array_slice($arguments, 1, $keysCount);
$prefixedKeys = array_map(static function ($key) use ($prefix) {
return $prefix . $key;
}, $keys);
$this->setRawArguments(array_merge(
[$arguments[0]],
$prefixedKeys,
array_slice($arguments, $keysCount + 1)
));
}
}
@@ -0,0 +1,29 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2026 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\TimeSeries;
/**
* @see https://redis.io/commands/ts.nrevrange/
*
* Query an explicit list of time series keys over a timestamp range in reverse
* direction and return a timestamp-major response: [timestamp, [value_for_key_0,
* value_for_key_1, ...]]. Rows are ordered by decreasing timestamp, the value
* array preserves the input key order and missing values are represented as NaN.
*/
class TSNREVRANGE extends TSNRANGE
{
public function getId()
{
return 'TS.NREVRANGE';
}
}
+6 -1
View File
@@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/zmscore/
@@ -31,4 +31,9 @@ class ZMSCORE extends RedisCommand
{
return 'ZMSCORE';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}
@@ -33,6 +33,7 @@ use Predis\Response\ErrorInterface as ErrorResponseInterface;
use Predis\Response\ServerException;
use Predis\Retry\Retry;
use Predis\Retry\Strategy\ExponentialBackoff;
use Throwable;
/**
* @author Daniele Alessandri <suppakilla@gmail.com>
@@ -753,9 +754,12 @@ class SentinelReplication extends AbstractAggregateConnection implements Replica
return $response;
};
$failCallback = function (CommunicationException $exception) {
$failCallback = function (Throwable $exception) {
$this->wipeServerList();
$exception->getConnection()->disconnect();
if ($exception instanceof CommunicationException) {
$exception->getConnection()->disconnect();
}
};
return $retry->callWithRetry($doCallback, $failCallback);
@@ -480,6 +480,7 @@ class PredisStrategyTest extends PredisTestCase
'ZREVRANGEBYSCORE' => 'keys-first',
'ZREVRANK' => 'keys-first',
'ZSCORE' => 'keys-first',
'ZMSCORE' => 'keys-first',
'ZUNIONSTORE' => 'keys-zaggregated',
'ZSCAN' => 'keys-first',
'ZLEXCOUNT' => 'keys-first',
@@ -503,6 +503,7 @@ class RedisStrategyTest extends PredisTestCase
'ZREVRANGEBYSCORE' => 'keys-first',
'ZREVRANK' => 'keys-first',
'ZSCORE' => 'keys-first',
'ZMSCORE' => 'keys-first',
'ZUNIONSTORE' => 'keys-zaggregated',
'ZSCAN' => 'keys-first',
'ZLEXCOUNT' => 'keys-first',
@@ -0,0 +1,84 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2026 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\Argument\TimeSeries;
use PHPUnit\Framework\TestCase;
class NRangeArgumentsTest extends TestCase
{
/**
* @var NRangeArguments
*/
private $arguments;
protected function setUp(): void
{
$this->arguments = new NRangeArguments();
}
/**
* @return void
*/
public function testInheritsCommonRangeModifiers(): void
{
$this->arguments
->latest()
->filterByTs(1000, 1001)
->filterByValue(1000, 1001)
->count(100);
$this->assertSame(
['LATEST', 'FILTER_BY_TS', 1000, 1001, 'FILTER_BY_VALUE', 1000, 1001, 'COUNT', 100],
$this->arguments->toArray()
);
}
/**
* @dataProvider aggregatorProvider
* @param array $arguments
* @param array $expectedResponse
* @return void
*/
public function testCreatesArgumentsWithAggregatorModifierAsSeparateTokens(array $arguments, array $expectedResponse): void
{
$this->arguments->aggregation(...$arguments);
$this->assertSame($expectedResponse, $this->arguments->toArray());
}
public function aggregatorProvider(): array
{
return [
'with single aggregator' => [
[NRangeArguments::AGG_SUM, 1000],
['AGGREGATION', NRangeArguments::AGG_SUM, 1000],
],
'with multiple aggregators as array' => [
[[NRangeArguments::AGG_MIN, NRangeArguments::AGG_MAX], 1000],
['AGGREGATION', 'min', 'max', 1000],
],
'with multiple aggregators as comma-separated string' => [
['min,max', 1000],
['AGGREGATION', 'min', 'max', 1000],
],
'with ALIGN modifier' => [
[[NRangeArguments::AGG_MIN, NRangeArguments::AGG_MAX], 1000, 10],
['ALIGN', 10, 'AGGREGATION', 'min', 'max', 1000],
],
'with multiple aggregators and all arguments' => [
[[NRangeArguments::AGG_MIN, NRangeArguments::AGG_MAX], 1000, 10, 10000, true],
['ALIGN', 10, 'AGGREGATION', 'min', 'max', 1000, 'BUCKETTIMESTAMP', 10000, 'EMPTY'],
],
];
}
}
@@ -485,6 +485,10 @@ class KeyPrefixProcessorTest extends PredisTestCase
['key', 'member'],
['prefix:key', 'member'],
],
['ZMSCORE',
['key', 'member1', 'member2'],
['prefix:key', 'member1', 'member2'],
],
['ZREMRANGEBYSCORE',
['key', 0, 10],
['prefix:key', 0, 10],
@@ -181,6 +181,41 @@ class FTSEARCH_Test extends PredisCommandTestCase
$this->assertNotEmpty($actualResponse);
}
/**
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion >= 8.9.0
*/
public function testSearchValuesByIndonesianLanguage(): void
{
$redis = $this->getClient();
$this->assertEquals(
'OK',
$redis->hmset('doc:1', 'content', 'mereka membaca buku di perpustakaan')
);
$ftCreateArguments = (new CreateArguments())
->prefix(['doc:'])
->language('indonesian');
$schema = [new TextField('content')];
$this->assertEquals('OK', $redis->ftcreate('idx_indonesian', $schema, $ftCreateArguments));
// Timeout to make sure that index created before search performed.
usleep(10000);
// The Indonesian stemmer reduces "membaca" to its root "baca", so querying
// the stem with LANGUAGE indonesian matches the indexed document.
$ftSearchArguments = (new SearchArguments())
->language('indonesian')
->noContent();
$this->assertSame([1, 'doc:1'], $redis->ftsearch('idx_indonesian', 'baca', $ftSearchArguments));
}
/**
* @group connected
* @group relay-resp3
@@ -0,0 +1,302 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2026 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\TimeSeries;
use Predis\Command\Argument\TimeSeries\CreateArguments;
use Predis\Command\Argument\TimeSeries\NRangeArguments;
use Predis\Command\PrefixableCommand;
use Predis\Command\Redis\PredisCommandTestCase;
use Predis\Response\ServerException;
/**
* @group commands
* @group realm-stack
*/
class TSNRANGE_Test extends PredisCommandTestCase
{
/**
* {@inheritDoc}
*/
protected function getExpectedCommand(): string
{
return TSNRANGE::class;
}
/**
* {@inheritDoc}
*/
protected function getExpectedId(): string
{
return 'TSNRANGE';
}
/**
* @group disconnected
* @dataProvider argumentsProvider
*/
public function testFilterArguments(array $actualArguments, array $expectedArguments): void
{
$command = $this->getCommand();
$command->setArguments($actualArguments);
$this->assertSameValues($expectedArguments, $command->getArguments());
}
/**
* @group disconnected
*/
public function testParseResponse(): void
{
$this->assertSame(1, $this->getCommand()->parseResponse(1));
}
/**
* @group disconnected
* @dataProvider parseResponseProvider
*/
public function testParseResponsePassesThroughTimestampMajorResults(array $response): void
{
$this->assertSame($response, $this->getCommand()->parseResponse($response));
}
public function parseResponseProvider(): array
{
return [
'single key' => [
[[1000, ['100']], [1020, ['120']]],
],
'multiple keys' => [
[[1000, ['100', '200']], [1020, ['120', '170']]],
],
];
}
/**
* @group disconnected
*/
public function testPrefixKeys(): void
{
/** @var PrefixableCommand $command */
$command = $this->getCommand();
$actualArguments = [['key1', 'key2'], 1000, 1001, (new NRangeArguments())->count(100)];
$prefix = 'prefix:';
$expectedArguments = [2, 'prefix:key1', 'prefix:key2', 1000, 1001, 'COUNT', 100];
$command->setArguments($actualArguments);
$command->prefixKeys($prefix);
$this->assertSame($expectedArguments, $command->getArguments());
}
/**
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion >= 8.9.0
*/
public function testReturnsQueriedRangeForMultipleKeysInForwardDirection(): void
{
$redis = $this->getClient();
$this->assertEquals('OK', $redis->tscreate('temp:TLV', (new CreateArguments())->labels('type', 'temp', 'location', 'TLV')));
$this->assertEquals('OK', $redis->tscreate('temp:JLM', (new CreateArguments())->labels('type', 'temp', 'location', 'JLM')));
$this->assertSame([1000, 1010, 1020], $redis->tsmadd('temp:TLV', 1000, 30, 'temp:TLV', 1010, 35, 'temp:TLV', 1020, 40));
$this->assertSame([1000, 1010, 1020], $redis->tsmadd('temp:JLM', 1000, 25, 'temp:JLM', 1010, 27, 'temp:JLM', 1020, 29));
$this->assertEquals(
[
[1000, ['30', '25']],
[1010, ['35', '27']],
[1020, ['40', '29']],
],
$redis->tsnrange(['temp:TLV', 'temp:JLM'], '-', '+')
);
}
/**
* @group connected
* @return void
* @requiresRedisVersion >= 8.9.0
*/
public function testReturnsQueriedRangeForMultipleKeysInForwardDirectionResp3(): void
{
$redis = $this->getResp3Client();
$this->assertEquals('OK', $redis->tscreate('temp:TLV', (new CreateArguments())->labels('type', 'temp', 'location', 'TLV')));
$this->assertEquals('OK', $redis->tscreate('temp:JLM', (new CreateArguments())->labels('type', 'temp', 'location', 'JLM')));
$this->assertSame([1000, 1010, 1020], $redis->tsmadd('temp:TLV', 1000, 30, 'temp:TLV', 1010, 35, 'temp:TLV', 1020, 40));
$this->assertSame([1000, 1010, 1020], $redis->tsmadd('temp:JLM', 1000, 25, 'temp:JLM', 1010, 27, 'temp:JLM', 1020, 29));
$this->assertEquals(
[
[1000, ['30', '25']],
[1010, ['35', '27']],
[1020, ['40', '29']],
],
$redis->tsnrange(['temp:TLV', 'temp:JLM'], '-', '+')
);
}
/**
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion >= 8.9.0
*/
public function testPreservesInputKeyOrder(): void
{
$redis = $this->getClient();
$this->assertEquals('OK', $redis->tscreate('temp:TLV', (new CreateArguments())->labels('type', 'temp', 'location', 'TLV')));
$this->assertEquals('OK', $redis->tscreate('temp:JLM', (new CreateArguments())->labels('type', 'temp', 'location', 'JLM')));
$this->assertSame([1000], $redis->tsmadd('temp:TLV', 1000, 30));
$this->assertSame([1000], $redis->tsmadd('temp:JLM', 1000, 25));
// Reversed key order should reverse the values in each row.
$this->assertEquals(
[[1000, ['25', '30']]],
$redis->tsnrange(['temp:JLM', 'temp:TLV'], '-', '+')
);
}
/**
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion >= 8.9.0
*/
public function testReturnsRangeLimitedByCount(): void
{
$redis = $this->getClient();
$this->assertEquals('OK', $redis->tscreate('temp:TLV', (new CreateArguments())->labels('type', 'temp', 'location', 'TLV')));
$this->assertEquals('OK', $redis->tscreate('temp:JLM', (new CreateArguments())->labels('type', 'temp', 'location', 'JLM')));
$this->assertSame([1000, 1010, 1020], $redis->tsmadd('temp:TLV', 1000, 30, 'temp:TLV', 1010, 35, 'temp:TLV', 1020, 40));
$this->assertSame([1000, 1010, 1020], $redis->tsmadd('temp:JLM', 1000, 25, 'temp:JLM', 1010, 27, 'temp:JLM', 1020, 29));
$arguments = (new NRangeArguments())->count(2);
$this->assertEquals(
[
[1000, ['30', '25']],
[1010, ['35', '27']],
],
$redis->tsnrange(['temp:TLV', 'temp:JLM'], '-', '+', $arguments)
);
}
/**
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion >= 8.9.0
*/
public function testReturnsRangeWithAggregationPerKey(): void
{
$redis = $this->getClient();
$this->assertEquals('OK', $redis->tscreate('stock:A', (new CreateArguments())->labels('type', 'stock', 'name', 'A')));
$this->assertEquals('OK', $redis->tscreate('stock:B', (new CreateArguments())->labels('type', 'stock', 'name', 'B')));
$this->assertSame([1000, 1010, 1020], $redis->tsmadd('stock:A', 1000, 100, 'stock:A', 1010, 110, 'stock:A', 1020, 120));
$this->assertSame([1000, 1010, 1020], $redis->tsmadd('stock:B', 1000, 200, 'stock:B', 1010, 210, 'stock:B', 1020, 220));
// Exactly numkeys aggregators are required, one per key.
$arguments = (new NRangeArguments())->aggregation([NRangeArguments::AGG_MIN, NRangeArguments::AGG_MAX], 1000);
$response = $redis->tsnrange(['stock:A', 'stock:B'], '-', '+', $arguments);
// Response is timestamp-major and each row holds one aggregated value
// per queried key, preserving the input key order.
$this->assertNotEmpty($response);
foreach ($response as $row) {
$this->assertIsInt($row[0]);
$this->assertCount(2, $row[1]);
}
}
/**
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion >= 8.9.0
*/
public function testThrowsExceptionOnNonExistingKey(): void
{
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('ERR TSDB: the key does not exist');
$redis->tsnrange(['non_existing_key'], 1000, 1000);
}
public function argumentsProvider(): array
{
return [
'with single key' => [
[['key'], 10000, 10001],
[1, 'key', 10000, 10001],
],
'with multiple keys' => [
[['key1', 'key2'], 10000, 10001],
[2, 'key1', 'key2', 10000, 10001],
],
'with duplicate keys preserved' => [
[['key', 'key'], 10000, 10001],
[2, 'key', 'key', 10000, 10001],
],
'with LATEST modifier' => [
[['key'], 10000, 10001, (new NRangeArguments())->latest()],
[1, 'key', 10000, 10001, 'LATEST'],
],
'with FILTER_BY_TS modifier' => [
[['key'], 10000, 10001, (new NRangeArguments())->filterByTs(1000, 1001)],
[1, 'key', 10000, 10001, 'FILTER_BY_TS', 1000, 1001],
],
'with FILTER_BY_VALUE modifier' => [
[['key'], 10000, 10001, (new NRangeArguments())->filterByValue(1000, 1001)],
[1, 'key', 10000, 10001, 'FILTER_BY_VALUE', 1000, 1001],
],
'with COUNT modifier' => [
[['key'], 10000, 10001, (new NRangeArguments())->count(100)],
[1, 'key', 10000, 10001, 'COUNT', 100],
],
'with AGGREGATION modifier - default arguments' => [
[['key1', 'key2'], 10000, 10001, (new NRangeArguments())->aggregation(['min', 'max'], 100)],
[2, 'key1', 'key2', 10000, 10001, 'AGGREGATION', 'min', 'max', 100],
],
'with AGGREGATION modifier - with ALIGN' => [
[['key'], 10000, 10001, (new NRangeArguments())->aggregation('sum', 100, 100)],
[1, 'key', 10000, 10001, 'ALIGN', 100, 'AGGREGATION', 'sum', 100],
],
'with AGGREGATION modifier - with BUCKETTIMESTAMP' => [
[['key'], 10000, 10001, (new NRangeArguments())->aggregation('sum', 100, 0, 1000)],
[1, 'key', 10000, 10001, 'AGGREGATION', 'sum', 100, 'BUCKETTIMESTAMP', 1000],
],
'with AGGREGATION modifier - with EMPTY' => [
[['key'], 10000, 10001, (new NRangeArguments())->aggregation('sum', 100, 0, 0, true)],
[1, 'key', 10000, 10001, 'AGGREGATION', 'sum', 100, 'EMPTY'],
],
'with all modifiers' => [
[['key1', 'key2'], 10000, 10001, (new NRangeArguments())->latest()->filterByTs(1000, 1001)->filterByValue(1000, 1001)->count(100)->aggregation(['min', 'max'], 100)],
[2, 'key1', 'key2', 10000, 10001, 'LATEST', 'FILTER_BY_TS', 1000, 1001, 'FILTER_BY_VALUE', 1000, 1001, 'COUNT', 100, 'AGGREGATION', 'min', 'max', 100],
],
];
}
}
@@ -0,0 +1,303 @@
<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2026 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\TimeSeries;
use Predis\Command\Argument\TimeSeries\CreateArguments;
use Predis\Command\Argument\TimeSeries\NRangeArguments;
use Predis\Command\PrefixableCommand;
use Predis\Command\Redis\PredisCommandTestCase;
use Predis\Response\ServerException;
/**
* @group commands
* @group realm-stack
*/
class TSNREVRANGE_Test extends PredisCommandTestCase
{
/**
* {@inheritDoc}
*/
protected function getExpectedCommand(): string
{
return TSNREVRANGE::class;
}
/**
* {@inheritDoc}
*/
protected function getExpectedId(): string
{
return 'TSNREVRANGE';
}
/**
* @group disconnected
* @dataProvider argumentsProvider
*/
public function testFilterArguments(array $actualArguments, array $expectedArguments): void
{
$command = $this->getCommand();
$command->setArguments($actualArguments);
$this->assertSameValues($expectedArguments, $command->getArguments());
}
/**
* @group disconnected
*/
public function testParseResponse(): void
{
$this->assertSame(1, $this->getCommand()->parseResponse(1));
}
/**
* @group disconnected
* @dataProvider parseResponseProvider
*/
public function testParseResponsePassesThroughTimestampMajorResults(array $response): void
{
$this->assertSame($response, $this->getCommand()->parseResponse($response));
}
public function parseResponseProvider(): array
{
return [
'single key' => [
[[1020, ['120']], [1000, ['100']]],
],
'multiple keys' => [
[[1020, ['120', '170']], [1000, ['100', '200']]],
],
];
}
/**
* @group disconnected
*/
public function testPrefixKeys(): void
{
/** @var PrefixableCommand $command */
$command = $this->getCommand();
$actualArguments = [['key1', 'key2'], 1000, 1001, (new NRangeArguments())->count(100)];
$prefix = 'prefix:';
$expectedArguments = [2, 'prefix:key1', 'prefix:key2', 1000, 1001, 'COUNT', 100];
$command->setArguments($actualArguments);
$command->prefixKeys($prefix);
$this->assertSame($expectedArguments, $command->getArguments());
}
/**
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion >= 8.9.0
*/
public function testReturnsQueriedRangeForMultipleKeysInReverseDirection(): void
{
$redis = $this->getClient();
$this->assertEquals('OK', $redis->tscreate('temp:TLV', (new CreateArguments())->labels('type', 'temp', 'location', 'TLV')));
$this->assertEquals('OK', $redis->tscreate('temp:JLM', (new CreateArguments())->labels('type', 'temp', 'location', 'JLM')));
$this->assertSame([1000, 1010, 1020], $redis->tsmadd('temp:TLV', 1000, 30, 'temp:TLV', 1010, 35, 'temp:TLV', 1020, 40));
$this->assertSame([1000, 1010, 1020], $redis->tsmadd('temp:JLM', 1000, 25, 'temp:JLM', 1010, 27, 'temp:JLM', 1020, 29));
$this->assertEquals(
[
[1020, ['40', '29']],
[1010, ['35', '27']],
[1000, ['30', '25']],
],
$redis->tsnrevrange(['temp:TLV', 'temp:JLM'], '-', '+')
);
}
/**
* @group connected
* @return void
* @requiresRedisVersion >= 8.9.0
*/
public function testReturnsQueriedRangeForMultipleKeysInReverseDirectionResp3(): void
{
$redis = $this->getResp3Client();
$this->assertEquals('OK', $redis->tscreate('temp:TLV', (new CreateArguments())->labels('type', 'temp', 'location', 'TLV')));
$this->assertEquals('OK', $redis->tscreate('temp:JLM', (new CreateArguments())->labels('type', 'temp', 'location', 'JLM')));
$this->assertSame([1000, 1010, 1020], $redis->tsmadd('temp:TLV', 1000, 30, 'temp:TLV', 1010, 35, 'temp:TLV', 1020, 40));
$this->assertSame([1000, 1010, 1020], $redis->tsmadd('temp:JLM', 1000, 25, 'temp:JLM', 1010, 27, 'temp:JLM', 1020, 29));
$this->assertEquals(
[
[1020, ['40', '29']],
[1010, ['35', '27']],
[1000, ['30', '25']],
],
$redis->tsnrevrange(['temp:TLV', 'temp:JLM'], '-', '+')
);
}
/**
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion >= 8.9.0
*/
public function testPreservesInputKeyOrder(): void
{
$redis = $this->getClient();
$this->assertEquals('OK', $redis->tscreate('temp:TLV', (new CreateArguments())->labels('type', 'temp', 'location', 'TLV')));
$this->assertEquals('OK', $redis->tscreate('temp:JLM', (new CreateArguments())->labels('type', 'temp', 'location', 'JLM')));
$this->assertSame([1000], $redis->tsmadd('temp:TLV', 1000, 30));
$this->assertSame([1000], $redis->tsmadd('temp:JLM', 1000, 25));
// Reversed key order should reverse the values in each row.
$this->assertEquals(
[[1000, ['25', '30']]],
$redis->tsnrevrange(['temp:JLM', 'temp:TLV'], '-', '+')
);
}
/**
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion >= 8.9.0
*/
public function testReturnsRangeLimitedByCount(): void
{
$redis = $this->getClient();
$this->assertEquals('OK', $redis->tscreate('temp:TLV', (new CreateArguments())->labels('type', 'temp', 'location', 'TLV')));
$this->assertEquals('OK', $redis->tscreate('temp:JLM', (new CreateArguments())->labels('type', 'temp', 'location', 'JLM')));
$this->assertSame([1000, 1010, 1020], $redis->tsmadd('temp:TLV', 1000, 30, 'temp:TLV', 1010, 35, 'temp:TLV', 1020, 40));
$this->assertSame([1000, 1010, 1020], $redis->tsmadd('temp:JLM', 1000, 25, 'temp:JLM', 1010, 27, 'temp:JLM', 1020, 29));
$arguments = (new NRangeArguments())->count(2);
// In reverse direction COUNT keeps the rows with the highest timestamps.
$this->assertEquals(
[
[1020, ['40', '29']],
[1010, ['35', '27']],
],
$redis->tsnrevrange(['temp:TLV', 'temp:JLM'], '-', '+', $arguments)
);
}
/**
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion >= 8.9.0
*/
public function testReturnsRangeWithAggregationPerKey(): void
{
$redis = $this->getClient();
$this->assertEquals('OK', $redis->tscreate('stock:A', (new CreateArguments())->labels('type', 'stock', 'name', 'A')));
$this->assertEquals('OK', $redis->tscreate('stock:B', (new CreateArguments())->labels('type', 'stock', 'name', 'B')));
$this->assertSame([1000, 1010, 1020], $redis->tsmadd('stock:A', 1000, 100, 'stock:A', 1010, 110, 'stock:A', 1020, 120));
$this->assertSame([1000, 1010, 1020], $redis->tsmadd('stock:B', 1000, 200, 'stock:B', 1010, 210, 'stock:B', 1020, 220));
// Exactly numkeys aggregators are required, one per key.
$arguments = (new NRangeArguments())->aggregation([NRangeArguments::AGG_MIN, NRangeArguments::AGG_MAX], 1000);
$response = $redis->tsnrevrange(['stock:A', 'stock:B'], '-', '+', $arguments);
// Response is timestamp-major and each row holds one aggregated value
// per queried key, preserving the input key order.
$this->assertNotEmpty($response);
foreach ($response as $row) {
$this->assertIsInt($row[0]);
$this->assertCount(2, $row[1]);
}
}
/**
* @group connected
* @group relay-resp3
* @return void
* @requiresRedisVersion >= 8.9.0
*/
public function testThrowsExceptionOnNonExistingKey(): void
{
$redis = $this->getClient();
$this->expectException(ServerException::class);
$this->expectExceptionMessage('ERR TSDB: the key does not exist');
$redis->tsnrevrange(['non_existing_key'], 1000, 1000);
}
public function argumentsProvider(): array
{
return [
'with single key' => [
[['key'], 10000, 10001],
[1, 'key', 10000, 10001],
],
'with multiple keys' => [
[['key1', 'key2'], 10000, 10001],
[2, 'key1', 'key2', 10000, 10001],
],
'with duplicate keys preserved' => [
[['key', 'key'], 10000, 10001],
[2, 'key', 'key', 10000, 10001],
],
'with LATEST modifier' => [
[['key'], 10000, 10001, (new NRangeArguments())->latest()],
[1, 'key', 10000, 10001, 'LATEST'],
],
'with FILTER_BY_TS modifier' => [
[['key'], 10000, 10001, (new NRangeArguments())->filterByTs(1000, 1001)],
[1, 'key', 10000, 10001, 'FILTER_BY_TS', 1000, 1001],
],
'with FILTER_BY_VALUE modifier' => [
[['key'], 10000, 10001, (new NRangeArguments())->filterByValue(1000, 1001)],
[1, 'key', 10000, 10001, 'FILTER_BY_VALUE', 1000, 1001],
],
'with COUNT modifier' => [
[['key'], 10000, 10001, (new NRangeArguments())->count(100)],
[1, 'key', 10000, 10001, 'COUNT', 100],
],
'with AGGREGATION modifier - default arguments' => [
[['key1', 'key2'], 10000, 10001, (new NRangeArguments())->aggregation(['min', 'max'], 100)],
[2, 'key1', 'key2', 10000, 10001, 'AGGREGATION', 'min', 'max', 100],
],
'with AGGREGATION modifier - with ALIGN' => [
[['key'], 10000, 10001, (new NRangeArguments())->aggregation('sum', 100, 100)],
[1, 'key', 10000, 10001, 'ALIGN', 100, 'AGGREGATION', 'sum', 100],
],
'with AGGREGATION modifier - with BUCKETTIMESTAMP' => [
[['key'], 10000, 10001, (new NRangeArguments())->aggregation('sum', 100, 0, 1000)],
[1, 'key', 10000, 10001, 'AGGREGATION', 'sum', 100, 'BUCKETTIMESTAMP', 1000],
],
'with AGGREGATION modifier - with EMPTY' => [
[['key'], 10000, 10001, (new NRangeArguments())->aggregation('sum', 100, 0, 0, true)],
[1, 'key', 10000, 10001, 'AGGREGATION', 'sum', 100, 'EMPTY'],
],
'with all modifiers' => [
[['key1', 'key2'], 10000, 10001, (new NRangeArguments())->latest()->filterByTs(1000, 1001)->filterByValue(1000, 1001)->count(100)->aggregation(['min', 'max'], 100)],
[2, 'key1', 'key2', 10000, 10001, 'LATEST', 'FILTER_BY_TS', 1000, 1001, 'FILTER_BY_VALUE', 1000, 1001, 'COUNT', 100, 'AGGREGATION', 'min', 'max', 100],
],
];
}
}
+3 -5
View File
@@ -12,8 +12,6 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand;
/**
* @group commands
* @group realm-key
@@ -78,13 +76,13 @@ class UNLINK_Test extends PredisCommandTestCase
*/
public function testPrefixKeys(): void
{
/** @var PrefixableCommand $command */
/** @var UNLINK $command */
$command = $this->getCommand();
$actualArguments = ['arg1', 'arg2', 'arg3', 'arg4'];
$inputArguments = ['arg1', 'arg2', 'arg3', 'arg4'];
$prefix = 'prefix:';
$expectedArguments = ['prefix:arg1', 'prefix:arg2', 'prefix:arg3', 'prefix:arg4'];
$command->setArguments($actualArguments);
$command->setArguments($inputArguments);
$command->prefixKeys($prefix);
$this->assertSame($expectedArguments, $command->getArguments());
@@ -12,6 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\PrefixableCommand;
use Predis\Response\ServerException;
/**
@@ -58,6 +59,23 @@ class ZMSCORE_Test extends PredisCommandTestCase
$this->assertSame(1, $this->getCommand()->parseResponse(1));
}
/**
* @group disconnected
*/
public function testPrefixKeys(): void
{
/** @var PrefixableCommand $command */
$command = $this->getCommand();
$actualArguments = ['arg1', 'arg2', 'arg3', 'arg4'];
$prefix = 'prefix:';
$expectedArguments = ['prefix:arg1', 'arg2', 'arg3', 'arg4'];
$command->setArguments($actualArguments);
$command->prefixKeys($prefix);
$this->assertSame($expectedArguments, $command->getArguments());
}
/**
* @group connected
* @dataProvider membersProvider
@@ -17,6 +17,7 @@ use Predis\Command;
use Predis\Connection;
use Predis\Connection\Parameters;
use Predis\Connection\ParametersInterface;
use Predis\Connection\Resource\Exception\StreamInitException;
use Predis\Connection\Resource\StreamFactoryInterface;
use Predis\Connection\StreamConnection;
use Predis\Replication;
@@ -1496,6 +1497,73 @@ class SentinelReplicationTest extends PredisTestCase
));
}
/**
* @group disconnected
*/
public function testMethodExecuteCommandRetriesWriteCommandOnNewMasterOnStreamInitException(): void
{
$sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?role=sentinel');
$sentinel1
->expects($this->any())
->method('executeCommand')
->with($this->isRedisCommand(
'SENTINEL', ['get-master-addr-by-name', 'svc']
))
->willReturn(
['127.0.0.1', '6391']
);
$masterOld = $this->getMockConnection('tcp://127.0.0.1:6381?role=master');
$masterOld
->expects($this->any())
->method('isConnected')
->willReturn(true);
$masterOld
->expects($this->once())
->method('executeCommand')
->with(
$this->isRedisCommand('DEL', ['key'])
)
->willThrowException(
new StreamInitException('Connection refused [tcp://127.0.0.1:6381]')
);
$masterNew = $this->getMockConnection('tcp://127.0.0.1:6391?role=master');
$masterNew
->expects($this->any())
->method('isConnected')
->willReturn(true);
$masterNew
->expects($this->once())
->method('executeCommand')
->withConsecutive(
[$this->isRedisCommand('DEL', ['key'])]
)
->willReturnOnConsecutiveCalls(
1
);
/** @var Connection\FactoryInterface|MockObject */
$factory = $this->getMockBuilder('Predis\Connection\FactoryInterface')->getMock();
$factory
->expects($this->once())
->method('create')
->with([
'host' => '127.0.0.1',
'port' => '6391',
'role' => 'master',
])
->willReturn($masterNew);
$replication = $this->getReplicationConnection('svc', [$sentinel1], $factory);
$replication->add($masterOld);
$this->assertSame(1, $replication->executeCommand(
Command\RawCommand::create('del', 'key')
));
}
/**
* @group disconnected
*/