mirror of
https://github.com/predis/predis.git
synced 2026-08-23 17:23:32 +00:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ead747663 | |||
| 39cf6abf82 | |||
| dbae384a64 | |||
| d9ea0ce14f | |||
| 55f940e400 | |||
| 648be824d3 | |||
| c4e0f2f037 | |||
| 62f5ce6baf | |||
| 9cd4ed9e1e | |||
| 8491b1d880 | |||
| 8c8261432f | |||
| 5eccd6ee3c | |||
| 3d9588ec1a | |||
| e96f7f748a | |||
| dd0edf761a | |||
| 8cbbd073cb | |||
| 1e521b56cd | |||
| fcacd65576 | |||
| d5488f81f7 | |||
| 188dc6bf62 | |||
| 0bd57ab744 | |||
| b996b005cf | |||
| 82116b0366 | |||
| 5c1ddc9cfa | |||
| ea2fd9700d | |||
| b2ec264ff9 | |||
| bf7a70d6f0 | |||
| 4ecc44e1ee | |||
| 52d5b23db7 | |||
| d31a4c71ba | |||
| c92e305852 | |||
| b075564278 | |||
| f9e817f14c | |||
| 9e568396c6 | |||
| 2a91ebaba9 | |||
| ce15bd22b9 | |||
| b0439e503e | |||
| ffc1c25baa | |||
| ac165194f5 | |||
| 18ae294864 | |||
| 47de667aea | |||
| 588940c5cb | |||
| a1fe4013e1 | |||
| 3328ad76be | |||
| 17b717a8f5 | |||
| 95dbc2bb34 | |||
| 9bb5a1c71f | |||
| 8eb4804a5c | |||
| 5d99914939 | |||
| c45bb15767 | |||
| e0329d6c74 | |||
| 2175d87a96 | |||
| ce85fdadd1 | |||
| b15c0a02de | |||
| d2fbcc6b8c | |||
| 6596460a57 | |||
| 9ab0e31a75 | |||
| 11adccdaeb | |||
| f465792b27 | |||
| 1399d3863c | |||
| 1f1c9cf1a4 | |||
| dae2df0ae9 | |||
| 9338eaf573 | |||
| f378faaf22 | |||
| 8b150fcbf5 |
+1
-3
@@ -11,7 +11,7 @@ branches:
|
||||
except:
|
||||
- v0.5
|
||||
- v0.6
|
||||
- php5.2_backport
|
||||
- v0.6-PHP_5.2
|
||||
- documentation
|
||||
services: redis-server
|
||||
before_script:
|
||||
@@ -20,6 +20,4 @@ before_script:
|
||||
script:
|
||||
- vendor/bin/phpunit -c phpunit.xml.travisci
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
fast_finish: true
|
||||
|
||||
@@ -1,3 +1,31 @@
|
||||
v1.0.4 (2016-05-30)
|
||||
================================================================================
|
||||
|
||||
- Added new profile for Redis 3.2 with its new commands: `HSTRLEN`, `BITFIELD`,
|
||||
`GEOADD`, `GEOHASH`, `GEOPOS`, `GEODIST`, `GEORADIUS`, `GEORADIUSBYMEMBER`.
|
||||
The default server profile for Predis is still the one for Redis 3.0 you must
|
||||
set the `profile` client option to `3.2` when initializing the client in order
|
||||
to be able to use them when connecting to Redis 3.2.
|
||||
|
||||
- Various improvements in the handling of redis-cluster:
|
||||
|
||||
- If the connection to a specific node fails when executing a command, the
|
||||
client tries to connect to another node in order to refresh the slots map
|
||||
and perform a new attempt to execute the command.
|
||||
|
||||
- Connections to nodes can be preassigned to non-contiguous slot ranges via
|
||||
the `slots` parameter using a comma separator. This is how it looks like
|
||||
in practice: `tcp://127.0.0.1:6379?slots=0-5460,5500-5600,11000`.
|
||||
|
||||
- __FIX__: broken values returned by `Predis\Collection\Iterator\HashKey` when
|
||||
iterating hash keys containing integer fields (PR #330, ISSUE #331).
|
||||
|
||||
- __FIX__: prevent failures when `Predis\Connection\StreamConnection` serializes
|
||||
commands with holes in their arguments (e.g. `[0 => 'key:0', 2 => 'key:2']`).
|
||||
The same fix has been applied to `Predis\Protocol\Text\RequestSerializer`.
|
||||
(ISSUE #316).
|
||||
|
||||
|
||||
v1.0.3 (2015-07-30)
|
||||
================================================================================
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2009-2015 Daniele Alessandri
|
||||
Copyright (c) 2009-2016 Daniele Alessandri
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
|
||||
@@ -1,50 +1,46 @@
|
||||
# Predis #
|
||||
|
||||
[](https://packagist.org/packages/predis/predis)
|
||||
[](https://packagist.org/packages/predis/predis)
|
||||
[](https://packagist.org/packages/predis/predis)
|
||||
[](https://travis-ci.org/nrk/predis)
|
||||
[](http://hhvm.h4cc.de/package/predis/predis)
|
||||
[![Latest stable][ico-version-stable]][link-packagist]
|
||||
[![Latest development][ico-version-dev]][link-packagist]
|
||||
[![Software license][ico-license]](LICENSE)
|
||||
[![Monthly installs][ico-downloads-monthly]][link-downloads]
|
||||
[![Build status][ico-travis]][link-travis]
|
||||
[![HHVM support][ico-hhvm]][link-hhvm]
|
||||
|
||||
Predis is a flexible and feature-complete [Redis](http://redis.io) client library for PHP >= 5.3.
|
||||
Flexible and feature-complete [Redis](http://redis.io) client for PHP >= 5.3 and HHVM >= 2.3.0.
|
||||
|
||||
By default this library does not require any additional C extension, but it can be optionally paired
|
||||
with [phpiredis](https://github.com/nrk/phpiredis) to lower the overhead of serializing and parsing
|
||||
the [Redis RESP Protocol](http://redis.io/topics/protocol). An asynchronous implementation of Predis
|
||||
is available through [Predis\Async](https://github.com/nrk/predis-async) (__experimental__).
|
||||
Predis does not require any additional C extension by default, but it can be optionally paired with
|
||||
[phpiredis](https://github.com/nrk/phpiredis) to lower the overhead of the serialization and parsing
|
||||
of the [Redis RESP Protocol](http://redis.io/topics/protocol). For an __experimental__ asynchronous
|
||||
implementation of the client you can refer to [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 and not yet 100% compatible with the _standard_ PHP.
|
||||
|
||||
More details about this project can be found on the [frequently asked questions](FAQ.md) and on the
|
||||
[wiki](https://github.com/nrk/predis/wiki).
|
||||
More details about this project can be found on the [frequently asked questions](FAQ.md).
|
||||
|
||||
|
||||
## Main features ##
|
||||
|
||||
- Support for a wide range of Redis versions (from __2.0__ to __3.0__) using profiles.
|
||||
- Clustering 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 (write operations on master, read operations on slaves).
|
||||
- Transparent key prefixing for all known Redis commands using a customizable prefixing strategy.
|
||||
- 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`.
|
||||
- Support for different versions of Redis (from __2.0__ to __3.2__) using profiles.
|
||||
- Support for clustering using client-side sharding and pluggable keyspace distributors.
|
||||
- Support for [redis-cluster](http://redis.io/topics/cluster-tutorial) (Redis >= 3.0).
|
||||
- Support for standalone master-slave replication setups.
|
||||
- Transparent key prefixing of keys using a customizable prefix strategy.
|
||||
- Command pipelining on both single nodes and clusters (client-side sharding only).
|
||||
- Abstraction for Redis transactions (Redis >= 2.0) and CAS operations (Redis >= 2.2).
|
||||
- Abstraction for Lua scripting (Redis >= 2.6) and automatic switching between `EVALSHA` or `EVAL`.
|
||||
- Abstraction for `SCAN`, `SSCAN`, `ZSCAN` and `HSCAN` (Redis >= 2.8) based on PHP iterators.
|
||||
- Connections to Redis are established lazily by the client upon the first command.
|
||||
- Support for both TCP/IP and UNIX domain sockets and persistent connections.
|
||||
- Connections are established lazily by the client upon the first command and can be persisted.
|
||||
- Connections can be established via TCP/IP or UNIX domain sockets.
|
||||
- Support for [Webdis](http://webd.is) (requires both `ext-curl` and `ext-phpiredis`).
|
||||
- Support for custom connection classes for providing different network or protocol backends.
|
||||
- Flexible system for defining custom commands and server profiles.
|
||||
|
||||
|
||||
## How to use Predis ##
|
||||
## How to _install_ and use Predis ##
|
||||
|
||||
Predis is available on [Packagist](http://packagist.org/packages/predis/predis) which allows a quick
|
||||
_installation_ using [Composer](http://packagist.org/about-composer). Alternatively, the library can
|
||||
be found on our [own PEAR channel](http://pear.nrk.io) for a more traditional installation via PEAR.
|
||||
Ultimately, archives of each release are [available on GitHub](https://github.com/nrk/predis/tags).
|
||||
This library can be found on [Packagist](http://packagist.org/packages/predis/predis) for an easier
|
||||
management of projects dependencies using [Composer](http://packagist.org/about-composer) or on our
|
||||
[own PEAR channel](http://pear.nrk.io) for a more traditional installation using PEAR. Ultimately,
|
||||
compressed archives of each release are [available on GitHub](https://github.com/nrk/predis/tags).
|
||||
|
||||
|
||||
### Loading the library ###
|
||||
@@ -52,7 +48,7 @@ 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-4 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md).
|
||||
Autoloading is handled automatically when dependencies are managed through Composer, but it is also
|
||||
possible to leverage its own autoloader in projects or scripts not having any autoload facility:
|
||||
possible to leverage its own autoloader in projects or scripts lacking any autoload facility:
|
||||
|
||||
```php
|
||||
// Prepend a base path if Predis is not available in your "include_path".
|
||||
@@ -61,11 +57,9 @@ require 'Predis/Autoloader.php';
|
||||
Predis\Autoloader::register();
|
||||
```
|
||||
|
||||
It is possible to create a [phar](http://www.php.net/manual/en/intro.phar.php) archive directly from
|
||||
the repository by launching `bin/create-phar`. The phar contains a stub defining its own autoloader
|
||||
so you just need to `require()` it to start using the library. Ultimately it is possible to generate
|
||||
a single big PHP file containing all the source code simply by launching `bin/create-single-file`,
|
||||
but this practice __is not__ encouraged.
|
||||
It is also possible to create a [phar](http://www.php.net/manual/en/intro.phar.php) archive directly
|
||||
from the repository by launching the `bin/create-phar` script. The generated phar already contains a
|
||||
stub defining its own autoloader, so you just need to `require()` it to start using the library.
|
||||
|
||||
|
||||
### Connecting to Redis ###
|
||||
@@ -95,8 +89,17 @@ $client = new Predis\Client([
|
||||
$client = new Predis\Client('tcp://10.0.0.1:6379');
|
||||
```
|
||||
|
||||
Starting with Predis v1.0.2 the client also understands the `redis` scheme in URI strings as defined
|
||||
by the [provisional IANA registration](http://www.iana.org/assignments/uri-schemes/prov/redis).
|
||||
It is also possible to connect to local instances of Redis using UNIX domain sockets, in this case
|
||||
the parameters must use the `unix` scheme and specify a path for the socket file:
|
||||
|
||||
```php
|
||||
$client = new Predis\Client(['scheme' => 'unix', 'path' => '/path/to/redis.sock']);
|
||||
$client = new Predis\Client('unix:///path/to/redis.sock');
|
||||
```
|
||||
|
||||
The connection schemes [`redis`](http://www.iana.org/assignments/uri-schemes/prov/redis) (alias of
|
||||
`tcp`) is also supported, with the difference that URI strings containing these schemes are parsed
|
||||
following the rules described on the IANA provisional registration.
|
||||
|
||||
The actual list of supported connection parameters can vary depending on each connection backend so
|
||||
it is recommended to refer to their specific documentation or implementation for details.
|
||||
@@ -117,46 +120,79 @@ See the [aggregate connections](#aggregate-connections) section of this document
|
||||
|
||||
### Client configuration ###
|
||||
|
||||
Various aspects of the client can be configured simply by passing options to the second argument of
|
||||
`Predis\Client::__construct()`:
|
||||
Many aspects and behaviors of the client can be configured by passing specific client options to the
|
||||
second argument of `Predis\Client::__construct()`:
|
||||
|
||||
```php
|
||||
$client = new Predis\Client($parameters, ['profile' => '2.8', 'prefix' => 'sample:']);
|
||||
```
|
||||
|
||||
Options are managed through a mini DI-alike container while their values can be lazily initialized
|
||||
only when needed. This is a list of the options supported by default:
|
||||
Options are managed using a mini DI-alike container and their values can be lazily initialized only
|
||||
when needed. The client options supported by default in Predis are:
|
||||
|
||||
- `profile`: which profile to use in order to match a specific version of Redis.
|
||||
- `prefix`: a prefix string that is automatically applied to keys found in commands.
|
||||
- `profile`: specifies the profile to use to match a specific version of Redis.
|
||||
- `prefix`: prefix string 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).
|
||||
- `replication`: which backend to use for replication (predis or custom configuration).
|
||||
- `aggregate`: custom connections aggregator (overrides both `cluster` and `replication`).
|
||||
- `connections`: list of connection backends or a connection factory instance.
|
||||
- `cluster`: specifies a cluster backend (`predis`, `redis` or callable object).
|
||||
- `replication`: specifies a replication backend (`TRUE` or callable object).
|
||||
- `aggregate`: overrides `cluster` and `replication` to provide a custom connections aggregator.
|
||||
|
||||
Users can provide custom options with their values or lazy callable initializers that are stored in
|
||||
the options container for later use through the library.
|
||||
Users can also provide custom options with values or callable objects (for lazy initialization) that
|
||||
are stored in the options container for later use 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 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 only for write operations. The replication behavior
|
||||
is fully configurable.
|
||||
Aggregate connections are the foundation upon which Predis implements clustering and replication and
|
||||
they are used to group multiple connections to single Redis nodes and hide the specific logic needed
|
||||
to handle them properly depending on the context. Aggregate connections usually require an array of
|
||||
connection parameters when creating a new client instance.
|
||||
|
||||
#### Cluster ####
|
||||
|
||||
By default, when no specific client options are set and an array of connection parameters is passed
|
||||
to the client's constructor, Predis configures itself to work in clustering mode using a traditional
|
||||
client-side sharding approach to create a cluster of independent nodes and distribute the keyspace
|
||||
among them. This approach needs some form of external health monitoring of nodes and requires manual
|
||||
operations to rebalance the keyspace when changing its configuration by adding or removing nodes:
|
||||
|
||||
```php
|
||||
$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
|
||||
|
||||
$client = new Predis\Client($parameters);
|
||||
```
|
||||
|
||||
Along with Redis 3.0, a new supervised and coordinated type of clustering was introduced in the form
|
||||
of [redis-cluster](http://redis.io/topics/cluster-tutorial). This kind of approach uses a different
|
||||
algorithm to distribute the keyspaces, with Redis nodes coordinating themselves by communicating via
|
||||
a gossip protocol to handle health status, rebalancing, nodes discovery and request redirection. In
|
||||
order to connect to a cluster managed by redis-cluster, the client requires a list of its nodes (not
|
||||
necessarily complete since it will automatically discover new nodes if necessary) and the `cluster`
|
||||
client options set to `redis`:
|
||||
|
||||
```php
|
||||
$parameters = ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
|
||||
$options = ['cluster' => 'redis'];
|
||||
|
||||
$client = new Predis\Client($parameters, $options);
|
||||
```
|
||||
|
||||
#### Replication ####
|
||||
|
||||
The client can be configured to operate in a master-slave setup by executing read-only commands on
|
||||
slave nodes and automatically switch to the master node as soon as it detects a command that will
|
||||
perform a write operation. This is the basic configuration needed to work with replication:
|
||||
The client can be configured to operate in a single master / multiple slaves setup to provide better
|
||||
service availability. When using replication, Predis recognizes read-only commands and sends them to
|
||||
a random slave in order to provide some sort of load-balancing and switches to the master as soon as
|
||||
it detects a command that performs any kind of operation that would end up modifying the keyspace or
|
||||
the value of a key. Instead of raising a connection error when a slave fails, the client attempts to
|
||||
fall back to a different slave among the ones provided in the configuration.
|
||||
|
||||
The basic configuration needed to use the client in replication mode requires one Redis server to be
|
||||
identified as the master (this can be done via connection parameters using the `alias` parameter set
|
||||
to `master`) and one or more servers acting as slaves:
|
||||
|
||||
```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'];
|
||||
$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
|
||||
$options = ['replication' => true];
|
||||
|
||||
$client = new Predis\Client($parameters, $options);
|
||||
@@ -169,7 +205,7 @@ when certain Lua scripts do not perform write operations it is possible to provi
|
||||
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'];
|
||||
$parameters = ['tcp://10.0.0.1?alias=master', 'tcp://10.0.0.2', 'tcp://10.0.0.3'];
|
||||
$options = ['replication' => function () {
|
||||
// Set scripts that won't trigger a switch from a slave to the master node.
|
||||
$strategy = new Predis\Replication\ReplicationStrategy();
|
||||
@@ -183,29 +219,8 @@ $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
|
||||
[basic](examples/replication_simple.php) and [complex](examples/replication_complex.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, you
|
||||
can specify only a few nodes and the client 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 the library.
|
||||
The [`examples`](examples/) directory contains a few scripts that demonstrate how the client can be
|
||||
configured and used to leverage replication in both basic and complex scenarios.
|
||||
|
||||
|
||||
### Command pipelines ###
|
||||
@@ -286,7 +301,7 @@ $response = $client->executeRaw(['SET', 'foo', 'bar']);
|
||||
### Script commands ###
|
||||
|
||||
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) directly,
|
||||
directly [`EVAL`](http://redis.io/commands/eval) and [`EVALSHA`](http://redis.io/commands/evalsha),
|
||||
Predis offers script commands as an higher level abstraction built upon them 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
|
||||
@@ -407,3 +422,15 @@ found [on its project page](http://travis-ci.org/nrk/predis).
|
||||
### License ###
|
||||
|
||||
The code for Predis is distributed under the terms of the MIT license (see [LICENSE](LICENSE)).
|
||||
|
||||
[ico-license]: https://img.shields.io/github/license/nrk/predis.svg?style=flat-square
|
||||
[ico-version-stable]: https://img.shields.io/packagist/v/predis/predis.svg?style=flat-square
|
||||
[ico-version-dev]: https://img.shields.io/packagist/vpre/predis/predis.svg?style=flat-square
|
||||
[ico-downloads-monthly]: https://img.shields.io/packagist/dm/predis/predis.svg?style=flat-square
|
||||
[ico-travis]: https://img.shields.io/travis/nrk/predis.svg?style=flat-square
|
||||
[ico-hhvm]: https://img.shields.io/hhvm/predis/predis.svg?style=flat-square
|
||||
|
||||
[link-packagist]: https://packagist.org/packages/predis/predis
|
||||
[link-travis]: https://travis-ci.org/nrk/predis
|
||||
[link-downloads]: https://packagist.org/packages/predis/predis/stats
|
||||
[link-hhvm]: http://hhvm.h4cc.de/package/predis/predis
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "predis/predis",
|
||||
"type": "library",
|
||||
"description": "Flexible and feature-complete PHP client library for Redis",
|
||||
"description": "Flexible and feature-complete Redis client for PHP and HHVM",
|
||||
"keywords": ["nosql", "redis", "predis"],
|
||||
"homepage": "http://github.com/nrk/predis",
|
||||
"license": "MIT",
|
||||
@@ -19,7 +19,7 @@
|
||||
"php": ">=5.3.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0"
|
||||
"phpunit/phpunit": "~4.8"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol",
|
||||
|
||||
+2
-2
@@ -7,10 +7,10 @@
|
||||
|
||||
[package]
|
||||
name = "Predis"
|
||||
desc = "Flexible and feature-complete PHP client library for Redis"
|
||||
desc = "Flexible and feature-complete Redis client for PHP and HHVM"
|
||||
homepage = "http://github.com/nrk/predis"
|
||||
license = "MIT"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
stability = "stable"
|
||||
channel = "pear.nrk.io"
|
||||
|
||||
|
||||
+10
-4
@@ -1,10 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit bootstrap="tests/bootstrap.php"
|
||||
colors="true"
|
||||
beStrictAboutTestSize="true"
|
||||
checkForUnintentionallyCoveredCode="true"
|
||||
beStrictAboutTestsThatDoNotTestAnything="true">
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnError="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="true"
|
||||
beStrictAboutTestSize="true"
|
||||
beStrictAboutTestsThatDoNotTestAnything="true">
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Predis Test Suite">
|
||||
|
||||
+10
-4
@@ -1,10 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit bootstrap="tests/bootstrap.php"
|
||||
colors="true"
|
||||
beStrictAboutTestSize="true"
|
||||
checkForUnintentionallyCoveredCode="true"
|
||||
beStrictAboutTestsThatDoNotTestAnything="true">
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnError="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="true"
|
||||
beStrictAboutTestSize="true"
|
||||
beStrictAboutTestsThatDoNotTestAnything="true">
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Predis Test Suite">
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ use Predis\Transaction\MultiExec as MultiExecTransaction;
|
||||
*/
|
||||
class Client implements ClientInterface
|
||||
{
|
||||
const VERSION = '1.0.3';
|
||||
const VERSION = '1.0.4';
|
||||
|
||||
protected $connection;
|
||||
protected $options;
|
||||
|
||||
@@ -38,6 +38,7 @@ use Predis\Command\CommandInterface;
|
||||
* @method $this append($key, $value)
|
||||
* @method $this bitcount($key, $start = null, $end = null)
|
||||
* @method $this bitop($operation, $destkey, $key)
|
||||
* @method $this bitfield($key, ...)
|
||||
* @method $this decr($key)
|
||||
* @method $this decrby($key, $decrement)
|
||||
* @method $this get($key)
|
||||
@@ -71,6 +72,7 @@ use Predis\Command\CommandInterface;
|
||||
* @method $this hset($key, $field, $value)
|
||||
* @method $this hsetnx($key, $field, $value)
|
||||
* @method $this hvals($key)
|
||||
* @method $this hstrlen($key, $field)
|
||||
* @method $this blpop(array $keys, $timeout)
|
||||
* @method $this brpop(array $keys, $timeout)
|
||||
* @method $this brpoplpush($source, $destination, $timeout)
|
||||
@@ -97,7 +99,7 @@ use Predis\Command\CommandInterface;
|
||||
* @method $this sismember($key, $member)
|
||||
* @method $this smembers($key)
|
||||
* @method $this smove($source, $destination, $member)
|
||||
* @method $this spop($key)
|
||||
* @method $this spop($key, $count = null)
|
||||
* @method $this srandmember($key, $count = null)
|
||||
* @method $this srem($key, $member)
|
||||
* @method $this sscan($key, $cursor, array $options = null)
|
||||
@@ -121,6 +123,7 @@ use Predis\Command\CommandInterface;
|
||||
* @method $this zscore($key, $member)
|
||||
* @method $this zscan($key, $cursor, array $options = null)
|
||||
* @method $this zrangebylex($key, $start, $stop, array $options = null)
|
||||
* @method $this zrevrangebylex($key, $start, $stop, array $options = null)
|
||||
* @method $this zremrangebylex($key, $min, $max)
|
||||
* @method $this zlexcount($key, $min, $max)
|
||||
* @method $this pfadd($key, array $elements)
|
||||
@@ -154,6 +157,12 @@ use Predis\Command\CommandInterface;
|
||||
* @method $this slowlog($subcommand, $argument = null)
|
||||
* @method $this time()
|
||||
* @method $this command()
|
||||
* @method $this geoadd($key, $longitude, $latitude, $member)
|
||||
* @method $this geohash($key, array $members)
|
||||
* @method $this geopos($key, array $members)
|
||||
* @method $this geodist($key, $member1, $member2, $unit = null)
|
||||
* @method $this georadius($key, $longitude, $latitude, $radius, $unit, array $options = null)
|
||||
* @method $this georadiusbymember($key, $member, $radius, $unit, array $options = null)
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
|
||||
+11
-2
@@ -46,6 +46,7 @@ use Predis\Profile\ProfileInterface;
|
||||
* @method int append($key, $value)
|
||||
* @method int bitcount($key, $start = null, $end = null)
|
||||
* @method int bitop($operation, $destkey, $key)
|
||||
* @method array bitfield($key, ...)
|
||||
* @method int decr($key)
|
||||
* @method int decrby($key, $decrement)
|
||||
* @method string get($key)
|
||||
@@ -79,6 +80,7 @@ use Predis\Profile\ProfileInterface;
|
||||
* @method int hset($key, $field, $value)
|
||||
* @method int hsetnx($key, $field, $value)
|
||||
* @method array hvals($key)
|
||||
* @method int hstrlen($key, $field)
|
||||
* @method array blpop(array $keys, $timeout)
|
||||
* @method array brpop(array $keys, $timeout)
|
||||
* @method array brpoplpush($source, $destination, $timeout)
|
||||
@@ -105,7 +107,7 @@ use Predis\Profile\ProfileInterface;
|
||||
* @method int sismember($key, $member)
|
||||
* @method array smembers($key)
|
||||
* @method int smove($source, $destination, $member)
|
||||
* @method string spop($key)
|
||||
* @method string spop($key, $count = null)
|
||||
* @method string srandmember($key, $count = null)
|
||||
* @method int srem($key, $member)
|
||||
* @method array sscan($key, $cursor, array $options = null)
|
||||
@@ -123,12 +125,13 @@ use Predis\Profile\ProfileInterface;
|
||||
* @method int zremrangebyrank($key, $start, $stop)
|
||||
* @method int zremrangebyscore($key, $min, $max)
|
||||
* @method array zrevrange($key, $start, $stop, array $options = null)
|
||||
* @method array zrevrangebyscore($key, $min, $max, array $options = null)
|
||||
* @method array zrevrangebyscore($key, $max, $min, array $options = null)
|
||||
* @method int zrevrank($key, $member)
|
||||
* @method int zunionstore($destination, array $keys, array $options = null)
|
||||
* @method string zscore($key, $member)
|
||||
* @method array zscan($key, $cursor, array $options = null)
|
||||
* @method array zrangebylex($key, $start, $stop, array $options = null)
|
||||
* @method array zrevrangebylex($key, $start, $stop, array $options = null)
|
||||
* @method int zremrangebylex($key, $min, $max)
|
||||
* @method int zlexcount($key, $min, $max)
|
||||
* @method int pfadd($key, array $elements)
|
||||
@@ -162,6 +165,12 @@ use Predis\Profile\ProfileInterface;
|
||||
* @method mixed slowlog($subcommand, $argument = null)
|
||||
* @method array time()
|
||||
* @method array command()
|
||||
* @method int geoadd($key, $longitude, $latitude, $member)
|
||||
* @method array geohash($key, array $members)
|
||||
* @method array geopos($key, array $members)
|
||||
* @method string geodist($key, $member1, $member2, $unit = null)
|
||||
* @method array georadius($key, $longitude, $latitude, $radius, $unit, array $options = null)
|
||||
* @method array georadiusbymember($key, $member, $radius, $unit, array $options = null)
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
|
||||
@@ -43,7 +43,7 @@ abstract class ClusterStrategy implements StrategyInterface
|
||||
|
||||
return array(
|
||||
/* commands operating on the key space */
|
||||
'EXISTS' => $getKeyFromFirstArgument,
|
||||
'EXISTS' => $getKeyFromAllArguments,
|
||||
'DEL' => $getKeyFromAllArguments,
|
||||
'TYPE' => $getKeyFromFirstArgument,
|
||||
'EXPIRE' => $getKeyFromFirstArgument,
|
||||
@@ -53,7 +53,7 @@ abstract class ClusterStrategy implements StrategyInterface
|
||||
'PEXPIREAT' => $getKeyFromFirstArgument,
|
||||
'TTL' => $getKeyFromFirstArgument,
|
||||
'PTTL' => $getKeyFromFirstArgument,
|
||||
'SORT' => $getKeyFromFirstArgument, // TODO
|
||||
'SORT' => array($this, 'getKeyFromSortCommand'),
|
||||
'DUMP' => $getKeyFromFirstArgument,
|
||||
'RESTORE' => $getKeyFromFirstArgument,
|
||||
|
||||
@@ -80,6 +80,7 @@ abstract class ClusterStrategy implements StrategyInterface
|
||||
'SUBSTR' => $getKeyFromFirstArgument,
|
||||
'BITOP' => array($this, 'getKeyFromBitOp'),
|
||||
'BITCOUNT' => $getKeyFromFirstArgument,
|
||||
'BITFIELD' => $getKeyFromFirstArgument,
|
||||
|
||||
/* commands operating on lists */
|
||||
'LINSERT' => $getKeyFromFirstArgument,
|
||||
@@ -164,6 +165,14 @@ abstract class ClusterStrategy implements StrategyInterface
|
||||
/* scripting */
|
||||
'EVAL' => array($this, 'getKeyFromScriptingCommands'),
|
||||
'EVALSHA' => array($this, 'getKeyFromScriptingCommands'),
|
||||
|
||||
/* commands performing geospatial operations */
|
||||
'GEOADD' => $getKeyFromFirstArgument,
|
||||
'GEOHASH' => $getKeyFromFirstArgument,
|
||||
'GEOPOS' => $getKeyFromFirstArgument,
|
||||
'GEODIST' => $getKeyFromFirstArgument,
|
||||
'GEORADIUS' => array($this, 'getKeyFromGeoradiusCommands'),
|
||||
'GEORADIUSBYMEMBER' => array($this, 'getKeyFromGeoradiusCommands'),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -261,6 +270,35 @@ abstract class ClusterStrategy implements StrategyInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from SORT command.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getKeyFromSortCommand(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
$firstKey = $arguments[0];
|
||||
|
||||
if (1 === $argc = count($arguments)) {
|
||||
return $firstKey;
|
||||
}
|
||||
|
||||
$keys = array($firstKey);
|
||||
|
||||
for ($i = 1; $i < $argc; ++$i) {
|
||||
if (strtoupper($arguments[$i]) === 'STORE') {
|
||||
$keys[] = $arguments[++$i];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->checkSameSlotForKeys($keys)) {
|
||||
return $firstKey;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from BLPOP and BRPOP commands.
|
||||
*
|
||||
@@ -293,6 +331,39 @@ abstract class ClusterStrategy implements StrategyInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from GEORADIUS and GEORADIUSBYMEMBER commands.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getKeyFromGeoradiusCommands(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
$argc = count($arguments);
|
||||
$startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4;
|
||||
|
||||
if ($argc > $startIndex) {
|
||||
$keys = array($arguments[0]);
|
||||
|
||||
for ($i = $startIndex; $i < $argc; ++$i) {
|
||||
$argument = strtoupper($arguments[$i]);
|
||||
if ($argument === 'STORE' || $argument === 'STOREDIST') {
|
||||
$keys[] = $arguments[++$i];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->checkSameSlotForKeys($keys)) {
|
||||
return $arguments[0];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return $arguments[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the key from ZINTERSTORE and ZUNIONSTORE commands.
|
||||
*
|
||||
|
||||
@@ -50,7 +50,11 @@ class HashKey extends CursorBasedIterator
|
||||
*/
|
||||
protected function extractNext()
|
||||
{
|
||||
$this->position = key($this->elements);
|
||||
$this->current = array_shift($this->elements);
|
||||
if ($kv = each($this->elements)) {
|
||||
$this->position = $kv[0];
|
||||
$this->current = $kv[1];
|
||||
|
||||
unset($this->elements[$this->position]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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/geoadd
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class GeospatialGeoAdd extends Command
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'GEOADD';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(array $arguments)
|
||||
{
|
||||
if (count($arguments) === 2 && is_array($arguments[1])) {
|
||||
foreach (array_pop($arguments) as $item) {
|
||||
$arguments = array_merge($arguments, $item);
|
||||
}
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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/geodist
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class GeospatialGeoDist extends Command
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'GEODIST';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?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/geohash
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class GeospatialGeoHash extends Command
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'GEOHASH';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(array $arguments)
|
||||
{
|
||||
if (count($arguments) === 2 && is_array($arguments[1])) {
|
||||
$members = array_pop($arguments);
|
||||
$arguments = array_merge($arguments, $members);
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?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/geopos
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class GeospatialGeoPos extends Command
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'GEOPOS';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(array $arguments)
|
||||
{
|
||||
if (count($arguments) === 2 && is_array($arguments[1])) {
|
||||
$members = array_pop($arguments);
|
||||
$arguments = array_merge($arguments, $members);
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?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/georadius
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class GeospatialGeoRadius extends Command
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'GEORADIUS';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function filterArguments(array $arguments)
|
||||
{
|
||||
if ($arguments && is_array(end($arguments))) {
|
||||
$options = array_change_key_case(array_pop($arguments), CASE_UPPER);
|
||||
|
||||
if (isset($options['WITHCOORD']) && $options['WITHCOORD'] == true) {
|
||||
$arguments[] = 'WITHCOORD';
|
||||
}
|
||||
|
||||
if (isset($options['WITHDIST']) && $options['WITHDIST'] == true) {
|
||||
$arguments[] = 'WITHDIST';
|
||||
}
|
||||
|
||||
if (isset($options['WITHHASH']) && $options['WITHHASH'] == true) {
|
||||
$arguments[] = 'WITHHASH';
|
||||
}
|
||||
|
||||
if (isset($options['COUNT'])) {
|
||||
$arguments[] = 'COUNT';
|
||||
$arguments[] = $options['COUNT'];
|
||||
}
|
||||
|
||||
if (isset($options['SORT'])) {
|
||||
$arguments[] = strtoupper($options['SORT']);
|
||||
}
|
||||
|
||||
if (isset($options['STORE'])) {
|
||||
$arguments[] = 'STORE';
|
||||
$arguments[] = $options['STORE'];
|
||||
}
|
||||
|
||||
if (isset($options['STOREDIST'])) {
|
||||
$arguments[] = 'STOREDIST';
|
||||
$arguments[] = $options['STOREDIST'];
|
||||
}
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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/georadiusbymember
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class GeospatialGeoRadiusByMember extends GeospatialGeoRadius
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'GEORADIUSBYMEMBER';
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ class KeyPrefixProcessor implements ProcessorInterface
|
||||
$this->prefix = $prefix;
|
||||
$this->commands = array(
|
||||
/* ---------------- Redis 1.2 ---------------- */
|
||||
'EXISTS' => 'static::first',
|
||||
'EXISTS' => 'static::all',
|
||||
'DEL' => 'static::all',
|
||||
'TYPE' => 'static::first',
|
||||
'KEYS' => 'static::first',
|
||||
@@ -159,6 +159,13 @@ class KeyPrefixProcessor implements ProcessorInterface
|
||||
'BITPOS' => 'static::first',
|
||||
/* ---------------- Redis 3.2 ---------------- */
|
||||
'HSTRLEN' => 'static::first',
|
||||
'BITFIELD' => 'static::first',
|
||||
'GEOADD' => 'static::first',
|
||||
'GEOHASH' => 'static::first',
|
||||
'GEOPOS' => 'static::first',
|
||||
'GEODIST' => 'static::first',
|
||||
'GEORADIUS' => 'static::georadius',
|
||||
'GEORADIUSBYMEMBER' => 'static::georadius',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -338,7 +345,7 @@ class KeyPrefixProcessor implements ProcessorInterface
|
||||
|
||||
if (($count = count($arguments)) > 1) {
|
||||
for ($i = 1; $i < $count; ++$i) {
|
||||
switch ($arguments[$i]) {
|
||||
switch (strtoupper($arguments[$i])) {
|
||||
case 'BY':
|
||||
case 'STORE':
|
||||
$arguments[$i] = "$prefix{$arguments[++$i]}";
|
||||
@@ -412,4 +419,32 @@ class KeyPrefixProcessor implements ProcessorInterface
|
||||
$command->setRawArguments($arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the specified prefix to the key of a GEORADIUS command.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @param string $prefix Prefix string.
|
||||
*/
|
||||
public static function georadius(CommandInterface $command, $prefix)
|
||||
{
|
||||
if ($arguments = $command->getArguments()) {
|
||||
$arguments[0] = "$prefix{$arguments[0]}";
|
||||
$startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4;
|
||||
|
||||
if (($count = count($arguments)) > $startIndex) {
|
||||
for ($i = $startIndex; $i < $count; ++$i) {
|
||||
switch (strtoupper($arguments[$i])) {
|
||||
case 'STORE':
|
||||
case 'STOREDIST':
|
||||
$arguments[$i] = "$prefix{$arguments[++$i]}";
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$command->setRawArguments($arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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/bitfield
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class StringBitField extends Command
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return 'BITFIELD';
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,11 @@
|
||||
|
||||
namespace Predis\Command;
|
||||
|
||||
/**
|
||||
* @link http://redis.io/commands/zrevrangebylex
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class ZSetReverseRangeByLex extends ZSetRangeByLex
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -70,10 +70,10 @@ class Options implements OptionsInterface
|
||||
*/
|
||||
public function defined($option)
|
||||
{
|
||||
return (
|
||||
return
|
||||
array_key_exists($option, $this->options) ||
|
||||
array_key_exists($option, $this->input)
|
||||
);
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,9 +23,24 @@ use Predis\Replication\ReplicationStrategy;
|
||||
*/
|
||||
class MasterSlaveReplication implements ReplicationInterface
|
||||
{
|
||||
/**
|
||||
* @var ReplicationStrategy
|
||||
*/
|
||||
protected $strategy;
|
||||
|
||||
/**
|
||||
* @var NodeConnectionInterface
|
||||
*/
|
||||
protected $master;
|
||||
protected $slaves;
|
||||
|
||||
/**
|
||||
* @var NodeConnectionInterface[]
|
||||
*/
|
||||
protected $slaves = array();
|
||||
|
||||
/**
|
||||
* @var NodeConnectionInterface
|
||||
*/
|
||||
protected $current;
|
||||
|
||||
/**
|
||||
@@ -33,7 +48,6 @@ class MasterSlaveReplication implements ReplicationInterface
|
||||
*/
|
||||
public function __construct(ReplicationStrategy $strategy = null)
|
||||
{
|
||||
$this->slaves = array();
|
||||
$this->strategy = $strategy ?: new ReplicationStrategy();
|
||||
}
|
||||
|
||||
@@ -144,6 +158,7 @@ class MasterSlaveReplication implements ReplicationInterface
|
||||
if (!$connection instanceof NodeConnectionInterface) {
|
||||
$connection = $this->getConnectionById($connection);
|
||||
}
|
||||
|
||||
if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) {
|
||||
throw new \InvalidArgumentException('Invalid connection or connection not found.');
|
||||
}
|
||||
@@ -192,7 +207,9 @@ class MasterSlaveReplication implements ReplicationInterface
|
||||
*/
|
||||
protected function pickSlave()
|
||||
{
|
||||
return $this->slaves[array_rand($this->slaves)];
|
||||
if ($this->slaves) {
|
||||
return $this->slaves[array_rand($this->slaves)];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,10 +11,12 @@
|
||||
|
||||
namespace Predis\Connection\Aggregate;
|
||||
|
||||
use Predis\ClientException;
|
||||
use Predis\Cluster\RedisStrategy as RedisClusterStrategy;
|
||||
use Predis\Cluster\StrategyInterface;
|
||||
use Predis\Command\CommandInterface;
|
||||
use Predis\Command\RawCommand;
|
||||
use Predis\Connection\ConnectionException;
|
||||
use Predis\Connection\FactoryInterface;
|
||||
use Predis\Connection\NodeConnectionInterface;
|
||||
use Predis\NotSupportedException;
|
||||
@@ -51,6 +53,7 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
private $slotsMap;
|
||||
private $strategy;
|
||||
private $connections;
|
||||
private $retryLimit = 5;
|
||||
|
||||
/**
|
||||
* @param FactoryInterface $connections Optional connection factory.
|
||||
@@ -64,6 +67,20 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
$this->strategy = $strategy ?: new RedisClusterStrategy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of retries for commands upon server failure.
|
||||
*
|
||||
* -1 = unlimited retry attempts
|
||||
* 0 = no retry attempts (fails immediatly)
|
||||
* n = fail only after n retry attempts
|
||||
*
|
||||
* @param int $retry Number of retry attempts.
|
||||
*/
|
||||
public function setRetryLimit($retry)
|
||||
{
|
||||
$this->retryLimit = (int) $retry;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -118,6 +135,8 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
$this->slotsMap
|
||||
);
|
||||
|
||||
$this->slots = array_diff($this->slots, array($connection));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -165,11 +184,59 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
continue;
|
||||
}
|
||||
|
||||
$slots = explode('-', $parameters->slots, 2);
|
||||
$this->setSlots($slots[0], $slots[1], $connectionID);
|
||||
foreach (explode(',', $parameters->slots) as $slotRange) {
|
||||
$slots = explode('-', $slotRange, 2);
|
||||
|
||||
if (!isset($slots[1])) {
|
||||
$slots[1] = $slots[0];
|
||||
}
|
||||
|
||||
$this->setSlots($slots[0], $slots[1], $connectionID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the specified node of the cluster to fetch the updated slots map.
|
||||
*
|
||||
* When the connection fails, this method tries to execute the same command
|
||||
* on a different connection picked at random from the pool of known nodes,
|
||||
* up until the retry limit is reached.
|
||||
*
|
||||
* @param NodeConnectionInterface $connection Connection to a node of the cluster.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function queryClusterNodeForSlotsMap(NodeConnectionInterface $connection)
|
||||
{
|
||||
$retries = 0;
|
||||
$command = RawCommand::create('CLUSTER', 'SLOTS');
|
||||
|
||||
RETRY_COMMAND: {
|
||||
try {
|
||||
$response = $connection->executeCommand($command);
|
||||
} catch (ConnectionException $exception) {
|
||||
$connection = $exception->getConnection();
|
||||
$connection->disconnect();
|
||||
|
||||
$this->remove($connection);
|
||||
|
||||
if ($retries === $this->retryLimit) {
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
if (!$connection = $this->getRandomConnection()) {
|
||||
throw new ClientException('No connections left in the pool for `CLUSTER SLOTS`');
|
||||
}
|
||||
|
||||
++$retries;
|
||||
goto RETRY_COMMAND;
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an updated slots map fetching the cluster configuration using
|
||||
* the CLUSTER SLOTS command against the specified node or a random one from
|
||||
@@ -185,8 +252,9 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
return array();
|
||||
}
|
||||
|
||||
$command = RawCommand::create('CLUSTER', 'SLOTS');
|
||||
$response = $connection->executeCommand($command);
|
||||
$this->resetSlotsMap();
|
||||
|
||||
$response = $this->queryClusterNodeForSlotsMap($connection);
|
||||
|
||||
foreach ($response as $slots) {
|
||||
// We only support master servers for now, so we ignore subsequent
|
||||
@@ -203,9 +271,19 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
return $this->slotsMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the slots map cache.
|
||||
*/
|
||||
public function resetSlotsMap()
|
||||
{
|
||||
$this->slotsMap = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current slots map for the cluster.
|
||||
*
|
||||
* The order of the returned $slot => $server dictionary is not guaranteed.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSlotsMap()
|
||||
@@ -252,6 +330,10 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
*/
|
||||
protected function guessNode($slot)
|
||||
{
|
||||
if (!$this->pool) {
|
||||
throw new ClientException('No connections available in the pool');
|
||||
}
|
||||
|
||||
if (!isset($this->slotsMap)) {
|
||||
$this->buildSlotsMap();
|
||||
}
|
||||
@@ -446,12 +528,54 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that a command is executed one more time on connection failure.
|
||||
*
|
||||
* The connection to the node that generated the error is evicted from the
|
||||
* pool before trying to fetch an updated slots map from another node. If
|
||||
* the new slots map points to an unreachable server the client gives up and
|
||||
* throws the exception as the nodes participating in the cluster may still
|
||||
* have to agree that something changed in the configuration of the cluster.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
* @param string $method Actual method.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function retryCommandOnFailure(CommandInterface $command, $method)
|
||||
{
|
||||
$failure = false;
|
||||
|
||||
RETRY_COMMAND: {
|
||||
try {
|
||||
$response = $this->getConnection($command)->$method($command);
|
||||
} catch (ConnectionException $exception) {
|
||||
$connection = $exception->getConnection();
|
||||
$connection->disconnect();
|
||||
|
||||
$this->remove($connection);
|
||||
|
||||
if ($failure) {
|
||||
throw $exception;
|
||||
} elseif ($this->useClusterSlots) {
|
||||
$this->askSlotsMap();
|
||||
}
|
||||
|
||||
$failure = true;
|
||||
|
||||
goto RETRY_COMMAND;
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function writeRequest(CommandInterface $command)
|
||||
{
|
||||
$this->getConnection($command)->writeRequest($command);
|
||||
$this->retryCommandOnFailure($command, __FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -459,7 +583,7 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
*/
|
||||
public function readResponse(CommandInterface $command)
|
||||
{
|
||||
return $this->getConnection($command)->readResponse($command);
|
||||
return $this->retryCommandOnFailure($command, __FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -467,8 +591,7 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
*/
|
||||
public function executeCommand(CommandInterface $command)
|
||||
{
|
||||
$connection = $this->getConnection($command);
|
||||
$response = $connection->executeCommand($command);
|
||||
$response = $this->retryCommandOnFailure($command, __FUNCTION__);
|
||||
|
||||
if ($response instanceof ErrorResponseInterface) {
|
||||
return $this->onErrorResponse($command, $response);
|
||||
@@ -517,7 +640,7 @@ class RedisCluster implements ClusterInterface, \IteratorAggregate, \Countable
|
||||
|
||||
/**
|
||||
* Enables automatic fetching of the current slots map from one of the nodes
|
||||
* using the CLUSTER SLOTS command. This option is disabled by default but
|
||||
* using the CLUSTER SLOTS command. This option is enabled by default as
|
||||
* 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
|
||||
|
||||
@@ -123,7 +123,7 @@ class StreamConnection extends AbstractConnection
|
||||
protected function unixStreamInitializer(ParametersInterface $parameters)
|
||||
{
|
||||
if (!isset($parameters->path)) {
|
||||
throw new InvalidArgumentException('Missing UNIX domain socket path.');
|
||||
throw new \InvalidArgumentException('Missing UNIX domain socket path.');
|
||||
}
|
||||
|
||||
$uri = "unix://{$parameters->path}";
|
||||
@@ -281,8 +281,7 @@ class StreamConnection extends AbstractConnection
|
||||
|
||||
$buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
|
||||
|
||||
for ($i = 0, $reqlen--; $i < $reqlen; ++$i) {
|
||||
$argument = $arguments[$i];
|
||||
foreach ($arguments as $argument) {
|
||||
$arglen = strlen($argument);
|
||||
$buffer .= "\${$arglen}\r\n{$argument}\r\n";
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ class Consumer implements \Iterator
|
||||
/**
|
||||
* Returns the last message payload retrieved from the server.
|
||||
*
|
||||
* @return Object
|
||||
* @return object
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
@@ -136,7 +136,7 @@ class Consumer implements \Iterator
|
||||
* Waits for a new message from the server generated by MONITOR and returns
|
||||
* it when available.
|
||||
*
|
||||
* @return Object
|
||||
* @return object
|
||||
*/
|
||||
private function getValue()
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@ final class Factory
|
||||
'2.6' => 'Predis\Profile\RedisVersion260',
|
||||
'2.8' => 'Predis\Profile\RedisVersion280',
|
||||
'3.0' => 'Predis\Profile\RedisVersion300',
|
||||
'3.2' => 'Predis\Profile\RedisVersion320',
|
||||
'dev' => 'Predis\Profile\RedisUnstable',
|
||||
'default' => 'Predis\Profile\RedisVersion300',
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Predis\Profile;
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class RedisUnstable extends RedisVersion300
|
||||
class RedisUnstable extends RedisVersion320
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
@@ -32,10 +32,7 @@ class RedisUnstable extends RedisVersion300
|
||||
public function getSupportedCommands()
|
||||
{
|
||||
return array_merge(parent::getSupportedCommands(), array(
|
||||
/* ---------------- Redis 3.2 ---------------- */
|
||||
|
||||
/* commands operating on hashes */
|
||||
'HSTRLEN' => 'Predis\Command\HashStringLength',
|
||||
// EMPTY
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
<?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 3.0.
|
||||
*
|
||||
* @author Daniele Alessandri <suppakilla@gmail.com>
|
||||
*/
|
||||
class RedisVersion320 extends RedisProfile
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return '3.2';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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',
|
||||
'MIGRATE' => 'Predis\Command\KeyMigrate',
|
||||
|
||||
/* 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',
|
||||
'SENTINEL' => 'Predis\Command\ServerSentinel',
|
||||
|
||||
/* ---------------- Redis 2.8 ---------------- */
|
||||
|
||||
/* commands operating on the key space */
|
||||
'SCAN' => 'Predis\Command\KeyScan',
|
||||
|
||||
/* commands operating on string values */
|
||||
'BITPOS' => 'Predis\Command\StringBitPos',
|
||||
|
||||
/* 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',
|
||||
'ZREVRANGEBYLEX' => 'Predis\Command\ZSetReverseRangeByLex',
|
||||
|
||||
/* commands operating on hashes */
|
||||
'HSCAN' => 'Predis\Command\HashScan',
|
||||
|
||||
/* publish - subscribe */
|
||||
'PUBSUB' => 'Predis\Command\PubSubPubsub',
|
||||
|
||||
/* 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.2 ---------------- */
|
||||
|
||||
/* commands operating on hashes */
|
||||
'HSTRLEN' => 'Predis\Command\HashStringLength',
|
||||
'BITFIELD' => 'Predis\Command\StringBitField',
|
||||
|
||||
/* commands performing geospatial operations */
|
||||
'GEOADD' => 'Predis\Command\GeospatialGeoAdd',
|
||||
'GEOHASH' => 'Predis\Command\GeospatialGeoHash',
|
||||
'GEOPOS' => 'Predis\Command\GeospatialGeoPos',
|
||||
'GEODIST' => 'Predis\Command\GeospatialGeoDist',
|
||||
'GEORADIUS' => 'Predis\Command\GeospatialGeoRadius',
|
||||
'GEORADIUSBYMEMBER' => 'Predis\Command\GeospatialGeoRadiusByMember',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -36,8 +36,7 @@ class RequestSerializer implements RequestSerializerInterface
|
||||
|
||||
$buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
|
||||
|
||||
for ($i = 0, $reqlen--; $i < $reqlen; ++$i) {
|
||||
$argument = $arguments[$i];
|
||||
foreach ($arguments as $argument) {
|
||||
$arglen = strlen($argument);
|
||||
$buffer .= "\${$arglen}\r\n{$argument}\r\n";
|
||||
}
|
||||
|
||||
@@ -18,17 +18,17 @@ namespace Predis\PubSub;
|
||||
*/
|
||||
abstract class AbstractConsumer implements \Iterator
|
||||
{
|
||||
const SUBSCRIBE = 'subscribe';
|
||||
const UNSUBSCRIBE = 'unsubscribe';
|
||||
const PSUBSCRIBE = 'psubscribe';
|
||||
const SUBSCRIBE = 'subscribe';
|
||||
const UNSUBSCRIBE = 'unsubscribe';
|
||||
const PSUBSCRIBE = 'psubscribe';
|
||||
const PUNSUBSCRIBE = 'punsubscribe';
|
||||
const MESSAGE = 'message';
|
||||
const PMESSAGE = 'pmessage';
|
||||
const PONG = 'pong';
|
||||
const MESSAGE = 'message';
|
||||
const PMESSAGE = 'pmessage';
|
||||
const PONG = 'pong';
|
||||
|
||||
const STATUS_VALID = 1; // 0b0001
|
||||
const STATUS_SUBSCRIBED = 2; // 0b0010
|
||||
const STATUS_PSUBSCRIBED = 4; // 0b0100
|
||||
const STATUS_VALID = 1; // 0b0001
|
||||
const STATUS_SUBSCRIBED = 2; // 0b0010
|
||||
const STATUS_PSUBSCRIBED = 4; // 0b0100
|
||||
|
||||
private $position = null;
|
||||
private $statusFlags = self::STATUS_VALID;
|
||||
|
||||
@@ -91,7 +91,7 @@ class DispatcherLoop
|
||||
* Binds a callback to a channel.
|
||||
*
|
||||
* @param string $channel Channel name.
|
||||
* @param Callable $callback A callback.
|
||||
* @param callable $callback A callback.
|
||||
*/
|
||||
public function attachCallback($channel, $callback)
|
||||
{
|
||||
|
||||
@@ -100,8 +100,69 @@ class ReplicationStrategy
|
||||
protected function isSortReadOnly(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
$argc = count($arguments);
|
||||
|
||||
return ($c = count($arguments)) === 1 ? true : $arguments[$c - 2] !== 'STORE';
|
||||
if ($argc > 1) {
|
||||
for ($i = 1; $i < $argc; ++$i) {
|
||||
$argument = strtoupper($arguments[$i]);
|
||||
if ($argument === 'STORE') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if BITFIELD performs a read-only operation by looking for certain
|
||||
* SET and INCRYBY modifiers in the arguments array of the command.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isBitfieldReadOnly(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
$argc = count($arguments);
|
||||
|
||||
if ($argc >= 2) {
|
||||
for ($i = 1; $i < $argc; ++$i) {
|
||||
$argument = strtoupper($arguments[$i]);
|
||||
if ($argument === 'SET' || $argument === 'INCRBY') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a GEORADIUS command is a readable operation by parsing the
|
||||
* arguments array of the specified commad instance.
|
||||
*
|
||||
* @param CommandInterface $command Command instance.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isGeoradiusReadOnly(CommandInterface $command)
|
||||
{
|
||||
$arguments = $command->getArguments();
|
||||
$argc = count($arguments);
|
||||
$startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4;
|
||||
|
||||
if ($argc > $startIndex) {
|
||||
for ($i = $startIndex; $i < $argc; ++$i) {
|
||||
$argument = strtoupper($arguments[$i]);
|
||||
if ($argument === 'STORE' || $argument === 'STOREDIST') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,6 +293,12 @@ class ReplicationStrategy
|
||||
'TIME' => true,
|
||||
'PFCOUNT' => true,
|
||||
'SORT' => array($this, 'isSortReadOnly'),
|
||||
'BITFIELD' => array($this, 'isBitfieldReadOnly'),
|
||||
'GEOHASH' => true,
|
||||
'GEOPOS' => true,
|
||||
'GEODIST' => true,
|
||||
'GEORADIUS' => array($this, 'isGeoradiusReadOnly'),
|
||||
'GEORADIUSBYMEMBER' => array($this, 'isGeoradiusReadOnly'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ class MultiExecState
|
||||
{
|
||||
const INITIALIZED = 1; // 0b00001
|
||||
const INSIDEBLOCK = 2; // 0b00010
|
||||
const DISCARDED = 4; // 0b00100
|
||||
const CAS = 8; // 0b01000
|
||||
const WATCH = 16; // 0b10000
|
||||
const DISCARDED = 4; // 0b00100
|
||||
const CAS = 8; // 0b01000
|
||||
const WATCH = 16; // 0b10000
|
||||
|
||||
private $flags;
|
||||
|
||||
|
||||
@@ -96,18 +96,6 @@ abstract class PredisConnectionTestCase extends PredisTestCase
|
||||
$this->assertEquals($parameters, $unserialized->getParameters());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
* @group slow
|
||||
* @expectedException \Predis\Connection\ConnectionException
|
||||
*/
|
||||
public function testThrowExceptionWhenUnableToConnect()
|
||||
{
|
||||
$parameters = array('host' => '169.254.10.10', 'timeout' => 0.5);
|
||||
$connection = $this->createConnectionWithParams($parameters, false);
|
||||
$connection->executeCommand($this->getCurrentProfile()->createCommand('ping'));
|
||||
}
|
||||
|
||||
// ******************************************************************** //
|
||||
// ---- INTEGRATION TESTS --------------------------------------------- //
|
||||
// ******************************************************************** //
|
||||
@@ -221,6 +209,19 @@ abstract class PredisConnectionTestCase extends PredisTestCase
|
||||
$this->assertEquals('PONG', $connection->executeCommand($cmdPing));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
*/
|
||||
public function testExecutesCommandWithHolesInArguments()
|
||||
{
|
||||
$profile = $this->getCurrentProfile();
|
||||
$cmdDel = $profile->createCommand('mget', array(0 => 'key:0', 2 => 'key:2'));
|
||||
|
||||
$connection = $this->createConnection();
|
||||
|
||||
$this->assertSame(array(null, null), $connection->executeCommand($cmdDel));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
*/
|
||||
@@ -415,9 +416,17 @@ abstract class PredisConnectionTestCase extends PredisTestCase
|
||||
*/
|
||||
public function testThrowsExceptionOnConnectionTimeout()
|
||||
{
|
||||
// TODO: float timeouts for connect() under HHVM 3.6.6 are broken and,
|
||||
// unfortunately, this is the version still being used by Travis CI.
|
||||
if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.6.6', '<=')) {
|
||||
$timeout = 1;
|
||||
} else {
|
||||
$timeout = 0.1;
|
||||
}
|
||||
|
||||
$connection = $this->createConnectionWithParams(array(
|
||||
'host' => '169.254.10.10',
|
||||
'timeout' => 0.1,
|
||||
'timeout' => $timeout,
|
||||
), false);
|
||||
|
||||
$connection->connect();
|
||||
@@ -431,9 +440,17 @@ abstract class PredisConnectionTestCase extends PredisTestCase
|
||||
*/
|
||||
public function testThrowsExceptionOnConnectionTimeoutIPv6()
|
||||
{
|
||||
// TODO: float timeouts for connect() under HHVM 3.6.6 are broken and,
|
||||
// unfortunately, this is the version still being used by Travis CI.
|
||||
if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.6.6', '<=')) {
|
||||
$timeout = 1;
|
||||
} else {
|
||||
$timeout = 0.1;
|
||||
}
|
||||
|
||||
$connection = $this->createConnectionWithParams(array(
|
||||
'host' => '0:0:0:0:0:ffff:a9fe:a0a',
|
||||
'timeout' => 0.1,
|
||||
'timeout' => $timeout,
|
||||
), false);
|
||||
|
||||
$connection->connect();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
use Predis\Command\CommandInterface;
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
|
||||
/**
|
||||
* Constraint that verifies a redis command.
|
||||
@@ -72,6 +73,7 @@ class RedisCommandConstraint extends \PHPUnit_Framework_Constraint
|
||||
*/
|
||||
public function toString()
|
||||
{
|
||||
$exporter = new Exporter();
|
||||
$string = 'is a Redis command';
|
||||
|
||||
if ($this->commandID) {
|
||||
@@ -80,7 +82,7 @@ class RedisCommandConstraint extends \PHPUnit_Framework_Constraint
|
||||
|
||||
if ($this->arguments) {
|
||||
$string .= " and the following arguments:\n\n";
|
||||
$string .= \PHPUnit_Util_Type::export($this->arguments);
|
||||
$string .= $exporter->export($this->arguments);
|
||||
}
|
||||
|
||||
return $string;
|
||||
|
||||
@@ -107,6 +107,24 @@ class PredisStrategyTest extends PredisTestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testKeysForSortCommand()
|
||||
{
|
||||
$strategy = $this->getClusterStrategy();
|
||||
$profile = Profile\Factory::getDevelopment();
|
||||
$arguments = array('{key}:1', 'value1', '{key}:2', 'value2');
|
||||
|
||||
$commandID = 'SORT';
|
||||
|
||||
$command = $profile->createCommand($commandID, array('{key}:1'));
|
||||
$this->assertNotNull($strategy->getSlot($command), $commandID);
|
||||
|
||||
$command = $profile->createCommand($commandID, array('{key}:1', array('STORE' => '{key}:2')));
|
||||
$this->assertNotNull($strategy->getSlot($command), $commandID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
@@ -152,6 +170,40 @@ class PredisStrategyTest extends PredisTestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testKeysForGeoradiusCommand()
|
||||
{
|
||||
$strategy = $this->getClusterStrategy();
|
||||
$profile = Profile\Factory::getDevelopment();
|
||||
|
||||
$commandID = 'GEORADIUS';
|
||||
|
||||
$command = $profile->createCommand($commandID, array('{key}:1', 10, 10, 1, 'km'));
|
||||
$this->assertNotNull($strategy->getSlot($command), $commandID);
|
||||
|
||||
$command = $profile->createCommand($commandID, array('{key}:1', 10, 10, 1, 'km', 'store', '{key}:2', 'storedist', '{key}:3'));
|
||||
$this->assertNotNull($strategy->getSlot($command), $commandID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testKeysForGeoradiusByMemberCommand()
|
||||
{
|
||||
$strategy = $this->getClusterStrategy();
|
||||
$profile = Profile\Factory::getDevelopment();
|
||||
|
||||
$commandID = 'GEORADIUSBYMEMBER';
|
||||
|
||||
$command = $profile->createCommand($commandID, array('{key}:1', 'member', 1, 'km'));
|
||||
$this->assertNotNull($strategy->getSlot($command), $commandID);
|
||||
|
||||
$command = $profile->createCommand($commandID, array('{key}:1', 'member', 1, 'km', 'store', '{key}:2', 'storedist', '{key}:3'));
|
||||
$this->assertNotNull($strategy->getSlot($command), $commandID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
@@ -255,7 +307,7 @@ class PredisStrategyTest extends PredisTestCase
|
||||
{
|
||||
$commands = array(
|
||||
/* commands operating on the key space */
|
||||
'EXISTS' => 'keys-first',
|
||||
'EXISTS' => 'keys-all',
|
||||
'DEL' => 'keys-all',
|
||||
'TYPE' => 'keys-first',
|
||||
'EXPIRE' => 'keys-first',
|
||||
@@ -265,7 +317,7 @@ class PredisStrategyTest extends PredisTestCase
|
||||
'PEXPIREAT' => 'keys-first',
|
||||
'TTL' => 'keys-first',
|
||||
'PTTL' => 'keys-first',
|
||||
'SORT' => 'keys-first', // TODO
|
||||
'SORT' => 'variable',
|
||||
'DUMP' => 'keys-first',
|
||||
'RESTORE' => 'keys-first',
|
||||
|
||||
@@ -292,6 +344,7 @@ class PredisStrategyTest extends PredisTestCase
|
||||
'SUBSTR' => 'keys-first',
|
||||
'BITOP' => 'keys-bitop',
|
||||
'BITCOUNT' => 'keys-first',
|
||||
'BITFIELD' => 'keys-first',
|
||||
|
||||
/* commands operating on lists */
|
||||
'LINSERT' => 'keys-first',
|
||||
@@ -376,6 +429,14 @@ class PredisStrategyTest extends PredisTestCase
|
||||
/* scripting */
|
||||
'EVAL' => 'keys-script',
|
||||
'EVALSHA' => 'keys-script',
|
||||
|
||||
/* commands performing geospatial operations */
|
||||
'GEOADD' => 'keys-first',
|
||||
'GEOHASH' => 'keys-first',
|
||||
'GEOPOS' => 'keys-first',
|
||||
'GEODIST' => 'keys-first',
|
||||
'GEORADIUS' => 'keys-georadius',
|
||||
'GEORADIUSBYMEMBER' => 'keys-georadius',
|
||||
);
|
||||
|
||||
if (isset($type)) {
|
||||
|
||||
@@ -135,6 +135,24 @@ class RedisStrategyTest extends PredisTestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testKeysForSortCommand()
|
||||
{
|
||||
$strategy = $this->getClusterStrategy();
|
||||
$profile = Profile\Factory::getDevelopment();
|
||||
$arguments = array('{key}:1', 'value1', '{key}:2', 'value2');
|
||||
|
||||
$commandID = 'SORT';
|
||||
|
||||
$command = $profile->createCommand($commandID, array('{key}:1'));
|
||||
$this->assertNotNull($strategy->getSlot($command), $commandID);
|
||||
|
||||
$command = $profile->createCommand($commandID, array('{key}:1', array('STORE' => '{key}:2')));
|
||||
$this->assertNotNull($strategy->getSlot($command), $commandID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
@@ -165,6 +183,40 @@ class RedisStrategyTest extends PredisTestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testKeysForGeoradiusCommand()
|
||||
{
|
||||
$strategy = $this->getClusterStrategy();
|
||||
$profile = Profile\Factory::getDevelopment();
|
||||
|
||||
$commandID = 'GEORADIUS';
|
||||
|
||||
$command = $profile->createCommand($commandID, array('{key}:1', 10, 10, 1, 'km'));
|
||||
$this->assertNotNull($strategy->getSlot($command), $commandID);
|
||||
|
||||
$command = $profile->createCommand($commandID, array('{key}:1', 10, 10, 1, 'km', 'store', '{key}:2', 'storedist', '{key}:3'));
|
||||
$this->assertNotNull($strategy->getSlot($command), $commandID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testKeysForGeoradiusByMemberCommand()
|
||||
{
|
||||
$strategy = $this->getClusterStrategy();
|
||||
$profile = Profile\Factory::getDevelopment();
|
||||
|
||||
$commandID = 'GEORADIUSBYMEMBER';
|
||||
|
||||
$command = $profile->createCommand($commandID, array('{key}:1', 'member', 1, 'km'));
|
||||
$this->assertNotNull($strategy->getSlot($command), $commandID);
|
||||
|
||||
$command = $profile->createCommand($commandID, array('{key}:1', 'member', 1, 'km', 'store', '{key}:2', 'storedist', '{key}:3'));
|
||||
$this->assertNotNull($strategy->getSlot($command), $commandID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
@@ -265,7 +317,7 @@ class RedisStrategyTest extends PredisTestCase
|
||||
{
|
||||
$commands = array(
|
||||
/* commands operating on the key space */
|
||||
'EXISTS' => 'keys-first',
|
||||
'EXISTS' => 'keys-all',
|
||||
'DEL' => 'keys-all',
|
||||
'TYPE' => 'keys-first',
|
||||
'EXPIRE' => 'keys-first',
|
||||
@@ -302,6 +354,7 @@ class RedisStrategyTest extends PredisTestCase
|
||||
'SUBSTR' => 'keys-first',
|
||||
'BITOP' => 'keys-bitop',
|
||||
'BITCOUNT' => 'keys-first',
|
||||
'BITFIELD' => 'keys-first',
|
||||
|
||||
/* commands operating on lists */
|
||||
'LINSERT' => 'keys-first',
|
||||
@@ -386,6 +439,14 @@ class RedisStrategyTest extends PredisTestCase
|
||||
/* scripting */
|
||||
'EVAL' => 'keys-script',
|
||||
'EVALSHA' => 'keys-script',
|
||||
|
||||
/* commands performing geospatial operations */
|
||||
'GEOADD' => 'keys-first',
|
||||
'GEOHASH' => 'keys-first',
|
||||
'GEOPOS' => 'keys-first',
|
||||
'GEODIST' => 'keys-first',
|
||||
'GEORADIUS' => 'keys-georadius',
|
||||
'GEORADIUSBYMEMBER' => 'keys-georadius',
|
||||
);
|
||||
|
||||
if (isset($type)) {
|
||||
|
||||
@@ -56,6 +56,51 @@ class HashKeyTest extends PredisTestCase
|
||||
$this->assertFalse($iterator->valid());
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://github.com/nrk/predis/pull/330
|
||||
* @link https://github.com/nrk/predis/issues/331
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testIterationWithIntegerFields()
|
||||
{
|
||||
$client = $this->getMock('Predis\Client', array('getProfile', 'hscan'));
|
||||
|
||||
$client->expects($this->any())
|
||||
->method('getProfile')
|
||||
->will($this->returnValue(Profile\Factory::get('2.8')));
|
||||
$client->expects($this->once())
|
||||
->method('hscan')
|
||||
->with('key:hash', 0, array())
|
||||
->will($this->returnValue(array(0, array(
|
||||
1 => 'a', 2 => 'b', 3 => 100, 'foo' => 'bar',
|
||||
))));
|
||||
|
||||
$iterator = new HashKey($client, 'key:hash');
|
||||
|
||||
$iterator->rewind();
|
||||
$this->assertTrue($iterator->valid());
|
||||
$this->assertSame('a', $iterator->current());
|
||||
$this->assertSame(1, $iterator->key());
|
||||
|
||||
$iterator->next();
|
||||
$this->assertTrue($iterator->valid());
|
||||
$this->assertSame('b', $iterator->current());
|
||||
$this->assertSame(2, $iterator->key());
|
||||
|
||||
$iterator->next();
|
||||
$this->assertTrue($iterator->valid());
|
||||
$this->assertSame(100, $iterator->current());
|
||||
$this->assertSame(3, $iterator->key());
|
||||
|
||||
$iterator->next();
|
||||
$this->assertTrue($iterator->valid());
|
||||
$this->assertSame('bar', $iterator->current());
|
||||
$this->assertSame('foo', $iterator->key());
|
||||
|
||||
$iterator->next();
|
||||
$this->assertFalse($iterator->valid());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
<?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-geospatial
|
||||
*/
|
||||
class GeospatialGeoAddTest extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand()
|
||||
{
|
||||
return 'Predis\Command\GeospatialGeoAdd';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId()
|
||||
{
|
||||
return 'GEOADD';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments()
|
||||
{
|
||||
$arguments = array('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania');
|
||||
$expected = array('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArgumentsWithMembersAsSingleArray()
|
||||
{
|
||||
$arguments = array('Sicily', array(
|
||||
array('13.361389', '38.115556', 'Palermo'),
|
||||
array('15.087269', '37.502669', 'Catania'),
|
||||
));
|
||||
|
||||
$expected = array('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse()
|
||||
{
|
||||
$raw = 1;
|
||||
$expected = 1;
|
||||
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->assertSame($expected, $command->parseResponse($raw));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
*/
|
||||
public function testCommandFillsSortedSet()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo');
|
||||
$this->assertSame(array('Palermo' => '3479099956230698'), $redis->zrange('Sicily', 0, -1, 'WITHSCORES'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
* @expectedException \Predis\Response\ServerException
|
||||
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
|
||||
*/
|
||||
public function testThrowsExceptionOnWrongType()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->lpush('Sicily', 'Palermo');
|
||||
$redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?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-geospatial
|
||||
*/
|
||||
class GeospatialGeoDistTest extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand()
|
||||
{
|
||||
return 'Predis\Command\GeospatialGeoDist';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId()
|
||||
{
|
||||
return 'GEODIST';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments()
|
||||
{
|
||||
$arguments = array('key', 'member:1', 'member:2', 'km');
|
||||
$expected = array('key', 'member:1', 'member:2', 'km');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse()
|
||||
{
|
||||
$raw = array('103.31822459492736');
|
||||
$expected = array('103.31822459492736');
|
||||
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->assertSame($expected, $command->parseResponse($raw));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
*/
|
||||
public function testCommandReturnsGeoDistance()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania');
|
||||
$this->assertSame('166.2742', $redis->geodist('Sicily', 'Palermo', 'Catania', 'km'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
* @expectedException \Predis\Response\ServerException
|
||||
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
|
||||
*/
|
||||
public function testThrowsExceptionOnWrongType()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->lpush('Sicily', 'Palermo');
|
||||
$redis->geodist('Sicily', 'Palermo', 'Catania');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?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-geospatial
|
||||
*/
|
||||
class GeospatialGeoHashTest extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand()
|
||||
{
|
||||
return 'Predis\Command\GeospatialGeoHash';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId()
|
||||
{
|
||||
return 'GEOHASH';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments()
|
||||
{
|
||||
$arguments = array('key', 'member:1', 'member:2');
|
||||
$expected = array('key', 'member:1', 'member:2');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArgumentsWithMembersAsSingleArray()
|
||||
{
|
||||
$arguments = array('key', array('member:1', 'member:2'));
|
||||
$expected = array('key', 'member:1', 'member:2');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse()
|
||||
{
|
||||
$raw = array('sqc8b49rny0', 'sqdtr74hyu0');
|
||||
$expected = array('sqc8b49rny0', 'sqdtr74hyu0');
|
||||
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->assertSame($expected, $command->parseResponse($raw));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
*/
|
||||
public function testCommandReturnsGeoHashes()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania');
|
||||
$this->assertSame(array('sqc8b49rny0', 'sqdtr74hyu0'), $redis->geohash('Sicily', 'Palermo', 'Catania'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
* @expectedException \Predis\Response\ServerException
|
||||
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
|
||||
*/
|
||||
public function testThrowsExceptionOnWrongType()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->lpush('Sicily', 'Palermo');
|
||||
$redis->geohash('Sicily', 'Palermo');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?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-geospatial
|
||||
*/
|
||||
class GeospatialGeoPosTest extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand()
|
||||
{
|
||||
return 'Predis\Command\GeospatialGeoPos';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId()
|
||||
{
|
||||
return 'GEOPOS';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments()
|
||||
{
|
||||
$arguments = array('key', 'member:1', 'member:2');
|
||||
$expected = array('key', 'member:1', 'member:2');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArgumentsWithMembersAsSingleArray()
|
||||
{
|
||||
$arguments = array('key', array('member:1', 'member:2'));
|
||||
$expected = array('key', 'member:1', 'member:2');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse()
|
||||
{
|
||||
$raw = array(
|
||||
array('13.361389338970184', '38.115556395496299'),
|
||||
array('15.087267458438873', '37.50266842333162'),
|
||||
);
|
||||
|
||||
$expected = array(
|
||||
array('13.361389338970184', '38.115556395496299'),
|
||||
array('15.087267458438873', '37.50266842333162'),
|
||||
);
|
||||
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->assertSame($expected, $command->parseResponse($raw));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
*/
|
||||
public function testCommandReturnsGeoPositions()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania');
|
||||
$this->assertEquals(array(
|
||||
array('13.361389338970184', '38.115556395496299'),
|
||||
array('15.087267458438873', '37.50266842333162'),
|
||||
), $redis->geopos('Sicily', 'Palermo', 'Catania'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
* @expectedException \Predis\Response\ServerException
|
||||
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
|
||||
*/
|
||||
public function testThrowsExceptionOnWrongType()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->lpush('Sicily', 'Palermo');
|
||||
$redis->geopos('Sicily', 'Palermo');
|
||||
}
|
||||
}
|
||||
@@ -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-geospatial
|
||||
*/
|
||||
class GeospatialGeoRadiusByMemberTest extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand()
|
||||
{
|
||||
return 'Predis\Command\GeospatialGeoRadiusByMember';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId()
|
||||
{
|
||||
return 'GEORADIUSBYMEMBER';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments()
|
||||
{
|
||||
$arguments = array(
|
||||
'Sicily', 'Agrigento', 100, 'km',
|
||||
'WITHCOORD', 'WITHDIST', 'WITHHASH', 'COUNT', 1, 'ASC', 'STORE', 'key:store', 'STOREDIST', 'key:storedist',
|
||||
);
|
||||
|
||||
$expected = array(
|
||||
'Sicily', 'Agrigento', 100, 'km',
|
||||
'WITHCOORD', 'WITHDIST', 'WITHHASH', 'COUNT', 1, 'ASC', 'STORE', 'key:store', 'STOREDIST', 'key:storedist',
|
||||
);
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArgumentsWithComplexOptions()
|
||||
{
|
||||
$arguments = array(
|
||||
'Sicily', 'Agrigento', 100, 'km', array(
|
||||
'store' => 'key:store',
|
||||
'storedist' => 'key:storedist',
|
||||
'withdist' => true,
|
||||
'withcoord' => true,
|
||||
'withhash' => true,
|
||||
'count' => 1,
|
||||
'sort' => 'asc',
|
||||
),
|
||||
);
|
||||
|
||||
$expected = array(
|
||||
'Sicily', 'Agrigento', 100, 'km',
|
||||
'WITHCOORD', 'WITHDIST', 'WITHHASH', 'COUNT', 1, 'ASC', 'STORE', 'key:store', 'STOREDIST', 'key:storedist',
|
||||
);
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArgumentsWithSpecificOptionsSetToFalse()
|
||||
{
|
||||
$arguments = array(
|
||||
'Sicily', 'Agrigento', 100, 'km', array(
|
||||
'store' => 'key:store',
|
||||
'storedist' => 'key:storedist',
|
||||
'withdist' => false,
|
||||
'withcoord' => false,
|
||||
'withhash' => false,
|
||||
'count' => 1,
|
||||
'sort' => 'asc',
|
||||
),
|
||||
);
|
||||
|
||||
$expected = array('Sicily', 'Agrigento', 100, 'km', 'COUNT', 1, 'ASC', 'STORE', 'key:store', 'STOREDIST', 'key:storedist');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponseWithNoOptions()
|
||||
{
|
||||
$raw = array(
|
||||
array('Agrigento', 'Palermo'),
|
||||
);
|
||||
|
||||
$expected = array(
|
||||
array('Agrigento', 'Palermo'),
|
||||
);
|
||||
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->assertSame($expected, $command->parseResponse($raw));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
*/
|
||||
public function testCommandReturnsGeoRadiusInfoWithNoOptions()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania', '13.583333', '37.316667', 'Agrigento');
|
||||
$this->assertEquals(array('Agrigento', 'Palermo'), $redis->georadiusbymember('Sicily', 'Agrigento', 100, 'km'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
*/
|
||||
public function testCommandReturnsGeoRadiusInfoWithOptions()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania', '13.583333', '37.316667', 'Agrigento');
|
||||
$this->assertEquals(array(
|
||||
array('Agrigento', '0.0000', array('13.5833314061164856', '37.31666804993816555')),
|
||||
array('Palermo', '90.9778', array('13.361389338970184', '38.115556395496299')),
|
||||
), $redis->georadiusbymember('Sicily', 'Agrigento', 100, 'km', 'WITHDIST', 'WITHCOORD'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
* @expectedException \Predis\Response\ServerException
|
||||
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
|
||||
*/
|
||||
public function testThrowsExceptionOnWrongType()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->lpush('Sicily', 'Palermo');
|
||||
$redis->georadiusbymember('Sicily', 'Agrigento', 200, 'km');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<?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-geospatial
|
||||
*/
|
||||
class GeospatialGeoRadiusTest extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand()
|
||||
{
|
||||
return 'Predis\Command\GeospatialGeoRadius';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId()
|
||||
{
|
||||
return 'GEORADIUS';
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArguments()
|
||||
{
|
||||
$arguments = array(
|
||||
'Sicily', 15, 37, 200, 'km',
|
||||
'WITHCOORD', 'WITHDIST', 'WITHHASH', 'COUNT', 1, 'ASC', 'STORE', 'key:store', 'STOREDIST', 'key:storedist',
|
||||
);
|
||||
|
||||
$expected = array(
|
||||
'Sicily', 15, 37, 200, 'km',
|
||||
'WITHCOORD', 'WITHDIST', 'WITHHASH', 'COUNT', 1, 'ASC', 'STORE', 'key:store', 'STOREDIST', 'key:storedist',
|
||||
);
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArgumentsWithComplexOptions()
|
||||
{
|
||||
$arguments = array(
|
||||
'Sicily', 15, 37, 200, 'km', array(
|
||||
'store' => 'key:store',
|
||||
'storedist' => 'key:storedist',
|
||||
'withdist' => true,
|
||||
'withcoord' => true,
|
||||
'withhash' => true,
|
||||
'count' => 1,
|
||||
'sort' => 'asc',
|
||||
),
|
||||
);
|
||||
|
||||
$expected = array(
|
||||
'Sicily', 15, 37, 200, 'km',
|
||||
'WITHCOORD', 'WITHDIST', 'WITHHASH', 'COUNT', 1, 'ASC', 'STORE', 'key:store', 'STOREDIST', 'key:storedist',
|
||||
);
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testFilterArgumentsWithSpecificOptionsSetToFalse()
|
||||
{
|
||||
$arguments = array(
|
||||
'Sicily', 15, 37, 200, 'km', array(
|
||||
'store' => 'key:store',
|
||||
'storedist' => 'key:storedist',
|
||||
'withdist' => false,
|
||||
'withcoord' => false,
|
||||
'withhash' => false,
|
||||
'count' => 1,
|
||||
'sort' => 'asc',
|
||||
),
|
||||
);
|
||||
|
||||
$expected = array('Sicily', 15, 37, 200, 'km', 'COUNT', 1, 'ASC', 'STORE', 'key:store', 'STOREDIST', 'key:storedist');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponseWithNoOptions()
|
||||
{
|
||||
$raw = array(
|
||||
array('Palermo', '190.4424'),
|
||||
array('Catania', '56.4413'),
|
||||
);
|
||||
|
||||
$expected = array(
|
||||
array('Palermo', '190.4424'),
|
||||
array('Catania', '56.4413'),
|
||||
);
|
||||
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->assertSame($expected, $command->parseResponse($raw));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
*/
|
||||
public function testCommandReturnsGeoRadiusInfoWithNoOptions()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania');
|
||||
$this->assertEquals(array('Palermo', 'Catania'), $redis->georadius('Sicily', 15, 37, 200, 'km'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
*/
|
||||
public function testCommandReturnsGeoRadiusInfoWithOptions()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania');
|
||||
$this->assertEquals(array(
|
||||
array('Palermo', '190.4424', array('13.361389338970184', '38.115556395496299')),
|
||||
array('Catania', '56.4413', array('15.087267458438873', '37.50266842333162')),
|
||||
), $redis->georadius('Sicily', 15, 37, 200, 'km', 'WITHDIST', 'WITHCOORD'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
* @expectedException \Predis\Response\ServerException
|
||||
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
|
||||
*/
|
||||
public function testThrowsExceptionOnWrongType()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->lpush('Sicily', 'Palermo');
|
||||
$redis->georadius('Sicily', 15, 37, 200, 'km');
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@ class HashIncrementByFloatTest extends PredisCommandTestCase
|
||||
/**
|
||||
* @group connected
|
||||
* @expectedException \Predis\Response\ServerException
|
||||
* @expectedExceptionMessage ERR hash value is not a valid float
|
||||
* @expectedExceptionMessageRegExp /ERR hash value is not a( valid)? float/
|
||||
*/
|
||||
public function testThrowsExceptionOnStringField()
|
||||
{
|
||||
|
||||
@@ -61,6 +61,7 @@ class HashStringLengthTest extends PredisCommandTestCase
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
*/
|
||||
public function testReturnsStringLengthOfSpecifiedField()
|
||||
{
|
||||
@@ -81,6 +82,7 @@ class HashStringLengthTest extends PredisCommandTestCase
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
* @expectedException \Predis\Response\ServerException
|
||||
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
|
||||
*/
|
||||
|
||||
@@ -864,6 +864,51 @@ class KeyPrefixProcessorTest extends PredisTestCase
|
||||
array('127.0.0.1', '6379', 'key', '0', '10', 'COPY', 'REPLACE'),
|
||||
array('127.0.0.1', '6379', 'prefix:key', '0', '10', 'COPY', 'REPLACE'),
|
||||
),
|
||||
array('EXISTS',
|
||||
array('key1', 'key2', 'key3'),
|
||||
array('prefix:key1', 'prefix:key2', 'prefix:key3'),
|
||||
),
|
||||
/* ---------------- Redis 3.2 ---------------- */
|
||||
array('HSTRLEN',
|
||||
array('key', 'field'),
|
||||
array('prefix:key', 'field'),
|
||||
),
|
||||
array('BITFIELD',
|
||||
array('key', 'GET', 'u8', '0', 'SET', 'u8', '0', '1'),
|
||||
array('prefix:key', 'GET', 'u8', '0', 'SET', 'u8', '0', '1'),
|
||||
),
|
||||
array('GEOADD',
|
||||
array('key', '13.361389', '38.115556', 'member:1', '15.087269', '37.502669', 'member:2'),
|
||||
array('prefix:key', '13.361389', '38.115556', 'member:1', '15.087269', '37.502669', 'member:2'),
|
||||
),
|
||||
array('GEOHASH',
|
||||
array('key', 'member:1', 'member:2'),
|
||||
array('prefix:key', 'member:1', 'member:2'),
|
||||
),
|
||||
array('GEOPOS',
|
||||
array('key', 'member:1', 'member:2'),
|
||||
array('prefix:key', 'member:1', 'member:2'),
|
||||
),
|
||||
array('GEODIST',
|
||||
array('key', 'member:1', 'member:2', 'km'),
|
||||
array('prefix:key', 'member:1', 'member:2', 'km'),
|
||||
),
|
||||
array('GEORADIUS',
|
||||
array('key', '15', '37', '200', 'km'),
|
||||
array('prefix:key', '15', '37', '200', 'km'),
|
||||
),
|
||||
array('GEORADIUS',
|
||||
array('key', '15', '37', '200', 'km', 'WITHDIST', 'STORE', 'key:store', 'STOREDIST', 'key:storedist'),
|
||||
array('prefix:key', '15', '37', '200', 'km', 'WITHDIST', 'STORE', 'prefix:key:store', 'STOREDIST', 'prefix:key:storedist'),
|
||||
),
|
||||
array('GEORADIUSBYMEMBER',
|
||||
array('key', 'member', '100', 'km'),
|
||||
array('prefix:key', 'member', '100', 'km'),
|
||||
),
|
||||
array('GEORADIUSBYMEMBER',
|
||||
array('key', 'member', '100', 'km', 'WITHDIST', 'STORE', 'key:store', 'STOREDIST', 'key:storedist'),
|
||||
array('prefix:key', 'member', '100', 'km', 'WITHDIST', 'STORE', 'prefix:key:store', 'STOREDIST', 'prefix:key:storedist'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ class PubSubSubscribeByPatternTest extends PredisCommandTestCase
|
||||
/**
|
||||
* @group connected
|
||||
* @expectedException \Predis\Response\ServerException
|
||||
* @expectedExceptionMessage ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context
|
||||
* @expectedExceptionMessageRegExp /ERR only .* allowed in this context/
|
||||
*/
|
||||
public function testCannotSendOtherCommandsAfterPsubscribe()
|
||||
{
|
||||
|
||||
@@ -145,7 +145,7 @@ class PubSubSubscribeTest extends PredisCommandTestCase
|
||||
/**
|
||||
* @group connected
|
||||
* @expectedException \Predis\Response\ServerException
|
||||
* @expectedExceptionMessage ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context
|
||||
* @expectedExceptionMessageRegExp /ERR only .* allowed in this context/
|
||||
*/
|
||||
public function testCannotSendOtherCommandsAfterSubscribe()
|
||||
{
|
||||
|
||||
@@ -114,9 +114,9 @@ addr=127.0.0.1:45395 fd=8 idle=0 flags=N db=0 sub=0 psub=0
|
||||
BUFFER;
|
||||
|
||||
$parsed = array(
|
||||
array('addr' => '127.0.0.1:45393','fd' => '6','idle' => '0','flags' => 'N','db' => '0','sub' => '0','psub' => '0'),
|
||||
array('addr' => '127.0.0.1:45394','fd' => '7','idle' => '0','flags' => 'N','db' => '0','sub' => '0','psub' => '0'),
|
||||
array('addr' => '127.0.0.1:45395','fd' => '8','idle' => '0','flags' => 'N','db' => '0','sub' => '0','psub' => '0'),
|
||||
array('addr' => '127.0.0.1:45393', 'fd' => '6', 'idle' => '0', 'flags' => 'N', 'db' => '0', 'sub' => '0', 'psub' => '0'),
|
||||
array('addr' => '127.0.0.1:45394', 'fd' => '7', 'idle' => '0', 'flags' => 'N', 'db' => '0', 'sub' => '0', 'psub' => '0'),
|
||||
array('addr' => '127.0.0.1:45395', 'fd' => '8', 'idle' => '0', 'flags' => 'N', 'db' => '0', 'sub' => '0', 'psub' => '0'),
|
||||
);
|
||||
|
||||
$this->assertSame($parsed, $command->parseResponse($raw));
|
||||
|
||||
@@ -107,8 +107,8 @@ class ServerCommandTest extends PredisCommandTestCase
|
||||
// Predis\Response\Status instead of plain strings. This class responds
|
||||
// to __toString() so the string conversion is implicit, but assertSame
|
||||
// checks for strict equality while assertEquals is loose.
|
||||
$expected = array(array('command', 0, array('readonly', 'loading', 'stale'), 0, 0, 0));
|
||||
$this->assertCount(1, $response = $redis->command('INFO', 'COMMAND'));
|
||||
$expected = array(array('get', 2, array('readonly', 'fast'), 1, 1, 1));
|
||||
$this->assertCount(1, $response = $redis->command('INFO', 'GET'));
|
||||
$this->assertEquals($expected, $response);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class ServerConfigTest extends PredisCommandTestCase
|
||||
*/
|
||||
public function testParseResponseOfConfigGet()
|
||||
{
|
||||
$raw = array('slowlog-log-slower-than','10000','slowlog-max-len','64','loglevel','verbose');
|
||||
$raw = array('slowlog-log-slower-than', '10000', 'slowlog-max-len', '64', 'loglevel', 'verbose');
|
||||
$expected = array(
|
||||
'slowlog-log-slower-than' => '10000',
|
||||
'slowlog-max-len' => '64',
|
||||
|
||||
@@ -267,7 +267,7 @@ BUFFER;
|
||||
'used_cpu_user_children' => '0.00',
|
||||
'cluster_enabled' => '0',
|
||||
'db0' => array('keys' => '2', 'expires' => '0'),
|
||||
'db5' => array('keys' => '1','expires' => '0'),
|
||||
'db5' => array('keys' => '1', 'expires' => '0'),
|
||||
);
|
||||
|
||||
$this->assertSame($expected, $this->getCommand()->parseResponse($raw));
|
||||
|
||||
@@ -38,8 +38,8 @@ class SetPopTest extends PredisCommandTestCase
|
||||
*/
|
||||
public function testFilterArguments()
|
||||
{
|
||||
$arguments = array('key');
|
||||
$expected = array('key');
|
||||
$arguments = array('key', 2);
|
||||
$expected = array('key', 2);
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
@@ -70,6 +70,22 @@ class SetPopTest extends PredisCommandTestCase
|
||||
$this->assertNull($redis->spop('letters'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
*/
|
||||
public function testPopsMoreRandomMembersFromSet()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->sadd('letters', 'a', 'b', 'c');
|
||||
|
||||
$this->assertSameValues(array('a', 'b', 'c'), $redis->spop('letters', 3));
|
||||
$this->assertEmpty($redis->spop('letters', 3));
|
||||
|
||||
$this->assertNull($redis->spop('letters'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @expectedException \Predis\Response\ServerException
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
<?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-string
|
||||
*/
|
||||
class StringBitFieldTest extends PredisCommandTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCommand()
|
||||
{
|
||||
return 'Predis\Command\StringBitField';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedId()
|
||||
{
|
||||
return 'BITFIELD';
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 testFilterMultipleArguments()
|
||||
{
|
||||
$arguments = array('key', 'incrby', 'u2', '100', '1', 'OVERFLOW', 'SAT', 'incrby', 'u2', '102', '1', 'GET', 'u2', '100');
|
||||
$expected = array('key', 'incrby', 'u2', '100', '1', 'OVERFLOW', 'SAT', 'incrby', 'u2', '102', '1', 'GET', 'u2', '100');
|
||||
|
||||
$command = $this->getCommand();
|
||||
$command->setArguments($arguments);
|
||||
|
||||
$this->assertSame($expected, $command->getArguments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponse()
|
||||
{
|
||||
$raw = array(1);
|
||||
$expected = array(1);
|
||||
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->assertSame($expected, $command->parseResponse($raw));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testParseResponseComplex()
|
||||
{
|
||||
$raw = array(1, 0, 3);
|
||||
$expected = array(1, 0, 3);
|
||||
|
||||
$command = $this->getCommand();
|
||||
|
||||
$this->assertSame($expected, $command->parseResponse($raw));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
*/
|
||||
public function testBitfieldWithGetModifier()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->setbit('string', 0, 1);
|
||||
$redis->setbit('string', 8, 1);
|
||||
|
||||
$this->assertSame(array(128), $redis->bitfield('string', 'GET', 'u8', 0));
|
||||
$this->assertSame(array(128, 1, 128), $redis->bitfield('string', 'GET', 'u8', 0, 'GET', 'u8', 1, 'GET', 'u8', 8));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
*/
|
||||
public function testBitfieldWithSetModifier()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->setbit('string', 0, 1);
|
||||
$redis->setbit('string', 8, 1);
|
||||
|
||||
$this->assertSame(array(128), $redis->bitfield('string', 'SET', 'u8', 0, 1));
|
||||
$this->assertSame(array(1, 128), $redis->bitfield('string', 'SET', 'u8', 0, 128, 'SET', 'u8', 8, 1));
|
||||
$this->assertSame(array(1, 128), $redis->bitfield('string', 'SET', 'u8', 8, 128, 'GET', 'u8', 8));
|
||||
|
||||
$this->assertSame("\x80\x80", $redis->get('string'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
*/
|
||||
public function testBitfieldWithIncrbyModifier()
|
||||
{
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->setbit('string', 0, 1);
|
||||
$redis->setbit('string', 8, 1);
|
||||
|
||||
$this->assertSame(array(138), $redis->bitfield('string', 'INCRBY', 'u8', 0, 10));
|
||||
$this->assertSame(array(143, 128), $redis->bitfield('string', 'INCRBY', 'u8', 0, 5, 'INCRBY', 'u8', 0, -15));
|
||||
|
||||
$this->assertSame("\x80\x80", $redis->get('string'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group connected
|
||||
* @requiresRedisVersion >= 3.2.0
|
||||
* @expectedException \Predis\Response\ServerException
|
||||
* @expectedExceptionMessage Operation against a key holding the wrong kind of value
|
||||
*/
|
||||
public function testThrowsExceptionOnWrongType()
|
||||
{
|
||||
$this->markTestSkipped('Currently skipped due issues in Redis (see antirez/redis#3259).');
|
||||
|
||||
$redis = $this->getClient();
|
||||
|
||||
$redis->lpush('metavars', 'foo');
|
||||
$redis->bitfield('metavars', 'SET', 'u4', '0', '1');
|
||||
}
|
||||
}
|
||||
@@ -333,7 +333,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testCanAssignConnectionsToCustomSlotsFromParameters()
|
||||
public function testCanAssignConnectionsToRangeOfSlotsFromParameters()
|
||||
{
|
||||
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6379?slots=0-5460');
|
||||
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6380?slots=5461-10921');
|
||||
@@ -344,15 +344,50 @@ class RedisClusterTest extends PredisTestCase
|
||||
$cluster->add($connection2);
|
||||
$cluster->add($connection3);
|
||||
|
||||
$cluster->buildSlotsMap();
|
||||
|
||||
$expectedMap = array_merge(
|
||||
array_fill(0, 5461, '127.0.0.1:6379'),
|
||||
array_fill(5460, 5461, '127.0.0.1:6380'),
|
||||
array_fill(10921, 5462, '127.0.0.1:6381')
|
||||
);
|
||||
|
||||
$actualMap = $cluster->getSlotsMap();
|
||||
ksort($actualMap);
|
||||
|
||||
$this->assertSame($expectedMap, $actualMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testCanAssignConnectionsToSingleSlotOrRangesOfSlotsFromParameters()
|
||||
{
|
||||
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6379?slots=0-5460,5500-5600,11000');
|
||||
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6380?slots=5461-5499,5600-10921');
|
||||
$connection3 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=10922-10999,11001-16383');
|
||||
|
||||
$cluster = new RedisCluster(new Connection\Factory());
|
||||
$cluster->add($connection1);
|
||||
$cluster->add($connection2);
|
||||
$cluster->add($connection3);
|
||||
|
||||
$cluster->buildSlotsMap();
|
||||
|
||||
$this->assertSame($expectedMap, $cluster->getSlotsMap());
|
||||
$expectedMap = array_merge(
|
||||
array_fill(0, 5461, '127.0.0.1:6379'),
|
||||
array_fill(5460, 39, '127.0.0.1:6380'),
|
||||
array_fill(5499, 101, '127.0.0.1:6379'),
|
||||
array_fill(5599, 5321, '127.0.0.1:6380'),
|
||||
array_fill(10921, 78, '127.0.0.1:6381'),
|
||||
array_fill(11000, 1, '127.0.0.1:6379'),
|
||||
array_fill(11000, 5383, '127.0.0.1:6381')
|
||||
);
|
||||
|
||||
$actualMap = $cluster->getSlotsMap();
|
||||
ksort($actualMap);
|
||||
|
||||
$this->assertSame($expectedMap, $actualMap);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -451,6 +486,281 @@ class RedisClusterTest extends PredisTestCase
|
||||
$cluster->readResponse($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testRetriesExecutingCommandOnConnectionFailureOnlyAfterFetchingNewSlotsMap()
|
||||
{
|
||||
$slotsmap = array(
|
||||
array(0, 5460, array('127.0.0.1', 9381), array()),
|
||||
array(5461, 10921, array('127.0.0.1', 6382), array()),
|
||||
array(10922, 16383, array('127.0.0.1', 6383), array()),
|
||||
);
|
||||
|
||||
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5460');
|
||||
$connection1->expects($this->once())
|
||||
->method('executeCommand')
|
||||
->with($this->isRedisCommand(
|
||||
'GET', array('node:1001')
|
||||
))
|
||||
->will($this->throwException(
|
||||
new Connection\ConnectionException($connection1, 'Unknown connection error [127.0.0.1:6381]')
|
||||
));
|
||||
|
||||
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6382?slots=5461-10921');
|
||||
$connection2->expects($this->any())
|
||||
->method('executeCommand')
|
||||
->with($this->isRedisCommand(
|
||||
'CLUSTER', array('SLOTS')
|
||||
))
|
||||
->will($this->returnValue($slotsmap));
|
||||
|
||||
$connection3 = $this->getMockConnection('tcp://127.0.0.1:6383?slots=10922-16383');
|
||||
$connection3->expects($this->any())
|
||||
->method('executeCommand')
|
||||
->with($this->isRedisCommand(
|
||||
'CLUSTER', array('SLOTS')
|
||||
))
|
||||
->will($this->returnValue($slotsmap));
|
||||
|
||||
$connection4 = $this->getMockConnection('tcp://127.0.0.1:9381');
|
||||
$connection4->expects($this->at(0))
|
||||
->method('executeCommand')
|
||||
->with($this->isRedisCommand(
|
||||
'GET', array('node:1001')
|
||||
))
|
||||
->will($this->returnValue('value:1001'));
|
||||
$connection4->expects($this->at(1))
|
||||
->method('executeCommand')
|
||||
->with($this->isRedisCommand(
|
||||
'GET', array('node:5001')
|
||||
))
|
||||
->will($this->returnValue('value:5001'));
|
||||
|
||||
$factory = $this->getMock('Predis\Connection\FactoryInterface');
|
||||
$factory->expects($this->once())
|
||||
->method('create')
|
||||
->with(array(
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '9381',
|
||||
))
|
||||
->will($this->returnValue($connection4));
|
||||
|
||||
$cluster = new RedisCluster($factory);
|
||||
|
||||
$cluster->add($connection1);
|
||||
$cluster->add($connection2);
|
||||
$cluster->add($connection3);
|
||||
|
||||
$this->assertSame('value:1001', $cluster->executeCommand(
|
||||
Command\RawCommand::create('get', 'node:1001')
|
||||
));
|
||||
|
||||
$this->assertSame('value:5001', $cluster->executeCommand(
|
||||
Command\RawCommand::create('get', 'node:5001')
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testRetriesExecutingCommandOnConnectionFailureButDoNotAskSlotsMapWhenDisabled()
|
||||
{
|
||||
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5500');
|
||||
$connection1->expects($this->once())
|
||||
->method('executeCommand')
|
||||
->with($this->isRedisCommand(
|
||||
'GET', array('node:1001')
|
||||
))
|
||||
->will($this->throwException(
|
||||
new Connection\ConnectionException($connection1, 'Unknown connection error [127.0.0.1:6381]')
|
||||
));
|
||||
|
||||
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6382?slots=5501-11000');
|
||||
$connection2->expects($this->once())
|
||||
->method('executeCommand')
|
||||
->with($this->isRedisCommand(
|
||||
'GET', array('node:1001')
|
||||
))
|
||||
->will($this->returnValue(
|
||||
new Response\Error('MOVED 1970 127.0.0.1:9381')
|
||||
));
|
||||
|
||||
$connection3 = $this->getMockConnection('tcp://127.0.0.1:6383?slots=11101-16383');
|
||||
$connection3->expects($this->never())
|
||||
->method('executeCommand');
|
||||
|
||||
$connection4 = $this->getMockConnection('tcp://127.0.0.1:9381');
|
||||
$connection4->expects($this->once())
|
||||
->method('executeCommand')
|
||||
->with($this->isRedisCommand(
|
||||
'GET', array('node:1001')
|
||||
))
|
||||
->will($this->returnValue('value:1001'));
|
||||
|
||||
$factory = $this->getMock('Predis\Connection\FactoryInterface');
|
||||
$factory->expects($this->once())
|
||||
->method('create')
|
||||
->with(array(
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '9381',
|
||||
))
|
||||
->will($this->returnValue($connection4));
|
||||
|
||||
// TODO: I'm not sure about mocking a protected method, but it'll do for now
|
||||
$cluster = $this->getMock('Predis\Connection\Aggregate\RedisCluster', array('getRandomConnection'), array($factory));
|
||||
$cluster->expects($this->never())
|
||||
->method('getRandomConnection');
|
||||
|
||||
$cluster->useClusterSlots(false);
|
||||
|
||||
$cluster->add($connection1);
|
||||
$cluster->add($connection2);
|
||||
$cluster->add($connection3);
|
||||
|
||||
$this->assertSame('value:1001', $cluster->executeCommand(
|
||||
Command\RawCommand::create('get', 'node:1001')
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
* @expectedException \Predis\ClientException
|
||||
* @expectedExceptionMessage No connections available in the pool
|
||||
*/
|
||||
public function testThrowsClientExceptionWhenExecutingCommandWithEmptyPool()
|
||||
{
|
||||
$factory = $this->getMock('Predis\Connection\FactoryInterface');
|
||||
$factory->expects($this->never())->method('create');
|
||||
|
||||
$cluster = new RedisCluster($factory);
|
||||
|
||||
$cluster->executeCommand(Command\RawCommand::create('get', 'node:1001'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testAskSlotsMapReturnEmptyArrayOnEmptyConnectionsPool()
|
||||
{
|
||||
$factory = $this->getMock('Predis\Connection\FactoryInterface');
|
||||
$factory->expects($this->never())->method('create');
|
||||
|
||||
$cluster = new RedisCluster($factory);
|
||||
|
||||
$this->assertEmpty($cluster->askSlotsMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testAskSlotsMapRetriesOnDifferentNodeOnConnectionFailure()
|
||||
{
|
||||
$slotsmap = array(
|
||||
array(0, 5460, array('127.0.0.1', 9381), array()),
|
||||
array(5461, 10921, array('127.0.0.1', 6382), array()),
|
||||
array(10922, 16383, array('127.0.0.1', 6383), array()),
|
||||
);
|
||||
|
||||
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5460');
|
||||
$connection1->expects($this->once())
|
||||
->method('executeCommand')
|
||||
->with($this->isRedisCommand(
|
||||
'CLUSTER', array('SLOTS')
|
||||
))
|
||||
->will($this->throwException(
|
||||
new Connection\ConnectionException($connection1, 'Unknown connection error [127.0.0.1:6381]')
|
||||
));
|
||||
|
||||
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6382?slots=5461-10921');
|
||||
$connection2->expects($this->once())
|
||||
->method('executeCommand')
|
||||
->with($this->isRedisCommand(
|
||||
'CLUSTER', array('SLOTS')
|
||||
))
|
||||
->will($this->throwException(
|
||||
new Connection\ConnectionException($connection2, 'Unknown connection error [127.0.0.1:6383]')
|
||||
));
|
||||
|
||||
$connection3 = $this->getMockConnection('tcp://127.0.0.1:6383?slots=10922-16383');
|
||||
$connection3->expects($this->once())
|
||||
->method('executeCommand')
|
||||
->with($this->isRedisCommand(
|
||||
'CLUSTER', array('SLOTS')
|
||||
))
|
||||
->will($this->returnValue($slotsmap));
|
||||
|
||||
$factory = $this->getMock('Predis\Connection\FactoryInterface');
|
||||
$factory->expects($this->never())->method('create');
|
||||
|
||||
// TODO: I'm not sure about mocking a protected method, but it'll do for now
|
||||
$cluster = $this->getMock('Predis\Connection\Aggregate\RedisCluster', array('getRandomConnection'), array($factory));
|
||||
$cluster->expects($this->exactly(3))
|
||||
->method('getRandomConnection')
|
||||
->will($this->onConsecutiveCalls($connection1, $connection2, $connection3));
|
||||
|
||||
$cluster->add($connection1);
|
||||
$cluster->add($connection2);
|
||||
$cluster->add($connection3);
|
||||
|
||||
$this->assertCount(16384, $cluster->askSlotsMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
* @expectedException \Predis\Connection\ConnectionException
|
||||
* @expectedExceptionMessage Unknown connection error [127.0.0.1:6382]
|
||||
*/
|
||||
public function testAskSlotsMapHonorsRetryLimitOnMultipleConnectionFailures()
|
||||
{
|
||||
$slotsmap = array(
|
||||
array(0, 5460, array('127.0.0.1', 9381), array()),
|
||||
array(5461, 10921, array('127.0.0.1', 6382), array()),
|
||||
array(10922, 16383, array('127.0.0.1', 6383), array()),
|
||||
);
|
||||
|
||||
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5460');
|
||||
$connection1->expects($this->any())
|
||||
->method('executeCommand')
|
||||
->with($this->isRedisCommand(
|
||||
'CLUSTER', array('SLOTS')
|
||||
))
|
||||
->will($this->throwException(
|
||||
new Connection\ConnectionException($connection1, 'Unknown connection error [127.0.0.1:6381]')
|
||||
));
|
||||
|
||||
$connection2 = $this->getMockConnection('tcp://127.0.0.1:6382?slots=5461-10921');
|
||||
$connection2->expects($this->any())
|
||||
->method('executeCommand')
|
||||
->with($this->isRedisCommand(
|
||||
'CLUSTER', array('SLOTS')
|
||||
))
|
||||
->will($this->throwException(
|
||||
new Connection\ConnectionException($connection2, 'Unknown connection error [127.0.0.1:6382]')
|
||||
));
|
||||
|
||||
$connection3 = $this->getMockConnection('tcp://127.0.0.1:6383?slots=10922-16383');
|
||||
$connection3->expects($this->never())
|
||||
->method('executeCommand');
|
||||
|
||||
$factory = $this->getMock('Predis\Connection\FactoryInterface');
|
||||
$factory->expects($this->never())->method('create');
|
||||
|
||||
// TODO: I'm not sure about mocking a protected method, but it'll do for now
|
||||
$cluster = $this->getMock('Predis\Connection\Aggregate\RedisCluster', array('getRandomConnection'), array($factory));
|
||||
$cluster->expects($this->exactly(2))
|
||||
->method('getRandomConnection')
|
||||
->will($this->onConsecutiveCalls($connection1, $connection2));
|
||||
|
||||
$cluster->add($connection1);
|
||||
$cluster->add($connection2);
|
||||
$cluster->add($connection3);
|
||||
|
||||
$cluster->setRetryLimit(1);
|
||||
|
||||
$cluster->askSlotsMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
@@ -673,11 +983,11 @@ class RedisClusterTest extends PredisTestCase
|
||||
{
|
||||
$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(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(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)),
|
||||
@@ -716,7 +1026,7 @@ class RedisClusterTest extends PredisTestCase
|
||||
$cmdGET = Command\RawCommand::create('GET', 'node:1001');
|
||||
$rspMOVED = new Response\Error('MOVED 1970 127.0.0.1:6380');
|
||||
$rspSlotsArray = array(
|
||||
array(0 , 8191, array('127.0.0.1', 6379)),
|
||||
array(0, 8191, array('127.0.0.1', 6379)),
|
||||
array(8192, 16383, array('127.0.0.1', 6380)),
|
||||
);
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ class FactoryTest extends PredisTestCase
|
||||
public function testCreateConnectionWithoutInitializationCommands()
|
||||
{
|
||||
$profile = $this->getMock('Predis\Profile\ProfileInterface');
|
||||
$profile->expects($this->never())->method('create');
|
||||
$profile->expects($this->never())->method('createCommand');
|
||||
|
||||
$factory = new Factory($profile);
|
||||
$parameters = new Parameters();
|
||||
|
||||
@@ -190,6 +190,13 @@ class RedisUnstableTest extends PredisProfileTestCase
|
||||
149 => 'PFMERGE',
|
||||
150 => 'COMMAND',
|
||||
151 => 'HSTRLEN',
|
||||
152 => 'BITFIELD',
|
||||
153 => 'GEOADD',
|
||||
154 => 'GEOHASH',
|
||||
155 => 'GEOPOS',
|
||||
156 => 'GEODIST',
|
||||
157 => 'GEORADIUS',
|
||||
158 => 'GEORADIUSBYMEMBER',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
<?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 RedisVersion320Test extends PredisProfileTestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProfile($version = null)
|
||||
{
|
||||
return new RedisVersion320();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getExpectedVersion()
|
||||
{
|
||||
return '3.2';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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 => 'MIGRATE',
|
||||
127 => 'PSETEX',
|
||||
128 => 'INCRBYFLOAT',
|
||||
129 => 'BITOP',
|
||||
130 => 'BITCOUNT',
|
||||
131 => 'HINCRBYFLOAT',
|
||||
132 => 'EVAL',
|
||||
133 => 'EVALSHA',
|
||||
134 => 'SCRIPT',
|
||||
135 => 'TIME',
|
||||
136 => 'SENTINEL',
|
||||
137 => 'SCAN',
|
||||
138 => 'BITPOS',
|
||||
139 => 'SSCAN',
|
||||
140 => 'ZSCAN',
|
||||
141 => 'ZLEXCOUNT',
|
||||
142 => 'ZRANGEBYLEX',
|
||||
143 => 'ZREMRANGEBYLEX',
|
||||
144 => 'ZREVRANGEBYLEX',
|
||||
145 => 'HSCAN',
|
||||
146 => 'PUBSUB',
|
||||
147 => 'PFADD',
|
||||
148 => 'PFCOUNT',
|
||||
149 => 'PFMERGE',
|
||||
150 => 'COMMAND',
|
||||
151 => 'HSTRLEN',
|
||||
152 => 'BITFIELD',
|
||||
153 => 'GEOADD',
|
||||
154 => 'GEOHASH',
|
||||
155 => 'GEOPOS',
|
||||
156 => 'GEODIST',
|
||||
157 => 'GEORADIUS',
|
||||
158 => 'GEORADIUSBYMEMBER',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -61,4 +61,26 @@ class RequestSerializerTest extends PredisTestCase
|
||||
|
||||
$this->assertSame("*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n", $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testSerializerDoesNotBreakOnArgumentsWithHoles()
|
||||
{
|
||||
$serializer = new RequestSerializer();
|
||||
|
||||
$command = $this->getMock('Predis\Command\CommandInterface');
|
||||
|
||||
$command->expects($this->once())
|
||||
->method('getId')
|
||||
->will($this->returnValue('DEL'));
|
||||
|
||||
$command->expects($this->once())
|
||||
->method('getArguments')
|
||||
->will($this->returnValue(array(0 => 'key:1', 2 => 'key:2')));
|
||||
|
||||
$result = $serializer->serialize($command);
|
||||
|
||||
$this->assertSame("*3\r\n$3\r\nDEL\r\n$5\r\nkey:1\r\n$5\r\nkey:2\r\n", $result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,105 @@ class ReplicationStrategyTest extends PredisTestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testBitFieldCommand()
|
||||
{
|
||||
$profile = Profile\Factory::getDevelopment();
|
||||
$strategy = new ReplicationStrategy();
|
||||
|
||||
$command = $profile->createCommand('BITFIELD', array('key'));
|
||||
$this->assertTrue(
|
||||
$strategy->isReadOperation($command),
|
||||
'BITFIELD with no modifiers is expected to be a read operation.'
|
||||
);
|
||||
|
||||
$command = $profile->createCommand('BITFIELD', array('key', 'GET', 'u4', '0'));
|
||||
$this->assertTrue(
|
||||
$strategy->isReadOperation($command),
|
||||
'BITFIELD with GET only is expected to be a read operation.'
|
||||
);
|
||||
|
||||
$command = $profile->createCommand('BITFIELD', array('key', 'SET', 'u4', '0', 1));
|
||||
$this->assertFalse(
|
||||
$strategy->isReadOperation($command),
|
||||
'BITFIELD with SET is expected to be a write operation.'
|
||||
);
|
||||
|
||||
$command = $profile->createCommand('BITFIELD', array('key', 'INCRBY', 'u4', '0', 1));
|
||||
$this->assertFalse(
|
||||
$strategy->isReadOperation($command),
|
||||
'BITFIELD with INCRBY is expected to be a write operation.'
|
||||
);
|
||||
|
||||
$command = $profile->createCommand('BITFIELD', array('key', 'GET', 'u4', '0', 'INCRBY', 'u4', '0', 1));
|
||||
$this->assertFalse(
|
||||
$strategy->isReadOperation($command),
|
||||
'BITFIELD with GET and INCRBY is expected to be a write operation.'
|
||||
);
|
||||
|
||||
$command = $profile->createCommand('BITFIELD', array('key', 'GET', 'u4', '0', 'SET', 'u4', '0', 1));
|
||||
$this->assertFalse(
|
||||
$strategy->isReadOperation($command),
|
||||
'BITFIELD with GET and SET is expected to be a write operation.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testGeoradiusCommand()
|
||||
{
|
||||
$profile = Profile\Factory::getDevelopment();
|
||||
$strategy = new ReplicationStrategy();
|
||||
|
||||
$command = $profile->createCommand('GEORADIUS', array('key:geo', 15, 37, 200, 'km'));
|
||||
$this->assertTrue(
|
||||
$strategy->isReadOperation($command),
|
||||
'GEORADIUS is expected to be a read operation.'
|
||||
);
|
||||
|
||||
$command = $profile->createCommand('GEORADIUS', array('key:geo', 15, 37, 200, 'km', 'store', 'key:store'));
|
||||
$this->assertFalse(
|
||||
$strategy->isReadOperation($command),
|
||||
'GEORADIUS with STORE is expected to be a write operation.'
|
||||
);
|
||||
|
||||
$command = $profile->createCommand('GEORADIUS', array('key:geo', 15, 37, 200, 'km', 'storedist', 'key:storedist'));
|
||||
$this->assertFalse(
|
||||
$strategy->isReadOperation($command),
|
||||
'GEORADIUS with STOREDIST is expected to be a write operation.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testGeoradiusByMemberCommand()
|
||||
{
|
||||
$profile = Profile\Factory::getDevelopment();
|
||||
$strategy = new ReplicationStrategy();
|
||||
|
||||
$command = $profile->createCommand('GEORADIUSBYMEMBER', array('key:geo', 15, 37, 200, 'km'));
|
||||
$this->assertTrue(
|
||||
$strategy->isReadOperation($command),
|
||||
'GEORADIUSBYMEMBER is expected to be a read operation.'
|
||||
);
|
||||
|
||||
$command = $profile->createCommand('GEORADIUSBYMEMBER', array('key:geo', 15, 37, 200, 'km', 'store', 'key:store'));
|
||||
$this->assertFalse(
|
||||
$strategy->isReadOperation($command),
|
||||
'GEORADIUSBYMEMBER with STORE is expected to be a write operation.'
|
||||
);
|
||||
|
||||
$command = $profile->createCommand('GEORADIUSBYMEMBER', array('key:geo', 15, 37, 200, 'km', 'storedist', 'key:storedist'));
|
||||
$this->assertFalse(
|
||||
$strategy->isReadOperation($command),
|
||||
'GEORADIUSBYMEMBER with STOREDIST is expected to be a write operation.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
* @expectedException \Predis\NotSupportedException
|
||||
@@ -316,6 +415,7 @@ class ReplicationStrategyTest extends PredisTestCase
|
||||
'SETRANGE' => 'write',
|
||||
'STRLEN' => 'read',
|
||||
'SUBSTR' => 'read',
|
||||
'BITFIELD' => 'variable',
|
||||
|
||||
/* commands operating on lists */
|
||||
'LINSERT' => 'write',
|
||||
@@ -392,6 +492,14 @@ class ReplicationStrategyTest extends PredisTestCase
|
||||
/* scripting */
|
||||
'EVAL' => 'write',
|
||||
'EVALSHA' => 'write',
|
||||
|
||||
/* commands performing geospatial operations */
|
||||
'GEOADD' => 'write',
|
||||
'GEOHASH' => 'read',
|
||||
'GEOPOS' => 'read',
|
||||
'GEODIST' => 'read',
|
||||
'GEORADIUS' => 'variable',
|
||||
'GEORADIUSBYMEMBER' => 'variable',
|
||||
);
|
||||
|
||||
if (isset($type)) {
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ Integration tests for commands that are not defined in the specified server prof
|
||||
of the `REDIS_SERVER_VERSION` constant in `phpunit.xml`) are marked as __skipped__ automatically.
|
||||
|
||||
By default, the test suite is configured to execute integration tests using the server profile for
|
||||
Redis v2.8. You can run the suite against a Redis instance built from the `unstable` branch with the
|
||||
Redis 3.2. You can run the suite against a Redis instance built from the `unstable` branch with the
|
||||
development profile by changing the `REDIS_SERVER_VERSION` to `dev` in the `phpunit.xml` file.
|
||||
|
||||
If you do not have a Redis instance up and running or available for testing, you can completely
|
||||
|
||||
Reference in New Issue
Block a user