mirror of
https://github.com/predis/predis.git
synced 2026-08-17 19:45:45 +00:00
Compare commits
95 Commits
v2.4.0-RC1
...
v0.6.5
| Author | SHA1 | Date | |
|---|---|---|---|
| f76d616d57 | |||
| c41bb2c314 | |||
| d4fb7d9143 | |||
| ca422b0300 | |||
| 79372cb99b | |||
| 6a2e4d0396 | |||
| 78814a473c | |||
| 0d7fe31110 | |||
| 118af2809c | |||
| 8dcd10dbbc | |||
| 3a6907241b | |||
| eae29bc3da | |||
| 727feb3f27 | |||
| 3924235501 | |||
| dc704c8cc4 | |||
| 8d6f65d3dd | |||
| 40dfabb139 | |||
| a605190354 | |||
| b5ce81a030 | |||
| a9e1d6b86b | |||
| 923d998f35 | |||
| eae8fb8971 | |||
| 4fc5ee65fd | |||
| 007ddaecfa | |||
| 455e56927a | |||
| 4b1302a93a | |||
| d46b0e0785 | |||
| cf522ff2b4 | |||
| db92c7a9b8 | |||
| 30926def60 | |||
| 7465daa0eb | |||
| 2c761d6c95 | |||
| 1e32f8aaa8 | |||
| 2a25b0e3f3 | |||
| 5a6a48fa17 | |||
| 64951b1799 | |||
| ec42796cb2 | |||
| c75bdd9509 | |||
| 12c3e611a0 | |||
| 0c9b822095 | |||
| 030d9740bd | |||
| 0bbbe064b5 | |||
| 87439ae631 | |||
| d8c227e074 | |||
| 7997eab57c | |||
| 2bc61ea0bc | |||
| d667bcb6bb | |||
| e3dd311dd3 | |||
| 34f4d5584b | |||
| 39619d0c54 | |||
| 07a998ebdf | |||
| b73b9682c0 | |||
| 355d6b6cf4 | |||
| fe2316a655 | |||
| 7b2cd4abd0 | |||
| 35fd6ca509 | |||
| dea03a6aa9 | |||
| fb5f878e21 | |||
| e3ee595768 | |||
| cc16311950 | |||
| f32cd19800 | |||
| 7db5e650ef | |||
| 975bd98933 | |||
| 7d5cccaf7c | |||
| 08b1761e10 | |||
| 24f49abc14 | |||
| b530f9f613 | |||
| d677c72ae3 | |||
| c3cf2acb51 | |||
| 8bfae69bf0 | |||
| 92d6ad62ce | |||
| fa4d654d38 | |||
| 9ac4571f91 | |||
| fea5e1c602 | |||
| ec51b14fbb | |||
| dc56526256 | |||
| 195c6eae99 | |||
| 9ae47c865e | |||
| d3ba25436c | |||
| 580cf80436 | |||
| d6bea4390e | |||
| fd7f4a7b48 | |||
| 92f7e8bf9b | |||
| 006b44e4bf | |||
| bd961a767b | |||
| b3599a8e6b | |||
| 0b46de424e | |||
| 73bdfc2d72 | |||
| 3077e76708 | |||
| 208fdf6daf | |||
| 7d7995ff8f | |||
| 418197af75 | |||
| 90cb9d437d | |||
| 4668bdca83 | |||
| 93a3c184ed |
@@ -1,13 +1,70 @@
|
||||
v0.6.1 (2010-xx-xx)
|
||||
v0.6.5 (2011-02-12)
|
||||
* FIX: due to an untested internal change introduced in v0.6.4, a wrong
|
||||
handling of bulk reads of zero-length values was producing protocol
|
||||
desynchronization errors (ISSUE #20).
|
||||
|
||||
v0.6.4 (2011-02-12)
|
||||
* Various performance improvements (15% ~ 25%) especially when dealing with
|
||||
long multibulk replies or when using clustered connections.
|
||||
|
||||
* Added the "on_retry" option to Predis\MultiExecBlock that can be used to
|
||||
specify an external callback (or any callable object) that gets invoked
|
||||
whenever a transaction is aborted by the server.
|
||||
|
||||
* Added inline (p)subscribtion via options when initializing an instance of
|
||||
Predis\PubSubContext.
|
||||
|
||||
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 +74,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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2009-2010 Daniele Alessandri
|
||||
Copyright (c) 2009-2011 Daniele Alessandri
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
|
||||
+4
-3
@@ -17,9 +17,10 @@ to be implemented soon in Predis.
|
||||
|
||||
## Main features ##
|
||||
|
||||
- Full support for Redis 2.0. Different versions of Redis are supported via server profiles.
|
||||
- Full support for Redis 2.0 and 2.2. 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++) {
|
||||
|
||||
@@ -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";
|
||||
?>
|
||||
+390
-225
File diff suppressed because it is too large
Load Diff
@@ -216,6 +216,7 @@ class PredisClientFeaturesTestSuite extends PHPUnit_Framework_TestCase {
|
||||
|
||||
function testResponseQueued() {
|
||||
$response = new \Predis\ResponseQueued();
|
||||
$this->assertTrue($response->skipParse);
|
||||
$this->assertTrue($response->queued);
|
||||
$this->assertEquals(\Predis\Protocol::QUEUED, (string)$response);
|
||||
}
|
||||
@@ -227,6 +228,7 @@ class PredisClientFeaturesTestSuite extends PHPUnit_Framework_TestCase {
|
||||
$errorMessage = 'ERROR MESSAGE';
|
||||
$response = new \Predis\ResponseError($errorMessage);
|
||||
|
||||
$this->assertTrue($response->skipParse);
|
||||
$this->assertTrue($response->error);
|
||||
$this->assertEquals($errorMessage, $response->message);
|
||||
$this->assertEquals($errorMessage, (string)$response);
|
||||
@@ -261,16 +263,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 +359,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,
|
||||
@@ -376,6 +381,12 @@ class PredisClientFeaturesTestSuite extends PHPUnit_Framework_TestCase {
|
||||
});
|
||||
}
|
||||
|
||||
function testResponseReader_EmptyBulkResponse() {
|
||||
$this->assertTrue($this->redis->set('foo', ''));
|
||||
$this->assertEquals('', $this->redis->get('foo'));
|
||||
$this->assertEquals('', $this->redis->get('foo'));
|
||||
}
|
||||
|
||||
|
||||
/* Client + CommandPipeline */
|
||||
|
||||
@@ -471,5 +482,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
@@ -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);
|
||||
|
||||
+271
-2
@@ -89,8 +89,11 @@ class RedisCommandTestSuite extends PHPUnit_Framework_TestCase {
|
||||
|
||||
function testGet() {
|
||||
$this->redis->set('foo', 'bar');
|
||||
|
||||
$this->assertEquals('bar', $this->redis->get('foo'));
|
||||
|
||||
$this->assertTrue($this->redis->set('foo', ''));
|
||||
$this->assertEquals('', $this->redis->get('foo'));
|
||||
|
||||
$this->assertNull($this->redis->get('fooDoesNotExist'));
|
||||
|
||||
// should throw an exception when trying to do a GET on non-string types
|
||||
@@ -223,6 +226,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 +265,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 +417,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 +438,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 +473,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 +500,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 +814,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 +1270,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 +1329,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 +1368,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));
|
||||
|
||||
Reference in New Issue
Block a user