Compare commits

...

14 Commits

Author SHA1 Message Date
Till Krüss 07105e0506 tag v2.4.1 2025-11-12 10:00:11 -08:00
Vladyslav Vildanov 07dc6ba6d2 fix(conn): Removed automatic conn_uid assignment (#1551)
* fix(conn): Removed automatic conn_uid assignment

* Restrict redis version

* Updated CHANGELOG.md

* Added test case

* Update CHANGELOG.md

---------

Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
2025-06-05 09:54:27 +03:00
Till Krüss 5142011581 Update CHANGELOG.md 2025-05-27 09:55:55 -07:00
Tim Düsterhus c4b9c34d86 Fix return type for ZCOUNT (#1546)
* Fix return type for ZCOUNT

ZCOUNT returns an `int`:

> Integer reply: the number of members in the specified score range.

* Update CHANGELOG.md

---------

Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
2025-05-27 10:39:05 +03:00
Till Krüss f49e13ee3a tag stable 2.4.0 2025-04-30 08:16:02 -07:00
Vladyslav Vildanov 6e3e2c0e78 [2.x] 8.0-RC2 testing (#1532) 2025-04-29 08:25:13 -07:00
Vladyslav Vildanov c35c422eda [2.x] Fix the problem of protocol loss during cluster MOVED/ASK (#1530)
Co-authored-by: Lynn Club <lynncho@live.com>
Co-authored-by: Lynn Club <10293848+lynnclub@users.noreply.github.com>
Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
2025-04-17 08:49:19 -07:00
Till Krüss 1b5ed7d516 ZPOPMIN & ZPOPMAX prefix tests 2025-04-15 13:25:42 -07:00
vladvildanov 5df852f227 Added tests cases 2025-04-15 09:16:08 -07:00
Till Krüss 9418df1924 fix changelog branch check 2025-04-15 09:15:52 -07:00
Till Krüss 0e4b3829a7 fix changelog check 2025-04-15 09:07:26 -07:00
Till Krüss 65f6127fab add changelog check 2025-04-15 08:54:08 -07:00
Till Krüss 7ff24b19ae Merge pull request #1528 from predis/vv-2.x-readme-8.0-support
(2.x) Update README.md (Redis 8.0 support)
2025-04-14 09:11:07 -07:00
vladvildanov 2babfc91d7 Update README.md (Redis 8.0 support) 2025-04-14 18:42:11 +03:00
14 changed files with 199 additions and 16 deletions
+29
View File
@@ -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;
+1 -1
View File
@@ -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-M05-pre"
["8.0"]="8.0-RC2-pre"
["7.4"]="7.4.2"
["7.2"]="7.2.7"
["6.2"]="6.2.17"
+7 -1
View File
@@ -1,6 +1,11 @@
## Changelog
## v2.4.0-RC1 (2024-11-21)
## v2.4.1 (2025-11-12)
### Fixed
- Fixed return type for `ZCOUNT` to be `int` (#1546)
- Removed automatic `conn_uid` parameter assignment (#1551)
## v2.4.0 (2025-04-30)
### Added
- Added new hash-field expiration commands (#1520)
- Added missing `FT._LIST` and `BITFIELD_RO` commands (#1521)
@@ -14,6 +19,7 @@
- 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)
+45 -1
View File
@@ -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).
@@ -138,6 +138,50 @@ it is still desired to have control of when the connection is opened or closed:
achieved by invoking `$client->connect()` and `$client->disconnect()`. Please note that the effect
of these methods on aggregate connections may differ depending on each specific implementation.
#### Persistent connections ####
To increase a performance of your application you may set up a client to use persistent TCP connection, this way
client saves a time on socket creation and connection handshake. By default, connection is created on first-command
execution and will be automatically closed by GC before the process is being killed.
However, if your application is backed by PHP-FPM the processes are idle, and you may set up it to be persistent and
reusable across multiple script execution within the same process.
To enable the persistent connection mode you should provide following configuration:
```php
// Standalone
$client = new Predis\Client(['persistent' => true]);
// Cluster
$client = new Predis\Client(
['tcp://host:port', 'tcp://host:port', 'tcp://host:port'],
['cluster' => 'redis', 'parameters' => ['persistent' => true]]
);
```
**Important**
If you operate on multiple clients within the same application, and they communicate with the same resource, by default
they will share the same socket (that's the default behaviour of persistent sockets). So in this case you would need
to additionally provide a `conn_uid` identifier for each client, this way each client will create its own socket so
the connection context won't be shared across clients. This socket behaviour explained
[here](https://www.php.net/manual/en/function.stream-socket-client.php#105393)
```php
// Standalone
$client1 = new Predis\Client(['persistent' => true, 'conn_uid' => 'id_1']);
$client2 = new Predis\Client(['persistent' => true, 'conn_uid' => 'id_2']);
// Cluster
$client1 = new Predis\Client(
['tcp://host:port', 'tcp://host:port', 'tcp://host:port'],
['cluster' => 'redis', 'parameters' => ['persistent' => true, 'conn_uid' => 'id_1']]
);
$client2 = new Predis\Client(
['tcp://host:port', 'tcp://host:port', 'tcp://host:port'],
['cluster' => 'redis', 'parameters' => ['persistent' => true, 'conn_uid' => 'id_2']]
);
```
### Client configuration ###
+1 -1
View File
@@ -1 +1 @@
2.4.0-RC1
2.4.0
+1 -1
View File
@@ -53,7 +53,7 @@ use Traversable;
*/
class Client implements ClientInterface, IteratorAggregate
{
public const VERSION = '2.4.0-RC1';
public const VERSION = '2.4.1';
/** @var OptionsInterface */
private $options;
+1 -1
View File
@@ -300,7 +300,7 @@ use Predis\Response\Status;
* @method array|null xread(int $count = null, int $block = null, array $streams = null, string ...$id)
* @method int zadd(string $key, array $membersAndScoresDictionary)
* @method int zcard(string $key)
* @method string zcount(string $key, int|string $min, int|string $max)
* @method int zcount(string $key, int|string $min, int|string $max)
* @method array zdiff(array $keys, bool $withScores = false)
* @method int zdiffstore(string $destination, array $keys)
* @method string zincrby(string $key, int $increment, string $member)
+12 -3
View File
@@ -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);
}
/**
-1
View File
@@ -41,7 +41,6 @@ class StreamConnection extends AbstractConnection
public function __construct(ParametersInterface $parameters)
{
parent::__construct($parameters);
$this->parameters->conn_uid = spl_object_hash($this);
}
/**
+22 -4
View File
@@ -1285,17 +1285,35 @@ class ClientTest extends PredisTestCase
/**
* @group connected
* @requiresRedisVersion >= 5.0.0
*/
public function testClientsCreateDifferentPersistentConnections(): void
{
$client1 = new Client($this->getParameters(['database' => 14, 'persistent' => true]));
$client2 = new Client($this->getParameters(['database' => 15, 'persistent' => true]));
$client1 = new Client($this->getParameters(['database' => 14, 'persistent' => true, 'conn_uid' => 1]));
$client2 = new Client($this->getParameters(['database' => 15, 'persistent' => true, 'conn_uid' => 2]));
$client1->set('foo', 'bar');
$client2->set('foo', 'baz');
$this->assertSame('bar', $client1->get('foo'));
$this->assertSame('baz', $client2->get('foo'));
$this->assertNotSame($client1->client('ID'), $client2->client('ID'));
}
/**
* @group connected
* @requiresRedisVersion >= 5.0.0
*/
public function testClientsCreateSamePersistentConnections(): void
{
$client1 = new Client($this->getParameters(['persistent' => true]));
$client2 = new Client($this->getParameters(['persistent' => true]));
$client1->set('foo', 'bar');
$client2->set('foo', 'baz');
$this->assertSame('baz', $client2->get('foo'));
$this->assertSame($client1->client('ID'), $client2->client('ID'));
}
/**
@@ -1307,11 +1325,11 @@ class ClientTest extends PredisTestCase
{
$client1 = new Client(
$this->getDefaultParametersArray(),
['cluster' => 'redis', 'parameters' => ['persistent' => true]]
['cluster' => 'redis', 'parameters' => ['persistent' => true, 'conn_uid' => 1]]
);
$client2 = new Client(
$this->getDefaultParametersArray(),
['cluster' => 'redis', 'parameters' => ['persistent' => true]]
['cluster' => 'redis', 'parameters' => ['persistent' => true, 'conn_uid' => 2]]
);
$client1->set('{shard1}foo', 'bar');
@@ -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'],
@@ -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',
])
@@ -403,6 +403,17 @@ class ParametersTest extends PredisTestCase
$this->assertSame($expected, Parameters::parse($uri));
}
/**
* @group disconnected
*/
public function testSetParameters(): void
{
$parameters = new Parameters();
$parameters->property = 'value';
$this->assertEquals('value', $parameters->property);
}
// ******************************************************************** //
// ---- HELPER METHODS ------------------------------------------------ //
// ******************************************************************** //