Browse Source

New commands: BITOP, BITCOUNT (Redis v2.6-dev).

Daniele Alessandri 13 years ago
parent
commit
2578e154d4

+ 17 - 0
lib/Predis/Command/Hash/CommandHashStrategy.php

@@ -75,6 +75,8 @@ class CommandHashStrategy implements CommandHashStrategyInterface
             'SETRANGE'              => $keyIsFirstArgument,
             'STRLEN'                => $keyIsFirstArgument,
             'SUBSTR'                => $keyIsFirstArgument,
+            'BITOP'                 => array($this, 'getKeyFromBitOp'),
+            'BITCOUNT'              => $keyIsFirstArgument,
 
             /* commands operating on lists */
             'LINSERT'               => $keyIsFirstArgument,
@@ -217,6 +219,21 @@ class CommandHashStrategy implements CommandHashStrategyInterface
         }
     }
 
+    /**
+     * Extracts the key from BITOP command.
+     *
+     * @param CommandInterface $command Command instance.
+     * @return string
+     */
+    protected function getKeyFromBitOp(CommandInterface $command)
+    {
+        $arguments = $command->getArguments();
+
+        if ($this->checkSameHashForKeys(array_slice($arguments, 1, count($arguments)))) {
+            return $arguments[1];
+        }
+    }
+
     /**
      * Extracts the key from ZINTERSTORE and ZUNIONSTORE commands.
      *

+ 18 - 0
lib/Predis/Command/PrefixHelpers.php

@@ -67,6 +67,24 @@ class PrefixHelpers
         $command->setRawArguments($arguments);
     }
 
+    /**
+     * Applies the specified prefix to all the arguments but the first one.
+     *
+     * @param CommandInterface $command Command instance.
+     * @param string $prefix Prefix string.
+     */
+    public static function skipFirst(CommandInterface $command, $prefix)
+    {
+        $arguments = $command->getArguments();
+        $length = count($arguments);
+
+        for ($i = 1; $i < $length; $i++) {
+            $arguments[$i] = "$prefix{$arguments[$i]}";
+        }
+
+        $command->setRawArguments($arguments);
+    }
+
     /**
      * Applies the specified prefix to all the arguments but the last one.
      *

+ 27 - 0
lib/Predis/Command/StringBitCount.php

@@ -0,0 +1,27 @@
+<?php
+
+/*
+ * This file is part of the Predis package.
+ *
+ * (c) Daniele Alessandri <suppakilla@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Predis\Command;
+
+/**
+ * @link http://redis.io/commands/bitcount
+ * @author Daniele Alessandri <suppakilla@gmail.com>
+ */
+class StringBitCount extends PrefixableCommand
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function getId()
+    {
+        return 'BITCOUNT';
+    }
+}

+ 49 - 0
lib/Predis/Command/StringBitOp.php

@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of the Predis package.
+ *
+ * (c) Daniele Alessandri <suppakilla@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Predis\Command;
+
+/**
+ * @link http://redis.io/commands/bitop
+ * @author Daniele Alessandri <suppakilla@gmail.com>
+ */
+class StringBitOp extends AbstractCommand implements PrefixableCommandInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function getId()
+    {
+        return 'BITOP';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function filterArguments(Array $arguments)
+    {
+        if (count($arguments) === 3 && is_array($arguments[2])) {
+            list($operation, $destination, ) = $arguments;
+            $arguments = $arguments[2];
+            array_unshift($arguments, $operation, $destination);
+        }
+
+        return $arguments;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function prefixKeys($prefix)
+    {
+        PrefixHelpers::skipFirst($this, $prefix);
+    }
+}

+ 1 - 0
lib/Predis/Connection/MasterSlaveReplication.php

@@ -409,6 +409,7 @@ class MasterSlaveReplication implements ReplicationConnectionInterface
             'ECHO'              => true,
             'QUIT'              => true,
             'OBJECT'            => true,
+            'BITCOUNT'          => true,
             'TIME'              => true,
             'SORT'              => array($this, 'isSortReadOnly'),
         );

+ 2 - 0
lib/Predis/Profile/ServerVersion26.php

@@ -217,6 +217,8 @@ class ServerVersion26 extends ServerProfile
             /* commands operating on string values */
             'psetex'                    => 'Predis\Command\StringPreciseSetExpire',
             'incrbyfloat'               => 'Predis\Command\StringIncrementByFloat',
+            'bitop'                     => 'Predis\Command\StringBitOp',
+            'bitcount'                  => 'Predis\Command\StringBitCount',
 
             /* commands operating on hashes */
             'hincrbyfloat'              => 'Predis\Command\HashIncrementByFloat',

+ 18 - 0
tests/Predis/Command/Hash/CommandHashStrategyTest.php

@@ -140,6 +140,22 @@ class CommandHashStrategyTest extends StandardTestCase
         }
     }
 
+    /**
+     * @group disconnected
+     */
+    public function testKeysForBitOpCommand()
+    {
+        $distribution = new HashRing();
+        $hashstrategy = new CommandHashStrategy();
+        $profile = ServerProfile::getDevelopment();
+        $arguments = array('AND', '{key}:destination', '{key}:src:1', '{key}:src:2');
+
+        foreach ($this->getExpectedCommands('keys-bitop') as $commandID) {
+            $command = $profile->createCommand($commandID, $arguments);
+            $this->assertNotNull($hashstrategy->getHash($distribution, $command), $commandID);
+        }
+    }
+
     // ******************************************************************** //
     // ---- HELPER METHODS ------------------------------------------------ //
     // ******************************************************************** //
@@ -186,6 +202,8 @@ class CommandHashStrategyTest extends StandardTestCase
             'SETRANGE'              => 'keys-first',
             'STRLEN'                => 'keys-first',
             'SUBSTR'                => 'keys-first',
+            'BITOP'                 => 'keys-bitop',
+            'BITCOUNT'              => 'keys-first',
 
             /* commands operating on lists */
             'LINSERT'               => 'keys-first',

+ 108 - 0
tests/Predis/Command/StringBitCountTest.php

@@ -0,0 +1,108 @@
+<?php
+
+/*
+ * This file is part of the Predis package.
+ *
+ * (c) Daniele Alessandri <suppakilla@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Predis\Command;
+
+use \PHPUnit_Framework_TestCase as StandardTestCase;
+
+/**
+ * @group commands
+ * @group realm-string
+ */
+class StringBitCountTest extends CommandTestCase
+{
+    /**
+     * {@inheritdoc}
+     */
+    protected function getExpectedCommand()
+    {
+        return 'Predis\Command\StringBitCount';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getExpectedId()
+    {
+        return 'BITCOUNT';
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testFilterArguments()
+    {
+        $arguments = array('key', 0, 10);
+        $expected = array('key', 0, 10);
+
+        $command = $this->getCommand();
+        $command->setArguments($arguments);
+
+        $this->assertSame($expected, $command->getArguments());
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testParseResponse()
+    {
+        $raw = 10;
+        $expected = 10;
+
+        $command = $this->getCommand();
+
+        $this->assertSame($expected, $command->parseResponse($raw));
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testPrefixKeys()
+    {
+        $arguments = array('key', 0, 10);
+        $expected = array('prefix:key', 0, 10);
+
+        $command = $this->getCommandWithArgumentsArray($arguments);
+        $command->prefixKeys('prefix:');
+
+        $this->assertSame($expected, $command->getArguments());
+    }
+
+    /**
+     * @group connected
+     */
+    public function testReturnsNumberOfBitsSet()
+    {
+        $redis = $this->getClient();
+
+        $redis->setbit('key', 1, 1);
+        $redis->setbit('key', 10, 1);
+        $redis->setbit('key', 16, 1);
+        $redis->setbit('key', 22, 1);
+        $redis->setbit('key', 32, 1);
+
+        $this->assertSame(5, $redis->bitcount('key'), 'Count bits set (without range)');
+        $this->assertSame(3, $redis->bitcount('key', 2, 4), 'Count bits set (with range)');
+    }
+
+    /**
+     * @group connected
+     * @expectedException Predis\ServerException
+     * @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
+     */
+    public function testThrowsExceptionOnWrongType()
+    {
+        $redis = $this->getClient();
+
+        $redis->lpush('key', 'list');
+        $redis->bitcount('key');
+    }
+}

+ 193 - 0
tests/Predis/Command/StringBitOpTest.php

@@ -0,0 +1,193 @@
+<?php
+
+/*
+ * This file is part of the Predis package.
+ *
+ * (c) Daniele Alessandri <suppakilla@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Predis\Command;
+
+use \PHPUnit_Framework_TestCase as StandardTestCase;
+
+/**
+ * @group commands
+ * @group realm-string
+ */
+class StringBitOpTest extends CommandTestCase
+{
+    /**
+     * {@inheritdoc}
+     */
+    protected function getExpectedCommand()
+    {
+        return 'Predis\Command\StringBitOp';
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function getExpectedId()
+    {
+        return 'BITOP';
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testFilterArguments()
+    {
+        $arguments = array('AND', 'key:dst', 'key:01', 'key:02');
+        $expected = array('AND', 'key:dst', 'key:01', 'key:02');
+
+        $command = $this->getCommand();
+        $command->setArguments($arguments);
+
+        $this->assertSame($expected, $command->getArguments());
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testFilterArgumentsKeysAsSingleArray()
+    {
+        $arguments = array('AND', 'key:dst', array('key:01', 'key:02'));
+        $expected = array('AND', 'key:dst', 'key:01', 'key:02');
+
+        $command = $this->getCommand();
+        $command->setArguments($arguments);
+
+        $this->assertSame($expected, $command->getArguments());
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testParseResponse()
+    {
+        $raw = 10;
+        $expected = 10;
+
+        $command = $this->getCommand();
+
+        $this->assertSame($expected, $command->parseResponse($raw));
+    }
+
+    /**
+     * @group disconnected
+     */
+    public function testPrefixKeys()
+    {
+        $arguments = array('AND', 'key:dst', 'key:01', 'key:02');
+        $expected = array('AND', 'prefix:key:dst', 'prefix:key:01', 'prefix:key:02');
+
+        $command = $this->getCommandWithArgumentsArray($arguments);
+        $command->prefixKeys('prefix:');
+
+        $this->assertSame($expected, $command->getArguments());
+    }
+
+    /**
+     * @group connected
+     */
+    public function testCanPerformBitwiseAND()
+    {
+        $redis = $this->getClient();
+
+        $redis->set('key:src:1', "h\x80");
+        $redis->set('key:src:2', "R");
+
+        $this->assertSame(2, $redis->bitop('AND', 'key:dst', 'key:src:1', 'key:src:2'));
+        $this->assertSame("@\x00", $redis->get('key:dst'));
+    }
+
+    /**
+     * @group connected
+     */
+    public function testCanPerformBitwiseOR()
+    {
+        $redis = $this->getClient();
+
+        $redis->set('key:src:1', "h\x80");
+        $redis->set('key:src:2', "R");
+
+        $this->assertSame(2, $redis->bitop('OR', 'key:dst', 'key:src:1', 'key:src:2'));
+        $this->assertSame("z\x80", $redis->get('key:dst'));
+    }
+
+    /**
+     * @group connected
+     */
+    public function testCanPerformBitwiseXOR()
+    {
+        $redis = $this->getClient();
+
+        $redis->set('key:src:1', "h\x80");
+        $redis->set('key:src:2', "R");
+
+        $this->assertSame(2, $redis->bitop('XOR', 'key:dst', 'key:src:1', 'key:src:2'));
+        $this->assertSame(":\x80", $redis->get('key:dst'));
+    }
+
+    /**
+     * @group connected
+     */
+    public function testCanPerformBitwiseNOT()
+    {
+        $redis = $this->getClient();
+
+        $redis->set('key:src:1', "h\x80");
+
+        $this->assertSame(2, $redis->bitop('NOT', 'key:dst', 'key:src:1'));
+        $this->assertSame("\x97\x7f", $redis->get('key:dst'));
+    }
+
+    /**
+     * @group connected
+     * @expectedException Predis\ServerException
+     * @expectedExceptionMessage ERR BITOP NOT must be called with a single source key.
+     */
+    public function testBitwiseNOTAcceptsOnlyOneSourceKey()
+    {
+        $this->getClient()->bitop('NOT', 'key:dst', 'key:src:1', 'key:src:2');
+    }
+
+    /**
+     * @group connected
+     * @expectedException Predis\ServerException
+     * @expectedExceptionMessage ERR syntax error
+     */
+    public function testThrowsExceptionOnInvalidOperation()
+    {
+        $this->getClient()->bitop('NOOP', 'key:dst', 'key:src:1', 'key:src:2');
+    }
+
+    /**
+     * @group connected
+     * @expectedException Predis\ServerException
+     * @expectedExceptionMessage ERR Operation against a key holding the wrong kind of value
+     */
+    public function testThrowsExceptionOnInvalidSourceKey()
+    {
+        $redis = $this->getClient();
+
+        $redis->lpush('key:src:1', 'list');
+        $redis->bitop('AND', 'key:dst', 'key:src:1', 'key:src:2');
+    }
+
+    /**
+     * @group connected
+     */
+    public function testDoesNotThrowExceptionOnInvalidDestinationKey()
+    {
+        $redis = $this->getClient();
+
+        $redis->lpush('key:dst', 'list');
+        $redis->bitop('AND', 'key:dst', 'key:src:1', 'key:src:2');
+
+        $this->assertSame('none', $redis->type('key:dst'));
+    }
+}

+ 7 - 5
tests/Predis/Profile/ServerVersion26Test.php

@@ -164,11 +164,13 @@ class ServerVersion26Test extends ServerVersionTestCase
             123 => 'pexpireat',
             124 => 'psetex',
             125 => 'incrbyfloat',
-            126 => 'hincrbyfloat',
-            127 => 'eval',
-            128 => 'evalsha',
-            129 => 'script',
-            130 => 'time',
+            126 => 'bitop',
+            127 => 'bitcount',
+            128 => 'hincrbyfloat',
+            129 => 'eval',
+            130 => 'evalsha',
+            131 => 'script',
+            132 => 'time',
         );
     }
 }

+ 7 - 5
tests/Predis/Profile/ServerVersionNextTest.php

@@ -164,11 +164,13 @@ class ServerVersionNextTest extends ServerVersionTestCase
             123 => 'pexpireat',
             124 => 'psetex',
             125 => 'incrbyfloat',
-            126 => 'hincrbyfloat',
-            127 => 'eval',
-            128 => 'evalsha',
-            129 => 'script',
-            130 => 'time',
+            126 => 'bitop',
+            127 => 'bitcount',
+            128 => 'hincrbyfloat',
+            129 => 'eval',
+            130 => 'evalsha',
+            131 => 'script',
+            132 => 'time',
         );
     }
 }