mirror of
https://github.com/predis/predis.git
synced 2026-09-15 21:07:08 +00:00
New commands: BITOP, BITCOUNT (Redis v2.6-dev).
This commit is contained in:
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -409,6 +409,7 @@ class MasterSlaveReplication implements ReplicationConnectionInterface
|
||||
'ECHO' => true,
|
||||
'QUIT' => true,
|
||||
'OBJECT' => true,
|
||||
'BITCOUNT' => true,
|
||||
'TIME' => true,
|
||||
'SORT' => array($this, 'isSortReadOnly'),
|
||||
);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user