mirror of
https://github.com/predis/predis.git
synced 2026-09-20 15:26:46 +00:00
Added support for INCREX command (#1674)
* Added support for INCREX command * Fixed RESP2 and RESP3 incompatibility * Removed non-printable characters * Codestyle fixes
This commit is contained in:
committed by
GitHub
parent
268449ae69
commit
f12e6694b5
@@ -42,7 +42,7 @@ jobs:
|
||||
run: |
|
||||
# Mapping of original redis versions to client test containers
|
||||
declare -A redis_clients_version_mapping=(
|
||||
["8.8"]="unstable-24805570909-debian"
|
||||
["8.8"]="unstable-25700596106-debian"
|
||||
["8.6"]="8.6.1"
|
||||
["8.4"]="8.4.0"
|
||||
["8.2"]="8.2.2"
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
- Added XNACK support (#1666)
|
||||
- Added support for multiple aggregators for TS range commands (#1670)
|
||||
- Added testing for subkey notification channels (#1671)
|
||||
- Added support for INCREX command (#1674)
|
||||
|
||||
### Changed
|
||||
- Include command name in unsupported container command error messages (#1653)
|
||||
|
||||
@@ -153,6 +153,7 @@ use Predis\Command\Redis\VADD;
|
||||
* @method $this incr($key)
|
||||
* @method $this incrby($key, $increment)
|
||||
* @method $this incrbyfloat($key, $increment)
|
||||
* @method $this increx(string $key, int|float|string $value, ?int $lbound = null, ?int $ubound = null, ?string $overflow = null, ?string $expireType = null, $expireValue = null, bool $enx = false)
|
||||
* @method $this mget(array $keys)
|
||||
* @method $this mset(array $dictionary)
|
||||
* @method $this msetex(array $dictionary, ?string $existModifier = null, ?string $expireResolution = null, ?int $expireTTL = null)
|
||||
|
||||
@@ -163,6 +163,7 @@ use Predis\Response\Status;
|
||||
* @method int incr(string $key)
|
||||
* @method int incrby(string $key, int $increment)
|
||||
* @method string incrbyfloat(string $key, int|float $increment)
|
||||
* @method array increx(string $key, int|float|string $value, ?int $lbound = null, ?int $ubound = null, ?string $overflow = null, ?string $expireType = null, $expireValue = null, bool $enx = false)
|
||||
* @method array mget(string[]|string $keyOrKeys, string ...$keys = null)
|
||||
* @method mixed mset(array $dictionary)
|
||||
* @method array msetex(array $dictionary, ?string $existModifier = null, ?string $expireResolution = null, ?int $expireTTL = null)
|
||||
|
||||
@@ -68,6 +68,7 @@ abstract class ClusterStrategy implements StrategyInterface
|
||||
'INCR' => $getKeyFromFirstArgument,
|
||||
'INCRBY' => $getKeyFromFirstArgument,
|
||||
'INCRBYFLOAT' => $getKeyFromFirstArgument,
|
||||
'INCREX' => $getKeyFromFirstArgument,
|
||||
'SETBIT' => $getKeyFromFirstArgument,
|
||||
'SETEX' => $getKeyFromFirstArgument,
|
||||
'MSET' => [$this, 'getKeyFromInterleavedArguments'],
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
<?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;
|
||||
|
||||
use Predis\Command\PrefixableCommand as RedisCommand;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* @see http://redis.io/commands/increx
|
||||
*/
|
||||
class INCREX extends RedisCommand
|
||||
{
|
||||
public const BY_INT = 'BYINT';
|
||||
public const BY_FLOAT = 'BYFLOAT';
|
||||
|
||||
public const OVERFLOW_FAIL = 'FAIL';
|
||||
public const OVERFLOW_SAT = 'SAT';
|
||||
public const OVERFLOW_REJECT = 'REJECT';
|
||||
|
||||
public const EXPIRE_EX = 'EX';
|
||||
public const EXPIRE_PX = 'PX';
|
||||
public const EXPIRE_EXAT = 'EXAT';
|
||||
public const EXPIRE_PXAT = 'PXAT';
|
||||
public const EXPIRE_PERSIST = 'PERSIST';
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private static $byEnum = [self::BY_INT, self::BY_FLOAT];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private static $overflowEnum = [
|
||||
self::OVERFLOW_FAIL,
|
||||
self::OVERFLOW_SAT,
|
||||
self::OVERFLOW_REJECT,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private static $expireEnum = [
|
||||
self::EXPIRE_EX,
|
||||
self::EXPIRE_PX,
|
||||
self::EXPIRE_EXAT,
|
||||
self::EXPIRE_PXAT,
|
||||
self::EXPIRE_PERSIST,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'INCREX';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Arguments: [key, value, ?lbound, ?ubound, ?overflow, ?expireType, ?expireValue, ?enx]
|
||||
*/
|
||||
public function setArguments(array $arguments)
|
||||
{
|
||||
$processed = [$arguments[0]];
|
||||
|
||||
if (array_key_exists(1, $arguments)) {
|
||||
$byType = $this->resolveByType($arguments[1]);
|
||||
$processed[] = $byType;
|
||||
$processed[] = $arguments[1];
|
||||
}
|
||||
|
||||
if (isset($arguments[2]) && $arguments[2] !== null) {
|
||||
$processed[] = 'LBOUND';
|
||||
$processed[] = $arguments[2];
|
||||
}
|
||||
|
||||
if (isset($arguments[3]) && $arguments[3] !== null) {
|
||||
$processed[] = 'UBOUND';
|
||||
$processed[] = $arguments[3];
|
||||
}
|
||||
|
||||
$overflow = $arguments[4] ?? null;
|
||||
if ($overflow !== null && $overflow !== '') {
|
||||
$overflow = strtoupper($overflow);
|
||||
|
||||
if (!in_array($overflow, self::$overflowEnum, true)) {
|
||||
$allowed = implode(', ', self::$overflowEnum);
|
||||
throw new UnexpectedValueException("Overflow policy accepts only: {$allowed} values");
|
||||
}
|
||||
|
||||
$processed[] = 'OVERFLOW';
|
||||
$processed[] = $overflow;
|
||||
}
|
||||
|
||||
$expireType = $arguments[5] ?? null;
|
||||
if ($expireType !== null && $expireType !== '') {
|
||||
$expireType = strtoupper($expireType);
|
||||
|
||||
if (!in_array($expireType, self::$expireEnum, true)) {
|
||||
$allowed = implode(', ', self::$expireEnum);
|
||||
throw new UnexpectedValueException("Expire modifier accepts only: {$allowed} values");
|
||||
}
|
||||
|
||||
if ($expireType === self::EXPIRE_PERSIST) {
|
||||
$processed[] = self::EXPIRE_PERSIST;
|
||||
} else {
|
||||
if (!array_key_exists(6, $arguments) || $arguments[6] === null) {
|
||||
throw new UnexpectedValueException("{$expireType} requires a value");
|
||||
}
|
||||
|
||||
$processed[] = $expireType;
|
||||
$processed[] = $arguments[6];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($arguments[7])) {
|
||||
$processed[] = 'ENX';
|
||||
}
|
||||
|
||||
parent::setArguments($processed);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Normalizes the response to native numeric types so RESP2 and RESP3 are
|
||||
* consistent: RESP2 returns BYFLOAT results as bulk strings while RESP3
|
||||
* returns native doubles. After parsing, callers always see int|float.
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
if (!is_array($data)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
return array_map(static function ($v) {
|
||||
if (!is_string($v) || !is_numeric($v)) {
|
||||
return $v;
|
||||
}
|
||||
|
||||
return strpbrk($v, '.eE') !== false ? (float) $v : (int) $v;
|
||||
}, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|float|string $value
|
||||
* @return string Either BYINT or BYFLOAT
|
||||
*/
|
||||
private function resolveByType($value): string
|
||||
{
|
||||
if (is_int($value)) {
|
||||
return self::BY_INT;
|
||||
}
|
||||
|
||||
if (is_float($value)) {
|
||||
return self::BY_FLOAT;
|
||||
}
|
||||
|
||||
if (!is_string($value) || !is_numeric($value)) {
|
||||
throw new UnexpectedValueException(
|
||||
'Increment value must be an int, float, or numeric string'
|
||||
);
|
||||
}
|
||||
|
||||
// Numeric string: pick BYFLOAT when it carries a decimal point or
|
||||
// exponent, otherwise treat it as an integer.
|
||||
if (strpbrk($value, '.eE') !== false) {
|
||||
return self::BY_FLOAT;
|
||||
}
|
||||
|
||||
return self::BY_INT;
|
||||
}
|
||||
|
||||
public function prefixKeys($prefix)
|
||||
{
|
||||
$this->applyPrefixForFirstArgument($prefix);
|
||||
}
|
||||
}
|
||||
@@ -387,6 +387,7 @@ class PredisStrategyTest extends PredisTestCase
|
||||
'INCR' => 'keys-first',
|
||||
'INCRBY' => 'keys-first',
|
||||
'INCRBYFLOAT' => 'keys-first',
|
||||
'INCREX' => 'keys-first',
|
||||
'SETBIT' => 'keys-first',
|
||||
'SETEX' => 'keys-first',
|
||||
'MSET' => 'keys-interleaved',
|
||||
|
||||
@@ -410,6 +410,7 @@ class RedisStrategyTest extends PredisTestCase
|
||||
'INCR' => 'keys-first',
|
||||
'INCRBY' => 'keys-first',
|
||||
'INCRBYFLOAT' => 'keys-first',
|
||||
'INCREX' => 'keys-first',
|
||||
'SETBIT' => 'keys-first',
|
||||
'SETEX' => 'keys-first',
|
||||
'MSET' => 'keys-interleaved',
|
||||
|
||||
@@ -0,0 +1,461 @@
|
||||
<?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;
|
||||
|
||||
use Predis\Command\PrefixableCommand;
|
||||
use stdClass;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* @group commands
|
||||
* @group realm-string
|
||||
*/
|
||||
class INCREX_Test extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand(): string
|
||||
{
|
||||
return INCREX::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId(): string
|
||||
{
|
||||
return 'INCREX';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
* @dataProvider argumentsProvider
|
||||
*/
|
||||
public function testFilterArguments(array $actual, array $expected): void
|
||||
{
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($actual);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
public function argumentsProvider(): array
|
||||
{
|
||||
return [
|
||||
'int value BYINT' => [
|
||||
['key', 5],
|
||||
['key', 'BYINT', 5],
|
||||
],
|
||||
'negative int BYINT' => [
|
||||
['key', -3],
|
||||
['key', 'BYINT', -3],
|
||||
],
|
||||
'float value BYFLOAT' => [
|
||||
['key', 1.5],
|
||||
['key', 'BYFLOAT', 1.5],
|
||||
],
|
||||
'numeric string without decimal BYINT' => [
|
||||
['key', '5'],
|
||||
['key', 'BYINT', '5'],
|
||||
],
|
||||
'numeric string with decimal BYFLOAT' => [
|
||||
['key', '1.5'],
|
||||
['key', 'BYFLOAT', '1.5'],
|
||||
],
|
||||
'numeric string with exponent BYFLOAT' => [
|
||||
['key', '1e3'],
|
||||
['key', 'BYFLOAT', '1e3'],
|
||||
],
|
||||
'with LBOUND and UBOUND' => [
|
||||
['key', 5, 0, 100],
|
||||
['key', 'BYINT', 5, 'LBOUND', 0, 'UBOUND', 100],
|
||||
],
|
||||
'with LBOUND only' => [
|
||||
['key', 1, 0],
|
||||
['key', 'BYINT', 1, 'LBOUND', 0],
|
||||
],
|
||||
'with UBOUND only' => [
|
||||
['key', 1, null, 100],
|
||||
['key', 'BYINT', 1, 'UBOUND', 100],
|
||||
],
|
||||
'with OVERFLOW SAT' => [
|
||||
['key', 5, null, 100, 'SAT'],
|
||||
['key', 'BYINT', 5, 'UBOUND', 100, 'OVERFLOW', 'SAT'],
|
||||
],
|
||||
'with EX expiration' => [
|
||||
['key', 1, null, null, null, 'EX', 60],
|
||||
['key', 'BYINT', 1, 'EX', 60],
|
||||
],
|
||||
'with PERSIST' => [
|
||||
['key', 1, null, null, null, 'PERSIST'],
|
||||
['key', 'BYINT', 1, 'PERSIST'],
|
||||
],
|
||||
'with ENX flag' => [
|
||||
['key', 1, null, null, null, 'EX', 60, true],
|
||||
['key', 'BYINT', 1, 'EX', 60, 'ENX'],
|
||||
],
|
||||
'all options with int' => [
|
||||
['key', 5, 0, 100, 'REJECT', 'PX', 5000, true],
|
||||
['key', 'BYINT', 5, 'LBOUND', 0, 'UBOUND', 100, 'OVERFLOW', 'REJECT', 'PX', 5000, 'ENX'],
|
||||
],
|
||||
'all options with float' => [
|
||||
['key', 1.5, 0, 100, 'REJECT', 'PX', 5000, true],
|
||||
['key', 'BYFLOAT', 1.5, 'LBOUND', 0, 'UBOUND', 100, 'OVERFLOW', 'REJECT', 'PX', 5000, 'ENX'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testThrowsExceptionOnNonNumericStringValue(): void
|
||||
{
|
||||
$this->expectException(UnexpectedValueException::class);
|
||||
$this->expectExceptionMessage('numeric string');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments(['key', 'not-a-number']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testThrowsExceptionOnNullValue(): void
|
||||
{
|
||||
$this->expectException(UnexpectedValueException::class);
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments(['key', null]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testThrowsExceptionOnInvalidValueType(): void
|
||||
{
|
||||
$this->expectException(UnexpectedValueException::class);
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments(['key', new stdClass()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testThrowsExceptionOnInvalidOverflow(): void
|
||||
{
|
||||
$this->expectException(UnexpectedValueException::class);
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments(['key', 1, null, null, 'INVALID']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testThrowsExceptionOnInvalidExpireType(): void
|
||||
{
|
||||
$this->expectException(UnexpectedValueException::class);
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments(['key', 1, null, null, null, 'INVALID', 60]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testThrowsExceptionWhenExpireMissingValue(): void
|
||||
{
|
||||
$this->expectException(UnexpectedValueException::class);
|
||||
$this->expectExceptionMessage('EX requires a value');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments(['key', 1, null, null, null, 'EX']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse(): void
|
||||
{
|
||||
// BYINT response - ints stay ints
|
||||
$this->assertSame([5, 1], $this->getCommand()->parseResponse([5, 1]));
|
||||
|
||||
// RESP2 BYFLOAT response - bulk strings converted to native floats
|
||||
$this->assertSame([5.5, 1.5], $this->getCommand()->parseResponse(['5.5', '1.5']));
|
||||
|
||||
// RESP3 BYFLOAT response - already native floats, untouched
|
||||
$this->assertSame([5.5, 1.5], $this->getCommand()->parseResponse([5.5, 1.5]));
|
||||
|
||||
// Scientific notation
|
||||
$this->assertSame([1000.0, 500.0], $this->getCommand()->parseResponse(['1e3', '5e2']));
|
||||
|
||||
// Non-array response - passed through (e.g. null)
|
||||
$this->assertNull($this->getCommand()->parseResponse(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testPrefixKeys(): void
|
||||
{
|
||||
/** @var PrefixableCommand $command */
|
||||
$command = $this->getCommand();
|
||||
$actualArguments = ['arg1', 5];
|
||||
$prefix = 'prefix:';
|
||||
$expectedArguments = ['prefix:arg1', 'BYINT', 5];
|
||||
|
||||
$command->setArguments($actualArguments);
|
||||
$command->prefixKeys($prefix);
|
||||
|
||||
$this->assertSame($expectedArguments, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 8.8.0
|
||||
*/
|
||||
public function testIncrementsByIntegerValue(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->set('cnt', 10);
|
||||
|
||||
$this->assertSame([15, 5], $redis->increx('cnt', 5));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 8.8.0
|
||||
*/
|
||||
public function testIncrementsByFloatValue(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->set('cnt', '10');
|
||||
|
||||
$this->assertSame([11.5, 1.5], $redis->increx('cnt', 1.5));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 8.8.0
|
||||
*/
|
||||
public function testIncrementsByNumericIntegerString(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->set('cnt', 10);
|
||||
|
||||
$this->assertSame([17, 7], $redis->increx('cnt', '7'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 8.8.0
|
||||
*/
|
||||
public function testIncrementsByNumericFloatString(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->set('cnt', '10');
|
||||
|
||||
$this->assertSame([12.5, 2.5], $redis->increx('cnt', '2.5'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 8.8.0
|
||||
*/
|
||||
public function testRespectsLowerAndUpperBounds(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->set('cnt', 5);
|
||||
|
||||
$this->assertSame([8, 3], $redis->increx('cnt', 3, 0, 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 8.8.0
|
||||
*/
|
||||
public function testOverflowSatSaturatesToBound(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->set('cnt', 10);
|
||||
|
||||
$this->assertSame([50, 40], $redis->increx('cnt', 1000, null, 50, 'SAT'));
|
||||
$this->assertSame('50', $redis->get('cnt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 8.8.0
|
||||
*/
|
||||
public function testOverflowRejectLeavesValueUnchanged(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->set('cnt', 10);
|
||||
|
||||
$redis->increx('cnt', 1000, null, 50, 'REJECT');
|
||||
$this->assertSame('10', $redis->get('cnt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 8.8.0
|
||||
*/
|
||||
public function testOverflowFailRaisesError(): void
|
||||
{
|
||||
$this->expectException('Predis\Response\ServerException');
|
||||
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->set('cnt', 10);
|
||||
$redis->increx('cnt', 1000, null, 50, 'FAIL');
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 8.8.0
|
||||
*/
|
||||
public function testExpirationWithPx(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->set('cnt', 10);
|
||||
|
||||
$redis->increx('cnt', 1, null, null, null, 'PX', 60000);
|
||||
$this->assertGreaterThan(0, $redis->pttl('cnt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 8.8.0
|
||||
*/
|
||||
public function testExpirationWithPxat(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->set('cnt', 10);
|
||||
|
||||
$future = (int) ((microtime(true) + 60) * 1000);
|
||||
|
||||
$redis->increx('cnt', 1, null, null, null, 'PXAT', $future);
|
||||
$this->assertGreaterThan(0, $redis->pttl('cnt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 8.8.0
|
||||
*/
|
||||
public function testPersistRemovesTtl(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->set('cnt', 10);
|
||||
$redis->expire('cnt', 60);
|
||||
$this->assertGreaterThan(0, $redis->ttl('cnt'));
|
||||
|
||||
$redis->increx('cnt', 1, null, null, null, 'PERSIST');
|
||||
$this->assertSame(-1, $redis->ttl('cnt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 8.8.0
|
||||
*/
|
||||
public function testEnxSetsExpirationOnlyWhenAbsent(): void
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->set('cnt', 10);
|
||||
|
||||
$redis->increx('cnt', 1, null, null, null, 'EX', 60, true);
|
||||
$firstTtl = $redis->ttl('cnt');
|
||||
$this->assertGreaterThan(0, $firstTtl);
|
||||
|
||||
$redis->increx('cnt', 1, null, null, null, 'EX', 9999, true);
|
||||
$secondTtl = $redis->ttl('cnt');
|
||||
$this->assertLessThanOrEqual($firstTtl, $secondTtl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 8.8.0
|
||||
*/
|
||||
public function testIncrementsByIntegerValueResp3(): void
|
||||
{
|
||||
$redis = $this->getResp3Client();
|
||||
|
||||
$redis->set('cnt', 10);
|
||||
|
||||
$this->assertSame([15, 5], $redis->increx('cnt', 5));
|
||||
}
|
||||
|
||||
/**
|
||||
* RESP2 returns BYFLOAT results as bulk strings while RESP3 returns native
|
||||
* doubles. After parseResponse, callers should see the same native numeric
|
||||
* types regardless of protocol.
|
||||
*
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 8.8.0
|
||||
*/
|
||||
public function testResponseTypesAreConsistentAcrossResp2AndResp3(): void
|
||||
{
|
||||
$resp2 = $this->getClient();
|
||||
$resp2->set('cnt', '10');
|
||||
$resp2Float = $resp2->increx('cnt', 1.5);
|
||||
$resp2->set('cnt', 10);
|
||||
$resp2Int = $resp2->increx('cnt', 5);
|
||||
|
||||
$resp3 = $this->getResp3Client(false);
|
||||
$resp3->set('cnt', '10');
|
||||
$resp3Float = $resp3->increx('cnt', 1.5);
|
||||
$resp3->set('cnt', 10);
|
||||
$resp3Int = $resp3->increx('cnt', 5);
|
||||
|
||||
// BYFLOAT: both protocols yield native floats after normalization
|
||||
$this->assertSame($resp2Float, $resp3Float);
|
||||
$this->assertIsFloat($resp2Float[0]);
|
||||
$this->assertIsFloat($resp2Float[1]);
|
||||
$this->assertIsFloat($resp3Float[0]);
|
||||
$this->assertIsFloat($resp3Float[1]);
|
||||
|
||||
// BYINT: both protocols yield native ints
|
||||
$this->assertSame($resp2Int, $resp3Int);
|
||||
$this->assertIsInt($resp2Int[0]);
|
||||
$this->assertIsInt($resp2Int[1]);
|
||||
$this->assertIsInt($resp3Int[0]);
|
||||
$this->assertIsInt($resp3Int[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 8.8.0
|
||||
*/
|
||||
public function testThrowsExceptionOnWrongType(): void
|
||||
{
|
||||
$this->expectException('Predis\Response\ServerException');
|
||||
$this->expectExceptionMessage('Operation against a key holding the wrong kind of value');
|
||||
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->lpush('foo', ['bar']);
|
||||
$redis->increx('foo', 1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user