mirror of
https://github.com/predis/predis.git
synced 2026-08-21 18:00:57 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f49e13ee3a | |||
| 6e3e2c0e78 | |||
| c35c422eda | |||
| 1b5ed7d516 | |||
| 5df852f227 | |||
| 9418df1924 | |||
| 0e4b3829a7 | |||
| 65f6127fab | |||
| 7ff24b19ae | |||
| 2babfc91d7 | |||
| fdd52d0284 | |||
| 48a861053e |
@@ -165,3 +165,32 @@ jobs:
|
||||
|
||||
- name: Search for misspellings
|
||||
run: $(python -m site --user-base)/bin/codespell
|
||||
|
||||
changelog:
|
||||
|
||||
name: Changelog
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
|
||||
if: >-
|
||||
github.event_name == 'pull_request' &&
|
||||
!contains(github.event.head_commit.message, 'nochangelog') &&
|
||||
!contains(github.event.head_commit.message, 'no-changelog') &&
|
||||
!contains(github.event.head_commit.message, 'no changelog') &&
|
||||
!contains(github.event.pull_request.labels.*.name, 'no-changelog')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check for CHANGELOG entry
|
||||
env:
|
||||
TARGET: ${{ github.event.pull_request.base.ref }}
|
||||
run: |
|
||||
FILES_CHANGED=$(git diff --name-only origin/$TARGET...HEAD | grep -E 'CHANGELOG\.md' -c)
|
||||
if [ "$FILES_CHANGED" != "1" ]; then
|
||||
echo "CHANGELOG.md was not updated";
|
||||
exit 1;
|
||||
fi;
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
run: |
|
||||
# Mapping of original redis versions to client test containers
|
||||
declare -A redis_clients_version_mapping=(
|
||||
["8.0"]="8.0-M04-pre"
|
||||
["8.0"]="8.0-RC2-pre"
|
||||
["7.4"]="7.4.2"
|
||||
["7.2"]="7.2.7"
|
||||
["6.2"]="6.2.17"
|
||||
|
||||
+19
-2
@@ -1,8 +1,25 @@
|
||||
## Changelog
|
||||
|
||||
## Unreleased
|
||||
## v2.4.0 (2025-04-30)
|
||||
### Added
|
||||
- Added new hash-field expiration commands (#1520)
|
||||
- Added missing `FT._LIST` and `BITFIELD_RO` commands (#1521)
|
||||
|
||||
### Changed
|
||||
- Update `WATCH` command to accept `string|string[]` (#1476)
|
||||
- Optimize cluster slotmap with compact slot range object (#1493)
|
||||
|
||||
### Fixed
|
||||
- Fixed PHP 8.4 compatibility with `stream_context_set_option()`
|
||||
- Fixed `EVAL_RO` cluster support (#1449)
|
||||
- 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)
|
||||
- Fixed protocol loss during redis cluster `MOVED` / `ASK` (#1530)
|
||||
|
||||
### Maintenance
|
||||
- Added CI testing with Redis 8.0 (#1510)
|
||||
- Added test coverage for compatibility with Redis 8.0 (#1513)
|
||||
- Use parallel on PHP-CS-Fixer (#1489)
|
||||
|
||||
## v2.3.0 (2024-11-21)
|
||||
### Added
|
||||
|
||||
@@ -14,7 +14,7 @@ More details about this project can be found on the [frequently asked questions]
|
||||
|
||||
## Main features ##
|
||||
|
||||
- Support for Redis from __3.0__ to __7.4__.
|
||||
- Support for Redis from __3.0__ to __8.0__.
|
||||
- Support for clustering using client-side sharding and pluggable keyspace distributors.
|
||||
- Support for [redis-cluster](http://redis.io/topics/cluster-tutorial) (Redis >= 3.0).
|
||||
- Support for master-slave replication setups and [redis-sentinel](http://redis.io/topics/sentinel).
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ use Traversable;
|
||||
*/
|
||||
class Client implements ClientInterface, IteratorAggregate
|
||||
{
|
||||
public const VERSION = '2.3.1-dev';
|
||||
public const VERSION = '2.4.0';
|
||||
|
||||
/** @var OptionsInterface */
|
||||
private $options;
|
||||
|
||||
@@ -44,6 +44,8 @@ use Predis\Command\Redis\Container\FunctionContainer;
|
||||
use Predis\Command\Redis\Container\Json\JSONDEBUG;
|
||||
use Predis\Command\Redis\Container\Search\FTCONFIG;
|
||||
use Predis\Command\Redis\Container\Search\FTCURSOR;
|
||||
use Predis\Command\Redis\HGETEX;
|
||||
use Predis\Command\Redis\HSETEX;
|
||||
|
||||
/**
|
||||
* Interface defining a client-side context such as a pipeline or transaction.
|
||||
@@ -164,7 +166,9 @@ use Predis\Command\Redis\Container\Search\FTCURSOR;
|
||||
* @method $this hpexpireat(string $key, int $unixTimeMilliseconds, array $fields, string $flag = null)
|
||||
* @method $this hpexpiretime(string $key, array $fields)
|
||||
* @method $this hget($key, $field)
|
||||
* @method $this hgetex(string $key, array $fields, string $modifier = HGETEX::NULL)
|
||||
* @method $this hgetall($key)
|
||||
* @method $this hgetdel(string $key, array $fields)
|
||||
* @method $this hincrby($key, $field, $increment)
|
||||
* @method $this hincrbyfloat($key, $field, $increment)
|
||||
* @method $this hkeys($key)
|
||||
@@ -174,6 +178,7 @@ use Predis\Command\Redis\Container\Search\FTCURSOR;
|
||||
* @method $this hrandfield(string $key, int $count = 1, bool $withValues = false)
|
||||
* @method $this hscan($key, $cursor, ?array $options = null)
|
||||
* @method $this hset($key, $field, $value)
|
||||
* @method $this hsetex(string $key, array $fieldValueMap, string $setModifier = HSETEX::SET_NULL, string $ttlModifier = HSETEX::TTL_NULL, int|bool $ttlModifierValue = false)
|
||||
* @method $this hsetnx($key, $field, $value)
|
||||
* @method $this httl(string $key, array $fields)
|
||||
* @method $this hpttl(string $key, array $fields)
|
||||
|
||||
@@ -45,6 +45,8 @@ use Predis\Command\Redis\Container\FunctionContainer;
|
||||
use Predis\Command\Redis\Container\Json\JSONDEBUG;
|
||||
use Predis\Command\Redis\Container\Search\FTCONFIG;
|
||||
use Predis\Command\Redis\Container\Search\FTCURSOR;
|
||||
use Predis\Command\Redis\HGETEX;
|
||||
use Predis\Command\Redis\HSETEX;
|
||||
use Predis\Configuration\OptionsInterface;
|
||||
use Predis\Connection\ConnectionInterface;
|
||||
use Predis\Response\Status;
|
||||
@@ -173,7 +175,9 @@ use Predis\Response\Status;
|
||||
* @method array|null hpexpireat(string $key, int $unixTimeMilliseconds, array $fields, string $flag = null)
|
||||
* @method array|null hpexpiretime(string $key, array $fields)
|
||||
* @method string|null hget(string $key, string $field)
|
||||
* @method array|null hgetex(string $key, array $fields, string $modifier = HGETEX::NULL, int|bool $modifierValue = false)
|
||||
* @method array hgetall(string $key)
|
||||
* @method array hgetdel(string $key, array $fields)
|
||||
* @method int hincrby(string $key, string $field, int $increment)
|
||||
* @method string hincrbyfloat(string $key, string $field, int|float $increment)
|
||||
* @method array hkeys(string $key)
|
||||
@@ -183,6 +187,7 @@ use Predis\Response\Status;
|
||||
* @method array hrandfield(string $key, int $count = 1, bool $withValues = false)
|
||||
* @method array hscan(string $key, $cursor, ?array $options = null)
|
||||
* @method int hset(string $key, string $field, string $value)
|
||||
* @method int hsetex(string $key, array $fieldValueMap, string $setModifier = HSETEX::SET_NULL, string $ttlModifier = HSETEX::TTL_NULL, int|bool $ttlModifierValue = false)
|
||||
* @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)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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 HGETDEL extends RedisCommand
|
||||
{
|
||||
public function getId()
|
||||
{
|
||||
return 'HGETDEL';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $arguments
|
||||
* @return void
|
||||
*/
|
||||
public function setArguments(array $arguments)
|
||||
{
|
||||
$processedArguments = [$arguments[0], 'FIELDS', count($arguments[1])];
|
||||
$processedArguments = array_merge($processedArguments, $arguments[1]);
|
||||
|
||||
parent::setArguments($processedArguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?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;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class HGETEX extends RedisCommand
|
||||
{
|
||||
public const NULL = '';
|
||||
public const EX = 'ex';
|
||||
public const PX = 'px';
|
||||
public const EXAT = 'exat';
|
||||
public const PXAT = 'pxat';
|
||||
public const PERSIST = 'persist';
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private static $modifierEnum = [
|
||||
self::EX => 'EX',
|
||||
self::PX => 'PX',
|
||||
self::EXAT => 'EXAT',
|
||||
self::PXAT => 'PXAT',
|
||||
self::PERSIST => 'PERSIST',
|
||||
];
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return 'HGETEX';
|
||||
}
|
||||
|
||||
public function setArguments(array $arguments)
|
||||
{
|
||||
$processedArguments = [$arguments[0]];
|
||||
|
||||
// Only required arguments
|
||||
if (!array_key_exists(2, $arguments) || $arguments[2] == '') {
|
||||
array_push($processedArguments, 'FIELDS', count($arguments[1]));
|
||||
$processedArguments = array_merge($processedArguments, $arguments[1]);
|
||||
parent::setArguments($processedArguments);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array(strtoupper($arguments[2]), self::$modifierEnum)) {
|
||||
$enumValues = implode(', ', array_keys(self::$modifierEnum));
|
||||
throw new UnexpectedValueException("Modifier argument accepts only: {$enumValues} values");
|
||||
}
|
||||
|
||||
// PERSIST requires no additional value
|
||||
if (strtoupper($arguments[2]) === self::$modifierEnum['persist']) {
|
||||
$processedArguments[] = self::$modifierEnum['persist'];
|
||||
array_push($processedArguments, 'FIELDS', count($arguments[1]));
|
||||
$processedArguments = array_merge($processedArguments, $arguments[1]);
|
||||
parent::setArguments($processedArguments);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!array_key_exists(3, $arguments) || !is_int($arguments[3])) {
|
||||
throw new UnexpectedValueException('Modifier value is missing or incorrect type');
|
||||
}
|
||||
|
||||
// Order matters so FIELDS should be at the end
|
||||
array_push($processedArguments, self::$modifierEnum[strtolower($arguments[2])], $arguments[3], 'FIELDS', count($arguments[1]));
|
||||
$processedArguments = array_merge($processedArguments, $arguments[1]);
|
||||
|
||||
parent::setArguments($processedArguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?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;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class HSETEX extends RedisCommand
|
||||
{
|
||||
public const TTL_NULL = '';
|
||||
public const TTL_EX = 'ex';
|
||||
public const TTL_PX = 'px';
|
||||
public const TTL_EXAT = 'exat';
|
||||
public const TTL_PXAT = 'pxat';
|
||||
public const TTL_KEEP_TTL = 'keepttl';
|
||||
|
||||
public const SET_NULL = '';
|
||||
public const SET_FNX = 'fnx';
|
||||
public const SET_FXX = 'fxx';
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private static $ttlModifierEnum = [
|
||||
self::TTL_EX => 'EX',
|
||||
self::TTL_PX => 'PX',
|
||||
self::TTL_EXAT => 'EXAT',
|
||||
self::TTL_PXAT => 'PXAT',
|
||||
self::TTL_KEEP_TTL => 'KEEPTTL',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private static $setModifierEnum = [
|
||||
self::SET_FNX => 'FNX',
|
||||
self::SET_FXX => 'FXX',
|
||||
];
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return 'HSETEX';
|
||||
}
|
||||
|
||||
public function setArguments(array $arguments)
|
||||
{
|
||||
$processedArguments = [$arguments[0]];
|
||||
$flatArray = [];
|
||||
|
||||
// Convert key => value, into key, value
|
||||
array_walk($arguments[1], function ($value, $key) use (&$flatArray) {
|
||||
array_push($flatArray, $key, $value);
|
||||
});
|
||||
|
||||
// Only required arguments
|
||||
if (!array_key_exists(2, $arguments)) {
|
||||
array_push($processedArguments, 'FIELDS', count($flatArray) / 2);
|
||||
$processedArguments = array_merge($processedArguments, $flatArray);
|
||||
parent::setArguments($processedArguments);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($arguments[2] !== '') {
|
||||
if (!in_array(strtoupper($arguments[2]), self::$setModifierEnum)) {
|
||||
$enumValues = implode(', ', array_keys(self::$setModifierEnum));
|
||||
throw new UnexpectedValueException("Modifier argument accepts only: {$enumValues} values");
|
||||
}
|
||||
|
||||
$processedArguments[] = self::$setModifierEnum[strtolower($arguments[2])];
|
||||
}
|
||||
|
||||
// Required + set modifier
|
||||
if (!array_key_exists(3, $arguments) || $arguments[3] == '') {
|
||||
array_push($processedArguments, 'FIELDS', count($flatArray) / 2);
|
||||
$processedArguments = array_merge($processedArguments, $flatArray);
|
||||
parent::setArguments($processedArguments);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array(strtoupper($arguments[3]), self::$ttlModifierEnum)) {
|
||||
$enumValues = implode(', ', array_keys(self::$ttlModifierEnum));
|
||||
throw new UnexpectedValueException("Modifier argument accepts only: {$enumValues} values");
|
||||
}
|
||||
|
||||
// KEEPTTL requires no additional value
|
||||
if (strtoupper($arguments[3]) === self::$ttlModifierEnum[self::TTL_KEEP_TTL]) {
|
||||
$processedArguments[] = self::$ttlModifierEnum[self::TTL_KEEP_TTL];
|
||||
array_push($processedArguments, 'FIELDS', count($flatArray) / 2);
|
||||
$processedArguments = array_merge($processedArguments, $flatArray);
|
||||
parent::setArguments($processedArguments);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!array_key_exists(4, $arguments) || !is_int($arguments[4])) {
|
||||
throw new UnexpectedValueException('Modifier value is missing or incorrect type');
|
||||
}
|
||||
|
||||
// Order matters so FIELDS should be at the end
|
||||
array_push($processedArguments, self::$ttlModifierEnum[strtolower($arguments[3])], $arguments[4], 'FIELDS', count($flatArray) / 2);
|
||||
$processedArguments = array_merge($processedArguments, $flatArray);
|
||||
|
||||
parent::setArguments($processedArguments);
|
||||
}
|
||||
}
|
||||
@@ -254,7 +254,7 @@ class RedisCluster implements ClusterInterface, IteratorAggregate, Countable
|
||||
}
|
||||
|
||||
if (!$connection = $this->getRandomConnection()) {
|
||||
throw new ClientException('No connections left in the pool for `CLUSTER SLOTS`');
|
||||
throw new ClientException('No connections left in the pool for `CLUSTER SLOTS` (' . $exception->getMessage() . ')');
|
||||
}
|
||||
|
||||
usleep($retryAfter * 1000);
|
||||
@@ -337,10 +337,19 @@ class RedisCluster implements ClusterInterface, IteratorAggregate, Countable
|
||||
{
|
||||
$separator = strrpos($connectionID, ':');
|
||||
|
||||
return $this->connections->create([
|
||||
$parameters = [
|
||||
'host' => substr($connectionID, 0, $separator),
|
||||
'port' => substr($connectionID, $separator + 1),
|
||||
]);
|
||||
];
|
||||
|
||||
$existConnection = current($this->pool);
|
||||
if ($existConnection instanceof NodeConnectionInterface) {
|
||||
$existParameters = $existConnection->getParameters()->toArray();
|
||||
unset($existParameters['alias'], $existParameters['slots']);
|
||||
$parameters = array_merge($existParameters, $parameters);
|
||||
}
|
||||
|
||||
return $this->connections->create($parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1008,6 +1008,14 @@ class KeyPrefixProcessorTest extends PredisTestCase
|
||||
['key', 'MAXLEN', 100],
|
||||
['prefix:key', 'MAXLEN', 100],
|
||||
],
|
||||
['ZPOPMIN',
|
||||
['key'],
|
||||
['prefix:key'],
|
||||
],
|
||||
['ZPOPMAX',
|
||||
['key'],
|
||||
['prefix:key'],
|
||||
],
|
||||
/* ---------------- Redis 6.2 ---------------- */
|
||||
['GETDEL',
|
||||
['key'],
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?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 HGETDEL_Test extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getExpectedCommand(): string
|
||||
{
|
||||
return HGETDEL::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getExpectedId(): string
|
||||
{
|
||||
return 'HGETDEL';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments(): void
|
||||
{
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments(['key', ['field1', 'field2']]);
|
||||
|
||||
$this->assertSame(['key', 'FIELDS', 2, 'field1', 'field2'], $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse(): void
|
||||
{
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->assertSame(0, $command->parseResponse(0));
|
||||
$this->assertSame(1, $command->parseResponse(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.9.0
|
||||
*/
|
||||
public function testReturnsAndRemoveFieldsFromHash(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->hset('hashkey', 'field1', 'value1', 'field2', 'value2', 'field3', 'value3');
|
||||
|
||||
$this->assertSame(['value1', 'value2'], $redis->hgetdel('hashkey', ['field1', 'field2']));
|
||||
$this->assertSame(['field3' => 'value3'], $redis->hgetall('hashkey'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
<?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 UnexpectedValueException;
|
||||
|
||||
class HGETEX_Test extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand(): string
|
||||
{
|
||||
return HGETEX::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId(): string
|
||||
{
|
||||
return 'HGETEX';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
* @dataProvider argumentsProvider
|
||||
*/
|
||||
public function testFilterArguments(array $actualArguments, array $expectedArguments): void
|
||||
{
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($actualArguments);
|
||||
|
||||
$this->assertSame($expectedArguments, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse(): void
|
||||
{
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->assertSame(1, $command->parseResponse(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @dataProvider hashProvider
|
||||
* @param array $hash
|
||||
* @param array $arguments
|
||||
* @param array $expectedResponse
|
||||
* @param float $timeout
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.9.0
|
||||
*/
|
||||
public function testReturnsValueAndSetExpirationTimeForGivenHash(
|
||||
array $hash,
|
||||
array $arguments,
|
||||
array $expectedResponse,
|
||||
float $timeout
|
||||
): void {
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->hset(...$hash);
|
||||
|
||||
$this->assertSame($expectedResponse, $redis->hgetex(...$arguments));
|
||||
$this->sleep($timeout);
|
||||
$this->assertSame([], $redis->hgetall('hash_key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.9.0
|
||||
*/
|
||||
public function testRemovesAssociatedTTLFromHash()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->hsetex(
|
||||
'hash_key', ['field1' => 'value1'],
|
||||
HSETEX::SET_NULL, HSETEX::TTL_EX, 100
|
||||
);
|
||||
|
||||
$this->assertGreaterThan(0, $redis->hexpiretime('hash_key', ['field1'])[0]);
|
||||
$redis->hgetex('hash_key', ['field1'], HGETEX::PERSIST);
|
||||
$this->assertEquals(-1, $redis->hexpiretime('hash_key', ['field1'])[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @dataProvider unexpectedValuesProvider
|
||||
* @param array $arguments
|
||||
* @param string $expectedExceptionMessage
|
||||
* @return void
|
||||
*/
|
||||
public function testThrowsExceptionOnUnexpectedValueGiven(
|
||||
array $arguments,
|
||||
string $expectedExceptionMessage
|
||||
): void {
|
||||
$redis = $this->getClient();
|
||||
|
||||
$this->expectException(UnexpectedValueException::class);
|
||||
$this->expectExceptionMessage($expectedExceptionMessage);
|
||||
|
||||
$redis->hgetex(...$arguments);
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with default arguments' => [
|
||||
['key', ['field1', 'field2']],
|
||||
['key', 'FIELDS', 2, 'field1', 'field2'],
|
||||
],
|
||||
'with EX modifier' => [
|
||||
['key', ['field1', 'field2'], HGETEX::EX, 10],
|
||||
['key', 'EX', 10, 'FIELDS', 2, 'field1', 'field2'],
|
||||
],
|
||||
'with PX modifier' => [
|
||||
['key', ['field1', 'field2'], HGETEX::PX, 10],
|
||||
['key', 'PX', 10, 'FIELDS', 2, 'field1', 'field2'],
|
||||
],
|
||||
'with EXAT modifier' => [
|
||||
['key', ['field1', 'field2'], HGETEX::EXAT, 10],
|
||||
['key', 'EXAT', 10, 'FIELDS', 2, 'field1', 'field2'],
|
||||
],
|
||||
'with PXAT modifier' => [
|
||||
['key', ['field1', 'field2'], HGETEX::PXAT, 10],
|
||||
['key', 'PXAT', 10, 'FIELDS', 2, 'field1', 'field2'],
|
||||
],
|
||||
'with PERSIST modifier' => [
|
||||
['key', ['field1', 'field2'], HGETEX::PERSIST],
|
||||
['key', 'PERSIST', 'FIELDS', 2, 'field1', 'field2'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function hashProvider(): array
|
||||
{
|
||||
return [
|
||||
'with expiration - EX modifier' => [
|
||||
['hash_key', 'field1', 'value1', 'field2', 'value2'],
|
||||
['hash_key', ['field1', 'field2'], HGETEX::EX, 1],
|
||||
['value1', 'value2'],
|
||||
1.2,
|
||||
],
|
||||
'with expiration - PX modifier' => [
|
||||
['hash_key', 'field1', 'value1', 'field2', 'value2'],
|
||||
['hash_key', ['field1', 'field2'], HGETEX::PX, 100],
|
||||
['value1', 'value2'],
|
||||
0.2,
|
||||
],
|
||||
'with expiration - EXAT modifier' => [
|
||||
['hash_key', 'field1', 'value1', 'field2', 'value2'],
|
||||
['hash_key', ['field1', 'field2'], HGETEX::EXAT, time() + 1],
|
||||
['value1', 'value2'],
|
||||
2,
|
||||
],
|
||||
'with expiration - PXAT modifier' => [
|
||||
['hash_key', 'field1', 'value1', 'field2', 'value2'],
|
||||
['hash_key', ['field1', 'field2'], HGETEX::PXAT, (time() * 1000) + 100],
|
||||
['value1', 'value2'],
|
||||
0.3,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function unexpectedValuesProvider(): array
|
||||
{
|
||||
return [
|
||||
'with wrong modifier' => [
|
||||
['key', ['field1', 'field2'], 'wrong', 10],
|
||||
'Modifier argument accepts only: ex, px, exat, pxat, persist values',
|
||||
],
|
||||
'with wrong type value' => [
|
||||
['key', ['field1', 'field2'], 'ex', true],
|
||||
'Modifier value is missing or incorrect type',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
<?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 UnexpectedValueException;
|
||||
|
||||
class HSETEX_Test extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand(): string
|
||||
{
|
||||
return HSETEX::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId(): string
|
||||
{
|
||||
return 'HSETEX';
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @group slow
|
||||
* @dataProvider hashProvider
|
||||
* @param array $arguments
|
||||
* @param int $expectedResponse
|
||||
* @param float $timeout
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.9.0
|
||||
*/
|
||||
public function testSetHashWithExpiration(
|
||||
array $arguments,
|
||||
int $expectedResponse,
|
||||
float $timeout
|
||||
): void {
|
||||
$redis = $this->getClient();
|
||||
|
||||
$this->assertSame($expectedResponse, $redis->hsetex(...$arguments));
|
||||
|
||||
$this->sleep($timeout);
|
||||
|
||||
$this->assertSame([], $redis->hgetall('hash_key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.9.0
|
||||
*/
|
||||
public function testSetHashFieldsIfNotExists(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$this->assertSame(
|
||||
1,
|
||||
$redis->hsetex('hash_key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_FNX)
|
||||
);
|
||||
$this->assertSame(
|
||||
0,
|
||||
$redis->hsetex('hash_key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_FNX)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.9.0
|
||||
*/
|
||||
public function testSetHashFieldsOnlyIfExists(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$this->assertSame(
|
||||
0,
|
||||
$redis->hsetex('hash_key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_FXX)
|
||||
);
|
||||
$this->assertSame(
|
||||
1,
|
||||
$redis->hsetex('hash_key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_FNX)
|
||||
);
|
||||
$this->assertSame(
|
||||
1,
|
||||
$redis->hsetex('hash_key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_FXX)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @return void
|
||||
* @requiresRedisVersion >= 7.9.0
|
||||
*/
|
||||
public function testSetHashFieldRetainingTTLValue(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$this->assertSame(
|
||||
1,
|
||||
$redis->hsetex(
|
||||
'hash_key',
|
||||
['field1' => 'value1', 'field2' => 'value2'],
|
||||
HSETEX::SET_FNX,
|
||||
HSETEX::TTL_EX,
|
||||
100
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertGreaterThan(0, $redis->hexpiretime('hash_key', ['field1'])[0]);
|
||||
|
||||
$this->assertSame(
|
||||
1,
|
||||
$redis->hsetex(
|
||||
'hash_key',
|
||||
['field1' => 'value1'],
|
||||
HSETEX::SET_FXX,
|
||||
HSETEX::TTL_KEEP_TTL
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertGreaterThan(0, $redis->hexpiretime('hash_key', ['field1'])[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @dataProvider unexpectedValuesProvider
|
||||
* @param array $arguments
|
||||
* @param string $expectedExceptionMessage
|
||||
* @return void
|
||||
*/
|
||||
public function testThrowsExceptionOnUnexpectedValueGiven(
|
||||
array $arguments,
|
||||
string $expectedExceptionMessage
|
||||
): void {
|
||||
$redis = $this->getClient();
|
||||
|
||||
$this->expectException(UnexpectedValueException::class);
|
||||
$this->expectExceptionMessage($expectedExceptionMessage);
|
||||
|
||||
$redis->hsetex(...$arguments);
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'with default arguments' => [
|
||||
['key', ['field1' => 'value1', 'field2' => 'value2']],
|
||||
['key', 'FIELDS', 2, 'field1', 'value1', 'field2', 'value2'],
|
||||
],
|
||||
'with FNX modifier' => [
|
||||
['key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_FNX],
|
||||
['key', 'FNX', 'FIELDS', 2, 'field1', 'value1', 'field2', 'value2'],
|
||||
],
|
||||
'with FXX modifier' => [
|
||||
['key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_FXX],
|
||||
['key', 'FXX', 'FIELDS', 2, 'field1', 'value1', 'field2', 'value2'],
|
||||
],
|
||||
'with EX modifier' => [
|
||||
['key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_NULL, HSETEX::TTL_EX, 10],
|
||||
['key', 'EX', 10, 'FIELDS', 2, 'field1', 'value1', 'field2', 'value2'],
|
||||
],
|
||||
'with PX modifier' => [
|
||||
['key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_NULL, HSETEX::TTL_PX, 10],
|
||||
['key', 'PX', 10, 'FIELDS', 2, 'field1', 'value1', 'field2', 'value2'],
|
||||
],
|
||||
'with EXAT modifier' => [
|
||||
['key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_NULL, HSETEX::TTL_EXAT, 10],
|
||||
['key', 'EXAT', 10, 'FIELDS', 2, 'field1', 'value1', 'field2', 'value2'],
|
||||
],
|
||||
'with PXAT modifier' => [
|
||||
['key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_NULL, HSETEX::TTL_PXAT, 10],
|
||||
['key', 'PXAT', 10, 'FIELDS', 2, 'field1', 'value1', 'field2', 'value2'],
|
||||
],
|
||||
'with KEEPTTL modifier' => [
|
||||
['key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_NULL, HSETEX::TTL_KEEP_TTL],
|
||||
['key', 'KEEPTTL', 'FIELDS', 2, 'field1', 'value1', 'field2', 'value2'],
|
||||
],
|
||||
'with combined modifiers' => [
|
||||
['key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_FXX, HSETEX::TTL_PXAT, 10],
|
||||
['key', 'FXX', 'PXAT', 10, 'FIELDS', 2, 'field1', 'value1', 'field2', 'value2'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function hashProvider(): array
|
||||
{
|
||||
return [
|
||||
'with EX modifier' => [
|
||||
['key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_NULL, HSETEX::TTL_EX, 1],
|
||||
1,
|
||||
1.2,
|
||||
],
|
||||
'with PX modifier' => [
|
||||
['key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_NULL, HSETEX::TTL_PX, 100],
|
||||
1,
|
||||
0.2,
|
||||
],
|
||||
'with EXAT modifier' => [
|
||||
['key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_NULL, HSETEX::TTL_EXAT, time() + 1],
|
||||
1,
|
||||
2,
|
||||
],
|
||||
'with PXAT modifier' => [
|
||||
['key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_NULL, HSETEX::TTL_PXAT, (time() * 1000) + 100],
|
||||
1,
|
||||
0.3,
|
||||
],
|
||||
'with combined modifiers' => [
|
||||
['key', ['field1' => 'value1', 'field2' => 'value2'], HSETEX::SET_FNX, HSETEX::TTL_PX, 100],
|
||||
1,
|
||||
0.2,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function unexpectedValuesProvider(): array
|
||||
{
|
||||
return [
|
||||
'with wrong set modifier' => [
|
||||
['key', ['field1', 'field2'], 'wrong'],
|
||||
'Modifier argument accepts only: fnx, fxx values',
|
||||
],
|
||||
'with wrong ttl modifier' => [
|
||||
['key', ['field1', 'field2'], '', 'wrong'],
|
||||
'Modifier argument accepts only: ex, px, exat, pxat, keepttl values',
|
||||
],
|
||||
'with wrong ttl modifier value' => [
|
||||
['key', ['field1', 'field2'], '', HSETEX::TTL_PXAT, 'wrong'],
|
||||
'Modifier value is missing or incorrect type',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -319,6 +319,8 @@ BUFFER;
|
||||
*/
|
||||
public function testExposeSearchInformation(): void
|
||||
{
|
||||
$this->markTestSkipped('Skipped due to a bug in 8.0-M05. Should be removed in the next version.');
|
||||
|
||||
$redis = $this->getClient();
|
||||
|
||||
$this->assertArrayHasKey('search', $redis->info('modules')['Modules']);
|
||||
|
||||
@@ -69,7 +69,7 @@ class TSINFO_Test extends PredisCommandTestCase
|
||||
public function testReturnsInformationAboutGivenTimeSeries(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
$expectedResponse = ['totalSamples', 0, 'memoryUsage', 4239, 'firstTimestamp', 0, 'lastTimestamp', 0,
|
||||
$expectedResponse = ['totalSamples', 0, 'memoryUsage', 5000, 'firstTimestamp', 0, 'lastTimestamp', 0,
|
||||
'retentionTime', 60000, 'chunkCount', 1, 'chunkSize', 4096, 'chunkType', 'compressed', 'duplicatePolicy',
|
||||
'max', 'labels', [['sensor_id', '2'], ['area_id', '32']], 'sourceKey', null, 'rules', [],
|
||||
'ignoreMaxTimeDiff', 0, 'ignoreMaxValDiff', 0];
|
||||
@@ -84,7 +84,7 @@ class TSINFO_Test extends PredisCommandTestCase
|
||||
$redis->tscreate('temperature:2:32', $arguments)
|
||||
);
|
||||
|
||||
$this->assertEquals($expectedResponse, $redis->tsinfo('temperature:2:32'));
|
||||
$this->assertEqualsWithDelta($expectedResponse, $redis->tsinfo('temperature:2:32'), 1000);
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
|
||||
@@ -319,12 +319,14 @@ class RedisClusterTest extends PredisTestCase
|
||||
->withConsecutive(
|
||||
[
|
||||
[
|
||||
'scheme' => 'tcp',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '6383',
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
'scheme' => 'tcp',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '6384',
|
||||
],
|
||||
@@ -644,6 +646,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with([
|
||||
'scheme' => 'tcp',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '9381',
|
||||
])
|
||||
@@ -710,6 +713,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with([
|
||||
'scheme' => 'tcp',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '9381',
|
||||
])
|
||||
@@ -1020,6 +1024,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with([
|
||||
'scheme' => 'tcp',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '6381',
|
||||
])
|
||||
@@ -1108,6 +1113,58 @@ class RedisClusterTest extends PredisTestCase
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with([
|
||||
'scheme' => 'tcp',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '6381',
|
||||
])
|
||||
->willReturn($connection3);
|
||||
|
||||
$cluster = new RedisCluster($factory);
|
||||
$cluster->useClusterSlots(false);
|
||||
|
||||
$cluster->add($connection1);
|
||||
$cluster->add($connection2);
|
||||
|
||||
$this->assertSame('foobar', $cluster->executeCommand($command));
|
||||
$this->assertSame('foobar', $cluster->executeCommand($command));
|
||||
$this->assertCount(3, $cluster);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testNotTCPMovedResponseWithConnectionNotInPool(): void
|
||||
{
|
||||
$movedResponse = new Response\Error('MOVED 1970 127.0.0.1:6381');
|
||||
|
||||
$command = $this->getCommandFactory()->create('get', ['node:1001']);
|
||||
|
||||
$connection1 = $this->getMockConnection('tls://127.0.0.1:6379');
|
||||
$connection1
|
||||
->expects($this->once())
|
||||
->method('executeCommand')
|
||||
->with($command)
|
||||
->willReturn($movedResponse);
|
||||
|
||||
$connection2 = $this->getMockConnection('tls://127.0.0.1:6380');
|
||||
$connection2
|
||||
->expects($this->never())
|
||||
->method('executeCommand');
|
||||
|
||||
$connection3 = $this->getMockConnection('tls://127.0.0.1:6381');
|
||||
$connection3
|
||||
->expects($this->exactly(2))
|
||||
->method('executeCommand')
|
||||
->with($command)
|
||||
->willReturnOnConsecutiveCalls('foobar', 'foobar');
|
||||
|
||||
/** @var Connection\FactoryInterface|MockObject */
|
||||
$factory = $this->getMockBuilder('Predis\Connection\FactoryInterface')->getMock();
|
||||
$factory
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with([
|
||||
'scheme' => 'tls',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '6381',
|
||||
])
|
||||
@@ -1153,6 +1210,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with([
|
||||
'scheme' => 'tcp',
|
||||
'host' => '2001:db8:0:f101::2',
|
||||
'port' => '6379',
|
||||
])
|
||||
@@ -1250,6 +1308,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with([
|
||||
'scheme' => 'tcp',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '6380',
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user