mirror of
https://github.com/predis/predis.git
synced 2026-08-21 05:11:55 +00:00
Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4123fcd85d | |||
| 647cd4c502 | |||
| cab266f359 | |||
| cea6354b55 | |||
| 7155dfdfb4 | |||
| dea768044c | |||
| 70d1c116b6 | |||
| 001fb45a32 | |||
| 8dd7f40d21 | |||
| d5c650d51a | |||
| 7bb85cae1c | |||
| 43a6ae181e | |||
| f538442ad9 | |||
| c2d0737e98 | |||
| d6bd47ca47 | |||
| 0235ba1a30 | |||
| 287b4c76ce | |||
| ba6cfa7887 | |||
| adb4d82ea8 | |||
| 8dac9c097e | |||
| 0df05ae008 | |||
| c85137924c | |||
| b1761c55c5 | |||
| 5b8c2e21f6 | |||
| f6e97d4b94 | |||
| 18f5cea8e6 | |||
| 727072c086 | |||
| 5fde716098 | |||
| 239864d9a0 | |||
| b006d2306c | |||
| 34e394cc10 | |||
| 79539c3bcf | |||
| 74817b5596 | |||
| f967ecd66b | |||
| 261d0b824f | |||
| b77da8476c | |||
| 3fbd29d00b | |||
| 8373663486 | |||
| 18853d1dbb | |||
| 1da827c0ae | |||
| aa8fd5cea4 | |||
| 8d353cc223 | |||
| 0a580c9d1b | |||
| 58d1cc12dc | |||
| a8862f0fdd | |||
| e148dc831a | |||
| c4b592298c | |||
| 0e3b105959 | |||
| 749bbfe0e3 | |||
| c2ae1c612e | |||
| a85f89e2c7 | |||
| dbb802f0f1 | |||
| 64b79511bd | |||
| 5a9e747b01 | |||
| cb0fbd01b4 | |||
| 83df2ef11c | |||
| c94e5d3a10 | |||
| c90fbfc0ff | |||
| 21d6f0e7c6 | |||
| f4a950b275 | |||
| 5ec51c899f | |||
| e1d700dd0c | |||
| 809f76a7b3 | |||
| ab6cbe7e10 | |||
| 80bcb9f475 | |||
| c8e671a42a | |||
| 838e6af403 | |||
| ba2ffb612a | |||
| fdaf73987b | |||
| 50f8922df2 | |||
| 60733eb5c9 | |||
| a13dd4e180 | |||
| 1678e486b1 |
+11
-1
@@ -3,6 +3,8 @@ php:
|
||||
- 5.3
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- hhvm
|
||||
branches:
|
||||
except:
|
||||
- v0.5
|
||||
@@ -10,4 +12,12 @@ branches:
|
||||
- php5.2_backport
|
||||
- documentation
|
||||
services: redis-server
|
||||
script: phpunit -c phpunit.xml.travisci
|
||||
before_script:
|
||||
- composer self-update
|
||||
- composer install --no-interaction --prefer-source --dev
|
||||
script:
|
||||
- vendor/bin/phpunit -c phpunit.xml.travisci
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
fast_finish: true
|
||||
|
||||
@@ -1,3 +1,68 @@
|
||||
v0.8.7 (2014-08-01)
|
||||
================================================================================
|
||||
|
||||
- Added `3.0` in the server profiles aliases list for Redis 3.0. `2.8` is still
|
||||
the default server profile and `dev` still targets Redis 3.0.
|
||||
|
||||
- Added `COMMAND` to the server profile for Redis 2.8.
|
||||
|
||||
- Switched internally to the `CLUSTER SLOTS` command instead of `CLUSTER NODES`
|
||||
to fetch the updated slots map from redis-cluster. This change requires users
|
||||
to upgrade Redis nodes to >= 3.0.0b7.
|
||||
|
||||
- The updated slots map is now fetched automatically from redis-cluster upon the
|
||||
first `-MOVED` response by default. This change makes it possible to feed the
|
||||
client constructor with only a few nodes of the actual cluster composition,
|
||||
without needing a more complex configuration.
|
||||
|
||||
- Implemented support for `PING` in PUB/SUB loop for Redis >= 3.0.0b8.
|
||||
|
||||
- The default client-side sharding strategy and the one for redis-cluster now
|
||||
share the same implementations as they follow the same rules. One difference,
|
||||
aside from the different hashing function used to calculate distribution, is
|
||||
in how empty hash tags like {} are treated by redis-cluster.
|
||||
|
||||
- __FIX__: the patch applied to fix #180 introduced a regression affecting read/
|
||||
write timeouts in `Predis\Connection\PhpiredisStreamConnection`. Unfortunately
|
||||
the only possible solution requires PHP 5.4+. On PHP 5.3, read/write timeouts
|
||||
will be ignored from now on.
|
||||
|
||||
|
||||
v0.8.6 (2014-07-15)
|
||||
================================================================================
|
||||
|
||||
- Redis 2.8 is now the default server profile as there are no changes that would
|
||||
break compatibility with previous releases.
|
||||
|
||||
- Added `PFADD`, `PFCOUNT`, `PFMERGE` to the server profile for Redis 2.8 for
|
||||
handling the HyperLogLog data structure introduced in Redis 2.8.9.
|
||||
|
||||
- Added `ZLEXCOUNT`, `ZRANGEBYLEX`, `ZREMRANGEBYLEX` to the server profile for
|
||||
Redis 2.8 for handling lexicographic operations on members of sorted sets.
|
||||
|
||||
- Added support for key hash tags when using redis-cluster (Redis 3.0.0b1).
|
||||
|
||||
- __FIX__: minor tweaks to make Predis compatible with HHVM >= 2.4.0.
|
||||
|
||||
- __FIX__: responses to `INFO` are now properly parsed and will not break when
|
||||
redis sentinel is being used (ISSUE #154).
|
||||
|
||||
- __FIX__: added missing support for `INCRBYFLOAT` in cluster and replication
|
||||
configurations (ISSUE #159).
|
||||
|
||||
- __FIX__: fix parsing of the output of `CLUSTER NODES` to fetch the slots map
|
||||
from a node when redis-cluster has slaves in its configuration (ISSUE #165).
|
||||
|
||||
- __FIX__: prevent a stack overflow when iterating over large Redis collections
|
||||
using our abstraction for cursor-based iterators (ISSUE #182).
|
||||
|
||||
- __FIX__: properly discards transactions when the server immediately returns an
|
||||
error response (e.g. -OOM or -ERR on invalid arguments for a command) instead
|
||||
of a +QUEUED response (ISSUE #187).
|
||||
|
||||
- Upgraded to PHPUnit 4.* for the test suite.
|
||||
|
||||
|
||||
v0.8.5 (2014-01-16)
|
||||
================================================================================
|
||||
|
||||
|
||||
@@ -10,18 +10,22 @@ By default Predis does not require any additional C extension, but it can be opt
|
||||
Redis protocol. An asynchronous implementation of the client, albeit experimental, is also available
|
||||
through [Predis\Async](https://github.com/nrk/predis-async).
|
||||
|
||||
Predis can be used with [HHVM](http://www.hhvm.com) >= 2.3.0, but there are no guarantees you will
|
||||
not run into unexpected issues (especially when the JIT compiler is enabled via `Eval.Jit = true`)
|
||||
due to HHVM being still under heavy development, thus unstable and not yet 100% compatible with PHP.
|
||||
|
||||
More details about the project can be found in our [frequently asked questions](FAQ.md) section or
|
||||
on the online [wiki](https://github.com/nrk/predis/wiki).
|
||||
|
||||
|
||||
## Main features ##
|
||||
|
||||
- Wide range of Redis versions supported (from __1.2__ to __2.8__ and unstable) using profiles.
|
||||
- Clustering via client-side sharding using consistent hashing or custom distributors.
|
||||
- Smart support for [redis-cluster](http://redis.io/topics/cluster-spec) (Redis >= 3.0).
|
||||
- Wide range of Redis versions supported (from __1.2__ to __3.0__ and __unstable__) using profiles.
|
||||
- Cluster of nodes via client-side sharding using consistent hashing or custom distributors.
|
||||
- Smart support for [redis-cluster](http://redis.io/topics/cluster-tutorial) (Redis >= 3.0).
|
||||
- Support for master-slave replication configurations (write on master, read from slaves).
|
||||
- Transparent key prefixing for all Redis commands.
|
||||
- Command pipelining (works on both single and aggregate connections).
|
||||
- Transparent key prefixing for all known Redis commands.
|
||||
- Command pipelining (works on both single nodes and aggregate connections).
|
||||
- Abstraction for Redis transactions (Redis >= 2.0) supporting CAS operations (Redis >= 2.2).
|
||||
- Abstraction for Lua scripting (Redis >= 2.6) with automatic switching between `EVALSHA` or `EVAL`.
|
||||
- Abstraction for `SCAN`, `SSCAN`, `ZSCAN` and `HSCAN` (Redis >= 2.8) based on PHP iterators.
|
||||
@@ -45,8 +49,8 @@ Ultimately, archives of each release are [available on GitHub](https://github.co
|
||||
Predis relies on the autoloading features of PHP to load its files when needed and complies with the
|
||||
[PSR-0 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) which makes
|
||||
it compatible with most PHP frameworks. Autoloading is handled automatically when dependencies are
|
||||
managed using Composer, but you can also leverage its own autoloader if you are going to use it in a
|
||||
project or script without any PSR-0 compliant autoloading facility:
|
||||
managed through Composer, but it is also possible to leverage its own autoloader in projects or
|
||||
scripts not having any autoload facility:
|
||||
|
||||
```php
|
||||
// Prepend a base path if Predis is not available in your "include_path".
|
||||
@@ -58,14 +62,14 @@ Predis\Autoloader::register();
|
||||
It is possible to easily create a [phar](http://www.php.net/manual/en/intro.phar.php) archive from
|
||||
the repository just by launching `bin/create-phar`. The generated phar contains a stub defining an
|
||||
autoloader function for Predis, so you just need to require the phar to start using the library.
|
||||
Alternatively, it is also possible to generate one single PHP file that holds every class like older
|
||||
versions of Predis by launching `bin/create-single-file`, but this practice __is not__ encouraged.
|
||||
Alternatively it is possible to generate one single PHP file holding every class like older versions
|
||||
of Predis by launching `bin/create-single-file`, but this practice __is not__ encouraged.
|
||||
|
||||
|
||||
### Connecting to Redis ###
|
||||
|
||||
When not specifying any connection parameter to create a new client, Predis assumes `127.0.0.1` and
|
||||
`6379` as the default host and port and uses a connection timeout of 5 seconds:
|
||||
`6379` as the default host and port with a connection timeout of 5 seconds:
|
||||
|
||||
```php
|
||||
$client = new Predis\Client();
|
||||
@@ -89,9 +93,9 @@ $client = new Predis\Client([
|
||||
$client = new Predis\Client('tcp://10.0.0.1:6379');
|
||||
```
|
||||
|
||||
When an array of connections parameters is provided, Predis automatically works in clustering mode
|
||||
using client-side sharding. Both named arrays and URI strings can be mixed for providing each node
|
||||
configuration:
|
||||
When an array of connection parameters is provided, Predis automatically works in cluster mode using
|
||||
client-side sharding. Both named arrays and URI strings can be mixed when providing configurations
|
||||
for each node:
|
||||
|
||||
```php
|
||||
$client = new Predis\Client([
|
||||
@@ -101,12 +105,12 @@ $client = new Predis\Client([
|
||||
```
|
||||
|
||||
The actual list of supported connection parameters can vary depending on each connection backend so
|
||||
it is recommended to refer to their specific documentation for details.
|
||||
it is recommended to refer to their specific documentation or implementation for details.
|
||||
|
||||
|
||||
### Client configuration ###
|
||||
|
||||
Various aspects of the client can be easily configured by passing options to the second argument of
|
||||
Various aspects of the client can be configured simply by passing options to the second argument of
|
||||
`Predis\Client::__construct()`. Options are managed using a mini DI-alike container and their values
|
||||
are usually lazily initialized only when needed. Predis by default supports the following options:
|
||||
|
||||
@@ -114,7 +118,7 @@ are usually lazily initialized only when needed. Predis by default supports the
|
||||
- `prefix`: a prefix string that is automatically applied to keys found in commands.
|
||||
- `exceptions`: whether the client should throw or return responses upon Redis errors.
|
||||
- `connections`: connection backends or a connection factory to be used by the client.
|
||||
- `cluster`: which backend to use for clustering (predis, redis or custom configuration).
|
||||
- `cluster`: which backend to use for clustering (`predis`, `redis` or custom configuration).
|
||||
- `replication`: which backend to use for replication (predis or custom configuration).
|
||||
|
||||
Users can provide custom option values, they are stored in the options container and can be accessed
|
||||
@@ -123,11 +127,71 @@ later through the library.
|
||||
|
||||
### Aggregate connections ###
|
||||
|
||||
Predis is able to aggregate multiple connections which is the base for clustering and replication.
|
||||
By default the client implements clustering using either client-side sharding (default) or a Redis
|
||||
backed solution using [redis-cluster](http://redis.io/topics/cluster-tutorial). As for replication,
|
||||
Predis can handle single-master and multiple-slaves setups by executing read operations on slaves
|
||||
and switching to the master for write operations. The replication behaviour is fully configurable.
|
||||
Predis is able to aggregate multiple connections which is the base for cluster and replication. By
|
||||
default the client implements a cluster of nodes using either client-side sharding (default) or a
|
||||
Redis-backed solution using [redis-cluster](http://redis.io/topics/cluster-tutorial).
|
||||
As for replication, Predis can handle a single-master and multiple-slaves setup by executing read
|
||||
operations on slaves and switching to the master for write operations. The replication behavior is
|
||||
fully configurable.
|
||||
|
||||
#### Replication ####
|
||||
|
||||
The client can be configured to work in a master / slave replication setup by executing read-only
|
||||
commands on slave nodes and automatically switch to the master node as soon as a command performing
|
||||
a write operation is executed. This is the basic configuration needed to work with replication:
|
||||
|
||||
```php
|
||||
// Parameters require one master node specifically marked with `alias=master`.
|
||||
$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2?alias=slave-01'];
|
||||
$options = ['replication' => true];
|
||||
|
||||
$client = new Predis\Client($parameters, $options);
|
||||
```
|
||||
|
||||
While Predis is able to distinguish commands performing write and read-only operations, `EVAL` and
|
||||
`EVALSHA` represent a corner case in which the client switches to the master node because it is not
|
||||
able to tell when a Lua script is safe to be executed on slaves. While this is the default behavior,
|
||||
when certain Lua scripts do not perform write operations it is possible to provide an hint to tell
|
||||
the client to stick with slaves for their execution.
|
||||
|
||||
```php
|
||||
$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2?alias=slave-01'];
|
||||
$options = ['replication' => function () {
|
||||
// Set scripts that won't trigger a switch from a slave to the master node.
|
||||
$strategy = new Predis\Replication\ReplicationStrategy();
|
||||
$strategy->setScriptReadOnly($LUA_SCRIPT);
|
||||
|
||||
return new Predis\Connection\Aggregate\MasterSlaveReplication($strategy);
|
||||
}];
|
||||
|
||||
$client = new Predis\Client($parameters, $options);
|
||||
$client->eval($LUA_SCRIPT, 0); // Sticks to slave using `eval`...
|
||||
$client->evalsha(sha1($LUA_SCRIPT), 0); // ... and `evalsha`, too.
|
||||
```
|
||||
|
||||
The `examples` directory contains two complete scripts showing how replication can be configured for
|
||||
[simple](examples/MasterSlaveReplication.php) or [complex](examples/MasterSlaveReplicationComplex.php)
|
||||
scenarios.
|
||||
|
||||
#### Cluster ####
|
||||
|
||||
Simply passing an array of connection parameters to the client constructor configures Predis to work
|
||||
in cluster mode using client-side sharding. If you, on the other hand, want to leverage Redis >= 3.0
|
||||
nodes coordinated by redis-cluster, then the client must be initialized like this:
|
||||
|
||||
```php
|
||||
$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2'];
|
||||
$options = ['cluster' => 'redis'];
|
||||
|
||||
$client = new Predis\Client($parameters, $options);
|
||||
```
|
||||
|
||||
When using redis-cluster, it is not necessary to pass all of the nodes that compose your cluster but
|
||||
you can simply specify only a few nodes: Predis will automatically fetch the full and updated slots
|
||||
map directly from Redis by contacting one of the servers.
|
||||
|
||||
__NOTE__: our support for redis-cluster does not currently consider master / slave replication but
|
||||
this feature will be added in a future release of this library.
|
||||
|
||||
|
||||
### Command pipelines ###
|
||||
@@ -175,43 +239,7 @@ __NOTE__: the method `transaction()` is available since `v0.8.5`, older versions
|
||||
for the same purpose but it has been deprecated and will be removed in the next major release.
|
||||
|
||||
|
||||
### Customizable connection backends ###
|
||||
|
||||
Predis can use different connection backends to connect to Redis. Two of them leverage a third party
|
||||
extension such as [phpiredis](https://github.com/nrk/phpiredis) resulting in major performance gains
|
||||
especially when dealing with big multibulk responses. While one is based on PHP streams, the other
|
||||
is based on socket resources provided by `ext-socket`. Both support TCP/IP or UNIX domain sockets:
|
||||
|
||||
```php
|
||||
$client = new Predis\Client('tcp://127.0.0.1', [
|
||||
'connections' => [
|
||||
'tcp' => 'Predis\Connection\PhpiredisStreamConnection', // PHP streams
|
||||
'unix' => 'Predis\Connection\PhpiredisConnection', // ext-socket
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
Developers can create their own connection classes to add support for new network backends, extend
|
||||
existing ones or provide completely different implementations. Connection classes must implement
|
||||
`Predis\Connection\SingleConnectionInterface` or extend `Predis\Connection\AbstractConnection`:
|
||||
|
||||
```php
|
||||
class MyConnectionClass implements Predis\Connection\SingleConnectionInterface
|
||||
{
|
||||
// Implementation goes here...
|
||||
}
|
||||
|
||||
// Use MyConnectionClass to handle connections for the `tcp` scheme:
|
||||
$client = new Predis\Client('tcp://127.0.0.1', [
|
||||
'connections' => ['tcp' => 'MyConnectionClass'],
|
||||
]);
|
||||
```
|
||||
|
||||
For a more in-depth insight on how to create new connection backends you can refer to the actual
|
||||
implementation of the standard connection classes available in the `Predis\Connection` namespace.
|
||||
|
||||
|
||||
### Adding support for new commands ###
|
||||
### Adding new Redis commands ###
|
||||
|
||||
While we try to update Predis to stay up to date with all the commands available in Redis, you might
|
||||
prefer to stick with an older version of the library or provide a different way to filter arguments
|
||||
@@ -236,15 +264,16 @@ $response = $client->newcmd();
|
||||
```
|
||||
|
||||
|
||||
### Scriptable commands ###
|
||||
### Script commands ###
|
||||
|
||||
A scriptable command is just an abstraction for [Lua scripting](http://redis.io/commands/eval) that
|
||||
aims to simplify the usage of scripting with Redis >= 2.6. Scriptable commands can be registered in
|
||||
the server profile used by the client and are accessible as if they were plain Redis commands, but
|
||||
they define a Lua script that gets transmitted to Redis for remote execution. Internally, scriptable
|
||||
commands use by default [EVALSHA](http://redis.io/commands/evalsha) and identify a Lua script by its
|
||||
SHA1 hash to save bandwidth but [EVAL](http://redis.io/commands/eval) is automatically preferred as
|
||||
a fall back when needed:
|
||||
While it is possible to leverage [Lua scripting](http://redis.io/commands/eval) on Redis 2.6+ using
|
||||
[`EVAL`](http://redis.io/commands/eval) and [`EVALSHA`](http://redis.io/commands/evalsha), Predis
|
||||
offers script commands as an higher level abstraction aiming to make things simple. Script commands
|
||||
can be registered in the server profile used by the client and are accessible as if they were plain
|
||||
Redis commands, but they define Lua scripts that get transmitted to the server for remote execution.
|
||||
Internally they use [`EVALSHA`](http://redis.io/commands/evalsha) by default and identify a script
|
||||
by its SHA1 hash to save bandwidth, but [`EVAL`](http://redis.io/commands/eval) is used as a fall
|
||||
back when needed:
|
||||
|
||||
```php
|
||||
// Define a new scriptable command by extending Predis\Command\ScriptedCommand:
|
||||
@@ -266,7 +295,7 @@ LUA;
|
||||
}
|
||||
}
|
||||
|
||||
// Inject your scriptable command in the current profile:
|
||||
// Inject your script command in the current profile:
|
||||
$client = new Predis\Client();
|
||||
$client->getProfile()->defineCommand('lpushrand', 'ListPushRandomValue');
|
||||
|
||||
@@ -274,6 +303,42 @@ $response = $client->lpushrand('random_values', $seed = mt_rand());
|
||||
```
|
||||
|
||||
|
||||
### Customizable connection backends ###
|
||||
|
||||
Predis can use different connection backends to connect to Redis. Two of them leverage a third party
|
||||
extension such as [phpiredis](https://github.com/nrk/phpiredis) resulting in major performance gains
|
||||
especially when dealing with big multibulk responses. While one is based on PHP streams, the other
|
||||
is based on socket resources provided by `ext-socket`. Both support TCP/IP or UNIX domain sockets:
|
||||
|
||||
```php
|
||||
$client = new Predis\Client('tcp://127.0.0.1', [
|
||||
'connections' => [
|
||||
'tcp' => 'Predis\Connection\PhpiredisStreamConnection', // PHP streams
|
||||
'unix' => 'Predis\Connection\PhpiredisConnection', // ext-socket
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
Developers can create their own connection classes to support whole new network backends, extend
|
||||
existing ones or provide completely different implementations. Connection classes must implement
|
||||
`Predis\Connection\SingleConnectionInterface` or extend `Predis\Connection\AbstractConnection`:
|
||||
|
||||
```php
|
||||
class MyConnectionClass implements Predis\Connection\SingleConnectionInterface
|
||||
{
|
||||
// Implementation goes here...
|
||||
}
|
||||
|
||||
// Use MyConnectionClass to handle connections for the `tcp` scheme:
|
||||
$client = new Predis\Client('tcp://127.0.0.1', [
|
||||
'connections' => ['tcp' => 'MyConnectionClass'],
|
||||
]);
|
||||
```
|
||||
|
||||
For a more in-depth insight on how to create new connection backends you can refer to the actual
|
||||
implementation of the standard connection classes available in the `Predis\Connection` namespace.
|
||||
|
||||
|
||||
## Development ##
|
||||
|
||||
|
||||
@@ -292,13 +357,13 @@ in production environments or containing data you are interested in!
|
||||
|
||||
Predis has a comprehensive test suite covering every aspect of the library. This test suite performs
|
||||
integration tests against a running instance of Redis (>= 2.4.0 is required) to verify the correct
|
||||
behaviour of the implementation of each command and automatically skips commands not defined in the
|
||||
behavior of the implementation of each command and automatically skips commands not defined in the
|
||||
specified Redis profile. If you do not have Redis up and running, integration tests can be disabled.
|
||||
By default the test suite is configured to execute integration tests using the profile for Redis 2.6
|
||||
but can optionally target a Redis instance built from `2.8` or `unstable` branches by modifying the
|
||||
`phpunit.xml` file and setting `REDIS_SERVER_VERSION` respectively to `2.8` or `dev` so that the
|
||||
correct server profile will be used. You can refer to [the tests README](tests/README.md) for more
|
||||
detailed information about testing Predis.
|
||||
By default the test suite is configured to execute integration tests using the profile for Redis 2.8
|
||||
(which is the current stable version of Redis) but can optionally target a Redis instance built from
|
||||
the `unstable` branch by modifying `phpunit.xml` and setting `REDIS_SERVER_VERSION` to `dev` so that
|
||||
the development server profile will be used. You can refer to [the tests README](tests/README.md)
|
||||
for more detailed information about testing Predis.
|
||||
|
||||
Predis uses Travis CI for continuous integration and the history for past and current builds can be
|
||||
found [on its project page](http://travis-ci.org/nrk/predis).
|
||||
|
||||
+172
-4
@@ -12,7 +12,11 @@
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// In order to be able to execute this script to create a PEAR package of Predis
|
||||
// both `onion` and `pear` must be available and executable in your $PATH.
|
||||
// the `pear` binary must be available and executable in your $PATH.
|
||||
// The parts used to parse author and version strings are taken from Onion (used
|
||||
// by this library in the past) just to keep on relying on the package.ini file
|
||||
// to simplify things. We might consider to switch to using the PEAR classes
|
||||
// directly in the future.
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
function executeWithBackup($file, $callback)
|
||||
@@ -36,18 +40,182 @@ function executeWithBackup($file, $callback)
|
||||
}
|
||||
}
|
||||
|
||||
function buildPackage()
|
||||
function parseAuthor($string)
|
||||
{
|
||||
passthru('onion build && pear -q package && rm package.xml');
|
||||
$author = array();
|
||||
|
||||
if (preg_match('/^\s*(.+?)\s*(?:"(\S+)"\s*)?<(\S+)>\s*$/x', $string , $regs)) {
|
||||
if (count($regs) == 4) {
|
||||
list($orig,$name,$user,$email) = $regs;
|
||||
$author['name'] = $name;
|
||||
$author['user'] = $user;
|
||||
$author['email'] = $email;
|
||||
} elseif (count($regs) == 3) {
|
||||
list($orig,$name,$email) = $regs;
|
||||
$author['name'] = $name;
|
||||
$author['email'] = $email;
|
||||
}
|
||||
} else {
|
||||
$author['name'] = $string;
|
||||
}
|
||||
|
||||
return $author;
|
||||
}
|
||||
|
||||
executeWithBackup(__DIR__.'/../phpunit.xml.dist', function ($file) {
|
||||
function parseVersion($string)
|
||||
{
|
||||
$version_pattern = '([0-9.]+)';
|
||||
|
||||
if (preg_match("/^\s*$version_pattern\s*\$/x", $string, $regs)) {
|
||||
return array('min' => $regs[1] ?: '0.0.0');
|
||||
} elseif (preg_match("/^\s*[>=]+\s*$version_pattern\s*\$/x", $string, $regs)) {
|
||||
return array('min' => $regs[1] ?: '0.0.0');
|
||||
} elseif (preg_match("/^\s*[<=]+\s*$version_pattern\s*\$/x", $string, $regs)) {
|
||||
return array('max' => $regs[1]);
|
||||
} elseif (preg_match("/^\s*$version_pattern\s*<=>\s*$version_pattern\s*\$/x", $string, $regs)) {
|
||||
return array(
|
||||
'min' => $regs[1] ?: '0.0.0',
|
||||
'max' => $regs[2],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function addRolePath($pkg, $path, $role)
|
||||
{
|
||||
if (is_dir($path)) {
|
||||
$dirRoot = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$dirTree = new RecursiveIteratorIterator($dirRoot, RecursiveIteratorIterator::CHILD_FIRST);
|
||||
|
||||
foreach ($dirTree as $fileinfo) {
|
||||
if ($fileinfo->isFile()) {
|
||||
addPackageFile($pkg, $fileinfo, $role, $path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach (glob($path) as $filename) {
|
||||
addPackageFile($pkg, new SplFileInfo($filename), $role);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addPackageFile($pkg, $fileinfo, $role, $baseDir = '')
|
||||
{
|
||||
$fileNode = $pkg->contents->dir->addChild('file');
|
||||
$fileNode->addAttribute('name', $filepath = $fileinfo->getPathname());
|
||||
$fileNode->addAttribute('role', $role);
|
||||
$fileNode->addAttribute('md5sum', md5_file($filepath));
|
||||
|
||||
$installNode = $pkg->phprelease->filelist->addChild('install');
|
||||
$installNode->addAttribute('name', $filepath);
|
||||
$installNode->addAttribute('as', !$baseDir ? basename($filepath) : substr($filepath, strlen($baseDir) + 1));
|
||||
}
|
||||
|
||||
function generatePackageXml($packageINI)
|
||||
{
|
||||
$XML = <<<XML
|
||||
<?xml version="1.0"?>
|
||||
<package packagerversion="1.4.10" version="2.0"
|
||||
xmlns="http://pear.php.net/dtd/package-2.0"
|
||||
xmlns:tasks="http://pear.php.net/dtd/tasks-1.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
|
||||
http://pear.php.net/dtd/tasks-1.0.xsd
|
||||
http://pear.php.net/dtd/package-2.0
|
||||
http://pear.php.net/dtd/package-2.0.xsd" />
|
||||
XML;
|
||||
|
||||
$cfg = parse_ini_file($packageINI, true);
|
||||
$pkg = new SimpleXMLElement($XML);
|
||||
|
||||
$pkg->name = $cfg['package']['name'];
|
||||
$pkg->channel = $cfg['package']['channel'];
|
||||
$pkg->summary = $cfg['package']['desc'];
|
||||
$pkg->description = $cfg['package']['desc'];
|
||||
|
||||
$author = parseAuthor($cfg['package']['author']);
|
||||
$pkg->addChild('lead');
|
||||
$pkg->lead->name = $author['name'];
|
||||
$pkg->lead->user = $author['user'];
|
||||
$pkg->lead->email = $author['email'];
|
||||
$pkg->lead->active = 'yes';
|
||||
|
||||
$datetime = new DateTime('now');
|
||||
$pkg->date = $datetime->format('Y-m-d');
|
||||
$pkg->time = $datetime->format('H:i:s');
|
||||
|
||||
$pkg->addChild('version');
|
||||
$pkg->version->release = $cfg['package']['version'];
|
||||
$pkg->version->api = $cfg['package']['version'];
|
||||
|
||||
$pkg->addChild('stability');
|
||||
$pkg->stability->release = $cfg['package']['stability'];
|
||||
$pkg->stability->api = $cfg['package']['stability'];
|
||||
|
||||
$pkg->license = $cfg['package']['license'];
|
||||
$pkg->notes = '-';
|
||||
|
||||
$pkg->addChild('contents')->addChild('dir')->addAttribute('name', '/');
|
||||
|
||||
$pkg->addChild('dependencies')->addChild('required');
|
||||
foreach ($cfg['require'] as $required => $version) {
|
||||
$version = parseVersion($version);
|
||||
$pkg->dependencies->required->addChild($required);
|
||||
|
||||
if (isset($version['min'])) {
|
||||
$pkg->dependencies->required->$required->min = $version['min'];
|
||||
}
|
||||
if (isset($version['max'])) {
|
||||
$pkg->dependencies->required->$required->min = $version['max'];
|
||||
}
|
||||
}
|
||||
|
||||
$pkg->addChild('phprelease')->addChild('filelist');
|
||||
|
||||
$pathToRole = array(
|
||||
'doc' => 'doc', 'docs' => 'doc', 'examples' => 'doc',
|
||||
'lib' => 'php', 'src' => 'php',
|
||||
'test' => 'test', 'tests' => 'test',
|
||||
);
|
||||
|
||||
foreach (array_merge($pathToRole, $cfg['roles'] ?: array()) as $path => $role) {
|
||||
addRolePath($pkg, $path, $role);
|
||||
}
|
||||
|
||||
return $pkg;
|
||||
}
|
||||
|
||||
function savePackageXml($xml)
|
||||
{
|
||||
$dom = new DOMDocument("1.0");
|
||||
$dom->preserveWhiteSpace = false;
|
||||
$dom->formatOutput = true;
|
||||
$dom->loadXML($xml->asXML());
|
||||
|
||||
file_put_contents('package.xml', $dom->saveXML());
|
||||
}
|
||||
|
||||
function buildPackage()
|
||||
{
|
||||
passthru('pear -q package && rm package.xml');
|
||||
}
|
||||
|
||||
function modifyPhpunitXml($file)
|
||||
{
|
||||
$cfg = new SimpleXMLElement($file, null, true);
|
||||
|
||||
$cfg[0]['bootstrap'] = str_replace('tests/', '', $cfg[0]['bootstrap']);
|
||||
$cfg->testsuites->testsuite->directory = str_replace('tests/', '', $cfg->testsuites->testsuite->directory);
|
||||
|
||||
$cfg->saveXml($file);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
executeWithBackup(__DIR__.'/../phpunit.xml.dist', function ($file) {
|
||||
modifyPhpunitXml($file);
|
||||
|
||||
$pkg = generatePackageXml('package.ini');
|
||||
savePackageXml($pkg);
|
||||
|
||||
buildPackage();
|
||||
});
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
"require": {
|
||||
"php": ">=5.3.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol",
|
||||
"ext-curl": "Allows access to Webdis when paired with phpiredis"
|
||||
|
||||
@@ -84,6 +84,11 @@ for ($i = 0; $i < 100; $i++) {
|
||||
$server1 = $client->getClientFor('first')->info();
|
||||
$server2 = $client->getClientFor('second')->info();
|
||||
|
||||
if (isset($server1['Keyspace'], $server2['Keyspace'])) {
|
||||
$server1 = $server1['Keyspace'];
|
||||
$server2 = $server2['Keyspace'];
|
||||
}
|
||||
|
||||
printf("Server '%s' has %d keys while server '%s' has %d keys.\n",
|
||||
'first', $server1['db15']['keys'], 'second', $server2['db15']['keys']
|
||||
);
|
||||
|
||||
@@ -20,7 +20,6 @@ require 'SharedConfigurations.php';
|
||||
|
||||
use Predis\Command\ScriptedCommand;
|
||||
use Predis\Connection\MasterSlaveReplication;
|
||||
use Predis\Profile\ServerProfile;
|
||||
use Predis\Replication\ReplicationStrategy;
|
||||
|
||||
// ------------------------------------------------------------------------- //
|
||||
@@ -28,7 +27,8 @@ use Predis\Replication\ReplicationStrategy;
|
||||
// Define a new scripted command that returns all the fields
|
||||
// of a variable number of hashes with a single roundtrip.
|
||||
|
||||
class HashMultipleGetAll extends ScriptedCommand {
|
||||
class HashMultipleGetAll extends ScriptedCommand
|
||||
{
|
||||
const BODY = <<<EOS
|
||||
local hashes = {}
|
||||
for _, key in pairs(KEYS) do
|
||||
@@ -38,7 +38,8 @@ end
|
||||
return hashes
|
||||
EOS;
|
||||
|
||||
public function getScript() {
|
||||
public function getScript()
|
||||
{
|
||||
return self::BODY;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ use Predis\Transaction\MultiExecContext;
|
||||
*/
|
||||
class Client implements ClientInterface
|
||||
{
|
||||
const VERSION = '0.8.5';
|
||||
const VERSION = '0.8.7';
|
||||
|
||||
private $options;
|
||||
private $profile;
|
||||
|
||||
@@ -23,8 +23,8 @@ use Predis\Command\ScriptedCommand;
|
||||
*/
|
||||
class PredisClusterHashStrategy implements CommandHashStrategyInterface
|
||||
{
|
||||
private $commands;
|
||||
private $hashGenerator;
|
||||
protected $commands;
|
||||
protected $hashGenerator;
|
||||
|
||||
/**
|
||||
* @param HashGeneratorInterface $hashGenerator Hash generator instance.
|
||||
@@ -42,117 +42,126 @@ class PredisClusterHashStrategy implements CommandHashStrategyInterface
|
||||
*/
|
||||
protected function getDefaultCommands()
|
||||
{
|
||||
$keyIsFirstArgument = array($this, 'getKeyFromFirstArgument');
|
||||
$keysAreAllArguments = array($this, 'getKeyFromAllArguments');
|
||||
$getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument');
|
||||
$getKeyFromAllArguments = array($this, 'getKeyFromAllArguments');
|
||||
|
||||
return array(
|
||||
/* commands operating on the key space */
|
||||
'EXISTS' => $keyIsFirstArgument,
|
||||
'DEL' => $keysAreAllArguments,
|
||||
'TYPE' => $keyIsFirstArgument,
|
||||
'EXPIRE' => $keyIsFirstArgument,
|
||||
'EXPIREAT' => $keyIsFirstArgument,
|
||||
'PERSIST' => $keyIsFirstArgument,
|
||||
'PEXPIRE' => $keyIsFirstArgument,
|
||||
'PEXPIREAT' => $keyIsFirstArgument,
|
||||
'TTL' => $keyIsFirstArgument,
|
||||
'PTTL' => $keyIsFirstArgument,
|
||||
'SORT' => $keyIsFirstArgument, // TODO
|
||||
'DUMP' => $keyIsFirstArgument,
|
||||
'RESTORE' => $keyIsFirstArgument,
|
||||
'EXISTS' => $getKeyFromFirstArgument,
|
||||
'DEL' => $getKeyFromAllArguments,
|
||||
'TYPE' => $getKeyFromFirstArgument,
|
||||
'EXPIRE' => $getKeyFromFirstArgument,
|
||||
'EXPIREAT' => $getKeyFromFirstArgument,
|
||||
'PERSIST' => $getKeyFromFirstArgument,
|
||||
'PEXPIRE' => $getKeyFromFirstArgument,
|
||||
'PEXPIREAT' => $getKeyFromFirstArgument,
|
||||
'TTL' => $getKeyFromFirstArgument,
|
||||
'PTTL' => $getKeyFromFirstArgument,
|
||||
'SORT' => $getKeyFromFirstArgument, // TODO
|
||||
'DUMP' => $getKeyFromFirstArgument,
|
||||
'RESTORE' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on string values */
|
||||
'APPEND' => $keyIsFirstArgument,
|
||||
'DECR' => $keyIsFirstArgument,
|
||||
'DECRBY' => $keyIsFirstArgument,
|
||||
'GET' => $keyIsFirstArgument,
|
||||
'GETBIT' => $keyIsFirstArgument,
|
||||
'MGET' => $keysAreAllArguments,
|
||||
'SET' => $keyIsFirstArgument,
|
||||
'GETRANGE' => $keyIsFirstArgument,
|
||||
'GETSET' => $keyIsFirstArgument,
|
||||
'INCR' => $keyIsFirstArgument,
|
||||
'INCRBY' => $keyIsFirstArgument,
|
||||
'SETBIT' => $keyIsFirstArgument,
|
||||
'SETEX' => $keyIsFirstArgument,
|
||||
'APPEND' => $getKeyFromFirstArgument,
|
||||
'DECR' => $getKeyFromFirstArgument,
|
||||
'DECRBY' => $getKeyFromFirstArgument,
|
||||
'GET' => $getKeyFromFirstArgument,
|
||||
'GETBIT' => $getKeyFromFirstArgument,
|
||||
'MGET' => $getKeyFromAllArguments,
|
||||
'SET' => $getKeyFromFirstArgument,
|
||||
'GETRANGE' => $getKeyFromFirstArgument,
|
||||
'GETSET' => $getKeyFromFirstArgument,
|
||||
'INCR' => $getKeyFromFirstArgument,
|
||||
'INCRBY' => $getKeyFromFirstArgument,
|
||||
'INCRBYFLOAT' => $getKeyFromFirstArgument,
|
||||
'SETBIT' => $getKeyFromFirstArgument,
|
||||
'SETEX' => $getKeyFromFirstArgument,
|
||||
'MSET' => array($this, 'getKeyFromInterleavedArguments'),
|
||||
'MSETNX' => array($this, 'getKeyFromInterleavedArguments'),
|
||||
'SETNX' => $keyIsFirstArgument,
|
||||
'SETRANGE' => $keyIsFirstArgument,
|
||||
'STRLEN' => $keyIsFirstArgument,
|
||||
'SUBSTR' => $keyIsFirstArgument,
|
||||
'SETNX' => $getKeyFromFirstArgument,
|
||||
'SETRANGE' => $getKeyFromFirstArgument,
|
||||
'STRLEN' => $getKeyFromFirstArgument,
|
||||
'SUBSTR' => $getKeyFromFirstArgument,
|
||||
'BITOP' => array($this, 'getKeyFromBitOp'),
|
||||
'BITCOUNT' => $keyIsFirstArgument,
|
||||
'BITCOUNT' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on lists */
|
||||
'LINSERT' => $keyIsFirstArgument,
|
||||
'LINDEX' => $keyIsFirstArgument,
|
||||
'LLEN' => $keyIsFirstArgument,
|
||||
'LPOP' => $keyIsFirstArgument,
|
||||
'RPOP' => $keyIsFirstArgument,
|
||||
'RPOPLPUSH' => $keysAreAllArguments,
|
||||
'LINSERT' => $getKeyFromFirstArgument,
|
||||
'LINDEX' => $getKeyFromFirstArgument,
|
||||
'LLEN' => $getKeyFromFirstArgument,
|
||||
'LPOP' => $getKeyFromFirstArgument,
|
||||
'RPOP' => $getKeyFromFirstArgument,
|
||||
'RPOPLPUSH' => $getKeyFromAllArguments,
|
||||
'BLPOP' => array($this, 'getKeyFromBlockingListCommands'),
|
||||
'BRPOP' => array($this, 'getKeyFromBlockingListCommands'),
|
||||
'BRPOPLPUSH' => array($this, 'getKeyFromBlockingListCommands'),
|
||||
'LPUSH' => $keyIsFirstArgument,
|
||||
'LPUSHX' => $keyIsFirstArgument,
|
||||
'RPUSH' => $keyIsFirstArgument,
|
||||
'RPUSHX' => $keyIsFirstArgument,
|
||||
'LRANGE' => $keyIsFirstArgument,
|
||||
'LREM' => $keyIsFirstArgument,
|
||||
'LSET' => $keyIsFirstArgument,
|
||||
'LTRIM' => $keyIsFirstArgument,
|
||||
'LPUSH' => $getKeyFromFirstArgument,
|
||||
'LPUSHX' => $getKeyFromFirstArgument,
|
||||
'RPUSH' => $getKeyFromFirstArgument,
|
||||
'RPUSHX' => $getKeyFromFirstArgument,
|
||||
'LRANGE' => $getKeyFromFirstArgument,
|
||||
'LREM' => $getKeyFromFirstArgument,
|
||||
'LSET' => $getKeyFromFirstArgument,
|
||||
'LTRIM' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on sets */
|
||||
'SADD' => $keyIsFirstArgument,
|
||||
'SCARD' => $keyIsFirstArgument,
|
||||
'SDIFF' => $keysAreAllArguments,
|
||||
'SDIFFSTORE' => $keysAreAllArguments,
|
||||
'SINTER' => $keysAreAllArguments,
|
||||
'SINTERSTORE' => $keysAreAllArguments,
|
||||
'SUNION' => $keysAreAllArguments,
|
||||
'SUNIONSTORE' => $keysAreAllArguments,
|
||||
'SISMEMBER' => $keyIsFirstArgument,
|
||||
'SMEMBERS' => $keyIsFirstArgument,
|
||||
'SSCAN' => $keyIsFirstArgument,
|
||||
'SPOP' => $keyIsFirstArgument,
|
||||
'SRANDMEMBER' => $keyIsFirstArgument,
|
||||
'SREM' => $keyIsFirstArgument,
|
||||
'SADD' => $getKeyFromFirstArgument,
|
||||
'SCARD' => $getKeyFromFirstArgument,
|
||||
'SDIFF' => $getKeyFromAllArguments,
|
||||
'SDIFFSTORE' => $getKeyFromAllArguments,
|
||||
'SINTER' => $getKeyFromAllArguments,
|
||||
'SINTERSTORE' => $getKeyFromAllArguments,
|
||||
'SUNION' => $getKeyFromAllArguments,
|
||||
'SUNIONSTORE' => $getKeyFromAllArguments,
|
||||
'SISMEMBER' => $getKeyFromFirstArgument,
|
||||
'SMEMBERS' => $getKeyFromFirstArgument,
|
||||
'SSCAN' => $getKeyFromFirstArgument,
|
||||
'SPOP' => $getKeyFromFirstArgument,
|
||||
'SRANDMEMBER' => $getKeyFromFirstArgument,
|
||||
'SREM' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on sorted sets */
|
||||
'ZADD' => $keyIsFirstArgument,
|
||||
'ZCARD' => $keyIsFirstArgument,
|
||||
'ZCOUNT' => $keyIsFirstArgument,
|
||||
'ZINCRBY' => $keyIsFirstArgument,
|
||||
'ZADD' => $getKeyFromFirstArgument,
|
||||
'ZCARD' => $getKeyFromFirstArgument,
|
||||
'ZCOUNT' => $getKeyFromFirstArgument,
|
||||
'ZINCRBY' => $getKeyFromFirstArgument,
|
||||
'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
|
||||
'ZRANGE' => $keyIsFirstArgument,
|
||||
'ZRANGEBYSCORE' => $keyIsFirstArgument,
|
||||
'ZRANK' => $keyIsFirstArgument,
|
||||
'ZREM' => $keyIsFirstArgument,
|
||||
'ZREMRANGEBYRANK' => $keyIsFirstArgument,
|
||||
'ZREMRANGEBYSCORE' => $keyIsFirstArgument,
|
||||
'ZREVRANGE' => $keyIsFirstArgument,
|
||||
'ZREVRANGEBYSCORE' => $keyIsFirstArgument,
|
||||
'ZREVRANK' => $keyIsFirstArgument,
|
||||
'ZSCORE' => $keyIsFirstArgument,
|
||||
'ZRANGE' => $getKeyFromFirstArgument,
|
||||
'ZRANGEBYSCORE' => $getKeyFromFirstArgument,
|
||||
'ZRANK' => $getKeyFromFirstArgument,
|
||||
'ZREM' => $getKeyFromFirstArgument,
|
||||
'ZREMRANGEBYRANK' => $getKeyFromFirstArgument,
|
||||
'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument,
|
||||
'ZREVRANGE' => $getKeyFromFirstArgument,
|
||||
'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument,
|
||||
'ZREVRANK' => $getKeyFromFirstArgument,
|
||||
'ZSCORE' => $getKeyFromFirstArgument,
|
||||
'ZUNIONSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
|
||||
'ZSCAN' => $keyIsFirstArgument,
|
||||
'ZSCAN' => $getKeyFromFirstArgument,
|
||||
'ZLEXCOUNT' => $getKeyFromFirstArgument,
|
||||
'ZRANGEBYLEX' => $getKeyFromFirstArgument,
|
||||
'ZREMRANGEBYLEX' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on hashes */
|
||||
'HDEL' => $keyIsFirstArgument,
|
||||
'HEXISTS' => $keyIsFirstArgument,
|
||||
'HGET' => $keyIsFirstArgument,
|
||||
'HGETALL' => $keyIsFirstArgument,
|
||||
'HMGET' => $keyIsFirstArgument,
|
||||
'HMSET' => $keyIsFirstArgument,
|
||||
'HINCRBY' => $keyIsFirstArgument,
|
||||
'HINCRBYFLOAT' => $keyIsFirstArgument,
|
||||
'HKEYS' => $keyIsFirstArgument,
|
||||
'HLEN' => $keyIsFirstArgument,
|
||||
'HSET' => $keyIsFirstArgument,
|
||||
'HSETNX' => $keyIsFirstArgument,
|
||||
'HVALS' => $keyIsFirstArgument,
|
||||
'HSCAN' => $keyIsFirstArgument,
|
||||
'HDEL' => $getKeyFromFirstArgument,
|
||||
'HEXISTS' => $getKeyFromFirstArgument,
|
||||
'HGET' => $getKeyFromFirstArgument,
|
||||
'HGETALL' => $getKeyFromFirstArgument,
|
||||
'HMGET' => $getKeyFromFirstArgument,
|
||||
'HMSET' => $getKeyFromFirstArgument,
|
||||
'HINCRBY' => $getKeyFromFirstArgument,
|
||||
'HINCRBYFLOAT' => $getKeyFromFirstArgument,
|
||||
'HKEYS' => $getKeyFromFirstArgument,
|
||||
'HLEN' => $getKeyFromFirstArgument,
|
||||
'HSET' => $getKeyFromFirstArgument,
|
||||
'HSETNX' => $getKeyFromFirstArgument,
|
||||
'HVALS' => $getKeyFromFirstArgument,
|
||||
'HSCAN' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on HyperLogLog */
|
||||
'PFADD' => $getKeyFromFirstArgument,
|
||||
'PFCOUNT' => $getKeyFromAllArguments,
|
||||
'PFMERGE' => $getKeyFromAllArguments,
|
||||
|
||||
/* scripting */
|
||||
'EVAL' => array($this, 'getKeyFromScriptingCommands'),
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
namespace Predis\Cluster;
|
||||
|
||||
use Predis\Cluster\Hash\CRC16HashGenerator;
|
||||
use Predis\Command\CommandInterface;
|
||||
use Predis\Command\ScriptedCommand;
|
||||
use Predis\Cluster\Hash\HashGeneratorInterface;
|
||||
|
||||
/**
|
||||
* Default class used by Predis to calculate hashes out of keys of
|
||||
@@ -21,272 +20,31 @@ use Predis\Command\ScriptedCommand;
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class RedisClusterHashStrategy implements CommandHashStrategyInterface
|
||||
class RedisClusterHashStrategy extends PredisClusterHashStrategy
|
||||
{
|
||||
private $commands;
|
||||
private $hashGenerator;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
public function __construct(HashGeneratorInterface $hashGenerator = null)
|
||||
{
|
||||
$this->commands = $this->getDefaultCommands();
|
||||
$this->hashGenerator = new CRC16HashGenerator();
|
||||
parent::__construct($hashGenerator ?: new CRC16HashGenerator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default map of supported commands with their handlers.
|
||||
* Returns only the hashable part of a key (delimited by "{...}"), or the
|
||||
* whole key if a key tag is not found in the string.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaultCommands()
|
||||
{
|
||||
$keyIsFirstArgument = array($this, 'getKeyFromFirstArgument');
|
||||
|
||||
return array(
|
||||
/* commands operating on the key space */
|
||||
'EXISTS' => $keyIsFirstArgument,
|
||||
'DEL' => array($this, 'getKeyFromAllArguments'),
|
||||
'TYPE' => $keyIsFirstArgument,
|
||||
'EXPIRE' => $keyIsFirstArgument,
|
||||
'EXPIREAT' => $keyIsFirstArgument,
|
||||
'PERSIST' => $keyIsFirstArgument,
|
||||
'PEXPIRE' => $keyIsFirstArgument,
|
||||
'PEXPIREAT' => $keyIsFirstArgument,
|
||||
'TTL' => $keyIsFirstArgument,
|
||||
'PTTL' => $keyIsFirstArgument,
|
||||
'SORT' => $keyIsFirstArgument, // TODO
|
||||
|
||||
/* commands operating on string values */
|
||||
'APPEND' => $keyIsFirstArgument,
|
||||
'DECR' => $keyIsFirstArgument,
|
||||
'DECRBY' => $keyIsFirstArgument,
|
||||
'GET' => $keyIsFirstArgument,
|
||||
'GETBIT' => $keyIsFirstArgument,
|
||||
'MGET' => array($this, 'getKeyFromAllArguments'),
|
||||
'SET' => $keyIsFirstArgument,
|
||||
'GETRANGE' => $keyIsFirstArgument,
|
||||
'GETSET' => $keyIsFirstArgument,
|
||||
'INCR' => $keyIsFirstArgument,
|
||||
'INCRBY' => $keyIsFirstArgument,
|
||||
'SETBIT' => $keyIsFirstArgument,
|
||||
'SETEX' => $keyIsFirstArgument,
|
||||
'MSET' => array($this, 'getKeyFromInterleavedArguments'),
|
||||
'MSETNX' => array($this, 'getKeyFromInterleavedArguments'),
|
||||
'SETNX' => $keyIsFirstArgument,
|
||||
'SETRANGE' => $keyIsFirstArgument,
|
||||
'STRLEN' => $keyIsFirstArgument,
|
||||
'SUBSTR' => $keyIsFirstArgument,
|
||||
'BITCOUNT' => $keyIsFirstArgument,
|
||||
|
||||
/* commands operating on lists */
|
||||
'LINSERT' => $keyIsFirstArgument,
|
||||
'LINDEX' => $keyIsFirstArgument,
|
||||
'LLEN' => $keyIsFirstArgument,
|
||||
'LPOP' => $keyIsFirstArgument,
|
||||
'RPOP' => $keyIsFirstArgument,
|
||||
'BLPOP' => array($this, 'getKeyFromBlockingListCommands'),
|
||||
'BRPOP' => array($this, 'getKeyFromBlockingListCommands'),
|
||||
'LPUSH' => $keyIsFirstArgument,
|
||||
'LPUSHX' => $keyIsFirstArgument,
|
||||
'RPUSH' => $keyIsFirstArgument,
|
||||
'RPUSHX' => $keyIsFirstArgument,
|
||||
'LRANGE' => $keyIsFirstArgument,
|
||||
'LREM' => $keyIsFirstArgument,
|
||||
'LSET' => $keyIsFirstArgument,
|
||||
'LTRIM' => $keyIsFirstArgument,
|
||||
|
||||
/* commands operating on sets */
|
||||
'SADD' => $keyIsFirstArgument,
|
||||
'SCARD' => $keyIsFirstArgument,
|
||||
'SISMEMBER' => $keyIsFirstArgument,
|
||||
'SMEMBERS' => $keyIsFirstArgument,
|
||||
'SSCAN' => $keyIsFirstArgument,
|
||||
'SPOP' => $keyIsFirstArgument,
|
||||
'SRANDMEMBER' => $keyIsFirstArgument,
|
||||
'SREM' => $keyIsFirstArgument,
|
||||
|
||||
/* commands operating on sorted sets */
|
||||
'ZADD' => $keyIsFirstArgument,
|
||||
'ZCARD' => $keyIsFirstArgument,
|
||||
'ZCOUNT' => $keyIsFirstArgument,
|
||||
'ZINCRBY' => $keyIsFirstArgument,
|
||||
'ZRANGE' => $keyIsFirstArgument,
|
||||
'ZRANGEBYSCORE' => $keyIsFirstArgument,
|
||||
'ZRANK' => $keyIsFirstArgument,
|
||||
'ZREM' => $keyIsFirstArgument,
|
||||
'ZREMRANGEBYRANK' => $keyIsFirstArgument,
|
||||
'ZREMRANGEBYSCORE' => $keyIsFirstArgument,
|
||||
'ZREVRANGE' => $keyIsFirstArgument,
|
||||
'ZREVRANGEBYSCORE' => $keyIsFirstArgument,
|
||||
'ZREVRANK' => $keyIsFirstArgument,
|
||||
'ZSCORE' => $keyIsFirstArgument,
|
||||
'ZSCAN' => $keyIsFirstArgument,
|
||||
|
||||
/* commands operating on hashes */
|
||||
'HDEL' => $keyIsFirstArgument,
|
||||
'HEXISTS' => $keyIsFirstArgument,
|
||||
'HGET' => $keyIsFirstArgument,
|
||||
'HGETALL' => $keyIsFirstArgument,
|
||||
'HMGET' => $keyIsFirstArgument,
|
||||
'HMSET' => $keyIsFirstArgument,
|
||||
'HINCRBY' => $keyIsFirstArgument,
|
||||
'HINCRBYFLOAT' => $keyIsFirstArgument,
|
||||
'HKEYS' => $keyIsFirstArgument,
|
||||
'HLEN' => $keyIsFirstArgument,
|
||||
'HSET' => $keyIsFirstArgument,
|
||||
'HSETNX' => $keyIsFirstArgument,
|
||||
'HVALS' => $keyIsFirstArgument,
|
||||
'HSCAN' => $keyIsFirstArgument,
|
||||
|
||||
/* scripting */
|
||||
'EVAL' => array($this, 'getKeyFromScriptingCommands'),
|
||||
'EVALSHA' => array($this, 'getKeyFromScriptingCommands'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of IDs for the supported commands.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSupportedCommands()
|
||||
{
|
||||
return array_keys($this->commands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an handler for the specified command ID.
|
||||
*
|
||||
* The signature of the callback must have a single parameter
|
||||
* of type Predis\Command\CommandInterface.
|
||||
*
|
||||
* When the callback argument is omitted or NULL, the previously
|
||||
* associated handler for the specified command ID is removed.
|
||||
*
|
||||
* @param string $commandId The ID of the command to be handled.
|
||||
* @param mixed $callback A valid callable object or NULL.
|
||||
*/
|
||||
public function setCommandHandler($commandId, $callback = null)
|
||||
{
|
||||
$commandId = strtoupper($commandId);
|
||||
|
||||
if (!isset($callback)) {
|
||||
unset($this->commands[$commandId]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_callable($callback)) {
|
||||
throw new \InvalidArgumentException("Callback must be a valid callable object or NULL");
|
||||
}
|
||||
|
||||
$this->commands[$commandId] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from the first argument of a command instance.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @param string $key A key.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromFirstArgument(CommandInterface $command)
|
||||
protected function extractKeyTag($key)
|
||||
{
|
||||
return $command->getArgument(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from a command that can accept multiple keys ensuring
|
||||
* that only one key is actually specified to comply with redis-cluster.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromAllArguments(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
|
||||
if (count($arguments) === 1) {
|
||||
return $arguments[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from a command that can accept multiple keys ensuring
|
||||
* that only one key is actually specified to comply with redis-cluster.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromInterleavedArguments(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
|
||||
if (count($arguments) === 2) {
|
||||
return $arguments[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from BLPOP and BRPOP commands ensuring that only one key
|
||||
* is actually specified to comply with redis-cluster.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromBlockingListCommands(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
|
||||
if (count($arguments) === 2) {
|
||||
return $arguments[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from EVAL and EVALSHA commands.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return string
|
||||
*/
|
||||
protected function getKeyFromScriptingCommands(CommandInterface $command)
|
||||
{
|
||||
if ($command instanceof ScriptedCommand) {
|
||||
$keys = $command->getKeys();
|
||||
} else {
|
||||
$keys = array_slice($args = $command->getArguments(), 2, $args[1]);
|
||||
}
|
||||
|
||||
if (count($keys) === 1) {
|
||||
return $keys[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHash(CommandInterface $command)
|
||||
{
|
||||
$hash = $command->getHash();
|
||||
|
||||
if (!isset($hash) && isset($this->commands[$cmdID = $command->getId()])) {
|
||||
$key = call_user_func($this->commands[$cmdID], $command);
|
||||
|
||||
if (isset($key)) {
|
||||
$hash = $this->hashGenerator->hash($key);
|
||||
$command->setHash($hash);
|
||||
if (false !== $start = strpos($key, '{')) {
|
||||
if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) {
|
||||
$key = substr($key, $start, $end - $start);
|
||||
}
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getKeyHash($key)
|
||||
{
|
||||
return $this->hashGenerator->hash($key);
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,16 +166,18 @@ abstract class CursorBasedIterator implements Iterator
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
if (!$this->elements && $this->fetchmore) {
|
||||
$this->fetch();
|
||||
}
|
||||
tryFetch: {
|
||||
if (!$this->elements && $this->fetchmore) {
|
||||
$this->fetch();
|
||||
}
|
||||
|
||||
if ($this->elements) {
|
||||
$this->extractNext();
|
||||
} elseif ($this->cursor) {
|
||||
$this->next();
|
||||
} else {
|
||||
$this->valid = false;
|
||||
if ($this->elements) {
|
||||
$this->extractNext();
|
||||
} elseif ($this->cursor) {
|
||||
goto tryFetch;
|
||||
} else {
|
||||
$this->valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/pfadd
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HyperLogLogAdd extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'PFADD';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(array $arguments)
|
||||
{
|
||||
return self::normalizeVariadic($arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseResponse($data)
|
||||
{
|
||||
return (bool) $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/pfcount
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HyperLogLogCount extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'PFCOUNT';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(array $arguments)
|
||||
{
|
||||
return self::normalizeArguments($arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prefixKeys($prefix)
|
||||
{
|
||||
PrefixHelpers::all($this, $prefix);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/pfmerge
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class HyperLogLogMerge extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'PFMERGE';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(array $arguments)
|
||||
{
|
||||
return self::normalizeArguments($arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prefixKeys($prefix)
|
||||
{
|
||||
PrefixHelpers::all($this, $prefix);
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ class RawCommand implements CommandInterface
|
||||
/**
|
||||
* Creates a new raw command using a variadic method.
|
||||
*
|
||||
* @param string $commandID Redis command ID.
|
||||
* @param string $commandID Redis command ID.
|
||||
* @param string ... Arguments list for the command.
|
||||
* @return CommandInterface
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/command
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class ServerCommand extends AbstractCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'COMMAND';
|
||||
}
|
||||
}
|
||||
@@ -34,27 +34,38 @@ class ServerInfo extends AbstractCommand
|
||||
$infoLines = preg_split('/\r?\n/', $data);
|
||||
|
||||
foreach ($infoLines as $row) {
|
||||
@list($k, $v) = explode(':', $row);
|
||||
|
||||
if ($row === '' || !isset($v)) {
|
||||
if (strpos($row, ':') === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!preg_match('/^db\d+$/', $k)) {
|
||||
if ($k === 'allocation_stats') {
|
||||
$info[$k] = $this->parseAllocationStats($v);
|
||||
continue;
|
||||
}
|
||||
|
||||
$info[$k] = $v;
|
||||
} else {
|
||||
$info[$k] = $this->parseDatabaseStats($v);
|
||||
}
|
||||
list($k, $v) = $this->parseRow($row);
|
||||
$info[$k] = $v;
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses single row of the reply buffer and returns the key-value pair.
|
||||
*
|
||||
* @param string $row Single row of the reply buffer.
|
||||
* @return array
|
||||
*/
|
||||
public function parseRow($row)
|
||||
{
|
||||
list($k, $v) = explode(':', $row, 2);
|
||||
|
||||
if (!preg_match('/^db\d+$/', $k)) {
|
||||
if ($k === 'allocation_stats') {
|
||||
$v = $this->parseAllocationStats($v);
|
||||
}
|
||||
} else {
|
||||
$v = $this->parseDatabaseStats($v);
|
||||
}
|
||||
|
||||
return array($k, $v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the reply buffer and extracts the statistics of each logical DB.
|
||||
*
|
||||
|
||||
@@ -41,18 +41,8 @@ class ServerInfoV26x extends ServerInfo
|
||||
continue;
|
||||
}
|
||||
|
||||
list($k, $v) = explode(':', $row);
|
||||
|
||||
if (!preg_match('/^db\d+$/', $k)) {
|
||||
if ($k === 'allocation_stats') {
|
||||
$current[$k] = $this->parseAllocationStats($v);
|
||||
continue;
|
||||
}
|
||||
|
||||
$current[$k] = $v;
|
||||
} else {
|
||||
$current[$k] = $this->parseDatabaseStats($v);
|
||||
}
|
||||
list($k, $v) = $this->parseRow($row);
|
||||
$current[$k] = $v;
|
||||
}
|
||||
|
||||
return $info;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/zlexcount
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class ZSetLexCount extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'ZLEXCOUNT';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/zrangebylex
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class ZSetRangeByLex extends ZSetRange
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'ZRANGEBYLEX';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function prepareOptions($options)
|
||||
{
|
||||
$opts = array_change_key_case($options, CASE_UPPER);
|
||||
$finalizedOpts = array();
|
||||
|
||||
if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) {
|
||||
$limit = array_change_key_case($opts['LIMIT'], CASE_UPPER);
|
||||
|
||||
$finalizedOpts[] = 'LIMIT';
|
||||
$finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0];
|
||||
$finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1];
|
||||
}
|
||||
|
||||
return $finalizedOpts;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function withScores()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/zremrangebylex
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class ZSetRemoveRangeByLex extends PrefixableCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'ZREMRANGEBYLEX';
|
||||
}
|
||||
}
|
||||
@@ -96,6 +96,52 @@ class PhpiredisStreamConnection extends StreamConnection
|
||||
return parent::checkParameters($parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function tcpStreamInitializer(ConnectionParametersInterface $parameters)
|
||||
{
|
||||
$uri = "tcp://{$parameters->host}:{$parameters->port}";
|
||||
$flags = STREAM_CLIENT_CONNECT;
|
||||
$socket = null;
|
||||
|
||||
if (isset($parameters->async_connect) && $parameters->async_connect) {
|
||||
$flags |= STREAM_CLIENT_ASYNC_CONNECT;
|
||||
}
|
||||
|
||||
if (isset($parameters->persistent) && $parameters->persistent) {
|
||||
$flags |= STREAM_CLIENT_PERSISTENT;
|
||||
$uri .= strpos($path = $parameters->path, '/') === 0 ? $path : "/$path";
|
||||
}
|
||||
|
||||
$resource = @stream_socket_client($uri, $errno, $errstr, $parameters->timeout, $flags);
|
||||
|
||||
if (!$resource) {
|
||||
$this->onConnectionError(trim($errstr), $errno);
|
||||
}
|
||||
|
||||
if (isset($parameters->read_write_timeout) && function_exists('socket_import_stream')) {
|
||||
$rwtimeout = (float) $parameters->read_write_timeout;
|
||||
$rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1;
|
||||
|
||||
$timeout = array(
|
||||
'sec' => $timeoutSeconds = floor($rwtimeout),
|
||||
'usec' => ($rwtimeout - $timeoutSeconds) * 1000000,
|
||||
);
|
||||
|
||||
$socket = $socket ?: socket_import_stream($resource);
|
||||
@socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout);
|
||||
@socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout);
|
||||
}
|
||||
|
||||
if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) {
|
||||
$socket = $socket ?: socket_import_stream($resource);
|
||||
socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
|
||||
}
|
||||
|
||||
return $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the protocol reader resource.
|
||||
*/
|
||||
@@ -151,7 +197,7 @@ class PhpiredisStreamConnection extends StreamConnection
|
||||
$reader = $this->reader;
|
||||
|
||||
while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) {
|
||||
$buffer = fread($socket, 4096);
|
||||
$buffer = stream_socket_recvfrom($socket, 4096);
|
||||
|
||||
if ($buffer === false || $buffer === '') {
|
||||
$this->onConnectionError('Error while reading bytes from the server');
|
||||
|
||||
@@ -26,7 +26,7 @@ use Predis\Command\RawCommand;
|
||||
* Abstraction for a Redis-backed cluster of nodes (Redis >= 3.0.0).
|
||||
*
|
||||
* This connection backend offers smart support for redis-cluster by handling
|
||||
* automatic slots map (re)generation upon -MOVE or -ASK responses returned by
|
||||
* automatic slots map (re)generation upon -MOVED or -ASK responses returned by
|
||||
* Redis when redirecting a client to a different node.
|
||||
*
|
||||
* The cluster can be pre-initialized using only a subset of the actual nodes in
|
||||
@@ -40,13 +40,13 @@ use Predis\Command\RawCommand;
|
||||
* It is also possible to ask for the full and updated slots map directly to one
|
||||
* of the nodes and optionally enable such a behaviour upon -MOVED redirections.
|
||||
* Asking for the cluster configuration to Redis is actually done by issuing a
|
||||
* CLUSTER NODES command to a random node in the pool.
|
||||
* CLUSTER SLOTS command to a random node in the pool.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class RedisCluster implements ClusterConnectionInterface, IteratorAggregate, Countable
|
||||
{
|
||||
private $askClusterNodes = false;
|
||||
private $askClusterNodes = true;
|
||||
private $defaultParameters = array();
|
||||
private $pool = array();
|
||||
private $slots = array();
|
||||
@@ -169,31 +169,35 @@ class RedisCluster implements ClusterConnectionInterface, IteratorAggregate, Cou
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the current slots map by fetching the cluster configuration to
|
||||
* one of the nodes by leveraging the CLUSTER NODES command.
|
||||
* Generates an updated slots map fetching the cluster configuration using
|
||||
* the CLUSTER SLOTS command against the specified node or a random one from
|
||||
* the pool.
|
||||
*
|
||||
* @param SingleConnectionInterface $connection Optional connection instance.
|
||||
* @return array
|
||||
*/
|
||||
public function askClusterNodes()
|
||||
public function askClusterNodes(SingleConnectionInterface $connection = null)
|
||||
{
|
||||
if (!$connection = $this->getRandomConnection()) {
|
||||
if (!$connection && !$connection = $this->getRandomConnection()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$cmdCluster = RawCommand::create('CLUSTER', 'NODES');
|
||||
$response = $connection->executeCommand($cmdCluster);
|
||||
$command = RawCommand::create('CLUSTER', 'SLOTS');
|
||||
$response = $connection->executeCommand($command);
|
||||
|
||||
$nodes = explode("\n", $response, -1);
|
||||
$count = count($nodes);
|
||||
foreach ($response as $slots) {
|
||||
// We only support master servers for now, so we ignore subsequent
|
||||
// elements in the $slots array identifying slaves.
|
||||
list($start, $end, $master) = $slots;
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$node = explode(' ', $nodes[$i], 9);
|
||||
$slots = explode('-', $node[8], 2);
|
||||
|
||||
if ($node[1] === ':0') {
|
||||
$this->setSlots($slots[0], $slots[1], (string) $connection);
|
||||
if ($master[0] === '') {
|
||||
$this->setSlots($start, $end, (string) $connection);
|
||||
} else {
|
||||
$this->setSlots($slots[0], $slots[1], $node[1]);
|
||||
$this->setSlots($start, $end, "{$master[0]}:{$master[1]}");
|
||||
}
|
||||
}
|
||||
|
||||
return $this->slotsMap;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -400,7 +404,7 @@ class RedisCluster implements ClusterConnectionInterface, IteratorAggregate, Cou
|
||||
}
|
||||
|
||||
if ($this->askClusterNodes) {
|
||||
$this->askClusterNodes();
|
||||
$this->askClusterNodes($connection);
|
||||
}
|
||||
|
||||
$this->move($connection, $slot);
|
||||
@@ -490,8 +494,8 @@ class RedisCluster implements ClusterConnectionInterface, IteratorAggregate, Cou
|
||||
|
||||
/**
|
||||
* Enables automatic fetching of the current slots map from one of the nodes
|
||||
* using the CLUSTER NODES command. This option is disabled by default but
|
||||
* asking the current slots map to Redis upon -MOVE responses may reduce
|
||||
* using the CLUSTER SLOTS command. This option is disabled by default but
|
||||
* asking the current slots map to Redis upon -MOVED responses may reduce
|
||||
* overhead by eliminating the trial-and-error nature of the node guessing
|
||||
* procedure, mostly when targeting many keys that would end up in a lot of
|
||||
* redirections.
|
||||
@@ -499,7 +503,7 @@ class RedisCluster implements ClusterConnectionInterface, IteratorAggregate, Cou
|
||||
* The slots map can still be manually fetched using the askClusterNodes()
|
||||
* method whether or not this option is enabled.
|
||||
*
|
||||
* @param bool $value Enable or disable the use of CLUSTER NODES.
|
||||
* @param bool $value Enable or disable the use of CLUSTER SLOTS.
|
||||
*/
|
||||
public function enableClusterNodes($value)
|
||||
{
|
||||
|
||||
@@ -76,7 +76,7 @@ class StreamConnection extends AbstractConnection
|
||||
* @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
|
||||
* @return resource
|
||||
*/
|
||||
private function tcpStreamInitializer(ConnectionParametersInterface $parameters)
|
||||
protected function tcpStreamInitializer(ConnectionParametersInterface $parameters)
|
||||
{
|
||||
$uri = "tcp://{$parameters->host}:{$parameters->port}";
|
||||
$flags = STREAM_CLIENT_CONNECT;
|
||||
@@ -104,7 +104,7 @@ class StreamConnection extends AbstractConnection
|
||||
stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds);
|
||||
}
|
||||
|
||||
if (isset($parameters->tcp_nodelay) && version_compare(PHP_VERSION, '5.4.0') >= 0) {
|
||||
if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) {
|
||||
$socket = socket_import_stream($resource);
|
||||
socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
|
||||
}
|
||||
@@ -183,7 +183,7 @@ class StreamConnection extends AbstractConnection
|
||||
$socket = $this->getResource();
|
||||
|
||||
while (($length = strlen($buffer)) > 0) {
|
||||
$written = fwrite($socket, $buffer);
|
||||
$written = @fwrite($socket, $buffer);
|
||||
|
||||
if ($length === $written) {
|
||||
return;
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace Predis\Connection;
|
||||
use Predis\NotSupportedException;
|
||||
use Predis\ResponseError;
|
||||
use Predis\Command\CommandInterface;
|
||||
use Predis\Connection\ConnectionException;
|
||||
use Predis\Protocol\ProtocolException;
|
||||
|
||||
/**
|
||||
|
||||
@@ -50,7 +50,7 @@ class ClientConnectionFactory extends AbstractOption
|
||||
return $factory;
|
||||
}
|
||||
|
||||
if (@class_exists($value)) {
|
||||
if (is_string($value) && @class_exists($value)) {
|
||||
$factory = new $value();
|
||||
|
||||
if (!$factory instanceof ConnectionFactoryInterface) {
|
||||
|
||||
@@ -65,8 +65,8 @@ class PipelineContext implements BasicClientInterface, ExecutableContextInterfac
|
||||
/**
|
||||
* Queues a command into the pipeline buffer.
|
||||
*
|
||||
* @param string $method Command ID.
|
||||
* @param array $arguments Arguments for the command.
|
||||
* @param string $method Command ID.
|
||||
* @param array $arguments Arguments for the command.
|
||||
* @return $this
|
||||
*/
|
||||
public function __call($method, $arguments)
|
||||
@@ -90,7 +90,7 @@ class PipelineContext implements BasicClientInterface, ExecutableContextInterfac
|
||||
/**
|
||||
* Queues a command instance into the pipeline buffer.
|
||||
*
|
||||
* @param CommandInterface $command Command to queue in the buffer.
|
||||
* @param CommandInterface $command Command to queue in the buffer.
|
||||
* @return $this
|
||||
*/
|
||||
public function executeCommand(CommandInterface $command)
|
||||
|
||||
@@ -78,7 +78,8 @@ abstract class ServerProfile implements ServerProfileInterface, CommandProcessin
|
||||
'2.4' => 'Predis\Profile\ServerVersion24',
|
||||
'2.6' => 'Predis\Profile\ServerVersion26',
|
||||
'2.8' => 'Predis\Profile\ServerVersion28',
|
||||
'default' => 'Predis\Profile\ServerVersion26',
|
||||
'3.0' => 'Predis\Profile\ServerVersion30',
|
||||
'default' => 'Predis\Profile\ServerVersion28',
|
||||
'dev' => 'Predis\Profile\ServerVersionNext',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -239,9 +239,20 @@ class ServerVersion28 extends ServerProfile
|
||||
|
||||
/* commands operating on sorted sets */
|
||||
'zscan' => 'Predis\Command\ZSetScan',
|
||||
'zlexcount' => 'Predis\Command\ZSetLexCount',
|
||||
'zrangebylex' => 'Predis\Command\ZSetRangeByLex',
|
||||
'zremrangebylex' => 'Predis\Command\ZSetRemoveRangeByLex',
|
||||
|
||||
/* commands operating on hashes */
|
||||
'hscan' => 'Predis\Command\HashScan',
|
||||
|
||||
/* commands operating on HyperLogLog */
|
||||
'pfadd' => 'Predis\Command\HyperLogLogAdd',
|
||||
'pfcount' => 'Predis\Command\HyperLogLogCount',
|
||||
'pfmerge' => 'Predis\Command\HyperLogLogMerge',
|
||||
|
||||
/* remote server control commands */
|
||||
'command' => 'Predis\Command\ServerCommand',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Profile;
|
||||
|
||||
/**
|
||||
* Server profile for Redis v3.0.x.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class ServerVersion30 extends ServerProfile
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return '3.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSupportedCommands()
|
||||
{
|
||||
return array(
|
||||
/* ---------------- Redis 1.2 ---------------- */
|
||||
|
||||
/* commands operating on the key space */
|
||||
'exists' => 'Predis\Command\KeyExists',
|
||||
'del' => 'Predis\Command\KeyDelete',
|
||||
'type' => 'Predis\Command\KeyType',
|
||||
'keys' => 'Predis\Command\KeyKeys',
|
||||
'randomkey' => 'Predis\Command\KeyRandom',
|
||||
'rename' => 'Predis\Command\KeyRename',
|
||||
'renamenx' => 'Predis\Command\KeyRenamePreserve',
|
||||
'expire' => 'Predis\Command\KeyExpire',
|
||||
'expireat' => 'Predis\Command\KeyExpireAt',
|
||||
'ttl' => 'Predis\Command\KeyTimeToLive',
|
||||
'move' => 'Predis\Command\KeyMove',
|
||||
'sort' => 'Predis\Command\KeySort',
|
||||
'dump' => 'Predis\Command\KeyDump',
|
||||
'restore' => 'Predis\Command\KeyRestore',
|
||||
|
||||
/* commands operating on string values */
|
||||
'set' => 'Predis\Command\StringSet',
|
||||
'setnx' => 'Predis\Command\StringSetPreserve',
|
||||
'mset' => 'Predis\Command\StringSetMultiple',
|
||||
'msetnx' => 'Predis\Command\StringSetMultiplePreserve',
|
||||
'get' => 'Predis\Command\StringGet',
|
||||
'mget' => 'Predis\Command\StringGetMultiple',
|
||||
'getset' => 'Predis\Command\StringGetSet',
|
||||
'incr' => 'Predis\Command\StringIncrement',
|
||||
'incrby' => 'Predis\Command\StringIncrementBy',
|
||||
'decr' => 'Predis\Command\StringDecrement',
|
||||
'decrby' => 'Predis\Command\StringDecrementBy',
|
||||
|
||||
/* commands operating on lists */
|
||||
'rpush' => 'Predis\Command\ListPushTail',
|
||||
'lpush' => 'Predis\Command\ListPushHead',
|
||||
'llen' => 'Predis\Command\ListLength',
|
||||
'lrange' => 'Predis\Command\ListRange',
|
||||
'ltrim' => 'Predis\Command\ListTrim',
|
||||
'lindex' => 'Predis\Command\ListIndex',
|
||||
'lset' => 'Predis\Command\ListSet',
|
||||
'lrem' => 'Predis\Command\ListRemove',
|
||||
'lpop' => 'Predis\Command\ListPopFirst',
|
||||
'rpop' => 'Predis\Command\ListPopLast',
|
||||
'rpoplpush' => 'Predis\Command\ListPopLastPushHead',
|
||||
|
||||
/* commands operating on sets */
|
||||
'sadd' => 'Predis\Command\SetAdd',
|
||||
'srem' => 'Predis\Command\SetRemove',
|
||||
'spop' => 'Predis\Command\SetPop',
|
||||
'smove' => 'Predis\Command\SetMove',
|
||||
'scard' => 'Predis\Command\SetCardinality',
|
||||
'sismember' => 'Predis\Command\SetIsMember',
|
||||
'sinter' => 'Predis\Command\SetIntersection',
|
||||
'sinterstore' => 'Predis\Command\SetIntersectionStore',
|
||||
'sunion' => 'Predis\Command\SetUnion',
|
||||
'sunionstore' => 'Predis\Command\SetUnionStore',
|
||||
'sdiff' => 'Predis\Command\SetDifference',
|
||||
'sdiffstore' => 'Predis\Command\SetDifferenceStore',
|
||||
'smembers' => 'Predis\Command\SetMembers',
|
||||
'srandmember' => 'Predis\Command\SetRandomMember',
|
||||
|
||||
/* commands operating on sorted sets */
|
||||
'zadd' => 'Predis\Command\ZSetAdd',
|
||||
'zincrby' => 'Predis\Command\ZSetIncrementBy',
|
||||
'zrem' => 'Predis\Command\ZSetRemove',
|
||||
'zrange' => 'Predis\Command\ZSetRange',
|
||||
'zrevrange' => 'Predis\Command\ZSetReverseRange',
|
||||
'zrangebyscore' => 'Predis\Command\ZSetRangeByScore',
|
||||
'zcard' => 'Predis\Command\ZSetCardinality',
|
||||
'zscore' => 'Predis\Command\ZSetScore',
|
||||
'zremrangebyscore' => 'Predis\Command\ZSetRemoveRangeByScore',
|
||||
|
||||
/* connection related commands */
|
||||
'ping' => 'Predis\Command\ConnectionPing',
|
||||
'auth' => 'Predis\Command\ConnectionAuth',
|
||||
'select' => 'Predis\Command\ConnectionSelect',
|
||||
'echo' => 'Predis\Command\ConnectionEcho',
|
||||
'quit' => 'Predis\Command\ConnectionQuit',
|
||||
|
||||
/* remote server control commands */
|
||||
'info' => 'Predis\Command\ServerInfoV26x',
|
||||
'slaveof' => 'Predis\Command\ServerSlaveOf',
|
||||
'monitor' => 'Predis\Command\ServerMonitor',
|
||||
'dbsize' => 'Predis\Command\ServerDatabaseSize',
|
||||
'flushdb' => 'Predis\Command\ServerFlushDatabase',
|
||||
'flushall' => 'Predis\Command\ServerFlushAll',
|
||||
'save' => 'Predis\Command\ServerSave',
|
||||
'bgsave' => 'Predis\Command\ServerBackgroundSave',
|
||||
'lastsave' => 'Predis\Command\ServerLastSave',
|
||||
'shutdown' => 'Predis\Command\ServerShutdown',
|
||||
'bgrewriteaof' => 'Predis\Command\ServerBackgroundRewriteAOF',
|
||||
|
||||
/* ---------------- Redis 2.0 ---------------- */
|
||||
|
||||
/* commands operating on string values */
|
||||
'setex' => 'Predis\Command\StringSetExpire',
|
||||
'append' => 'Predis\Command\StringAppend',
|
||||
'substr' => 'Predis\Command\StringSubstr',
|
||||
|
||||
/* commands operating on lists */
|
||||
'blpop' => 'Predis\Command\ListPopFirstBlocking',
|
||||
'brpop' => 'Predis\Command\ListPopLastBlocking',
|
||||
|
||||
/* commands operating on sorted sets */
|
||||
'zunionstore' => 'Predis\Command\ZSetUnionStore',
|
||||
'zinterstore' => 'Predis\Command\ZSetIntersectionStore',
|
||||
'zcount' => 'Predis\Command\ZSetCount',
|
||||
'zrank' => 'Predis\Command\ZSetRank',
|
||||
'zrevrank' => 'Predis\Command\ZSetReverseRank',
|
||||
'zremrangebyrank' => 'Predis\Command\ZSetRemoveRangeByRank',
|
||||
|
||||
/* commands operating on hashes */
|
||||
'hset' => 'Predis\Command\HashSet',
|
||||
'hsetnx' => 'Predis\Command\HashSetPreserve',
|
||||
'hmset' => 'Predis\Command\HashSetMultiple',
|
||||
'hincrby' => 'Predis\Command\HashIncrementBy',
|
||||
'hget' => 'Predis\Command\HashGet',
|
||||
'hmget' => 'Predis\Command\HashGetMultiple',
|
||||
'hdel' => 'Predis\Command\HashDelete',
|
||||
'hexists' => 'Predis\Command\HashExists',
|
||||
'hlen' => 'Predis\Command\HashLength',
|
||||
'hkeys' => 'Predis\Command\HashKeys',
|
||||
'hvals' => 'Predis\Command\HashValues',
|
||||
'hgetall' => 'Predis\Command\HashGetAll',
|
||||
|
||||
/* transactions */
|
||||
'multi' => 'Predis\Command\TransactionMulti',
|
||||
'exec' => 'Predis\Command\TransactionExec',
|
||||
'discard' => 'Predis\Command\TransactionDiscard',
|
||||
|
||||
/* publish - subscribe */
|
||||
'subscribe' => 'Predis\Command\PubSubSubscribe',
|
||||
'unsubscribe' => 'Predis\Command\PubSubUnsubscribe',
|
||||
'psubscribe' => 'Predis\Command\PubSubSubscribeByPattern',
|
||||
'punsubscribe' => 'Predis\Command\PubSubUnsubscribeByPattern',
|
||||
'publish' => 'Predis\Command\PubSubPublish',
|
||||
|
||||
/* remote server control commands */
|
||||
'config' => 'Predis\Command\ServerConfig',
|
||||
|
||||
/* ---------------- Redis 2.2 ---------------- */
|
||||
|
||||
/* commands operating on the key space */
|
||||
'persist' => 'Predis\Command\KeyPersist',
|
||||
|
||||
/* commands operating on string values */
|
||||
'strlen' => 'Predis\Command\StringStrlen',
|
||||
'setrange' => 'Predis\Command\StringSetRange',
|
||||
'getrange' => 'Predis\Command\StringGetRange',
|
||||
'setbit' => 'Predis\Command\StringSetBit',
|
||||
'getbit' => 'Predis\Command\StringGetBit',
|
||||
|
||||
/* commands operating on lists */
|
||||
'rpushx' => 'Predis\Command\ListPushTailX',
|
||||
'lpushx' => 'Predis\Command\ListPushHeadX',
|
||||
'linsert' => 'Predis\Command\ListInsert',
|
||||
'brpoplpush' => 'Predis\Command\ListPopLastPushHeadBlocking',
|
||||
|
||||
/* commands operating on sorted sets */
|
||||
'zrevrangebyscore' => 'Predis\Command\ZSetReverseRangeByScore',
|
||||
|
||||
/* transactions */
|
||||
'watch' => 'Predis\Command\TransactionWatch',
|
||||
'unwatch' => 'Predis\Command\TransactionUnwatch',
|
||||
|
||||
/* remote server control commands */
|
||||
'object' => 'Predis\Command\ServerObject',
|
||||
'slowlog' => 'Predis\Command\ServerSlowlog',
|
||||
|
||||
/* ---------------- Redis 2.4 ---------------- */
|
||||
|
||||
/* remote server control commands */
|
||||
'client' => 'Predis\Command\ServerClient',
|
||||
|
||||
/* ---------------- Redis 2.6 ---------------- */
|
||||
|
||||
/* commands operating on the key space */
|
||||
'pttl' => 'Predis\Command\KeyPreciseTimeToLive',
|
||||
'pexpire' => 'Predis\Command\KeyPreciseExpire',
|
||||
'pexpireat' => 'Predis\Command\KeyPreciseExpireAt',
|
||||
|
||||
/* commands operating on string values */
|
||||
'psetex' => 'Predis\Command\StringPreciseSetExpire',
|
||||
'incrbyfloat' => 'Predis\Command\StringIncrementByFloat',
|
||||
'bitop' => 'Predis\Command\StringBitOp',
|
||||
'bitcount' => 'Predis\Command\StringBitCount',
|
||||
|
||||
/* commands operating on hashes */
|
||||
'hincrbyfloat' => 'Predis\Command\HashIncrementByFloat',
|
||||
|
||||
/* scripting */
|
||||
'eval' => 'Predis\Command\ServerEval',
|
||||
'evalsha' => 'Predis\Command\ServerEvalSHA',
|
||||
'script' => 'Predis\Command\ServerScript',
|
||||
|
||||
/* remote server control commands */
|
||||
'time' => 'Predis\Command\ServerTime',
|
||||
|
||||
/* ---------------- Redis 2.8 ---------------- */
|
||||
|
||||
/* commands operating on the key space */
|
||||
'scan' => 'Predis\Command\KeyScan',
|
||||
|
||||
/* commands operating on sets */
|
||||
'sscan' => 'Predis\Command\SetScan',
|
||||
|
||||
/* commands operating on sorted sets */
|
||||
'zscan' => 'Predis\Command\ZSetScan',
|
||||
'zlexcount' => 'Predis\Command\ZSetLexCount',
|
||||
'zrangebylex' => 'Predis\Command\ZSetRangeByLex',
|
||||
'zremrangebylex' => 'Predis\Command\ZSetRemoveRangeByLex',
|
||||
|
||||
/* commands operating on hashes */
|
||||
'hscan' => 'Predis\Command\HashScan',
|
||||
|
||||
/* commands operating on HyperLogLog */
|
||||
'pfadd' => 'Predis\Command\HyperLogLogAdd',
|
||||
'pfcount' => 'Predis\Command\HyperLogLogCount',
|
||||
'pfmerge' => 'Predis\Command\HyperLogLogMerge',
|
||||
|
||||
/* remote server control commands */
|
||||
'command' => 'Predis\Command\ServerCommand',
|
||||
|
||||
/* ---------------- Redis 3.0 ---------------- */
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace Predis\Profile;
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class ServerVersionNext extends ServerVersion28
|
||||
class ServerVersionNext extends ServerVersion30
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
||||
@@ -24,6 +24,7 @@ abstract class AbstractPubSubContext implements \Iterator
|
||||
const PUNSUBSCRIBE = 'punsubscribe';
|
||||
const MESSAGE = 'message';
|
||||
const PMESSAGE = 'pmessage';
|
||||
const PONG = 'pong';
|
||||
|
||||
const STATUS_VALID = 1; // 0b0001
|
||||
const STATUS_SUBSCRIBED = 2; // 0b0010
|
||||
@@ -51,6 +52,17 @@ abstract class AbstractPubSubContext implements \Iterator
|
||||
return ($this->statusFlags & $value) === $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* PING the server with an optional payload that will be echoed as a
|
||||
* PONG message in the pub/sub loop.
|
||||
*
|
||||
* @param string $payload Optional PING payload.
|
||||
*/
|
||||
public function ping($payload = null)
|
||||
{
|
||||
$this->writeCommand('PING', array($payload));
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to the specified channels.
|
||||
*
|
||||
|
||||
@@ -133,6 +133,12 @@ class PubSubContext extends AbstractPubSubContext
|
||||
'payload' => $response[3],
|
||||
);
|
||||
|
||||
case self::PONG:
|
||||
return (object) array(
|
||||
'kind' => $response[0],
|
||||
'payload' => $response[1],
|
||||
);
|
||||
|
||||
default:
|
||||
$message = "Received an unknown message type {$response[0]} inside of a pubsub context";
|
||||
throw new ClientException($message);
|
||||
|
||||
@@ -201,6 +201,8 @@ class ReplicationStrategy
|
||||
'ZRANK' => true,
|
||||
'ZREVRANK' => true,
|
||||
'ZSCAN' => true,
|
||||
'ZLEXCOUNT' => true,
|
||||
'ZRANGEBYLEX' => true,
|
||||
'HGET' => true,
|
||||
'HMGET' => true,
|
||||
'HEXISTS' => true,
|
||||
@@ -217,6 +219,7 @@ class ReplicationStrategy
|
||||
'OBJECT' => true,
|
||||
'BITCOUNT' => true,
|
||||
'TIME' => true,
|
||||
'PFCOUNT' => true,
|
||||
'SORT' => array($this, 'isSortReadOnly'),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,11 +123,11 @@ class MultiExecContext implements BasicClientInterface, ExecutableContextInterfa
|
||||
|
||||
$profile = $client->getProfile();
|
||||
|
||||
if ($profile->supportsCommands(array('multi', 'exec', 'discard')) === false) {
|
||||
if ($profile->supportsCommands(array('MULTI', 'EXEC', 'DISCARD')) === false) {
|
||||
throw new NotSupportedException('The current profile does not support MULTI, EXEC and DISCARD');
|
||||
}
|
||||
|
||||
$this->canWatch = $profile->supportsCommands(array('watch', 'unwatch'));
|
||||
$this->canWatch = $profile->supportsCommands(array('WATCH', 'UNWATCH'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,23 +200,26 @@ class MultiExecContext implements BasicClientInterface, ExecutableContextInterfa
|
||||
/**
|
||||
* Executes the specified Redis command.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @return $this|mixed
|
||||
*/
|
||||
public function executeCommand(CommandInterface $command)
|
||||
{
|
||||
$this->initialize();
|
||||
$response = $this->client->executeCommand($command);
|
||||
|
||||
if ($this->checkState(self::STATE_CAS)) {
|
||||
return $response;
|
||||
return $this->client->executeCommand($command);
|
||||
}
|
||||
|
||||
if (!$response instanceof ResponseQueued) {
|
||||
$this->onProtocolError('The server did not respond with a QUEUED status reply');
|
||||
}
|
||||
$response = $this->client->getConnection()->executeCommand($command);
|
||||
|
||||
$this->commands->enqueue($command);
|
||||
if ($response instanceof ResponseQueued) {
|
||||
$this->commands->enqueue($command);
|
||||
} elseif ($response instanceof ResponseErrorInterface) {
|
||||
throw new AbortedMultiExecException($this, $response->getMessage());
|
||||
} else {
|
||||
$this->onProtocolError('The server did not return a +QUEUED status response.');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
+1
-2
@@ -10,7 +10,7 @@ name = "Predis"
|
||||
desc = "Flexible and feature-complete PHP client library for Redis"
|
||||
homepage = "http://github.com/nrk/predis"
|
||||
license = "MIT"
|
||||
version = "0.8.5"
|
||||
version = "0.8.7"
|
||||
stability = "stable"
|
||||
channel = "pear.nrk.io"
|
||||
|
||||
@@ -24,7 +24,6 @@ pearinstaller = "1.4.1"
|
||||
*.xml.dist = test
|
||||
*.md = doc
|
||||
LICENSE = doc
|
||||
lib = php
|
||||
|
||||
[optional phpiredis]
|
||||
hint = "Add support for faster protocol handling with phpiredis"
|
||||
|
||||
@@ -20,6 +20,8 @@ use Predis\Profile\ServerProfileInterface;
|
||||
*/
|
||||
abstract class PredisTestCase extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected $redisServerVersion = null;
|
||||
|
||||
/**
|
||||
* Verifies that a Redis command is a valid Predis\Command\CommandInterface
|
||||
* instance with the specified ID and command arguments.
|
||||
@@ -164,13 +166,17 @@ abstract class PredisTestCase extends PHPUnit_Framework_TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $expectedVersion Expected redis version.
|
||||
* @param string $operator Comparison operator.
|
||||
* @param callable $callback Callback for matching version.
|
||||
* @throws PHPUnit_Framework_SkippedTestError When expected redis version is not met.
|
||||
* Returns the server version of the Redis instance used by the test suite.
|
||||
*
|
||||
* @return string
|
||||
* @throws RuntimeException When the client cannot retrieve the current server version
|
||||
*/
|
||||
protected function executeOnRedisVersion($expectedVersion, $operator, $callback)
|
||||
protected function getRedisServerVersion()
|
||||
{
|
||||
if (isset($this->redisServerVersion)) {
|
||||
return $this->redisServerVersion;
|
||||
}
|
||||
|
||||
$client = $this->createClient(null, null, true);
|
||||
$info = array_change_key_case($client->info());
|
||||
|
||||
@@ -184,6 +190,20 @@ abstract class PredisTestCase extends PHPUnit_Framework_TestCase
|
||||
throw new RuntimeException('Unable to retrieve server info');
|
||||
}
|
||||
|
||||
$this->redisServerVersion = $version;
|
||||
|
||||
return $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $expectedVersion Expected redis version.
|
||||
* @param string $operator Comparison operator.
|
||||
* @param callable $callback Callback for matching version.
|
||||
* @throws PHPUnit_Framework_SkippedTestError When expected redis version is not met
|
||||
*/
|
||||
protected function executeOnRedisVersion($expectedVersion, $operator, $callback)
|
||||
{
|
||||
$version = $this->getRedisServerVersion();
|
||||
$comparation = version_compare($version, $expectedVersion);
|
||||
|
||||
if ($match = eval("return $comparation $operator 0;")) {
|
||||
@@ -211,23 +231,6 @@ abstract class PredisTestCase extends PHPUnit_Framework_TestCase
|
||||
return $match;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $expectedVersion Expected redis version.
|
||||
* @param string $message Optional message.
|
||||
* @param bool $remote Based on local profile or remote redis version.
|
||||
* @throws RuntimeException when unable to retrieve server info or redis version
|
||||
* @throws \PHPUnit_Framework_SkippedTestError when expected redis version is not met
|
||||
*/
|
||||
public function markTestSkippedOnRedisVersionBelow($expectedVersion, $message = '', $remote = true)
|
||||
{
|
||||
$callback = function ($test, $version) use ($message, $expectedVersion) {
|
||||
$test->markTestSkipped($message ?: "Test requires Redis $expectedVersion, current is $version.");
|
||||
};
|
||||
|
||||
$method = $remote ? 'executeOnRedisVersion' : 'executeOnProfileVersion';
|
||||
$this->$method($expectedVersion, '<', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep the test case with microseconds resolution.
|
||||
*
|
||||
@@ -237,4 +240,68 @@ abstract class PredisTestCase extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
usleep($seconds * 1000000);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function setRequiredRedisVersionFromAnnotation()
|
||||
{
|
||||
$annotations = $this->getAnnotations();
|
||||
|
||||
if (isset($annotations['method']['requiresRedisVersion'], $annotations['method']['group']) &&
|
||||
!empty($annotations['method']['requiresRedisVersion']) &&
|
||||
in_array('connected', $annotations['method']['group'])
|
||||
) {
|
||||
$this->required['requiresRedisVersion'] = $annotations['method']['requiresRedisVersion'][0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function checkRequiredRedisVersion()
|
||||
{
|
||||
if (!isset($this->required['requiresRedisVersion'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$srvVersion = $this->getRedisServerVersion();
|
||||
$expectation = explode(' ', $this->required['requiresRedisVersion'], 2);
|
||||
|
||||
if (count($expectation) === 1) {
|
||||
$expOperator = '>=';
|
||||
$expVersion = $expectation[0];
|
||||
} else {
|
||||
$expOperator = $expectation[0];
|
||||
$expVersion = $expectation[1];
|
||||
}
|
||||
|
||||
$comparation = version_compare($srvVersion, $expVersion);
|
||||
|
||||
if (!$match = eval("return $comparation $expOperator 0;")) {
|
||||
$this->markTestSkipped(
|
||||
"This test requires Redis $expOperator $expVersion but the current version is $srvVersion."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setRequirementsFromAnnotation()
|
||||
{
|
||||
parent::setRequirementsFromAnnotation();
|
||||
|
||||
$this->setRequiredRedisVersionFromAnnotation();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkRequirements()
|
||||
{
|
||||
parent::checkRequirements();
|
||||
|
||||
$this->checkRequiredRedisVersion();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,6 +277,7 @@ class PredisClusterHashStrategyTest extends PredisTestCase
|
||||
'GETSET' => 'keys-first',
|
||||
'INCR' => 'keys-first',
|
||||
'INCRBY' => 'keys-first',
|
||||
'INCRBYFLOAT' => 'keys-first',
|
||||
'SETBIT' => 'keys-first',
|
||||
'SETEX' => 'keys-first',
|
||||
'MSET' => 'keys-interleaved',
|
||||
@@ -341,6 +342,9 @@ class PredisClusterHashStrategyTest extends PredisTestCase
|
||||
'ZSCORE' => 'keys-first',
|
||||
'ZUNIONSTORE' => 'keys-zaggregated',
|
||||
'ZSCAN' => 'keys-first',
|
||||
'ZLEXCOUNT' => 'keys-first',
|
||||
'ZRANGEBYLEX' => 'keys-first',
|
||||
'ZREMRANGEBYLEX' => 'keys-first',
|
||||
|
||||
/* commands operating on hashes */
|
||||
'HDEL' => 'keys-first',
|
||||
@@ -358,6 +362,11 @@ class PredisClusterHashStrategyTest extends PredisTestCase
|
||||
'HVALS' => 'keys-first',
|
||||
'HSCAN' => 'keys-first',
|
||||
|
||||
/* commands operating on HyperLogLog */
|
||||
'PFADD' => 'keys-first',
|
||||
'PFCOUNT' => 'keys-all',
|
||||
'PFMERGE' => 'keys-all',
|
||||
|
||||
/* scripting */
|
||||
'EVAL' => 'keys-script',
|
||||
'EVALSHA' => 'keys-script',
|
||||
|
||||
@@ -22,14 +22,21 @@ class RedisClusterHashStrategyTest extends PredisTestCase
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testDoesNotSupportKeyTags()
|
||||
public function testSupportsKeyTags()
|
||||
{
|
||||
$strategy = $this->getHashStrategy();
|
||||
|
||||
$this->assertSame(35910, $strategy->getKeyHash('{foo}'));
|
||||
$this->assertSame(60032, $strategy->getKeyHash('{foo}:bar'));
|
||||
$this->assertSame(27528, $strategy->getKeyHash('{foo}:baz'));
|
||||
$this->assertSame(34064, $strategy->getKeyHash('bar:{foo}:bar'));
|
||||
$this->assertSame(44950, $strategy->getKeyHash('{foo}'));
|
||||
$this->assertSame(44950, $strategy->getKeyHash('{foo}:bar'));
|
||||
$this->assertSame(44950, $strategy->getKeyHash('{foo}:baz'));
|
||||
$this->assertSame(44950, $strategy->getKeyHash('bar:{foo}:baz'));
|
||||
$this->assertSame(44950, $strategy->getKeyHash('bar:{foo}:{baz}'));
|
||||
|
||||
$this->assertSame(44950, $strategy->getKeyHash('bar:{foo}:baz{}'));
|
||||
$this->assertSame(9415, $strategy->getKeyHash('{}bar:{foo}:baz'));
|
||||
|
||||
$this->assertSame(0, $strategy->getKeyHash(''));
|
||||
$this->assertSame(31641, $strategy->getKeyHash('{}'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,6 +275,8 @@ class RedisClusterHashStrategyTest extends PredisTestCase
|
||||
'TTL' => 'keys-first',
|
||||
'PTTL' => 'keys-first',
|
||||
'SORT' => 'keys-first', // TODO
|
||||
'DUMP' => 'keys-first',
|
||||
'RESTORE' => 'keys-first',
|
||||
|
||||
/* commands operating on string values */
|
||||
'APPEND' => 'keys-first',
|
||||
@@ -281,6 +290,7 @@ class RedisClusterHashStrategyTest extends PredisTestCase
|
||||
'GETSET' => 'keys-first',
|
||||
'INCR' => 'keys-first',
|
||||
'INCRBY' => 'keys-first',
|
||||
'INCRBYFLOAT' => 'keys-first',
|
||||
'SETBIT' => 'keys-first',
|
||||
'SETEX' => 'keys-first',
|
||||
'MSET' => 'keys-interleaved',
|
||||
@@ -289,6 +299,7 @@ class RedisClusterHashStrategyTest extends PredisTestCase
|
||||
'SETRANGE' => 'keys-first',
|
||||
'STRLEN' => 'keys-first',
|
||||
'SUBSTR' => 'keys-first',
|
||||
'BITOP' => 'keys-bitop',
|
||||
'BITCOUNT' => 'keys-first',
|
||||
|
||||
/* commands operating on lists */
|
||||
@@ -297,8 +308,10 @@ class RedisClusterHashStrategyTest extends PredisTestCase
|
||||
'LLEN' => 'keys-first',
|
||||
'LPOP' => 'keys-first',
|
||||
'RPOP' => 'keys-first',
|
||||
'RPOPLPUSH' => 'keys-all',
|
||||
'BLPOP' => 'keys-blockinglist',
|
||||
'BRPOP' => 'keys-blockinglist',
|
||||
'BRPOPLPUSH' => 'keys-blockinglist',
|
||||
'LPUSH' => 'keys-first',
|
||||
'LPUSHX' => 'keys-first',
|
||||
'RPUSH' => 'keys-first',
|
||||
@@ -311,6 +324,12 @@ class RedisClusterHashStrategyTest extends PredisTestCase
|
||||
/* commands operating on sets */
|
||||
'SADD' => 'keys-first',
|
||||
'SCARD' => 'keys-first',
|
||||
'SDIFF' => 'keys-all',
|
||||
'SDIFFSTORE' => 'keys-all',
|
||||
'SINTER' => 'keys-all',
|
||||
'SINTERSTORE' => 'keys-all',
|
||||
'SUNION' => 'keys-all',
|
||||
'SUNIONSTORE' => 'keys-all',
|
||||
'SISMEMBER' => 'keys-first',
|
||||
'SMEMBERS' => 'keys-first',
|
||||
'SSCAN' => 'keys-first',
|
||||
@@ -323,6 +342,7 @@ class RedisClusterHashStrategyTest extends PredisTestCase
|
||||
'ZCARD' => 'keys-first',
|
||||
'ZCOUNT' => 'keys-first',
|
||||
'ZINCRBY' => 'keys-first',
|
||||
'ZINTERSTORE' => 'keys-zaggregated',
|
||||
'ZRANGE' => 'keys-first',
|
||||
'ZRANGEBYSCORE' => 'keys-first',
|
||||
'ZRANK' => 'keys-first',
|
||||
@@ -333,7 +353,11 @@ class RedisClusterHashStrategyTest extends PredisTestCase
|
||||
'ZREVRANGEBYSCORE' => 'keys-first',
|
||||
'ZREVRANK' => 'keys-first',
|
||||
'ZSCORE' => 'keys-first',
|
||||
'ZUNIONSTORE' => 'keys-zaggregated',
|
||||
'ZSCAN' => 'keys-first',
|
||||
'ZLEXCOUNT' => 'keys-first',
|
||||
'ZRANGEBYLEX' => 'keys-first',
|
||||
'ZREMRANGEBYLEX' => 'keys-first',
|
||||
|
||||
/* commands operating on hashes */
|
||||
'HDEL' => 'keys-first',
|
||||
@@ -351,6 +375,11 @@ class RedisClusterHashStrategyTest extends PredisTestCase
|
||||
'HVALS' => 'keys-first',
|
||||
'HSCAN' => 'keys-first',
|
||||
|
||||
/* commands operating on HyperLogLog */
|
||||
'PFADD' => 'keys-first',
|
||||
'PFCOUNT' => 'keys-all',
|
||||
'PFMERGE' => 'keys-all',
|
||||
|
||||
/* scripting */
|
||||
'EVAL' => 'keys-script',
|
||||
'EVALSHA' => 'keys-script',
|
||||
|
||||
@@ -18,7 +18,7 @@ use Predis\Profile\ServerProfile;
|
||||
/**
|
||||
* @group realm-iterators
|
||||
*/
|
||||
class SortedSetTest extends PredisTestCase
|
||||
class SortedSetKeyTest extends PredisTestCase
|
||||
{
|
||||
/**
|
||||
* @group disconnected
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @group commands
|
||||
* @group realm-hyperloglog
|
||||
* @todo Add integration tests depending on the minor redis version
|
||||
*/
|
||||
class HyperLogLogAddTest extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand()
|
||||
{
|
||||
return 'Predis\Command\HyperLogLogAdd';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId()
|
||||
{
|
||||
return 'PFADD';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments()
|
||||
{
|
||||
$arguments = array('key', 'a', 'b', 'c');
|
||||
$expected = array('key', 'a', 'b', 'c');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArgumentsFieldsAsSingleArray()
|
||||
{
|
||||
$arguments = array('key', array('a', 'b', 'c'));
|
||||
$expected = array('key', 'a', 'b', 'c');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse()
|
||||
{
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->assertSame(false, $command->parseResponse(0));
|
||||
$this->assertSame(true, $command->parseResponse(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
* @expectedException Predis\ServerException
|
||||
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
|
||||
*/
|
||||
public function testThrowsExceptionOnWrongType()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->lpush('metavars', 'foo', 'hoge');
|
||||
$redis->pfadd('metavars', 'foofoo');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @group commands
|
||||
* @group realm-hyperloglog
|
||||
* @todo Add integration tests depending on the minor redis version
|
||||
*/
|
||||
class HyperLogLogCountTest extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand()
|
||||
{
|
||||
return 'Predis\Command\HyperLogLogCount';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId()
|
||||
{
|
||||
return 'PFCOUNT';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments()
|
||||
{
|
||||
$arguments = array('key:1', 'key:2');
|
||||
$expected = array('key:1', 'key:2');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArgumentsFieldsAsSingleArray()
|
||||
{
|
||||
$arguments = array(array('key:1', 'key:2'));
|
||||
$expected = array('key:1', 'key:2');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse()
|
||||
{
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->assertSame(0, $command->parseResponse(0));
|
||||
$this->assertSame(1, $command->parseResponse(1));
|
||||
$this->assertSame(10, $command->parseResponse(10));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
* @expectedException Predis\ServerException
|
||||
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
|
||||
*/
|
||||
public function testThrowsExceptionOnWrongType()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->lpush('metavars', 'foo', 'hoge');
|
||||
$redis->pfcount('metavars');
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
* @expectedException Predis\ServerException
|
||||
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
|
||||
*/
|
||||
public function testThrowsExceptionOnWrongTypeOfAtLeastOneKey()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->pfadd('metavars:1', 'foo', 'hoge');
|
||||
$redis->lpush('metavars:2', 'foofoo');
|
||||
$redis->pfcount('metavars:1', 'metavars:2');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @group commands
|
||||
* @group realm-hyperloglog
|
||||
* @todo Add integration tests depending on the minor redis version
|
||||
*/
|
||||
class HyperLogLogMergeTest extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand()
|
||||
{
|
||||
return 'Predis\Command\HyperLogLogMerge';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId()
|
||||
{
|
||||
return 'PFMERGE';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments()
|
||||
{
|
||||
$arguments = array('key:1', 'key:2', 'key:3');
|
||||
$expected = array('key:1', 'key:2', 'key:3');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArgumentsFieldsAsSingleArray()
|
||||
{
|
||||
$arguments = array(array('key:1', 'key:2', 'key:3'));
|
||||
$expected = array('key:1', 'key:2', 'key:3');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse()
|
||||
{
|
||||
$this->assertSame('OK', $this->getCommand()->parseResponse('OK'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
* @expectedException Predis\ServerException
|
||||
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
|
||||
*/
|
||||
public function testThrowsExceptionOnWrongType()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->pfadd('metavars:1', 'foo', 'hoge');
|
||||
$redis->lpush('metavars:2', 'foofoo');
|
||||
$redis->pfmerge('metavars:1', 'metavars:2');
|
||||
}
|
||||
}
|
||||
@@ -69,9 +69,12 @@ class RawCommandTest extends PredisTestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* The signature of RawCommand::create() requires one argument which is the
|
||||
* ID of the command (other arguments are fetched dinamically). If the first
|
||||
* argument is missing, PHP emits an E_WARNING.
|
||||
*
|
||||
* @group disconnected
|
||||
* @expectedException PHPUnit_Framework_Error_Warning
|
||||
* @expectedExceptionMessage Missing argument 1 for Predis\Command\RawCommand::create()
|
||||
*/
|
||||
public function testPHPWarningOnMissingCommandIDWithStaticCreate()
|
||||
{
|
||||
|
||||
@@ -143,11 +143,10 @@ BUFFER;
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.6.9
|
||||
*/
|
||||
public function testGetsNameOfConnection()
|
||||
{
|
||||
$this->markTestSkippedOnRedisVersionBelow('2.6.9');
|
||||
|
||||
$redis = $this->getClient();
|
||||
$clientName = $redis->client('GETNAME');
|
||||
$this->assertNull($clientName);
|
||||
@@ -159,11 +158,10 @@ BUFFER;
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.6.9
|
||||
*/
|
||||
public function testSetsNameOfConnection()
|
||||
{
|
||||
$this->markTestSkippedOnRedisVersionBelow('2.6.9');
|
||||
|
||||
$redis = $this->getClient();
|
||||
|
||||
$expectedConnectionName = 'foo-baz';
|
||||
@@ -185,13 +183,12 @@ BUFFER;
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @expectedException Predis\ServerException
|
||||
* @requiresRedisVersion >= 2.6.9
|
||||
* @dataProvider invalidConnectionNameProvider
|
||||
* @expectedException Predis\ServerException
|
||||
*/
|
||||
public function testInvalidSetNameOfConnection($invalidConnectionName)
|
||||
{
|
||||
$this->markTestSkippedOnRedisVersionBelow('2.6.9');
|
||||
|
||||
$redis = $this->getClient();
|
||||
$redis->client('SETNAME', $invalidConnectionName);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @group commands
|
||||
* @group realm-server
|
||||
*/
|
||||
class ServerCommandTest extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand()
|
||||
{
|
||||
return 'Predis\Command\ServerCommand';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId()
|
||||
{
|
||||
return 'COMMAND';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments()
|
||||
{
|
||||
$arguments = array('INFO', 'DEL');
|
||||
$expected = array('INFO', 'DEL');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse()
|
||||
{
|
||||
$raw = array(
|
||||
array('get', 2, array('readonly', 'fast'), 1, 1, 1),
|
||||
array('set', -3, array('write', 'denyoom'), 1, 1, 1),
|
||||
array('watch', -2, array('readonly', 'noscript', 'fast'), 1, -1, 1),
|
||||
array('unwatch', 1, array('readonly', 'noscript', 'fast'), 0, 0, 0),
|
||||
array('info', -1, array('readonly', 'loading', 'stale'), 0, 0, 0),
|
||||
);
|
||||
|
||||
$expected = $raw;
|
||||
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->assertSame($expected, $command->parseResponse($raw));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseEmptyResponse()
|
||||
{
|
||||
$raw = array(null);
|
||||
$expected = array(null);
|
||||
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->assertSame($expected, $command->parseResponse($raw));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.13
|
||||
*/
|
||||
public function testReturnsEmptyCommandInfoOnNonExistingCommand()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$this->assertCount(1, $response = $redis->command('INFO', 'FOOBAR'));
|
||||
$this->assertSame(array(null), $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.13
|
||||
*/
|
||||
public function testReturnsCommandInfoOnExistingCommand()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$expected = array(array('command', 0, array('readonly', 'loading', 'stale'), 0, 0, 0));
|
||||
$this->assertCount(1, $response = $redis->command('INFO', 'COMMAND'));
|
||||
$this->assertSame($expected, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.13
|
||||
*/
|
||||
public function testReturnsListOfCommandInfoWithNoArguments()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$this->assertGreaterThan(100, count($response = $redis->command()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @group commands
|
||||
* @group realm-zset
|
||||
*/
|
||||
class ZSetLexCountTest extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand()
|
||||
{
|
||||
return 'Predis\Command\ZSetLexCount';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId()
|
||||
{
|
||||
return 'ZLEXCOUNT';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments()
|
||||
{
|
||||
$arguments = array('key', '+', '-');
|
||||
$expected = array('key', '+', '-');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse()
|
||||
{
|
||||
$this->assertSame(1, $this->getCommand()->parseResponse(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testPrefixKeys()
|
||||
{
|
||||
$arguments = array('key', '+', '-');
|
||||
$expected = array('prefix:key', '+', '-');
|
||||
|
||||
$command = $this->getCommandWithArgumentsArray($arguments);
|
||||
$command->prefixKeys('prefix:');
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testPrefixKeysIgnoredOnEmptyArguments()
|
||||
{
|
||||
$command = $this->getCommand();
|
||||
$command->prefixKeys('prefix:');
|
||||
|
||||
$this->assertSame(array(), $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
*/
|
||||
public function testExclusiveIntervalRange()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
|
||||
|
||||
$this->assertSame(3, $redis->zlexcount('letters', '(b', '(f'));
|
||||
$this->assertSame(5, $redis->zlexcount('letters', '(b', '(z'));
|
||||
$this->assertSame(4, $redis->zlexcount('letters', '(0', '(e'));
|
||||
$this->assertSame(0, $redis->zlexcount('letters', '(f', '(b'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
*/
|
||||
public function testInclusiveIntervalRange()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
|
||||
|
||||
$this->assertSame(5, $redis->zlexcount('letters', '[b', '[f'));
|
||||
$this->assertSame(6, $redis->zlexcount('letters', '[b', '[z'));
|
||||
$this->assertSame(5, $redis->zlexcount('letters', '[0', '[e'));
|
||||
$this->assertSame(0, $redis->zlexcount('letters', '[f', '[b'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
*/
|
||||
public function testWholeRangeInterval()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
|
||||
|
||||
$this->assertSame(7, $redis->zlexcount('letters', '-', '+'));
|
||||
$this->assertSame(0, $redis->zlexcount('letters', '+', '-'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
* @expectedException Predis\ServerException
|
||||
* @expectedExceptionMessage min or max not valid string range item
|
||||
*/
|
||||
public function testThrowsExceptionOnInvalidRangeFormat()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
|
||||
$redis->zlexcount('letters', 'b', 'f');
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
* @expectedException Predis\ServerException
|
||||
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
|
||||
*/
|
||||
public function testThrowsExceptionOnWrongType()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->set('foo', 'bar');
|
||||
$redis->zlexcount('foo', '+', '-');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @group commands
|
||||
* @group realm-zset
|
||||
*/
|
||||
class ZSetRangeByLexTest extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand()
|
||||
{
|
||||
return 'Predis\Command\ZSetRangeByLex';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId()
|
||||
{
|
||||
return 'ZRANGEBYLEX';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments()
|
||||
{
|
||||
$modifiers = array(
|
||||
'limit' => array(0, 100),
|
||||
);
|
||||
|
||||
$arguments = array('zset', '[a', '[z', $modifiers);
|
||||
$expected = array('zset', '[a', '[z', 'LIMIT', 0, 100);
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArgumentsWithNamedLimit()
|
||||
{
|
||||
$arguments = array('zset', '[a', '[z', array('limit' => array('offset' => 1, 'count' => 2)));
|
||||
$expected = array('zset', '[a', '[z', 'LIMIT', 1, 2);
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse()
|
||||
{
|
||||
$raw = array('a', 'b', 'c');
|
||||
$expected = array('a', 'b', 'c');
|
||||
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->assertSame($expected, $command->parseResponse($raw));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testPrefixKeys()
|
||||
{
|
||||
$modifiers = array(
|
||||
'limit' => array(0, 100),
|
||||
);
|
||||
|
||||
$arguments = array('zset', '[a', '[z', $modifiers);
|
||||
$expected = array('prefix:zset', '[a', '[z', 'LIMIT', 0, 100);
|
||||
|
||||
$command = $this->getCommandWithArgumentsArray($arguments);
|
||||
$command->prefixKeys('prefix:');
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testPrefixKeysIgnoredOnEmptyArguments()
|
||||
{
|
||||
$command = $this->getCommand();
|
||||
$command->prefixKeys('prefix:');
|
||||
|
||||
$this->assertSame(array(), $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
*/
|
||||
public function testReturnsElementsInWholeRange()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
|
||||
|
||||
$this->assertSame(array('a', 'b', 'c', 'd', 'e', 'f', 'g'), $redis->zrangebylex('letters', '-', '+'));
|
||||
$this->assertSame(array(), $redis->zrangebylex('letters', '+', '-'));
|
||||
$this->assertSame(array(), $redis->zrangebylex('unknown', '-', '+'));
|
||||
$this->assertSame(array(), $redis->zrangebylex('unknown', '+', '-'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
*/
|
||||
public function testReturnsElementsInInclusiveRange()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
|
||||
|
||||
$this->assertSame(array('a'), $redis->zrangebylex('letters', '[a', '[a'));
|
||||
$this->assertSame(array('c', 'd', 'e', 'f'), $redis->zrangebylex('letters', '[c', '[f'));
|
||||
$this->assertSame(array('a', 'b', 'c'), $redis->zrangebylex('letters', '-', '[c'));
|
||||
$this->assertSame(array(), $redis->zrangebylex('letters', '+', '[c'));
|
||||
$this->assertSame(array(), $redis->zrangebylex('letters', '[x', '[z'));
|
||||
$this->assertSame(array(), $redis->zrangebylex('unknown', '[0', '[1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
*/
|
||||
public function testReturnsElementsInExclusiveRange()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
|
||||
|
||||
$this->assertSame(array(), $redis->zrangebylex('letters', '(a', '(a'));
|
||||
$this->assertSame(array('d', 'e'), $redis->zrangebylex('letters', '(c', '(f'));
|
||||
$this->assertSame(array('a', 'b'), $redis->zrangebylex('letters', '-', '(c'));
|
||||
$this->assertSame(array(), $redis->zrangebylex('letters', '+', '(c'));
|
||||
$this->assertSame(array(), $redis->zrangebylex('letters', '(x', '(z'));
|
||||
$this->assertSame(array(), $redis->zrangebylex('unknown', '(0', '(1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
*/
|
||||
public function testReturnsElementsInMixedRange()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
|
||||
|
||||
$this->assertSame(array(), $redis->zrangebylex('letters', '[a', '(a'));
|
||||
$this->assertSame(array(), $redis->zrangebylex('letters', '(a', '[a'));
|
||||
$this->assertSame(array('c', 'd', 'e'), $redis->zrangebylex('letters', '[c', '(f'));
|
||||
$this->assertSame(array('d', 'e', 'f'), $redis->zrangebylex('letters', '(c', '[f'));
|
||||
$this->assertSame(array(), $redis->zrangebylex('unknown', '[0', '(5'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
*/
|
||||
public function testRangeWithLimitModifier()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
|
||||
|
||||
$this->assertSame(array('c', 'd', 'e'), $redis->zrangebylex('letters', '-', '+', 'LIMIT', '2', '3'));
|
||||
$this->assertSame(array('c', 'd', 'e'), $redis->zrangebylex('letters', '-', '+', array('limit' => array(2, 3))));
|
||||
$this->assertSame(array('c', 'd', 'e'), $redis->zrangebylex('letters', '-', '+', array('limit' => array('offset' => 2, 'count' => 3))));
|
||||
$this->assertSame(array(), $redis->zrangebylex('letters', '[a', '[f', 'LIMIT', '2', '0'));
|
||||
$this->assertSame(array(), $redis->zrangebylex('letters', '[a', '[f', 'LIMIT', '-4', '2'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
* @expectedException Predis\ServerException
|
||||
* @expectedExceptionMessage min or max not valid string range item
|
||||
*/
|
||||
public function testThrowsExceptionOnInvalidRangeFormat()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
|
||||
$redis->zrangebylex('letters', 'b', 'f');
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
* @expectedException Predis\ServerException
|
||||
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
|
||||
*/
|
||||
public function testThrowsExceptionOnWrongType()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->set('foo', 'bar');
|
||||
$redis->zrangebylex('foo', '-', '+');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @group commands
|
||||
* @group realm-zset
|
||||
*/
|
||||
class ZSetRemoveRangeByLexTest extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand()
|
||||
{
|
||||
return 'Predis\Command\ZSetRemoveRangeByLex';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId()
|
||||
{
|
||||
return 'ZREMRANGEBYLEX';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments()
|
||||
{
|
||||
$arguments = array('key', '[a', '[b');
|
||||
$expected = array('key', '[a', '[b');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse()
|
||||
{
|
||||
$this->assertSame(1, $this->getCommand()->parseResponse(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testPrefixKeys()
|
||||
{
|
||||
$arguments = array('key', '[a', '[b');
|
||||
$expected = array('prefix:key', '[a', '[b');
|
||||
|
||||
$command = $this->getCommandWithArgumentsArray($arguments);
|
||||
$command->prefixKeys('prefix:');
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testPrefixKeysIgnoredOnEmptyArguments()
|
||||
{
|
||||
$command = $this->getCommand();
|
||||
$command->prefixKeys('prefix:');
|
||||
|
||||
$this->assertSame(array(), $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
*/
|
||||
public function testRemovesRangeByLexWithWholeRange()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
|
||||
|
||||
$this->assertSame(0, $redis->zremrangebylex('letters', '+', '-'));
|
||||
$this->assertSame(7, $redis->zremrangebylex('letters', '-', '+'));
|
||||
|
||||
$this->assertSame(array(), $redis->zrange('letters', 0, -1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
*/
|
||||
public function testRemovesRangeByLexWithInclusiveRange()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
|
||||
|
||||
$this->assertSame(3, $redis->zremrangebylex('letters', '[b', '[d'));
|
||||
$this->assertSame(array('a', 'e', 'f', 'g'), $redis->zrange('letters', 0, -1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
*/
|
||||
public function testRemovesRangeByLexWithExclusiveRange()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
|
||||
|
||||
$this->assertSame(3, $redis->zremrangebylex('letters', '(a', '(e'));
|
||||
$this->assertSame(array('a', 'e', 'f', 'g'), $redis->zrange('letters', 0, -1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
*/
|
||||
public function testRemovesRangeByLexWithMixedRange()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
|
||||
|
||||
$this->assertSame(3, $redis->zremrangebylex('letters', '[b', '(e'));
|
||||
$this->assertSame(array('a', 'e', 'f', 'g'), $redis->zrange('letters', 0, -1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
* @expectedException Predis\ServerException
|
||||
* @expectedExceptionMessage min or max not valid string range item
|
||||
*/
|
||||
public function testThrowsExceptionOnInvalidRangeFormat()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->zadd('letters', 0, 'a', 0, 'b', 0, 'c', 0, 'd', 0, 'e', 0, 'f', 0, 'g');
|
||||
$redis->zremrangebylex('letters', 'b', 'f');
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 2.8.9
|
||||
* @expectedException Predis\ServerException
|
||||
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
|
||||
*/
|
||||
public function testThrowsExceptionOnWrongType()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->set('foo', 'bar');
|
||||
$redis->zremrangebylex('foo', '[a', '[b');
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ namespace Predis\Connection;
|
||||
require_once __DIR__.'/../CommunicationExceptionTest.php';
|
||||
|
||||
use Predis\CommunicationExceptionTest;
|
||||
use Predis\Connection\SingleConnectionInterface;
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -16,7 +16,7 @@ use PredisTestCase;
|
||||
* @todo ConnectionParameters::define();
|
||||
* @todo ConnectionParameters::undefine();
|
||||
*/
|
||||
class ParametersTest extends PredisTestCase
|
||||
class ConnectionParametersTest extends PredisTestCase
|
||||
{
|
||||
/**
|
||||
* @group disconnected
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Predis\Connection;
|
||||
|
||||
use PredisTestCase;
|
||||
use Predis\ResponseError;
|
||||
use Predis\Command\RawCommand;
|
||||
use Predis\Profile\ServerProfile;
|
||||
|
||||
/**
|
||||
@@ -397,6 +398,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
$connection2->expects($this->never())->method('writeCommand');
|
||||
|
||||
$cluster = new RedisCluster();
|
||||
$cluster->enableClusterNodes(false);
|
||||
$cluster->add($connection1);
|
||||
$cluster->add($connection2);
|
||||
|
||||
@@ -417,6 +419,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
$connection2->expects($this->once())->method('readResponse')->with($command);
|
||||
|
||||
$cluster = new RedisCluster();
|
||||
$cluster->enableClusterNodes(false);
|
||||
$cluster->add($connection1);
|
||||
$cluster->add($connection2);
|
||||
|
||||
@@ -426,7 +429,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testDoesNotSupportKeyTags()
|
||||
public function testSupportsKeyTags()
|
||||
{
|
||||
$profile = ServerProfile::getDefault();
|
||||
|
||||
@@ -444,8 +447,8 @@ class RedisClusterTest extends PredisTestCase
|
||||
|
||||
$set = $profile->createCommand('set', array('{node:1001}:bar', 'foobar'));
|
||||
$get = $profile->createCommand('get', array('{node:1001}:bar'));
|
||||
$this->assertSame($connection2, $cluster->getConnection($set));
|
||||
$this->assertSame($connection2, $cluster->getConnection($get));
|
||||
$this->assertSame($connection1, $cluster->getConnection($set));
|
||||
$this->assertSame($connection1, $cluster->getConnection($get));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -476,6 +479,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
$factory->expects($this->never())->method('create');
|
||||
|
||||
$cluster = new RedisCluster($factory);
|
||||
$cluster->enableClusterNodes(false);
|
||||
$cluster->add($connection1);
|
||||
$cluster->add($connection2);
|
||||
|
||||
@@ -519,6 +523,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
->will($this->returnValue($connection3));
|
||||
|
||||
$cluster = new RedisCluster($factory);
|
||||
$cluster->enableClusterNodes(false);
|
||||
$cluster->add($connection1);
|
||||
$cluster->add($connection2);
|
||||
|
||||
@@ -552,6 +557,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
$factory->expects($this->never())->method('create');
|
||||
|
||||
$cluster = new RedisCluster($factory);
|
||||
$cluster->enableClusterNodes(false);
|
||||
$cluster->add($connection1);
|
||||
$cluster->add($connection2);
|
||||
|
||||
@@ -592,6 +598,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
->will($this->returnValue($connection3));
|
||||
|
||||
$cluster = new RedisCluster($factory);
|
||||
$cluster->enableClusterNodes(false);
|
||||
$cluster->add($connection1);
|
||||
$cluster->add($connection2);
|
||||
|
||||
@@ -600,6 +607,89 @@ class RedisClusterTest extends PredisTestCase
|
||||
$this->assertSame(3, count($cluster));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFetchSlotsMapFromClusterWithClusterSlotsCommand()
|
||||
{
|
||||
$response = array(
|
||||
array(12288, 13311, array('10.1.0.51', 6387), array('10.1.0.52', 6387)),
|
||||
array(3072 , 4095, array('10.1.0.52', 6392), array('10.1.0.51', 6392)),
|
||||
array(6144 , 7167, array('', 6384), array('10.1.0.52', 6384)),
|
||||
array(14336, 15359, array('10.1.0.51', 6388), array('10.1.0.52', 6388)),
|
||||
array(15360, 16383, array('10.1.0.52', 6398), array('10.1.0.51', 6398)),
|
||||
array(1024 , 2047, array('10.1.0.52', 6391), array('10.1.0.51', 6391)),
|
||||
array(11264, 12287, array('10.1.0.52', 6396), array('10.1.0.51', 6396)),
|
||||
array( 5120, 6143, array('10.1.0.52', 6393), array('10.1.0.51', 6393)),
|
||||
array( 0, 1023, array('10.1.0.51', 6381), array('10.1.0.52', 6381)),
|
||||
array(13312, 14335, array('10.1.0.52', 6397), array('10.1.0.51', 6397)),
|
||||
array( 4096, 5119, array('10.1.0.51', 6383), array('10.1.0.52', 6383)),
|
||||
array( 9216, 10239, array('10.1.0.52', 6395), array('10.1.0.51', 6395)),
|
||||
array( 8192, 9215, array('10.1.0.51', 6385), array('10.1.0.52', 6385)),
|
||||
array(10240, 11263, array('10.1.0.51', 6386), array('10.1.0.52', 6386)),
|
||||
array( 2048, 3071, array('10.1.0.51', 6382), array('10.1.0.52', 6382)),
|
||||
array( 7168, 8191, array('10.1.0.52', 6394), array('10.1.0.51', 6394)),
|
||||
);
|
||||
|
||||
$command = RawCommand::create('CLUSTER', 'SLOTS');
|
||||
|
||||
$connection1 = $this->getMockConnection('tcp://10.1.0.51:6384');
|
||||
$connection1->expects($this->once())
|
||||
->method('executeCommand')
|
||||
->with($command)
|
||||
->will($this->returnValue($response));
|
||||
|
||||
$factory = $this->getMock('Predis\Connection\ConnectionFactory');
|
||||
|
||||
$cluster = new RedisCluster($factory);
|
||||
$cluster->add($connection1);
|
||||
|
||||
$cluster->askClusterNodes();
|
||||
|
||||
$this->assertSame($cluster->getConnectionBySlot('6144'), $connection1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testAskSlotsMapToRedisClusterOnMovedResponseByDefault()
|
||||
{
|
||||
$cmdGET = RawCommand::create('GET', 'node:1001');
|
||||
$rspMOVED = new ResponseError('MOVED 1970 127.0.0.1:6380');
|
||||
$rspSlotsArray = array(
|
||||
array(0 , 8191, array('127.0.0.1', 6379)),
|
||||
array(8192, 16383, array('127.0.0.1', 6380)),
|
||||
);
|
||||
|
||||
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
|
||||
$connection1->expects($this->once())
|
||||
->method('executeCommand')
|
||||
->with($cmdGET)
|
||||
->will($this->returnValue($rspMOVED));
|
||||
|
||||
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
|
||||
$connection2->expects($this->at(0))
|
||||
->method('executeCommand')
|
||||
->with($this->isRedisCommand('CLUSTER', array('SLOTS')))
|
||||
->will($this->returnValue($rspSlotsArray));
|
||||
$connection2->expects($this->at(2))
|
||||
->method('executeCommand')
|
||||
->with($cmdGET)
|
||||
->will($this->returnValue('foobar'));
|
||||
|
||||
$factory = $this->getMock('Predis\Connection\ConnectionFactory');
|
||||
$factory->expects($this->once())
|
||||
->method('create')
|
||||
->with(array('host' => '127.0.0.1', 'port' => '6380'))
|
||||
->will($this->returnValue($connection2));
|
||||
|
||||
$cluster = new RedisCluster($factory);
|
||||
$cluster->add($connection1);
|
||||
|
||||
$this->assertSame('foobar', $cluster->executeCommand($cmdGET));
|
||||
$this->assertSame(2, count($cluster));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
* @expectedException Predis\NotSupportedException
|
||||
|
||||
@@ -19,7 +19,7 @@ use Predis\Command\Processor\ProcessorChain;
|
||||
*/
|
||||
class ServerProfileTest extends PredisTestCase
|
||||
{
|
||||
const DEFAULT_PROFILE_VERSION = '2.6';
|
||||
const DEFAULT_PROFILE_VERSION = '2.8';
|
||||
const DEVELOPMENT_PROFILE_VERSION = '3.0';
|
||||
|
||||
/**
|
||||
|
||||
@@ -176,7 +176,14 @@ class ServerVersion28Test extends PredisProfileTestCase
|
||||
135 => 'scan',
|
||||
136 => 'sscan',
|
||||
137 => 'zscan',
|
||||
138 => 'hscan',
|
||||
138 => 'zlexcount',
|
||||
139 => 'zrangebylex',
|
||||
140 => 'zremrangebylex',
|
||||
141 => 'hscan',
|
||||
142 => 'pfadd',
|
||||
143 => 'pfcount',
|
||||
144 => 'pfmerge',
|
||||
145 => 'command',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Profile;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ServerVersion30Test extends PredisProfileTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProfile($version = null)
|
||||
{
|
||||
return new ServerVersion30();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getExpectedVersion()
|
||||
{
|
||||
return '3.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getExpectedCommands()
|
||||
{
|
||||
return array(
|
||||
0 => 'exists',
|
||||
1 => 'del',
|
||||
2 => 'type',
|
||||
3 => 'keys',
|
||||
4 => 'randomkey',
|
||||
5 => 'rename',
|
||||
6 => 'renamenx',
|
||||
7 => 'expire',
|
||||
8 => 'expireat',
|
||||
9 => 'ttl',
|
||||
10 => 'move',
|
||||
11 => 'sort',
|
||||
12 => 'dump',
|
||||
13 => 'restore',
|
||||
14 => 'set',
|
||||
15 => 'setnx',
|
||||
16 => 'mset',
|
||||
17 => 'msetnx',
|
||||
18 => 'get',
|
||||
19 => 'mget',
|
||||
20 => 'getset',
|
||||
21 => 'incr',
|
||||
22 => 'incrby',
|
||||
23 => 'decr',
|
||||
24 => 'decrby',
|
||||
25 => 'rpush',
|
||||
26 => 'lpush',
|
||||
27 => 'llen',
|
||||
28 => 'lrange',
|
||||
29 => 'ltrim',
|
||||
30 => 'lindex',
|
||||
31 => 'lset',
|
||||
32 => 'lrem',
|
||||
33 => 'lpop',
|
||||
34 => 'rpop',
|
||||
35 => 'rpoplpush',
|
||||
36 => 'sadd',
|
||||
37 => 'srem',
|
||||
38 => 'spop',
|
||||
39 => 'smove',
|
||||
40 => 'scard',
|
||||
41 => 'sismember',
|
||||
42 => 'sinter',
|
||||
43 => 'sinterstore',
|
||||
44 => 'sunion',
|
||||
45 => 'sunionstore',
|
||||
46 => 'sdiff',
|
||||
47 => 'sdiffstore',
|
||||
48 => 'smembers',
|
||||
49 => 'srandmember',
|
||||
50 => 'zadd',
|
||||
51 => 'zincrby',
|
||||
52 => 'zrem',
|
||||
53 => 'zrange',
|
||||
54 => 'zrevrange',
|
||||
55 => 'zrangebyscore',
|
||||
56 => 'zcard',
|
||||
57 => 'zscore',
|
||||
58 => 'zremrangebyscore',
|
||||
59 => 'ping',
|
||||
60 => 'auth',
|
||||
61 => 'select',
|
||||
62 => 'echo',
|
||||
63 => 'quit',
|
||||
64 => 'info',
|
||||
65 => 'slaveof',
|
||||
66 => 'monitor',
|
||||
67 => 'dbsize',
|
||||
68 => 'flushdb',
|
||||
69 => 'flushall',
|
||||
70 => 'save',
|
||||
71 => 'bgsave',
|
||||
72 => 'lastsave',
|
||||
73 => 'shutdown',
|
||||
74 => 'bgrewriteaof',
|
||||
75 => 'setex',
|
||||
76 => 'append',
|
||||
77 => 'substr',
|
||||
78 => 'blpop',
|
||||
79 => 'brpop',
|
||||
80 => 'zunionstore',
|
||||
81 => 'zinterstore',
|
||||
82 => 'zcount',
|
||||
83 => 'zrank',
|
||||
84 => 'zrevrank',
|
||||
85 => 'zremrangebyrank',
|
||||
86 => 'hset',
|
||||
87 => 'hsetnx',
|
||||
88 => 'hmset',
|
||||
89 => 'hincrby',
|
||||
90 => 'hget',
|
||||
91 => 'hmget',
|
||||
92 => 'hdel',
|
||||
93 => 'hexists',
|
||||
94 => 'hlen',
|
||||
95 => 'hkeys',
|
||||
96 => 'hvals',
|
||||
97 => 'hgetall',
|
||||
98 => 'multi',
|
||||
99 => 'exec',
|
||||
100 => 'discard',
|
||||
101 => 'subscribe',
|
||||
102 => 'unsubscribe',
|
||||
103 => 'psubscribe',
|
||||
104 => 'punsubscribe',
|
||||
105 => 'publish',
|
||||
106 => 'config',
|
||||
107 => 'persist',
|
||||
108 => 'strlen',
|
||||
109 => 'setrange',
|
||||
110 => 'getrange',
|
||||
111 => 'setbit',
|
||||
112 => 'getbit',
|
||||
113 => 'rpushx',
|
||||
114 => 'lpushx',
|
||||
115 => 'linsert',
|
||||
116 => 'brpoplpush',
|
||||
117 => 'zrevrangebyscore',
|
||||
118 => 'watch',
|
||||
119 => 'unwatch',
|
||||
120 => 'object',
|
||||
121 => 'slowlog',
|
||||
122 => 'client',
|
||||
123 => 'pttl',
|
||||
124 => 'pexpire',
|
||||
125 => 'pexpireat',
|
||||
126 => 'psetex',
|
||||
127 => 'incrbyfloat',
|
||||
128 => 'bitop',
|
||||
129 => 'bitcount',
|
||||
130 => 'hincrbyfloat',
|
||||
131 => 'eval',
|
||||
132 => 'evalsha',
|
||||
133 => 'script',
|
||||
134 => 'time',
|
||||
135 => 'scan',
|
||||
136 => 'sscan',
|
||||
137 => 'zscan',
|
||||
138 => 'zlexcount',
|
||||
139 => 'zrangebylex',
|
||||
140 => 'zremrangebylex',
|
||||
141 => 'hscan',
|
||||
142 => 'pfadd',
|
||||
143 => 'pfcount',
|
||||
144 => 'pfmerge',
|
||||
145 => 'command',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -176,7 +176,14 @@ class ServerVersionNextTest extends PredisProfileTestCase
|
||||
135 => 'scan',
|
||||
136 => 'sscan',
|
||||
137 => 'zscan',
|
||||
138 => 'hscan',
|
||||
138 => 'zlexcount',
|
||||
139 => 'zrangebylex',
|
||||
140 => 'zremrangebylex',
|
||||
141 => 'hscan',
|
||||
142 => 'pfadd',
|
||||
143 => 'pfcount',
|
||||
144 => 'pfmerge',
|
||||
145 => 'command',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +144,39 @@ class PubSubContextTest extends PredisTestCase
|
||||
$this->assertNull($pubsub->next());
|
||||
}
|
||||
|
||||
public function testHandlesPongMessages()
|
||||
{
|
||||
$rawmessage = array('pong', '');
|
||||
|
||||
$connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
|
||||
$connection->expects($this->once())->method('read')->will($this->returnValue($rawmessage));
|
||||
|
||||
$client = new Client($connection);
|
||||
$pubsub = new PubSubContext($client, array('subscribe' => 'channel:foo'));
|
||||
|
||||
$message = $pubsub->current();
|
||||
$this->assertSame('pong', $message->kind);
|
||||
$this->assertSame('', $message->payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testHandlesPongMessagesWithPayload()
|
||||
{
|
||||
$rawmessage = array('pong', 'foobar');
|
||||
|
||||
$connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
|
||||
$connection->expects($this->once())->method('read')->will($this->returnValue($rawmessage));
|
||||
|
||||
$client = new Client($connection);
|
||||
$pubsub = new PubSubContext($client, array('subscribe' => 'channel:foo'));
|
||||
|
||||
$message = $pubsub->current();
|
||||
$this->assertSame('pong', $message->kind);
|
||||
$this->assertSame('foobar', $message->payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
@@ -302,6 +335,6 @@ class PubSubContextTest extends PredisTestCase
|
||||
|
||||
$this->assertSame(array('message1', 'message2', 'QUIT'), $messages);
|
||||
$this->assertFalse($pubsub->valid());
|
||||
$this->assertTrue($consumer->ping());
|
||||
$this->assertEquals('ECHO', $consumer->echo('ECHO'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,6 +286,7 @@ class ReplicationStrategyTest extends PredisTestCase
|
||||
'GETSET' => 'write',
|
||||
'INCR' => 'write',
|
||||
'INCRBY' => 'write',
|
||||
'INCRBYFLOAT' => 'write',
|
||||
'SETBIT' => 'write',
|
||||
'SETEX' => 'write',
|
||||
'MSET' => 'write',
|
||||
@@ -341,6 +342,9 @@ class ReplicationStrategyTest extends PredisTestCase
|
||||
'ZREVRANK' => 'read',
|
||||
'ZSCORE' => 'read',
|
||||
'ZSCAN' => 'read',
|
||||
'ZLEXCOUNT' => 'read',
|
||||
'ZRANGEBYLEX' => 'read',
|
||||
'ZREMRANGEBYLEX' => 'write',
|
||||
|
||||
/* commands operating on hashes */
|
||||
'HDEL' => 'write',
|
||||
@@ -357,6 +361,11 @@ class ReplicationStrategyTest extends PredisTestCase
|
||||
'HVALS' => 'read',
|
||||
'HSCAN' => 'read',
|
||||
|
||||
/* commands operating on HyperLogLog */
|
||||
'PFADD' => 'write',
|
||||
'PFMERGE' => 'write',
|
||||
'PFCOUNT' => 'read',
|
||||
|
||||
/* scripting */
|
||||
'EVAL' => 'write',
|
||||
'EVALSHA' => 'write',
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Predis\Transaction;
|
||||
use PredisTestCase;
|
||||
use Predis\Client;
|
||||
use Predis\ResponseQueued;
|
||||
use Predis\ResponseError;
|
||||
use Predis\ServerException;
|
||||
use Predis\Command\CommandInterface;
|
||||
|
||||
@@ -29,8 +30,15 @@ class MultiExecContextTest extends PredisTestCase
|
||||
*/
|
||||
public function testThrowsExceptionOnUnsupportedMultiExecInProfile()
|
||||
{
|
||||
$profile = $this->getMock('Predis\Profile\ServerProfileInterface');
|
||||
$profile->expects($this->once())
|
||||
->method('supportsCommands')
|
||||
->with(array('MULTI', 'EXEC', 'DISCARD'))
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
|
||||
$client = new Client($connection, array('profile' => '1.2'));
|
||||
$client = new Client($connection, array('profile' => $profile));
|
||||
|
||||
$tx = new MultiExecContext($client);
|
||||
}
|
||||
|
||||
@@ -41,10 +49,20 @@ class MultiExecContextTest extends PredisTestCase
|
||||
*/
|
||||
public function testThrowsExceptionOnUnsupportedWatchUnwatchInProfile()
|
||||
{
|
||||
$connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
|
||||
$client = new Client($connection, array('profile' => '2.0'));
|
||||
$tx = new MultiExecContext($client, array('options' => 'cas'));
|
||||
$profile = $this->getMock('Predis\Profile\ServerProfileInterface');
|
||||
$profile->expects($this->at(0))
|
||||
->method('supportsCommands')
|
||||
->with(array('MULTI', 'EXEC', 'DISCARD'))
|
||||
->will($this->returnValue(true));
|
||||
$profile->expects($this->at(1))
|
||||
->method('supportsCommands')
|
||||
->with(array('WATCH', 'UNWATCH'))
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
|
||||
$client = new Client($connection, array('profile' => $profile));
|
||||
|
||||
$tx = new MultiExecContext($client, array('options' => 'cas'));
|
||||
$tx->watch('foo');
|
||||
}
|
||||
|
||||
@@ -473,6 +491,50 @@ class MultiExecContextTest extends PredisTestCase
|
||||
$this->assertSame(array('MULTI', 'SET', 'ECHO', 'DISCARD'), self::commandsToIDs($commands));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testProperlyDiscardsTransactionAfterServerExceptionInBlock()
|
||||
{
|
||||
$connection = $this->getMockedConnection(function (CommandInterface $command) {
|
||||
switch ($command->getId()) {
|
||||
case 'MULTI':
|
||||
return true;
|
||||
|
||||
case 'ECHO':
|
||||
return new ResponseError('ERR simulated failure on ECHO');
|
||||
|
||||
case 'EXEC':
|
||||
return new ResponseError('EXECABORT Transaction discarded because of previous errors.');
|
||||
|
||||
default:
|
||||
return new ResponseQueued();
|
||||
}
|
||||
});
|
||||
|
||||
$client = new Client($connection);
|
||||
|
||||
// First attempt
|
||||
$tx = new MultiExecContext($client);
|
||||
|
||||
try {
|
||||
$tx->multi()->set('foo', 'bar')->echo('simulated failure')->exec();
|
||||
} catch (\Exception $exception) {
|
||||
$this->assertInstanceOf('Predis\Transaction\AbortedMultiExecException', $exception);
|
||||
$this->assertSame('ERR simulated failure on ECHO', $exception->getMessage());
|
||||
}
|
||||
|
||||
// Second attempt
|
||||
$tx = new MultiExecContext($client);
|
||||
|
||||
try {
|
||||
$tx->multi()->set('foo', 'bar')->echo('simulated failure')->exec();
|
||||
} catch (\Exception $exception) {
|
||||
$this->assertInstanceOf('Predis\Transaction\AbortedMultiExecException', $exception);
|
||||
$this->assertSame('ERR simulated failure on ECHO', $exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ******************************************************************** //
|
||||
// ---- INTEGRATION TESTS --------------------------------------------- //
|
||||
// ******************************************************************** //
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
if (file_exists(__DIR__.'/../autoload.php')) {
|
||||
require __DIR__.'/../autoload.php';
|
||||
} else if (@include('Predis/Autoloader.php')) {
|
||||
} elseif (@include('Predis/Autoloader.php')) {
|
||||
Predis\Autoloader::register();
|
||||
} else {
|
||||
die('ERROR: Unable to find a suitable mean to register Predis\Autoloader.');
|
||||
|
||||
Reference in New Issue
Block a user