فهرست منبع

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);
         "));
     }