瀏覽代碼

Backported changes from the mainline library to the PHP 5.2 branch (up to commit 975bd98)

Daniele Alessandri 14 年之前
父節點
當前提交
2fc80a5f0d
共有 5 個文件被更改,包括 417 次插入88 次删除
  1. 22 0
      CHANGELOG
  2. 114 70
      lib/Predis.php
  3. 150 5
      test/PredisClientFeatures.php
  4. 4 1
      test/PredisShared.php
  5. 127 12
      test/RedisCommandsTest.php

+ 22 - 0
CHANGELOG

@@ -1,3 +1,25 @@
+v0.6.2 (2010-xx-xx)
+  * 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.
 

+ 114 - 70
lib/Predis.php

@@ -175,7 +175,7 @@ class Predis_Client {
 
         $newClient = new Predis_Client();
         $newClient->setupClient($this->_options);
-        $newClient->setConnection($this->getConnection($connectionAlias));
+        $newClient->setConnection($connection);
         return $newClient;
     }
 
@@ -235,21 +235,25 @@ class Predis_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 */) {
+        $args = func_get_args();
+        return $this->sharedInitializer($args, 'initPipeline');
     }
 
     public function pipelineSafe($pipelineBlock = null) {
@@ -281,20 +285,8 @@ class Predis_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);
-        }
+        $args = func_get_args();
+        return $this->sharedInitializer($args, 'initMultiExec');
     }
 
     private function initMultiExec(Array $options = null, $transBlock = null) {
@@ -475,12 +467,12 @@ abstract class Predis_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() {
@@ -938,45 +930,52 @@ class Predis_MultiExecBlock {
         $blockException = null;
         $returnValues   = array();
 
-        try {
-            if ($block !== null) {
-                $this->setInsideBlock(true);
+        if ($block !== null) {
+            $this->setInsideBlock(true);
+            try {
                 $block($this);
-                $this->setInsideBlock(false);
             }
-
-            if ($this->_discarded === true) {
-                return;
+            catch (Predis_CommunicationException $exception) {
+                $blockException = $exception;
             }
-
-            $reply = $this->_redisClient->exec();
-            if ($reply === null) {
-                throw new Predis_AbortedMultiExec('The current transaction has been aborted by the server');
+            catch (Predis_ServerException $exception) {
+                $blockException = $exception;
             }
-
-            $execReply = $reply instanceof Iterator ? iterator_to_array($reply) : $reply;
-            $commands  = &$this->_commands;
-            $sizeofReplies = count($execReply);
-
-            if ($sizeofReplies !== count($commands)) {
-                $this->malformedServerResponse('Unexpected number of responses for a MultiExecBlock');
+            catch (Exception $exception) {
+                $blockException = $exception;
+                if ($this->_initialized === true) {
+                    $this->discard();
+                }
             }
-
-            for ($i = 0; $i < $sizeofReplies; $i++) {
-                $returnValues[] = $commands[$i]->parseResponse($execReply[$i] instanceof Iterator
-                    ? iterator_to_array($execReply[$i])
-                    : $execReply[$i]
-                );
-                unset($commands[$i]);
+            $this->setInsideBlock(false);
+            if ($blockException !== null) {
+                throw $blockException;
             }
         }
-        catch (Exception $exception) {
-            $this->setInsideBlock(false);
-            $blockException = $exception;
+
+        if ($this->_initialized === false) {
+            return;
         }
 
-        if ($blockException !== null) {
-            throw $blockException;
+        $reply = $this->_redisClient->exec();
+        if ($reply === null) {
+            throw new Predis_AbortedMultiExec('The current transaction has been aborted by the server');
+        }
+
+        $execReply = $reply instanceof Iterator ? iterator_to_array($reply) : $reply;
+        $commands  = &$this->_commands;
+        $sizeofReplies = count($execReply);
+
+        if ($sizeofReplies !== count($commands)) {
+            $this->malformedServerResponse('Unexpected number of responses for a Predis_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]);
         }
 
         return $returnValues;
@@ -1005,8 +1004,7 @@ class Predis_PubSubContext implements Iterator {
 
     public function __destruct() {
         if ($this->valid()) {
-            $this->_redisClient->unsubscribe();
-            $this->_redisClient->punsubscribe();
+            $this->closeContext();
         }
     }
 
@@ -1824,6 +1822,20 @@ class Predis_RedisServer_vNext extends Predis_RedisServer_v2_0 {
             /* transactions */
             'watch'                     => 'Predis_Commands_Watch',
             'unwatch'                   => 'Predis_Commands_Unwatch',
+
+            /* commands operating on string values */
+            'strlen'                    => 'Predis_Commands_Strlen',
+
+            /* 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',
+
+            /* commands operating on sorted sets */
+            'zrevrangebyscore'          => 'Predis_Commands_ZSetReverseRangeByScore',
         ));
     }
 }
@@ -2347,6 +2359,10 @@ class Predis_Commands_Substr extends Predis_MultiBulkCommand {
     public function getCommandId() { return 'SUBSTR'; }
 }
 
+class Predis_Commands_Strlen extends Predis_MultiBulkCommand {
+    public function getCommandId() { return 'STRLEN'; }
+}
+
 /* commands operating on the key space */
 class Predis_Commands_Keys extends Predis_MultiBulkCommand {
     public function canBeHashed()  { return false; }
@@ -2387,6 +2403,11 @@ class Predis_Commands_ExpireAt extends Predis_MultiBulkCommand {
     public function parseResponse($data) { return (bool) $data; }
 }
 
+class Predis_Commands_Persist extends Predis_MultiBulkCommand {
+    public function getCommandId() { return 'PERSIST'; }
+    public function parseResponse($data) { return (bool) $data; }
+}
+
 class Predis_Commands_DatabaseSize extends Predis_MultiBulkCommand {
     public function canBeHashed()  { return false; }
     public function getCommandId() { return 'DBSIZE'; }
@@ -2401,10 +2422,18 @@ class Predis_Commands_ListPushTail extends Predis_MultiBulkCommand {
     public function getCommandId() { return 'RPUSH'; }
 }
 
+class Predis_Commands_ListPushTailX extends Predis_MultiBulkCommand {
+    public function getCommandId() { return 'RPUSHX'; }
+}
+
 class Predis_Commands_ListPushHead extends Predis_MultiBulkCommand {
     public function getCommandId() { return 'LPUSH'; }
 }
 
+class Predis_Commands_ListPushHeadX extends Predis_MultiBulkCommand {
+    public function getCommandId() { return 'LPUSHX'; }
+}
+
 class Predis_Commands_ListLength extends Predis_MultiBulkCommand {
     public function getCommandId() { return 'LLEN'; }
 }
@@ -2453,6 +2482,10 @@ class Predis_Commands_ListPopLastBlocking extends Predis_MultiBulkCommand {
     public function getCommandId() { return 'BRPOP'; }
 }
 
+class Predis_Commands_ListInsert extends Predis_MultiBulkCommand {
+    public function getCommandId() { return 'LINSERT'; }
+}
+
 /* commands operating on sets */
 class Predis_Commands_SetAdd extends Predis_MultiBulkCommand {
     public function getCommandId() { return 'SADD'; }
@@ -2623,6 +2656,10 @@ class Predis_Commands_ZSetRangeByScore extends Predis_Commands_ZSetRange {
     }
 }
 
+class Predis_Commands_ZSetReverseRangeByScore extends Predis_Commands_ZSetRangeByScore {
+    public function getCommandId() { return 'ZREVRANGEBYSCORE'; }
+}
+
 class Predis_Commands_ZSetCount extends Predis_MultiBulkCommand {
     public function getCommandId() { return 'ZCOUNT'; }
 }
@@ -2767,16 +2804,15 @@ class Predis_Commands_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';
@@ -2788,20 +2824,22 @@ class Predis_Commands_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;
@@ -2853,6 +2891,12 @@ class Predis_Commands_Publish extends Predis_MultiBulkCommand {
 class Predis_Commands_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; }
 }
 

+ 150 - 5
test/PredisClientFeatures.php

@@ -260,11 +260,15 @@ 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();
+
         $expectedMessage = 'Error while reading line from the server';
         $thrownException = null;
         try {
@@ -275,7 +279,6 @@ class PredisClientFeaturesTestSuite extends PHPUnit_Framework_TestCase {
         }
         $this->assertType('Predis_CommunicationException', $thrownException);
         $this->assertEquals($expectedMessage, $thrownException->getMessage());
-        //$this->assertFalse($connection->isConnected());
     }
 
     function testConnection_GetSocketOpensConnection() {
@@ -370,8 +373,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,  
@@ -499,5 +502,147 @@ 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(p_anon("\$multi", "
+            \$multi->ping();
+            \$multi->set('foo', 'bar');
+            \$multi->get('foo');
+        "));
+
+        $this->assertType('array', $replies);
+        $this->assertEquals('bar', $replies[2]);
+    }
+
+    function testMultiExecBlock_EmptyCallableBlock() {
+        $client = RC::getConnection();
+        $client->flushdb();
+
+        $replies = $client->multiExec(p_anon("\$multi", ""));
+
+        $this->assertEquals(0, count($replies));
+    }
+
+    function testMultiExecBlock_ClientExceptionInCallableBlock() {
+        $client = RC::getConnection();
+        $client->flushdb();
+
+        $expectedMessage = 'TEST';
+        $thrownException = null;
+        try {
+            $client->multiExec(p_anon("\$multi", " 
+                \$multi->ping();
+                \$multi->set('foo', 'bar');
+                throw new Predis_ClientException('$expectedMessage');
+            "));
+        }
+        catch (Predis_ClientException $exception) {
+            $thrownException = $exception;
+        }
+        $this->assertType('Predis_ClientException', $thrownException);
+        $this->assertEquals($expectedMessage, $thrownException->getMessage());
+
+        $this->assertFalse($client->exists('foo'));
+    }
+
+    function testMultiExecBlock_ServerExceptionInCallableBlock() {
+        $client = RC::getConnection();
+        $client->flushdb();
+        $client->getResponseReader()->setHandler('-', new Predis_ResponseErrorSilentHandler());
+
+        $multi = $client->multiExec();
+        $multi->set('foo', 'bar');
+        $multi->lpush('foo', 'piyo'); // LIST operation on STRING type returns an ERROR
+        $multi->set('hoge', 'piyo');
+        $replies = $multi->execute();
+
+        $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();
+
+        $multi = $client->multiExec();
+        $multi->set('foo', 'bar');
+        $multi->discard();
+        $multi->set('hoge', 'piyo');
+        $replies = $multi->execute();
+
+        $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()->discard()->execute();
+        $this->assertEquals(0, count($replies));
+    }
+
+    function testMultiExecBlock_Watch() {
+        $client1 = RC::getConnection();
+        $client2 = RC::getConnection(true);
+        $client1->flushdb();
+
+        $thrownException = null;
+        try {
+            $multi = $client1->multiExec(array('watch' => 'sentinel'));
+            $multi->set('sentinel', 'client1');
+            $multi->get('sentinel');
+            $client2->set('sentinel', 'client2');
+            $multi->execute();
+        }
+        catch (PredisException $exception) {
+            $thrownException = $exception;
+        }
+        $this->assertType('Predis_AbortedMultiExec', $thrownException);
+        $this->assertEquals('The current transaction has been aborted by the server', $thrownException->getMessage());
+
+        $this->assertEquals('client2', $client1->get('sentinel'));
+    }
 }
 ?>

+ 4 - 1
test/PredisShared.php

@@ -48,7 +48,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();
         }

+ 127 - 12
test/RedisCommandsTest.php

@@ -241,6 +241,18 @@ 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, p_anon("\$test", "
+            \$test->redis->rpush('metavars', 'foo');
+            \$test->redis->strlen('metavars');
+        "));
+    }
+
 
     /* commands operating on the key space */
 
@@ -369,6 +381,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, p_anon("\$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'));
@@ -382,6 +408,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, p_anon("\$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'));
@@ -682,6 +722,22 @@ class RedisCommandTestSuite extends PHPUnit_Framework_TestCase {
         $this->assertEquals((float)(time() - $start), 2, '', 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, p_anon("\$test", "
+            \$test->redis->set('foo', 'bar');
+            \$test->redis->lset('foo', 0, 0);
+        "));
+    }
+
 
     /* commands operating on sets */
 
@@ -1161,6 +1217,40 @@ class RedisCommandTestSuite extends PHPUnit_Framework_TestCase {
             $this->redis->zrevrange('zset', 0, 2, array('withscores' => true))
         );
 
+        RC::testForServerException($this, RC::EXCEPTION_WRONG_TYPE, p_anon("\$test", "
+            \$test->redis->set('foo', 'bar');
+            \$test->redis->zrevrange('foo', 0, -1);
+        "));
+    }
+
+    function testZsetRangeByScore() {
+        $zset = RC::zsetAddAndReturn($this->redis, 'zset', RC::getZSetArray());
+
+        $this->assertEquals(
+            array('a'), 
+            $this->redis->zrangebyscore('zset', -10, -10)
+        );
+
+        $this->assertEquals(
+            array('c', 'd', 'e', 'f'), 
+            $this->redis->zrangebyscore('zset', 10, 30)
+        );
+
+        $this->assertEquals(
+            array('d', 'e'), 
+            $this->redis->zrangebyscore('zset', 20, 20)
+        );
+
+        $this->assertEquals(
+            array(), 
+            $this->redis->zrangebyscore('zset', 30, 0)
+        );
+
+        $this->assertEquals(
+            array(array('c', 10), array('d', 20), array('e', 20)), 
+            $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))
@@ -1188,41 +1278,66 @@ class RedisCommandTestSuite extends PHPUnit_Framework_TestCase {
 
         RC::testForServerException($this, RC::EXCEPTION_WRONG_TYPE, p_anon("\$test", "
             \$test->redis->set('foo', 'bar');
-            \$test->redis->zrevrange('foo', 0, -1);
+            \$test->redis->zrangebyscore('foo', 0, 0);
         "));
     }
 
-    function testZsetRangeByScore() {
+    function testZsetReverseRangeByScore() {
         $zset = RC::zsetAddAndReturn($this->redis, 'zset', RC::getZSetArray());
 
         $this->assertEquals(
             array('a'), 
-            $this->redis->zrangebyscore('zset', -10, -10)
+            $this->redis->zrevrangebyscore('zset', -10, -10)
         );
 
         $this->assertEquals(
-            array('c', 'd', 'e', 'f'), 
-            $this->redis->zrangebyscore('zset', 10, 30)
+            array('b', 'a'), 
+            $this->redis->zrevrangebyscore('zset', 0, -10)
         );
 
         $this->assertEquals(
-            array('d', 'e'), 
-            $this->redis->zrangebyscore('zset', 20, 20)
+            array('e', 'd'), 
+            $this->redis->zrevrangebyscore('zset', 20, 20)
         );
 
         $this->assertEquals(
-            array(), 
-            $this->redis->zrangebyscore('zset', 30, 0)
+            array('f', 'e', 'd', 'c', 'b'), 
+            $this->redis->zrevrangebyscore('zset', 30, 0)
         );
 
         $this->assertEquals(
-            array(array('c', 10), array('d', 20), array('e', 20)), 
-            $this->redis->zrangebyscore('zset', 10, 20, 'withscores')
+            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, p_anon("\$test", "
             \$test->redis->set('foo', 'bar');
-            \$test->redis->zrangebyscore('foo', 0, 0);
+            \$test->redis->zrevrangebyscore('foo', 0, 0);
         "));
     }