mirror of
https://github.com/predis/predis.git
synced 2026-08-20 01:51:47 +00:00
09895f27bf
We also changed our wording to indentify this kind of abstraction so instead of using "scripted commands" (kind of broken English) we now use "scriptable commands".
379 lines
13 KiB
PHP
379 lines
13 KiB
PHP
<?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\Replication;
|
|
|
|
use PHPUnit_Framework_TestCase as StandardTestCase;
|
|
|
|
use Predis\Command\CommandInterface;
|
|
use Predis\Profile;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
class ReplicationStrategyTest extends StandardTestCase
|
|
{
|
|
/**
|
|
* @group disconnected
|
|
*/
|
|
public function testReadCommands()
|
|
{
|
|
$profile = Profile\Factory::getDevelopment();
|
|
$strategy = new ReplicationStrategy();
|
|
|
|
foreach ($this->getExpectedCommands('read') as $commandId) {
|
|
$command = $profile->createCommand($commandId);
|
|
$this->assertTrue($strategy->isReadOperation($command));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @group disconnected
|
|
*/
|
|
public function testWriteCommands()
|
|
{
|
|
$profile = Profile\Factory::getDevelopment();
|
|
$strategy = new ReplicationStrategy();
|
|
|
|
foreach ($this->getExpectedCommands('write') as $commandId) {
|
|
$command = $profile->createCommand($commandId);
|
|
$this->assertFalse($strategy->isReadOperation($command), $commandId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @group disconnected
|
|
*/
|
|
public function testDisallowedCommands()
|
|
{
|
|
$profile = Profile\Factory::getDevelopment();
|
|
$strategy = new ReplicationStrategy();
|
|
|
|
foreach ($this->getExpectedCommands('disallowed') as $commandId) {
|
|
$command = $profile->createCommand($commandId);
|
|
$this->assertTrue($strategy->isDisallowedOperation($command), $commandId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @group disconnected
|
|
*/
|
|
public function testSortCommand()
|
|
{
|
|
$profile = Profile\Factory::getDevelopment();
|
|
$strategy = new ReplicationStrategy();
|
|
|
|
$cmdReadSort = $profile->createCommand('SORT', array('key:list'));
|
|
$this->assertTrue($strategy->isReadOperation($cmdReadSort), 'SORT [read-only]');
|
|
|
|
$cmdWriteSort = $profile->createCommand('SORT', array('key:list', array('store' => 'key:stored')));
|
|
$this->assertFalse($strategy->isReadOperation($cmdWriteSort), 'SORT [write with STORE]');
|
|
}
|
|
|
|
/**
|
|
* @group disconnected
|
|
* @expectedException Predis\NotSupportedException
|
|
* @expectedExceptionMessage The command INFO is not allowed in replication mode
|
|
*/
|
|
public function testUsingDisallowedCommandThrowsException()
|
|
{
|
|
$profile = Profile\Factory::getDevelopment();
|
|
$strategy = new ReplicationStrategy();
|
|
|
|
$command = $profile->createCommand('INFO');
|
|
$strategy->isReadOperation($command);
|
|
}
|
|
|
|
/**
|
|
* @group disconnected
|
|
*/
|
|
public function testDefaultIsWriteOperation()
|
|
{
|
|
$strategy = new ReplicationStrategy();
|
|
|
|
$command = $this->getMock('Predis\Command\CommandInterface');
|
|
$command->expects($this->any())
|
|
->method('getId')
|
|
->will($this->returnValue('CMDTEST'));
|
|
|
|
$this->assertFalse($strategy->isReadOperation($command));
|
|
}
|
|
|
|
/**
|
|
* @group disconnected
|
|
*/
|
|
public function testCanSetCommandAsReadOperation()
|
|
{
|
|
$strategy = new ReplicationStrategy();
|
|
|
|
$command = $this->getMock('Predis\Command\CommandInterface');
|
|
$command->expects($this->any())
|
|
->method('getId')
|
|
->will($this->returnValue('CMDTEST'));
|
|
|
|
|
|
$strategy->setCommandReadOnly('CMDTEST', true);
|
|
$this->assertTrue($strategy->isReadOperation($command));
|
|
}
|
|
|
|
/**
|
|
* @group disconnected
|
|
*/
|
|
public function testCanSetCommandAsWriteOperation()
|
|
{
|
|
$strategy = new ReplicationStrategy();
|
|
|
|
$command = $this->getMock('Predis\Command\CommandInterface');
|
|
$command->expects($this->any())
|
|
->method('getId')
|
|
->will($this->returnValue('CMDTEST'));
|
|
|
|
|
|
$strategy->setCommandReadOnly('CMDTEST', false);
|
|
$this->assertFalse($strategy->isReadOperation($command));
|
|
|
|
$strategy->setCommandReadOnly('GET', false);
|
|
$this->assertFalse($strategy->isReadOperation($command));
|
|
}
|
|
|
|
/**
|
|
* @group disconnected
|
|
*/
|
|
public function testCanUseCallableToCheckCommand()
|
|
{
|
|
$strategy = new ReplicationStrategy();
|
|
$profile = Profile\Factory::getDevelopment();
|
|
|
|
$strategy->setCommandReadOnly('SET', function ($command) {
|
|
return $command->getArgument(1) === true;
|
|
});
|
|
|
|
$command = $profile->createCommand('SET', array('trigger', false));
|
|
$this->assertFalse($strategy->isReadOperation($command));
|
|
|
|
$command = $profile->createCommand('SET', array('trigger', true));
|
|
$this->assertTrue($strategy->isReadOperation($command));
|
|
}
|
|
|
|
/**
|
|
* @group disconnected
|
|
*/
|
|
public function testSetLuaScriptAsReadOperation()
|
|
{
|
|
$strategy = new ReplicationStrategy();
|
|
$profile = Profile\Factory::getDevelopment();
|
|
|
|
$writeScript = 'redis.call("set", "foo", "bar")';
|
|
$readScript = 'return true';
|
|
|
|
$strategy->setScriptReadOnly($readScript, true);
|
|
|
|
$cmdEval = $profile->createCommand('EVAL', array($writeScript));
|
|
$cmdEvalSHA = $profile->createCommand('EVALSHA', array(sha1($writeScript)));
|
|
$this->assertFalse($strategy->isReadOperation($cmdEval));
|
|
$this->assertFalse($strategy->isReadOperation($cmdEvalSHA));
|
|
|
|
$cmdEval = $profile->createCommand('EVAL', array($readScript));
|
|
$cmdEvalSHA = $profile->createCommand('EVALSHA', array(sha1($readScript)));
|
|
$this->assertTrue($strategy->isReadOperation($cmdEval));
|
|
$this->assertTrue($strategy->isReadOperation($cmdEvalSHA));
|
|
}
|
|
|
|
/**
|
|
* @group disconnected
|
|
*/
|
|
public function testSetLuaScriptAsReadOperationWorksWithScriptCommand()
|
|
{
|
|
$strategy = new ReplicationStrategy();
|
|
|
|
$command = $this->getMock('Predis\Command\ScriptCommand', array('getScript'));
|
|
$command->expects($this->any())
|
|
->method('getScript')
|
|
->will($this->returnValue($script = 'return true'));
|
|
|
|
$strategy->setScriptReadOnly($script, function ($command) {
|
|
return $command->getArgument(2) === true;
|
|
});
|
|
|
|
$command->setArguments(array(false));
|
|
$this->assertFalse($strategy->isReadOperation($command));
|
|
|
|
$command->setArguments(array(true));
|
|
$this->assertTrue($strategy->isReadOperation($command));
|
|
}
|
|
|
|
/**
|
|
* @group disconnected
|
|
*/
|
|
public function testSetLuaScriptAsReadOperationWorksWithScriptCommandAndCallableCheck()
|
|
{
|
|
$strategy = new ReplicationStrategy();
|
|
|
|
$command = $this->getMock('Predis\Command\ScriptCommand', array('getScript'));
|
|
$command->expects($this->any())
|
|
->method('getScript')
|
|
->will($this->returnValue($script = 'return true'));
|
|
|
|
$command->setArguments(array('trigger', false));
|
|
|
|
$strategy->setScriptReadOnly($script, true);
|
|
|
|
$this->assertTrue($strategy->isReadOperation($command));
|
|
}
|
|
|
|
// ******************************************************************** //
|
|
// ---- HELPER METHODS ------------------------------------------------ //
|
|
// ******************************************************************** //
|
|
|
|
/**
|
|
* Returns the list of expected supported commands.
|
|
*
|
|
* @param string $type Optional type of command (based on its keys)
|
|
* @return array
|
|
*/
|
|
protected function getExpectedCommands($type = null)
|
|
{
|
|
$commands = array(
|
|
/* commands operating on the connection */
|
|
'EXISTS' => 'read',
|
|
'AUTH' => 'read',
|
|
'SELECT' => 'read',
|
|
'ECHO' => 'read',
|
|
'QUIT' => 'read',
|
|
'OBJECT' => 'read',
|
|
'BITCOUNT' => 'read',
|
|
'TIME' => 'read',
|
|
'SHUTDOWN' => 'disallowed',
|
|
'INFO' => 'disallowed',
|
|
'DBSIZE' => 'disallowed',
|
|
'LASTSAVE' => 'disallowed',
|
|
'CONFIG' => 'disallowed',
|
|
'MONITOR' => 'disallowed',
|
|
'SLAVEOF' => 'disallowed',
|
|
'SAVE' => 'disallowed',
|
|
'BGSAVE' => 'disallowed',
|
|
'BGREWRITEAOF' => 'disallowed',
|
|
'SLOWLOG' => 'disallowed',
|
|
|
|
/* commands operating on the key space */
|
|
'EXISTS' => 'read',
|
|
'DEL' => 'write',
|
|
'TYPE' => 'read',
|
|
'EXPIRE' => 'write',
|
|
'EXPIREAT' => 'write',
|
|
'PERSIST' => 'write',
|
|
'PEXPIRE' => 'write',
|
|
'PEXPIREAT' => 'write',
|
|
'TTL' => 'read',
|
|
'PTTL' => 'write',
|
|
'SORT' => 'variable',
|
|
'KEYS' => 'read',
|
|
'SCAN' => 'read',
|
|
'RANDOMKEY' => 'read',
|
|
|
|
/* commands operating on string values */
|
|
'APPEND' => 'write',
|
|
'DECR' => 'write',
|
|
'DECRBY' => 'write',
|
|
'GET' => 'read',
|
|
'GETBIT' => 'read',
|
|
'MGET' => 'read',
|
|
'SET' => 'write',
|
|
'GETRANGE' => 'read',
|
|
'GETSET' => 'write',
|
|
'INCR' => 'write',
|
|
'INCRBY' => 'write',
|
|
'SETBIT' => 'write',
|
|
'SETEX' => 'write',
|
|
'MSET' => 'write',
|
|
'MSETNX' => 'write',
|
|
'SETNX' => 'write',
|
|
'SETRANGE' => 'write',
|
|
'STRLEN' => 'read',
|
|
'SUBSTR' => 'read',
|
|
|
|
/* commands operating on lists */
|
|
'LINSERT' => 'write',
|
|
'LINDEX' => 'read',
|
|
'LLEN' => 'read',
|
|
'LPOP' => 'write',
|
|
'RPOP' => 'write',
|
|
'BLPOP' => 'write',
|
|
'BRPOP' => 'write',
|
|
'LPUSH' => 'write',
|
|
'LPUSHX' => 'write',
|
|
'RPUSH' => 'write',
|
|
'RPUSHX' => 'write',
|
|
'LRANGE' => 'read',
|
|
'LREM' => 'write',
|
|
'LSET' => 'write',
|
|
'LTRIM' => 'write',
|
|
|
|
/* commands operating on sets */
|
|
'SADD' => 'write',
|
|
'SCARD' => 'read',
|
|
'SISMEMBER' => 'read',
|
|
'SMEMBERS' => 'read',
|
|
'SSCAN' => 'read',
|
|
'SRANDMEMBER' => 'read',
|
|
'SPOP' => 'write',
|
|
'SREM' => 'write',
|
|
'SINTER' => 'read',
|
|
'SUNION' => 'read',
|
|
'SDIFF' => 'read',
|
|
|
|
/* commands operating on sorted sets */
|
|
'ZADD' => 'write',
|
|
'ZCARD' => 'read',
|
|
'ZCOUNT' => 'read',
|
|
'ZINCRBY' => 'write',
|
|
'ZRANGE' => 'read',
|
|
'ZRANGEBYSCORE' => 'read',
|
|
'ZRANK' => 'read',
|
|
'ZREM' => 'write',
|
|
'ZREMRANGEBYRANK' => 'write',
|
|
'ZREMRANGEBYSCORE' => 'write',
|
|
'ZREVRANGE' => 'read',
|
|
'ZREVRANGEBYSCORE' => 'read',
|
|
'ZREVRANK' => 'read',
|
|
'ZSCORE' => 'read',
|
|
'ZSCAN' => 'read',
|
|
|
|
/* commands operating on hashes */
|
|
'HDEL' => 'write',
|
|
'HEXISTS' => 'read',
|
|
'HGET' => 'read',
|
|
'HGETALL' => 'read',
|
|
'HMGET' => 'read',
|
|
'HINCRBY' => 'write',
|
|
'HINCRBYFLOAT' => 'write',
|
|
'HKEYS' => 'read',
|
|
'HLEN' => 'read',
|
|
'HSET' => 'write',
|
|
'HSETNX' => 'write',
|
|
'HVALS' => 'read',
|
|
'HSCAN' => 'read',
|
|
|
|
/* scripting */
|
|
'EVAL' => 'write',
|
|
'EVALSHA' => 'write',
|
|
);
|
|
|
|
if (isset($type)) {
|
|
$commands = array_filter($commands, function ($expectedType) use ($type) {
|
|
return $expectedType === $type;
|
|
});
|
|
}
|
|
|
|
return array_keys($commands);
|
|
}
|
|
}
|