Compare commits

...

59 Commits

Author SHA1 Message Date
Daniele Alessandri ec42796cb2 Bump version, update CHANGELOG, fasten seat belts and get ready for this new year release. 2011-01-01 18:49:46 +01:00
Daniele Alessandri c75bdd9509 Update README. 2011-01-01 17:14:08 +01:00
Daniele Alessandri 12c3e611a0 Update CHANGELOG. 2011-01-01 14:40:28 +01:00
Daniele Alessandri 0c9b822095 Code style changes only. 2011-01-01 13:49:55 +01:00
Daniele Alessandri 030d9740bd Cannot execute with a transaction block after using fluent interface style with Predis\MultiExecBlock. 2011-01-01 13:49:39 +01:00
Daniele Alessandri 0bbbe064b5 Improve fluent-interface style for transactions with CAS. 2011-01-01 13:49:39 +01:00
Daniele Alessandri 87439ae631 Cannot use the 'retry' option for transactions without providing a function block. 2011-01-01 13:49:39 +01:00
Daniele Alessandri d8c227e074 Add an example for MULTI/EXEC transactions using CAS. 2011-01-01 13:49:38 +01:00
Daniele Alessandri 7997eab57c Do not fail with empty transactions when using CAS. 2011-01-01 13:49:38 +01:00
Daniele Alessandri 2bc61ea0bc Update README. 2011-01-01 13:49:38 +01:00
Daniele Alessandri d667bcb6bb Fix and optimize WATCH with CAS. 2011-01-01 13:49:38 +01:00
Daniele Alessandri e3dd311dd3 Add support for optional automatic retries upon aborted transactions. 2011-01-01 13:49:08 +01:00
Daniele Alessandri 34f4d5584b Support DISCARD with CAS. 2011-01-01 13:49:08 +01:00
Daniele Alessandri 39619d0c54 Add support for check-and-set (CAS) for transactions with Predis\MultiExecBlock. 2011-01-01 13:49:08 +01:00
Daniele Alessandri 07a998ebdf Test suite: tiny fix in a test for LINSERT (again, this time for real). 2011-01-01 13:47:23 +01:00
Daniele Alessandri b73b9682c0 Test suite: tiny fix in a test for LINSERT. 2010-12-21 18:37:02 +01:00
Daniele Alessandri 355d6b6cf4 Update CHANGELOG. 2010-12-18 17:16:06 +01:00
Daniele Alessandri fe2316a655 Test suite: temporary fix for testSetExpire(). 2010-12-18 17:14:17 +01:00
Daniele Alessandri 7b2cd4abd0 Remove unused class. 2010-12-18 17:11:13 +01:00
Daniele Alessandri 35fd6ca509 New command: BRPOPLPUSH (Redis v2.2-dev). 2010-12-18 17:09:28 +01:00
Daniele Alessandri dea03a6aa9 Test suite: move up testStrlen(). 2010-12-18 16:53:31 +01:00
Daniele Alessandri fb5f878e21 New command: GETBIT (Redis v2.2-dev). 2010-12-18 16:53:26 +01:00
Daniele Alessandri e3ee595768 New command: SETBIT (Redis v2.2-dev). 2010-12-18 15:53:01 +01:00
Daniele Alessandri cc16311950 New command: SETRANGE (Redis v2.2-dev). 2010-12-18 13:11:59 +01:00
Daniele Alessandri f32cd19800 New command: GETRANGE (Redis v2.2-dev - replaces SUBSTR). 2010-12-18 12:36:04 +01:00
Daniele Alessandri 7db5e650ef Bump version and... release! 2010-11-28 20:23:39 +01:00
Daniele Alessandri 975bd98933 Update CHANGELOG. 2010-11-28 19:22:26 +01:00
Daniele Alessandri 7d5cccaf7c Overridden methods for UNWATCH and DISCARD in Predis\MultiExecBlock must return $this to not break method chaining. 2010-11-28 19:19:48 +01:00
Daniele Alessandri 08b1761e10 Fix a bunch of broken tests. 2010-11-28 17:21:46 +01:00
Daniele Alessandri 24f49abc14 Update CHANGELOG. 2010-11-28 16:54:02 +01:00
Daniele Alessandri b530f9f613 New command: PERSIST (Redis v2.2-dev). 2010-11-28 16:49:20 +01:00
Daniele Alessandri d677c72ae3 New command: ZREVRANGEBYSCORE (Redis v2.2-dev). 2010-11-28 16:39:07 +01:00
Daniele Alessandri c3cf2acb51 Update CHANGELOG. 2010-09-25 17:54:52 +02:00
Daniele Alessandri 8bfae69bf0 Make WATCH accept an array of keys that must be watched. 2010-09-25 17:52:23 +02:00
Daniele Alessandri 92d6ad62ce Test suite: add test for WATCH in Predis\MultiExecBlock. 2010-09-25 17:08:30 +02:00
Daniele Alessandri fa4d654d38 Update CHANGELOG. 2010-09-25 17:04:29 +02:00
Daniele Alessandri 9ac4571f91 Fix improper handling of a few corner cases for Predis\MultiExecBlock and send DISCARD to the server on uncaught exceptions inside a callable block. 2010-09-25 16:55:31 +02:00
Daniele Alessandri fea5e1c602 Test suite: add missing tests for Predis\MultiExecBlock. 2010-09-25 16:53:21 +02:00
Daniele Alessandri ec51b14fbb Fix an infinite recursion loop when calling Predis\Client::multiExec() (ISSUE #14). 2010-09-25 16:52:59 +02:00
Daniele Alessandri dc56526256 New commands: RPUSHX, LPUSHX (Redis v2.2-dev). 2010-09-25 16:38:16 +02:00
Daniele Alessandri 195c6eae99 New command: LINSERT (Redis v2.2-dev). 2010-09-25 16:34:18 +02:00
Daniele Alessandri 9ae47c865e New command: STRLEN (Redis v2.2-dev). 2010-09-25 16:31:35 +02:00
Daniele Alessandri d3ba25436c Slight micro-optimization. 2010-09-25 16:26:55 +02:00
Daniele Alessandri 580cf80436 Get the connection out of a cluster only once inside Predis\Client::getClientFor(). 2010-09-18 18:56:39 +02:00
Daniele Alessandri d6bea4390e Perform (p)unsubscribtion only if (p)subscribed to a channel when the destructor for Predis\PubSubContext is invoked. 2010-09-17 22:24:59 +02:00
Daniele Alessandri fd7f4a7b48 Make arguments handling more solid for the SORT command. 2010-09-17 22:11:37 +02:00
Daniele Alessandri 92f7e8bf9b Remove useless code duplication. 2010-09-17 21:56:31 +02:00
Daniele Alessandri 006b44e4bf Fix checks on the value for the STORE argument (ISSUE #13). 2010-09-17 11:36:47 +02:00
Daniele Alessandri bd961a767b Update CHANGELOG. It's release time! 2010-07-11 17:12:28 +02:00
Daniele Alessandri b3599a8e6b Update CHANGELOG. 2010-07-11 17:11:58 +02:00
Daniele Alessandri 0b46de424e Test suite: add a test for the LIMIT modifier of ZRANGEBYSCORE configured via associative array. 2010-07-11 17:02:33 +02:00
Daniele Alessandri 73bdfc2d72 Fix handling of the WEIGHT modifier for ZUNIONSTORE and ZINTERSTORE with more than two weights specified. 2010-07-11 16:35:03 +02:00
Daniele Alessandri 3077e76708 Update CHANGELOG. 2010-07-11 16:24:06 +02:00
Daniele Alessandri 208fdf6daf Update CHANGELOG. 2010-07-11 15:32:14 +02:00
Daniele Alessandri 7d7995ff8f Update CHANGELOG. 2010-07-07 20:07:15 +02:00
Daniele Alessandri 418197af75 Add tests for the new options handling for the ZRANGE commands family. 2010-07-07 19:59:16 +02:00
Daniele Alessandri 90cb9d437d Add support for the LIMIT modifier in ZRANGEBYSCORE. 2010-07-07 19:58:52 +02:00
Daniele Alessandri 4668bdca83 ZRANGE, ZREVRANGE and ZRANGEBYSCORE accepts an array instance for optional modifiers. 2010-07-07 19:58:41 +02:00
Daniele Alessandri 93a3c184ed Bump version number. 2010-07-02 22:31:39 +02:00
8 changed files with 933 additions and 157 deletions
+56 -12
View File
@@ -1,13 +1,54 @@
v0.6.1 (2010-xx-xx)
v0.6.3 (2011-01-01)
* New commands available in the Redis v2.2 profile (dev):
- Strings: SETRANGE, GETRANGE, SETBIT, GETBIT
- Lists : BRPOPLPUSH
* The abstraction for MULTI/EXEC transactions has been dramatically improved
by providing support for check-and-set (CAS) operations when using Redis >=
2.2. Aborted transactions can also be optionally replayed in automatic up
to a user-defined number of times, after which a Predis\AbortedMultiExec
exception is thrown.
v0.6.2 (2010-11-28)
* Minor internal improvements and clean ups.
* New commands available in the Redis v2.2 profile (dev):
- Strings: STRLEN
- Lists : LINSERT, RPUSHX, LPUSHX
- ZSets : ZREVRANGEBYSCORE
- Misc. : PERSIST
* WATCH also accepts a single array parameter with the keys that should be
monitored during a transaction.
* Improved the behaviour of Predis\MultiExecBlock in certain corner cases.
* Improved parameters checking for the SORT command.
* FIX: the STORE parameter for the SORT command didn't work correctly when
using '0' as the target key (ISSUE #13).
* FIX: the methods for UNWATCH and DISCARD do not break anymore method
chaining with Predis\MultiExecBlock.
v0.6.1 (2010-07-11)
* Minor internal improvements and clean ups.
* New commands available in the Redis v2.2 profile (dev):
- Misc. : WATCH, UNWATCH
* Minor internal improvements and clean ups.
* Optional modifiers for ZRANGE, ZREVRANGE and ZRANGEBYSCORE queries are
supported using an associative array passed as the last argument of their
respective methods.
* The constructor of Predis\Client::__construct now accepts also instances
of Predis\ConnectionParameters.
* The LIMIT modifier for ZRANGEBYSCORE can be specified using either:
- an indexed array: array($offset, $count)
- an associative array: array('offset' => $offset, 'count' => $count)
* Predis\MultiExecBlock and Predis\PubSubContext will throw an exception
* The method Predis\Client::__construct() now accepts also instances of
Predis\ConnectionParameters.
* Predis\MultiExecBlock and Predis\PubSubContext now throw an exception
when trying to create their instances using a profile that does not
support the required Redis commands or when the client is connected to
a cluster of connections.
@@ -17,14 +58,17 @@ v0.6.1 (2010-xx-xx)
- support for WATCH and UNWATCH when using the current development
profile (Redis v2.2) and aborted transactions.
* New method signature for Predis\Client::multiExec(). Now it is able to
accept an array of options for the underlying Predis\MultiExecBlock, but
it is still backwards compatible with previous releases of Predis.
* New signature for Predis\Client::multiExec() which is now able to accept
an array of options for the underlying instance of Predis\MultiExecBlock.
Backwards compatibility with previous releases of Predis is ensured.
* New method signature for Predis\Client::pipeline(). Now it is able to
accept an array of options for the underlying Predis\CommandPipeline,
but it is still backwards compatible with previous releases of Predis.
Predis\Client::pipelineSafe() is to be considered obsolete.
* New signature for Predis\Client::pipeline() which is now able to accept
an array of options for the underlying instance of Predis\CommandPipeline.
Backwards compatibility with previous releases of Predis is ensured.
The method Predis\Client::pipelineSafe() is to be considered deprecated.
* FIX: The WEIGHT modifier for ZUNIONSTORE and ZINTERSTORE was handled
incorrectly with more than two weights specified.
v0.6.0 (2010-05-24)
* Switched to the new multi-bulk request protocol for all of the commands
+3 -2
View File
@@ -20,6 +20,7 @@ to be implemented soon in Predis.
- Full support for Redis 2.0. Different versions of Redis are supported via server profiles.
- Client-side sharding (support for consistent hashing and custom distribution strategies).
- Command pipelining on single and multiple connections (transparent).
- Abstraction for Redis transactions (>= 2.0) with support for CAS operations (>= 2.2).
- Lazy connections (connections to Redis instances are only established just in time).
- Flexible system to define and register your own set of commands to a client instance.
@@ -60,10 +61,10 @@ Furthermore, a pipeline can be initialized on a cluster of redis instances in th
same exact way they are created on single connection. Sharding is still transparent
to the user:
$redis = Predis\Client::create(
$redis = new Predis\Client(array(
array('host' => '10.0.0.1', 'port' => 6379),
array('host' => '10.0.0.2', 'port' => 6379)
);
));
$replies = $redis->pipeline(function($pipe) {
for ($i = 0; $i < 1000; $i++) {
+1 -1
View File
@@ -1 +1 @@
0.6.0
0.6.3
+41
View File
@@ -0,0 +1,41 @@
<?php
require_once 'SharedConfigurations.php';
/*
This is an implementation of an atomic client-side ZPOP using the support for
check-and-set (CAS) operations with MULTI/EXEC transactions, as described in
"WATCH explained" from http://redis.io/topics/transactions
First, populate your database with a tiny sample data set:
./redis-cli
SELECT 15
ZADD zset 1 a
ZADD zset 2 b
ZADD zset 3 c
*/
function zpop($client, $zsetKey) {
$element = null;
$options = array(
'cas' => true, // Initialize with support for CAS operations
'watch' => $zsetKey, // Key that needs to be WATCHed to detect changes
'retry' => 3, // Number of retries on aborted transactions, after
// which the client bails out with an exception.
);
$txReply = $client->multiExec($options, function($tx)
use ($zsetKey, &$element) {
@list($element) = $tx->zrange($zsetKey, 0, 0);
if (isset($element)) {
$tx->multi(); // With CAS, MULTI *must* be explicitly invoked.
$tx->zrem($zsetKey, $element);
}
});
return $element;
}
$redis = new Predis\Client($single_server, 'dev');
$zpopped = zpop($redis, 'zset');
echo isset($zpopped) ? "ZPOPed $zpopped" : "Nothing to ZPOP!", "\n";
?>
+264 -135
View File
@@ -170,7 +170,7 @@ class Client {
$newClient = new Client();
$newClient->setupClient($this->_options);
$newClient->setConnection($this->getConnection($connectionAlias));
$newClient->setConnection($connection);
return $newClient;
}
@@ -230,21 +230,24 @@ class Client {
return $this->_connection->rawCommand($rawCommandData, $closesConnection);
}
public function pipeline(/* arguments */) {
$argv = func_get_args();
$argc = func_num_args();
private function sharedInitializer($argv, $initializer) {
$argc = count($argv);
if ($argc === 0) {
return $this->initPipeline();
return $this->$initializer();
}
else if ($argc === 1) {
list($arg0) = $argv;
return is_array($arg0) ? $this->initPipeline($arg0) : $this->initPipeline(null, $arg0);
return is_array($arg0) ? $this->$initializer($arg0) : $this->$initializer(null, $arg0);
}
else if ($argc === 2) {
list($arg0, $arg1) = $argv;
return $this->initPipeline($arg0, $arg1);
return $this->$initializer($arg0, $arg1);
}
return $this->$initializer($this, $arguments);
}
public function pipeline(/* arguments */) {
return $this->sharedInitializer(func_get_args(), 'initPipeline');
}
public function pipelineSafe($pipelineBlock = null) {
@@ -276,20 +279,7 @@ class Client {
}
public function multiExec(/* arguments */) {
$argv = func_get_args();
$argc = func_num_args();
if ($argc === 0) {
return $this->initMultiExec();
}
else if ($argc === 1) {
list($arg0) = $argv;
return is_array($arg0) ? $this->initMultiExec($arg0) : $this->initMultiExec(null, $arg0);
}
else if ($argc === 2) {
list($arg0, $arg1) = $argv;
return $this->initMultiExec($arg0, $arg1);
}
return $this->sharedInitializer(func_get_args(), 'initMultiExec');
}
private function initMultiExec(Array $options = null, $transBlock = null) {
@@ -470,12 +460,12 @@ abstract class Command {
public function setArguments(/* arguments */) {
$this->_arguments = $this->filterArguments(func_get_args());
$this->_hash = null;
unset($this->_hash);
}
public function setArgumentsArray(Array $arguments) {
$this->_arguments = $this->filterArguments($arguments);
$this->_hash = null;
unset($this->_hash);
}
public function getArguments() {
@@ -815,18 +805,15 @@ class CommandPipeline {
}
class MultiExecBlock {
private $_initialized, $_discarded, $_insideBlock;
private $_initialized, $_discarded, $_insideBlock, $_checkAndSet;
private $_redisClient, $_options, $_commands;
private $_supportsWatch;
public function __construct(Client $redisClient, Array $options = null) {
$this->checkCapabilities($redisClient);
$this->_initialized = false;
$this->_discarded = false;
$this->_insideBlock = false;
$this->_options = $options ?: array();
$this->_redisClient = $redisClient;
$this->_options = $options ?: array();
$this->_commands = array();
$this->reset();
}
private function checkCapabilities(Client $redisClient) {
@@ -852,136 +839,184 @@ class MultiExecBlock {
}
}
private function initialize() {
if ($this->_initialized === false) {
if (isset($this->_options['watch'])) {
$this->watch($this->_options['watch']);
}
$this->_redisClient->multi();
$this->_initialized = true;
$this->_discarded = false;
}
private function reset() {
$this->_initialized = false;
$this->_discarded = false;
$this->_checkAndSet = false;
$this->_insideBlock = false;
$this->_commands = array();
}
private function setInsideBlock($value) {
$this->_insideBlock = $value;
private function initialize() {
if ($this->_initialized === true) {
return;
}
$options = &$this->_options;
$this->_checkAndSet = isset($options['cas']) && $options['cas'];
if (isset($options['watch'])) {
$this->watch($options['watch']);
}
if (!$this->_checkAndSet || ($this->_discarded && $this->_checkAndSet)) {
$this->_redisClient->multi();
if ($this->_discarded) {
$this->_checkAndSet = false;
}
}
$this->_initialized = true;
$this->_discarded = false;
}
public function __call($method, $arguments) {
$this->initialize();
$command = $this->_redisClient->createCommand($method, $arguments);
$response = $this->_redisClient->executeCommand($command);
if (isset($response->queued)) {
$this->_commands[] = $command;
return $this;
$client = $this->_redisClient;
if ($this->_checkAndSet) {
return call_user_func_array(array($client, $method), $arguments);
}
else {
$this->malformedServerResponse('The server did not respond with a QUEUED status reply');
$command = $client->createCommand($method, $arguments);
$response = $client->executeCommand($command);
if (!isset($response->queued)) {
$this->malformedServerResponse(
'The server did not respond with a QUEUED status reply'
);
}
$this->_commands[] = $command;
return $this;
}
public function watch($keys) {
$this->isWatchSupported();
if ($this->_initialized === true) {
throw new \Predis\ClientException('WATCH inside MULTI is not allowed');
if ($this->_initialized && !$this->_checkAndSet) {
throw new ClientException('WATCH inside MULTI is not allowed');
}
$reply = null;
if (is_array($keys)) {
$reply = array();
foreach ($keys as $key) {
$reply = $this->_redisClient->watch($keys);
}
}
else {
$reply = $this->_redisClient->watch($keys);
}
return $reply;
return $this->_redisClient->watch($keys);
}
public function multi() {
if ($this->_initialized && $this->_checkAndSet) {
$this->_checkAndSet = false;
$this->_redisClient->multi();
return $this;
}
$this->initialize();
return $this;
}
public function unwatch() {
$this->isWatchSupported();
$this->_redisClient->unwatch();
return $this;
}
public function discard() {
$this->_redisClient->discard();
$this->_commands = array();
$this->_initialized = false;
$this->_discarded = true;
$this->reset();
$this->_discarded = true;
return $this;
}
public function exec() {
return $this->execute();
}
public function execute($block = null) {
private function checkBeforeExecution($block) {
if ($this->_insideBlock === true) {
throw new \Predis\ClientException(
"Cannot invoke 'execute' or 'exec' inside an active client transaction block"
);
}
if ($block && !is_callable($block)) {
throw new \InvalidArgumentException('Argument passed must be a callable object');
if ($block) {
if (!is_callable($block)) {
throw new \InvalidArgumentException(
'Argument passed must be a callable object'
);
}
if (count($this->_commands) > 0) {
throw new ClientException(
'Cannot execute a transaction block after using fluent interface'
);
}
}
if (isset($this->_options['retry']) && !isset($block)) {
$this->discard();
throw new \InvalidArgumentException(
'Automatic retries can be used only when a transaction block is provided'
);
}
}
$blockException = null;
$returnValues = array();
public function execute($block = null) {
$this->checkBeforeExecution($block);
try {
$reply = null;
$returnValues = array();
$attemptsLeft = isset($this->_options['retry']) ? (int)$this->_options['retry'] : 0;
do {
$blockException = null;
if ($block !== null) {
$this->setInsideBlock(true);
$block($this);
$this->setInsideBlock(false);
$this->_insideBlock = true;
try {
$block($this);
}
catch (CommunicationException $exception) {
$blockException = $exception;
}
catch (ServerException $exception) {
$blockException = $exception;
}
catch (\Exception $exception) {
$blockException = $exception;
if ($this->_initialized === true) {
$this->discard();
}
}
$this->_insideBlock = false;
if ($blockException !== null) {
throw $blockException;
}
}
if ($this->_discarded === true) {
if ($this->_initialized === false || count($this->_commands) == 0) {
return;
}
$reply = $this->_redisClient->exec();
if ($reply === null) {
throw new AbortedMultiExec('The current transaction has been aborted by the server');
if ($attemptsLeft === 0) {
throw new AbortedMultiExec(
'The current transaction has been aborted by the server'
);
}
$this->reset();
continue;
}
break;
} while ($attemptsLeft-- > 0);
$execReply = $reply instanceof \Iterator ? iterator_to_array($reply) : $reply;
$commands = &$this->_commands;
$sizeofReplies = count($execReply);
$execReply = $reply instanceof \Iterator ? iterator_to_array($reply) : $reply;
$sizeofReplies = count($execReply);
if ($sizeofReplies !== count($commands)) {
$this->malformedServerResponse('Unexpected number of responses for a MultiExecBlock');
}
for ($i = 0; $i < $sizeofReplies; $i++) {
$returnValues[] = $commands[$i]->parseResponse($execReply[$i] instanceof \Iterator
? iterator_to_array($execReply[$i])
: $execReply[$i]
);
unset($commands[$i]);
}
$commands = &$this->_commands;
if ($sizeofReplies !== count($commands)) {
$this->malformedServerResponse(
'Unexpected number of responses for a MultiExecBlock'
);
}
catch (\Exception $exception) {
$this->setInsideBlock(false);
$blockException = $exception;
}
if ($blockException !== null) {
throw $blockException;
for ($i = 0; $i < $sizeofReplies; $i++) {
$returnValues[] = $commands[$i]->parseResponse($execReply[$i] instanceof \Iterator
? iterator_to_array($execReply[$i])
: $execReply[$i]
);
unset($commands[$i]);
}
return $returnValues;
}
private function malformedServerResponse($message) {
// NOTE: a MULTI/EXEC block cannot be initialized on a clustered
// connection, which means that Predis\Client::getConnection
// will always return an instance of Predis\Connection.
Shared\Utils::onCommunicationException(new MalformedServerResponse(
// Since a MULTI/EXEC block cannot be initialized over a clustered
// connection, we can safely assume that Predis\Client::getConnection()
// will always return an instance of Predis\Connection.
Utils::onCommunicationException(new MalformedServerResponse(
$this->_redisClient->getConnection(), $message
));
}
@@ -1008,10 +1043,7 @@ class PubSubContext implements \Iterator {
}
public function __destruct() {
if ($this->valid()) {
$this->_redisClient->unsubscribe();
$this->_redisClient->punsubscribe();
}
$this->closeContext();
}
private function checkCapabilities(Client $redisClient) {
@@ -1824,6 +1856,25 @@ class RedisServer_vNext extends RedisServer_v2_0 {
/* transactions */
'watch' => '\Predis\Commands\Watch',
'unwatch' => '\Predis\Commands\Unwatch',
/* commands operating on string values */
'strlen' => '\Predis\Commands\Strlen',
'setrange' => '\Predis\Commands\SetRange',
'getrange' => '\Predis\Commands\Substr',
'setbit' => '\Predis\Commands\SetBit',
'getbit' => '\Predis\Commands\GetBit',
/* commands operating on the key space */
'persist' => '\Predis\Commands\Persist',
/* commands operating on lists */
'rpushx' => '\Predis\Commands\ListPushTailX',
'lpushx' => '\Predis\Commands\ListPushHeadX',
'linsert' => '\Predis\Commands\ListInsert',
'brpoplpush' => '\Predis\Commands\ListPopLastPushHeadBlocking',
/* commands operating on sorted sets */
'zrevrangebyscore' => '\Predis\Commands\ZSetReverseRangeByScore',
));
}
}
@@ -2347,10 +2398,26 @@ class Append extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'APPEND'; }
}
class SetRange extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'SETRANGE'; }
}
class Substr extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'SUBSTR'; }
}
class SetBit extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'SETBIT'; }
}
class GetBit extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'GETBIT'; }
}
class Strlen extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'STRLEN'; }
}
/* commands operating on the key space */
class Keys extends \Predis\MultiBulkCommand {
public function canBeHashed() { return false; }
@@ -2391,6 +2458,11 @@ class ExpireAt extends \Predis\MultiBulkCommand {
public function parseResponse($data) { return (bool) $data; }
}
class Persist extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'PERSIST'; }
public function parseResponse($data) { return (bool) $data; }
}
class DatabaseSize extends \Predis\MultiBulkCommand {
public function canBeHashed() { return false; }
public function getCommandId() { return 'DBSIZE'; }
@@ -2405,10 +2477,18 @@ class ListPushTail extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'RPUSH'; }
}
class ListPushTailX extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'RPUSHX'; }
}
class ListPushHead extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'LPUSH'; }
}
class ListPushHeadX extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'LPUSHX'; }
}
class ListLength extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'LLEN'; }
}
@@ -2437,8 +2517,8 @@ class ListPopLastPushHead extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'RPOPLPUSH'; }
}
class ListPopLastPushHeadBulk extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'RPOPLPUSH'; }
class ListPopLastPushHeadBlocking extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'BRPOPLPUSH'; }
}
class ListPopFirst extends \Predis\MultiBulkCommand {
@@ -2457,6 +2537,10 @@ class ListPopLastBlocking extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'BRPOP'; }
}
class ListInsert extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'LINSERT'; }
}
/* commands operating on sets */
class SetAdd extends \Predis\MultiBulkCommand {
public function getCommandId() { return 'SADD'; }
@@ -2550,8 +2634,9 @@ class ZSetUnionStore extends \Predis\MultiBulkCommand {
$finalizedOpts = array();
if (isset($opts['WEIGHTS']) && is_array($opts['WEIGHTS'])) {
$finalizedOpts[] = 'WEIGHTS';
$finalizedOpts[] = $opts['WEIGHTS'][0];
$finalizedOpts[] = $opts['WEIGHTS'][1];
foreach ($opts['WEIGHTS'] as $weight) {
$finalizedOpts[] = $weight;
}
}
if (isset($opts['AGGREGATE'])) {
$finalizedOpts[] = 'AGGREGATE';
@@ -2566,20 +2651,42 @@ class ZSetIntersectionStore extends \Predis\Commands\ZSetUnionStore {
}
class ZSetRange extends \Predis\MultiBulkCommand {
private $_withScores = false;
public function getCommandId() { return 'ZRANGE'; }
public function parseResponse($data) {
$arguments = $this->getArguments();
public function filterArguments(Array $arguments) {
if (count($arguments) === 4) {
if (strtolower($arguments[3]) === 'withscores') {
if ($data instanceof \Iterator) {
return new \Predis\Shared\MultiBulkResponseKVIterator($data);
}
$result = array();
for ($i = 0; $i < count($data); $i++) {
$result[] = array($data[$i], $data[++$i]);
}
return $result;
$lastType = gettype($arguments[3]);
if ($lastType === 'string' && strtolower($arguments[3]) === 'withscores') {
// used for compatibility with older versions
$arguments[3] = array('WITHSCORES' => true);
$lastType = 'array';
}
if ($lastType === 'array') {
$options = $this->prepareOptions(array_pop($arguments));
return array_merge($arguments, $options);
}
}
return $arguments;
}
protected function prepareOptions($options) {
$opts = array_change_key_case($options, CASE_UPPER);
$finalizedOpts = array();
if (isset($opts['WITHSCORES'])) {
$finalizedOpts[] = 'WITHSCORES';
$this->_withScores = true;
}
return $finalizedOpts;
}
public function parseResponse($data) {
if ($this->_withScores) {
if ($data instanceof \Iterator) {
return new \Predis\Shared\MultiBulkResponseKVIterator($data);
}
$result = array();
for ($i = 0; $i < count($data); $i++) {
$result[] = array($data[$i], $data[++$i]);
}
return $result;
}
return $data;
}
@@ -2591,6 +2698,21 @@ class ZSetReverseRange extends \Predis\Commands\ZSetRange {
class ZSetRangeByScore extends \Predis\Commands\ZSetRange {
public function getCommandId() { return 'ZRANGEBYSCORE'; }
protected function prepareOptions($options) {
$opts = array_change_key_case($options, CASE_UPPER);
$finalizedOpts = array();
if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) {
$limit = array_change_key_case($opts['LIMIT'], CASE_UPPER);
$finalizedOpts[] = 'LIMIT';
$finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0];
$finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1];
}
return array_merge($finalizedOpts, parent::prepareOptions($options));
}
}
class ZSetReverseRangeByScore extends \Predis\Commands\ZSetRangeByScore {
public function getCommandId() { return 'ZREVRANGEBYSCORE'; }
}
class ZSetCount extends \Predis\MultiBulkCommand {
@@ -2737,16 +2859,15 @@ class Sort extends \Predis\MultiBulkCommand {
return $arguments;
}
// TODO: add more parameters checks
$query = array($arguments[0]);
$sortParams = $arguments[1];
$sortParams = array_change_key_case($arguments[1], CASE_UPPER);
if (isset($sortParams['by'])) {
if (isset($sortParams['BY'])) {
$query[] = 'BY';
$query[] = $sortParams['by'];
$query[] = $sortParams['BY'];
}
if (isset($sortParams['get'])) {
$getargs = $sortParams['get'];
if (isset($sortParams['GET'])) {
$getargs = $sortParams['GET'];
if (is_array($getargs)) {
foreach ($getargs as $getarg) {
$query[] = 'GET';
@@ -2758,20 +2879,22 @@ class Sort extends \Predis\MultiBulkCommand {
$query[] = $getargs;
}
}
if (isset($sortParams['limit']) && is_array($sortParams['limit'])) {
if (isset($sortParams['LIMIT']) && is_array($sortParams['LIMIT'])
&& count($sortParams['LIMIT']) == 2) {
$query[] = 'LIMIT';
$query[] = $sortParams['limit'][0];
$query[] = $sortParams['limit'][1];
$query[] = $sortParams['LIMIT'][0];
$query[] = $sortParams['LIMIT'][1];
}
if (isset($sortParams['sort'])) {
$query[] = strtoupper($sortParams['sort']);
if (isset($sortParams['SORT'])) {
$query[] = strtoupper($sortParams['SORT']);
}
if (isset($sortParams['alpha']) && $sortParams['alpha'] == true) {
if (isset($sortParams['ALPHA']) && $sortParams['ALPHA'] == true) {
$query[] = 'ALPHA';
}
if (isset($sortParams['store']) && $sortParams['store'] == true) {
if (isset($sortParams['STORE'])) {
$query[] = 'STORE';
$query[] = $sortParams['store'];
$query[] = $sortParams['STORE'];
}
return $query;
@@ -2797,6 +2920,12 @@ class Discard extends \Predis\MultiBulkCommand {
class Watch extends \Predis\MultiBulkCommand {
public function canBeHashed() { return false; }
public function getCommandId() { return 'WATCH'; }
public function filterArguments(Array $arguments) {
if (isset($arguments[0]) && is_array($arguments[0])) {
return $arguments[0];
}
return $arguments;
}
public function parseResponse($data) { return (bool) $data; }
}
+283 -5
View File
@@ -261,16 +261,19 @@ class PredisClientFeaturesTestSuite extends PHPUnit_Framework_TestCase {
function testConnection_WriteCommandAndCloseConnection() {
$cmd = \Predis\RedisServerProfile::getDefault()->createCommand('quit');
$connection = new \Predis\Connection(RC::getConnectionParameters());
$connection->connect();
$connection = new \Predis\Connection(new \Predis\ConnectionParameters(
RC::getConnectionArguments() + array('read_write_timeout' => 0.5)
));
$connection->connect();
$this->assertTrue($connection->isConnected());
$connection->writeCommand($cmd);
$connection->disconnect();
$exceptionMessage = 'Error while reading line from the server';
RC::testForCommunicationException($this, $exceptionMessage, function() use($connection, $cmd) {
$connection->readResponse($cmd);
});
//$this->assertFalse($connection->isConnected());
}
function testConnection_GetSocketOpensConnection() {
@@ -354,8 +357,8 @@ class PredisClientFeaturesTestSuite extends PHPUnit_Framework_TestCase {
function testResponseReader_OptionExceptionOnError() {
$connection = new \Predis\Connection(RC::getConnectionParameters());
$responseReader = $connection->getResponseReader();
$connection->rawCommand("SET key 5\r\nvalue\r\n");
$rawCmdUnexpected = "LPUSH key 5\r\nvalue\r\n";
$connection->rawCommand("*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n");
$rawCmdUnexpected = "*3\r\n$5\r\nLPUSH\r\n$3\r\nkey\r\n$5\r\nvalue\r\n";
$responseReader->setHandler(
\Predis\Protocol::PREFIX_ERROR,
@@ -471,5 +474,280 @@ class PredisClientFeaturesTestSuite extends PHPUnit_Framework_TestCase {
$this->assertEquals('bar', $replies[3][0]);
$this->assertEquals('piyo', $replies[3][1]);
}
/* Client + MultiExecBlock */
function testMultiExecBlock_Simple() {
$client = RC::getConnection();
$client->flushdb();
$multi = $client->multiExec();
$this->assertType('\Predis\MultiExecBlock', $multi);
$this->assertType('\Predis\MultiExecBlock', $multi->set('foo', 'bar'));
$this->assertType('\Predis\MultiExecBlock', $multi->set('hoge', 'piyo'));
$this->assertType('\Predis\MultiExecBlock', $multi->mset(array(
'foofoo' => 'barbar', 'hogehoge' => 'piyopiyo'
)));
$this->assertType('\Predis\MultiExecBlock', $multi->mget(array(
'foo', 'hoge', 'foofoo', 'hogehoge'
)));
$replies = $multi->execute();
$this->assertType('array', $replies);
$this->assertEquals(4, count($replies));
$this->assertEquals(4, count($replies[3]));
$this->assertEquals('barbar', $replies[3][2]);
}
function testMultiExecBlock_FluentInterface() {
$client = RC::getConnection();
$client->flushdb();
$replies = $client->multiExec()->ping()->set('foo', 'bar')->get('foo')->execute();
$this->assertType('array', $replies);
$this->assertEquals('bar', $replies[2]);
}
function testMultiExecBlock_CallableAnonymousBlock() {
$client = RC::getConnection();
$client->flushdb();
$replies = $client->multiExec(function($multi) {
$multi->ping();
$multi->set('foo', 'bar');
$multi->get('foo');
});
$this->assertType('array', $replies);
$this->assertEquals('bar', $replies[2]);
}
/**
* @expectedException Predis\ClientException
*/
function testMultiExecBlock_CannotMixFluentInterfaceAndAnonymousBlock() {
$emptyBlock = function($tx) { };
$tx = RC::getConnection()->multiExec()->get('foo')->execute($emptyBlock);
}
function testMultiExecBlock_EmptyCallableBlock() {
$client = RC::getConnection();
$client->flushdb();
$replies = $client->multiExec(function($multi) { });
$this->assertEquals(0, count($replies));
$options = array('cas' => true);
$replies = $client->multiExec($options, function($multi) { });
$this->assertEquals(0, count($replies));
$options = array('cas' => true);
$replies = $client->multiExec($options, function($multi) {
$multi->multi();
});
$this->assertEquals(0, count($replies));
}
function testMultiExecBlock_ClientExceptionInCallableBlock() {
$client = RC::getConnection();
$client->flushdb();
RC::testForClientException($this, 'TEST', function() use($client) {
$client->multiExec(function($multi) {
$multi->ping();
$multi->set('foo', 'bar');
throw new \Predis\ClientException("TEST");
});
});
$this->assertFalse($client->exists('foo'));
}
function testMultiExecBlock_ServerExceptionInCallableBlock() {
$client = RC::getConnection();
$client->flushdb();
$client->getResponseReader()->setHandler('-', new \Predis\ResponseErrorSilentHandler());
$replies = $client->multiExec(function($multi) {
$multi->set('foo', 'bar');
$multi->lpush('foo', 'piyo'); // LIST operation on STRING type returns an ERROR
$multi->set('hoge', 'piyo');
});
$this->assertType('array', $replies);
$this->assertType('\Predis\ResponseError', $replies[1]);
$this->assertTrue($client->exists('foo'));
$this->assertTrue($client->exists('hoge'));
}
function testMultiExecBlock_Discard() {
$client = RC::getConnection();
$client->flushdb();
$replies = $client->multiExec(function($multi) {
$multi->set('foo', 'bar');
$multi->discard();
$multi->set('hoge', 'piyo');
});
$this->assertEquals(1, count($replies));
$this->assertFalse($client->exists('foo'));
$this->assertTrue($client->exists('hoge'));
}
function testMultiExecBlock_DiscardEmpty() {
$client = RC::getConnection();
$client->flushdb();
$replies = $client->multiExec(function($multi) {
$multi->discard();
});
$this->assertEquals(0, count($replies));
}
function testMultiExecBlock_Watch() {
$client1 = RC::getConnection();
$client2 = RC::getConnection(true);
$client1->flushdb();
RC::testForAbortedMultiExecException($this, function()
use($client1, $client2) {
$client1->multiExec(array('watch' => 'sentinel'), function($multi)
use ($client2) {
$multi->set('sentinel', 'client1');
$multi->get('sentinel');
$client2->set('sentinel', 'client2');
});
});
$this->assertEquals('client2', $client1->get('sentinel'));
}
function testMultiExecBlock_CheckAndSet() {
$client = RC::getConnection();
$client->flushdb();
$client->set('foo', 'bar');
$options = array('watch' => 'foo', 'cas' => true);
$replies = $client->multiExec($options, function($tx) {
$tx->watch('foobar');
$foo = $tx->get('foo');
$tx->multi();
$tx->set('foobar', $foo);
$tx->mget('foo', 'foobar');
});
$this->assertType('array', $replies);
$this->assertEquals(array(true, array('bar', 'bar')), $replies);
$tx = $client->multiExec($options);
$tx->watch('foobar');
$foo = $tx->get('foo');
$replies = $tx->multi()
->set('foobar', $foo)
->mget('foo', 'foobar')
->execute();
$this->assertType('array', $replies);
$this->assertEquals(array(true, array('bar', 'bar')), $replies);
}
function testMultiExecBlock_RetryOnServerAbort() {
$client1 = RC::getConnection();
$client2 = RC::getConnection(true);
$client1->flushdb();
$retry = 3;
$attempts = 0;
RC::testForAbortedMultiExecException($this, function()
use($client1, $client2, $retry, &$attempts) {
$options = array('watch' => 'sentinel', 'retry' => $retry);
$client1->multiExec($options, function($tx)
use ($client2, &$attempts) {
$attempts++;
$tx->set('sentinel', 'client1');
$tx->get('sentinel');
$client2->set('sentinel', 'client2');
});
});
$this->assertEquals('client2', $client1->get('sentinel'));
$this->assertEquals($retry + 1, $attempts);
$retry = 3;
$attempts = 0;
RC::testForAbortedMultiExecException($this, function()
use($client1, $client2, $retry, &$attempts) {
$options = array(
'watch' => 'sentinel',
'cas' => true,
'retry' => $retry
);
$client1->multiExec($options, function($tx)
use ($client2, &$attempts) {
$attempts++;
$tx->incr('attempts');
$tx->multi();
$tx->set('sentinel', 'client1');
$tx->get('sentinel');
$client2->set('sentinel', 'client2');
});
});
$this->assertEquals('client2', $client1->get('sentinel'));
$this->assertEquals($retry + 1, $attempts);
$this->assertEquals($attempts, $client1->get('attempts'));
}
/**
* @expectedException InvalidArgumentException
*/
function testMultiExecBlock_RetryNotAvailableWithoutBlock() {
$options = array('watch' => 'foo', 'retry' => 1);
$tx = RC::getConnection()->multiExec($options);
$tx->multi()->get('foo')->exec();
}
function testMultiExecBlock_CheckAndSet_Discard() {
$client = RC::getConnection();
$client->flushdb();
$client->set('foo', 'bar');
$options = array('watch' => 'foo', 'cas' => true);
$replies = $client->multiExec($options, function($tx) {
$tx->watch('foobar');
$foo = $tx->get('foo');
$tx->multi();
$tx->set('foobar', $foo);
$tx->discard();
$tx->mget('foo', 'foobar');
});
$this->assertType('array', $replies);
$this->assertEquals(array(array('bar', null)), $replies);
$hijack = true;
$client->set('foo', 'bar');
$client2 = RC::getConnection(true);
$options = array('watch' => 'foo', 'cas' => true, 'retry' => 1);
$replies = $client->multiExec($options, function($tx)
use ($client2, &$hijack) {
$foo = $tx->get('foo');
$tx->multi();
$tx->set('foobar', $foo);
$tx->discard();
if ($hijack) {
$hijack = false;
$client2->set('foo', 'hijacked!');
}
$tx->mget('foo', 'foobar');
});
$this->assertType('array', $replies);
$this->assertEquals(array(array('hijacked!', null)), $replies);
}
}
?>
+18 -1
View File
@@ -20,11 +20,14 @@ class RC {
const EXCEPTION_WRONG_TYPE = 'Operation against a key holding the wrong kind of value';
const EXCEPTION_NO_SUCH_KEY = 'no such key';
const EXCEPTION_OUT_OF_RANGE = 'index out of range';
const EXCEPTION_OFFSET_RANGE = 'offset is out of range';
const EXCEPTION_INVALID_DB_IDX = 'invalid DB index';
const EXCEPTION_VALUE_NOT_INT = 'value is not an integer';
const EXCEPTION_EXEC_NO_MULTI = 'EXEC without MULTI';
const EXCEPTION_SETEX_TTL = 'invalid expire time in SETEX';
const EXCEPTION_HASH_VALNOTINT = 'hash value is not an integer';
const EXCEPTION_BIT_VALUE = 'bit is not an integer or out of range';
const EXCEPTION_BIT_OFFSET = 'bit offset is not an integer or out of range';
private static $_connection;
@@ -44,7 +47,10 @@ class RC {
return $connection;
}
public static function getConnection() {
public static function getConnection($new = false) {
if ($new == true) {
return self::createConnection();
}
if (self::$_connection === null || !self::$_connection->isConnected()) {
self::$_connection = self::createConnection();
}
@@ -149,6 +155,17 @@ class RC {
}
}
public static function testForAbortedMultiExecException($testcaseInstance, $wrapFunction) {
$thrownException = null;
try {
$wrapFunction($testcaseInstance);
}
catch (Predis\AbortedMultiExec $exception) {
$thrownException = $exception;
}
$testcaseInstance->assertType('Predis\AbortedMultiExec', $thrownException);
}
public static function pushTailAndReturn(Predis\Client $client, $keyName, Array $values, $wipeOut = 0) {
if ($wipeOut == true) {
$client->del($keyName);
+267 -1
View File
@@ -223,6 +223,28 @@ class RedisCommandTestSuite extends PHPUnit_Framework_TestCase {
});
}
function testSetRange() {
$this->assertEquals(6, $this->redis->setrange('var', 0, 'foobar'));
$this->assertEquals('foobar', $this->redis->get('var'));
$this->assertEquals(6, $this->redis->setrange('var', 3, 'foo'));
$this->assertEquals('foofoo', $this->redis->get('var'));
$this->assertEquals(16, $this->redis->setrange('var', 10, 'barbar'));
$this->assertEquals("foofoo\x00\x00\x00\x00barbar", $this->redis->get('var'));
$this->assertEquals(4, $this->redis->setrange('binary', 0, pack('l', -2147483648)));
list($unpacked) = array_values(unpack('l', $this->redis->get('binary')));
$this->assertEquals(-2147483648, $unpacked);
RC::testForServerException($this, RC::EXCEPTION_OFFSET_RANGE, function($test) {
$test->redis->setrange('var', -1, 'bogus');
});
RC::testForServerException($this, RC::EXCEPTION_WRONG_TYPE, function($test) {
$test->redis->rpush('metavars', 'foo');
$test->redis->setrange('metavars', 0, 'hoge');
});
}
function testSubstr() {
$this->redis->set('var', 'foobar');
$this->assertEquals('foo', $this->redis->substr('var', 0, 2));
@@ -240,6 +262,74 @@ class RedisCommandTestSuite extends PHPUnit_Framework_TestCase {
});
}
function testStrlen() {
$this->redis->set('var', 'foobar');
$this->assertEquals(6, $this->redis->strlen('var'));
$this->assertEquals(9, $this->redis->append('var', '___'));
$this->assertEquals(9, $this->redis->strlen('var'));
RC::testForServerException($this, RC::EXCEPTION_WRONG_TYPE, function($test) {
$test->redis->rpush('metavars', 'foo');
$test->redis->strlen('metavars');
});
}
function testSetBit() {
$this->assertEquals(0, $this->redis->setbit('binary', 31, 1));
$this->assertEquals(0, $this->redis->setbit('binary', 0, 1));
$this->assertEquals(4, $this->redis->strlen('binary'));
$this->assertEquals("\x80\x00\00\x01", $this->redis->get('binary'));
$this->assertEquals(1, $this->redis->setbit('binary', 0, 0));
$this->assertEquals(0, $this->redis->setbit('binary', 0, 0));
$this->assertEquals("\x00\x00\00\x01", $this->redis->get('binary'));
RC::testForServerException($this, RC::EXCEPTION_BIT_OFFSET, function($test) {
$test->redis->setbit('binary', -1, 1);
});
RC::testForServerException($this, RC::EXCEPTION_BIT_OFFSET, function($test) {
$test->redis->setbit('binary', 'invalid', 1);
});
RC::testForServerException($this, RC::EXCEPTION_BIT_VALUE, function($test) {
$test->redis->setbit('binary', 15, 255);
});
RC::testForServerException($this, RC::EXCEPTION_BIT_VALUE, function($test) {
$test->redis->setbit('binary', 15, 'invalid');
});
RC::testForServerException($this, RC::EXCEPTION_WRONG_TYPE, function($test) {
$test->redis->rpush('metavars', 'foo');
$test->redis->setbit('metavars', 0, 1);
});
}
function testGetBit() {
$this->redis->set('binary', "\x80\x00\00\x01");
$this->assertEquals(1, $this->redis->getbit('binary', 0));
$this->assertEquals(0, $this->redis->getbit('binary', 15));
$this->assertEquals(1, $this->redis->getbit('binary', 31));
$this->assertEquals(0, $this->redis->getbit('binary', 63));
RC::testForServerException($this, RC::EXCEPTION_BIT_OFFSET, function($test) {
$test->redis->getbit('binary', -1);
});
RC::testForServerException($this, RC::EXCEPTION_BIT_OFFSET, function($test) {
$test->redis->getbit('binary', 'invalid');
});
RC::testForServerException($this, RC::EXCEPTION_WRONG_TYPE, function($test) {
$test->redis->rpush('metavars', 'foo');
$test->redis->getbit('metavars', 0);
});
}
/* commands operating on the key space */
function testKeys() {
@@ -324,6 +414,18 @@ class RedisCommandTestSuite extends PHPUnit_Framework_TestCase {
$this->assertEquals(-1, $this->redis->ttl('foo'));
}
function testPersist() {
$this->redis->set('foo', 'bar');
$this->assertTrue($this->redis->expire('foo', 1));
$this->assertEquals(1, $this->redis->ttl('foo'));
$this->assertTrue($this->redis->persist('foo'));
$this->assertEquals(-1, $this->redis->ttl('foo'));
$this->assertFalse($this->redis->persist('foo'));
$this->assertFalse($this->redis->persist('foobar'));
}
function testSetExpire() {
$this->assertTrue($this->redis->setex('foo', 10, 'bar'));
$this->assertTrue($this->redis->exists('foo'));
@@ -333,7 +435,8 @@ class RedisCommandTestSuite extends PHPUnit_Framework_TestCase {
sleep(2);
$this->assertFalse($this->redis->exists('hoge'));
RC::testForServerException($this, RC::EXCEPTION_VALUE_NOT_INT, function($test) {
// TODO: do not check the error message RC::EXCEPTION_VALUE_NOT_INT for now
RC::testForServerException($this, null, function($test) {
$test->redis->setex('hoge', 2.5, 'piyo');
});
RC::testForServerException($this, RC::EXCEPTION_SETEX_TTL, function($test) {
@@ -367,6 +470,20 @@ class RedisCommandTestSuite extends PHPUnit_Framework_TestCase {
});
}
function testPushTailX() {
$this->assertEquals(0, $this->redis->rpushx('numbers', 1));
$this->assertEquals(1, $this->redis->rpush('numbers', 2));
$this->assertEquals(2, $this->redis->rpushx('numbers', 3));
$this->assertEquals(2, $this->redis->llen('numbers'));
$this->assertEquals(array(2, 3), $this->redis->lrange('numbers', 0, -1));
RC::testForServerException($this, RC::EXCEPTION_WRONG_TYPE, function($test) {
$test->redis->set('foo', 'bar');
$test->redis->rpushx('foo', 'bar');
});
}
function testPushHead() {
// NOTE: List push operations return the list length since Redis commit 520b5a3
$this->assertEquals(1, $this->redis->lpush('metavars', 'foo'));
@@ -380,6 +497,20 @@ class RedisCommandTestSuite extends PHPUnit_Framework_TestCase {
});
}
function testPushHeadX() {
$this->assertEquals(0, $this->redis->lpushx('numbers', 1));
$this->assertEquals(1, $this->redis->lpush('numbers', 2));
$this->assertEquals(2, $this->redis->lpushx('numbers', 3));
$this->assertEquals(2, $this->redis->llen('numbers'));
$this->assertEquals(array(3, 2), $this->redis->lrange('numbers', 0, -1));
RC::testForServerException($this, RC::EXCEPTION_WRONG_TYPE, function($test) {
$test->redis->set('foo', 'bar');
$test->redis->lpushx('foo', 'bar');
});
}
function testListLength() {
$this->assertEquals(1, $this->redis->rpush('metavars', 'foo'));
$this->assertEquals(2, $this->redis->rpush('metavars', 'hoge'));
@@ -680,6 +811,47 @@ class RedisCommandTestSuite extends PHPUnit_Framework_TestCase {
$this->assertEquals((float)(time() - $start), 2, '', 1);
}
function testListBlockingPopLastPushHead() {
// TODO: this test does not cover all the aspects of BLPOP/BRPOP as it
// does not run with a concurrent client pushing items on lists.
$numbers = RC::pushTailAndReturn($this->redis, 'numbers', array(1, 2, 3));
$src_count = count($numbers);
$dst_count = 0;
while ($item = $this->redis->brpoplpush('numbers', 'temporary', 1)) {
$this->assertEquals(--$src_count, $this->redis->llen('numbers'));
$this->assertEquals(++$dst_count, $this->redis->llen('temporary'));
$this->assertEquals(array_pop($numbers), $this->redis->lindex('temporary', 0));
}
$start = time();
$this->assertNull($this->redis->brpoplpush('numbers', 'temporary', 2));
$this->assertEquals(2, (float)(time() - $start), '', 1);
RC::testForServerException($this, RC::EXCEPTION_WRONG_TYPE, function($test) {
$test->redis->del('numbers');
$test->redis->del('temporary');
$test->redis->set('numbers', 'foobar');
$test->redis->brpoplpush('numbers', 'temporary', 1);
});
}
function testListInsert() {
$numbers = RC::pushTailAndReturn($this->redis, 'numbers', RC::getArrayOfNumbers());
$this->assertEquals(11, $this->redis->linsert('numbers', 'before', 0, -2));
$this->assertEquals(12, $this->redis->linsert('numbers', 'after', -2, -1));
$this->assertEquals(array(-2, -1, 0, 1), $this->redis->lrange('numbers', 0, 3));
$this->assertEquals(-1, $this->redis->linsert('numbers', 'after', 100, 200));
$this->assertEquals(-1, $this->redis->linsert('numbers', 'before', 100, 50));
RC::testForServerException($this, RC::EXCEPTION_WRONG_TYPE, function($test) {
$test->redis->set('foo', 'bar');
$test->redis->linsert('foo', 'before', 0, 0);
});
}
/* commands operating on sets */
@@ -1095,6 +1267,11 @@ class RedisCommandTestSuite extends PHPUnit_Framework_TestCase {
$this->redis->zrange('zset', 0, 2, 'withscores')
);
$this->assertEquals(
array(array('a', -10), array('b', 0), array('c', 10)),
$this->redis->zrange('zset', 0, 2, array('withscores' => true))
);
RC::testForServerException($this, RC::EXCEPTION_WRONG_TYPE, function($test) {
$test->redis->set('foo', 'bar');
$test->redis->zrange('foo', 0, -1);
@@ -1149,6 +1326,11 @@ class RedisCommandTestSuite extends PHPUnit_Framework_TestCase {
$this->redis->zrevrange('zset', 0, 2, 'withscores')
);
$this->assertEquals(
array(array('f', 30), array('e', 20), array('d', 20)),
$this->redis->zrevrange('zset', 0, 2, array('withscores' => true))
);
RC::testForServerException($this, RC::EXCEPTION_WRONG_TYPE, function($test) {
$test->redis->set('foo', 'bar');
$test->redis->zrevrange('foo', 0, -1);
@@ -1183,12 +1365,96 @@ class RedisCommandTestSuite extends PHPUnit_Framework_TestCase {
$this->redis->zrangebyscore('zset', 10, 20, 'withscores')
);
$this->assertEquals(
array(array('c', 10), array('d', 20), array('e', 20)),
$this->redis->zrangebyscore('zset', 10, 20, array('withscores' => true))
);
$this->assertEquals(
array('d', 'e'),
$this->redis->zrangebyscore('zset', 10, 20, array('limit' => array(1, 2)))
);
$this->assertEquals(
array('d', 'e'),
$this->redis->zrangebyscore('zset', 10, 20, array(
'limit' => array('offset' => 1, 'count' => 2)
))
);
$this->assertEquals(
array(array('d', 20), array('e', 20)),
$this->redis->zrangebyscore('zset', 10, 20, array(
'limit' => array(1, 2),
'withscores' => true,
))
);
RC::testForServerException($this, RC::EXCEPTION_WRONG_TYPE, function($test) {
$test->redis->set('foo', 'bar');
$test->redis->zrangebyscore('foo', 0, 0);
});
}
function testZsetReverseRangeByScore() {
$zset = RC::zsetAddAndReturn($this->redis, 'zset', RC::getZSetArray());
$this->assertEquals(
array('a'),
$this->redis->zrevrangebyscore('zset', -10, -10)
);
$this->assertEquals(
array('b', 'a'),
$this->redis->zrevrangebyscore('zset', 0, -10)
);
$this->assertEquals(
array('e', 'd'),
$this->redis->zrevrangebyscore('zset', 20, 20)
);
$this->assertEquals(
array('f', 'e', 'd', 'c', 'b'),
$this->redis->zrevrangebyscore('zset', 30, 0)
);
$this->assertEquals(
array(array('e', 20), array('d', 20), array('c', 10)),
$this->redis->zrevrangebyscore('zset', 20, 10, 'withscores')
);
$this->assertEquals(
array(array('e', 20), array('d', 20), array('c', 10)),
$this->redis->zrevrangebyscore('zset', 20, 10, array('withscores' => true))
);
$this->assertEquals(
array('d', 'c'),
$this->redis->zrevrangebyscore('zset', 20, 10, array('limit' => array(1, 2)))
);
$this->assertEquals(
array('d', 'c'),
$this->redis->zrevrangebyscore('zset', 20, 10, array(
'limit' => array('offset' => 1, 'count' => 2)
))
);
$this->assertEquals(
array(array('d', 20), array('c', 10)),
$this->redis->zrevrangebyscore('zset', 20, 10, array(
'limit' => array(1, 2),
'withscores' => true,
))
);
RC::testForServerException($this, RC::EXCEPTION_WRONG_TYPE, function($test) {
$test->redis->set('foo', 'bar');
$test->redis->zrevrangebyscore('foo', 0, 0);
});
}
function testZsetUnionStore() {
$zsetA = RC::zsetAddAndReturn($this->redis, 'zseta', array('a' => 1, 'b' => 2, 'c' => 3));
$zsetB = RC::zsetAddAndReturn($this->redis, 'zsetb', array('b' => 1, 'c' => 2, 'd' => 3));