diff --git a/lib/Predis/Commands/PrefixHelpers.php b/lib/Predis/Commands/PrefixHelpers.php index 98180c6e..b97b4fcb 100644 --- a/lib/Predis/Commands/PrefixHelpers.php +++ b/lib/Predis/Commands/PrefixHelpers.php @@ -67,6 +67,24 @@ class PrefixHelpers $command->setRawArguments($arguments); } + /** + * Applies the specified prefix to all the arguments but the first one. + * + * @param ICommand $command Command instance. + * @param string $prefix Prefix string. + */ + public static function skipFirst(ICommand $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. * diff --git a/lib/Predis/Commands/StringBitOp.php b/lib/Predis/Commands/StringBitOp.php new file mode 100644 index 00000000..e7d31421 --- /dev/null +++ b/lib/Predis/Commands/StringBitOp.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Commands; + +/** + * @link http://redis.io/commands/bitop + * @author Daniele Alessandri + */ +class StringBitOp extends Command implements IPrefixable +{ + /** + * {@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); + } + + /** + * {@inheritdoc} + */ + protected function canBeHashed() + { + return $this->checkSameHashForKeys( + array_slice(($args = $this->getArguments()), 1, count($args)) + ); + } +} diff --git a/lib/Predis/Network/MasterSlaveReplication.php b/lib/Predis/Network/MasterSlaveReplication.php index 91bab9b1..3c0f89be 100644 --- a/lib/Predis/Network/MasterSlaveReplication.php +++ b/lib/Predis/Network/MasterSlaveReplication.php @@ -409,6 +409,7 @@ class MasterSlaveReplication implements IConnectionReplication 'ECHO' => true, 'QUIT' => true, 'OBJECT' => true, + 'BITCOUNT' => true, 'SORT' => array($this, 'isSortReadOnly'), ); } diff --git a/lib/Predis/Profiles/ServerVersion26.php b/lib/Predis/Profiles/ServerVersion26.php index e35118e8..eed287bb 100644 --- a/lib/Predis/Profiles/ServerVersion26.php +++ b/lib/Predis/Profiles/ServerVersion26.php @@ -217,6 +217,8 @@ class ServerVersion26 extends ServerProfile /* commands operating on string values */ 'psetex' => 'Predis\Commands\StringPreciseSetExpire', 'incrbyfloat' => 'Predis\Commands\StringIncrementByFloat', + 'bitop' => 'Predis\Commands\StringBitOp', + 'bitcount' => 'Predis\Commands\StringBitCount', /* commands operating on hashes */ 'hincrbyfloat' => 'Predis\Commands\HashIncrementByFloat', diff --git a/tests/Predis/Commands/StringBitOpTest.php b/tests/Predis/Commands/StringBitOpTest.php new file mode 100644 index 00000000..89ad0fe0 --- /dev/null +++ b/tests/Predis/Commands/StringBitOpTest.php @@ -0,0 +1,193 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Commands; + +use \PHPUnit_Framework_TestCase as StandardTestCase; + +/** + * @group commands + * @group realm-string + */ +class StringBitOpTest extends CommandTestCase +{ + /** + * {@inheritdoc} + */ + protected function getExpectedCommand() + { + return 'Predis\Commands\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')); + } +} diff --git a/tests/Predis/Profiles/ServerVersion26Test.php b/tests/Predis/Profiles/ServerVersion26Test.php index 51dff295..2a62b10c 100644 --- a/tests/Predis/Profiles/ServerVersion26Test.php +++ b/tests/Predis/Profiles/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', ); } } diff --git a/tests/Predis/Profiles/ServerVersionNextTest.php b/tests/Predis/Profiles/ServerVersionNextTest.php index 34c21432..f857f142 100644 --- a/tests/Predis/Profiles/ServerVersionNextTest.php +++ b/tests/Predis/Profiles/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', ); } }