diff --git a/CHANGELOG.md b/CHANGELOG.md index 27fe2d8e..db7eafd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,10 @@ v0.8.0 (201x-xx-xx) context to make the execution atomic: if a pipeline fails at a certain point then the whole pipeline is discarded. +- The key-hashing mechanism for commands is now handled externally and is no + more a competence of each command class. This change is neeeded to support + both client-side sharding and Redis cluster. + - `Predis\Options\Option` is now abstract, see `Predis\Option\AbstractOption`. diff --git a/lib/Predis/Command/AbstractCommand.php b/lib/Predis/Command/AbstractCommand.php index f8ef5bc0..efa03ee5 100644 --- a/lib/Predis/Command/AbstractCommand.php +++ b/lib/Predis/Command/AbstractCommand.php @@ -11,8 +11,6 @@ namespace Predis\Command; -use Predis\Helpers; -use Predis\Distribution\HashGeneratorInterface; /** * Base class for Redis commands. @@ -76,57 +74,21 @@ abstract class AbstractCommand implements CommandInterface } /** - * Checks if the command can return an hash for client-side sharding. - * - * @return Boolean + * {@inheritdoc} */ - protected function canBeHashed() + public function setHash($hash) { - return isset($this->arguments[0]); - } - - /** - * Checks if the specified array of keys will generate the same hash. - * - * @param array $keys Array of keys. - * @return Boolean - */ - protected function checkSameHashForKeys(Array $keys) - { - if (($count = count($keys)) === 0) { - return false; - } - - $currentKey = Helpers::extractKeyTag($keys[0]); - - for ($i = 1; $i < $count; $i++) { - $nextKey = Helpers::extractKeyTag($keys[$i]); - if ($currentKey !== $nextKey) { - return false; - } - $currentKey = $nextKey; - } - - return true; + $this->hash = $hash; } /** * {@inheritdoc} */ - public function getHash(HashGeneratorInterface $hasher) + public function getHash() { if (isset($this->hash)) { return $this->hash; } - - if ($this->canBeHashed()) { - $key = Helpers::extractKeyTag($this->arguments[0]); - $this->hash = $hasher->hash($key); - - return $this->hash; - } - - return null; } /** diff --git a/lib/Predis/Command/CommandInterface.php b/lib/Predis/Command/CommandInterface.php index 41b4a176..d995da73 100644 --- a/lib/Predis/Command/CommandInterface.php +++ b/lib/Predis/Command/CommandInterface.php @@ -11,8 +11,6 @@ namespace Predis\Command; -use Predis\Distribution\HashGeneratorInterface; - /** * Defines an abstraction representing a Redis command. * @author Daniele Alessandri @@ -27,13 +25,18 @@ interface CommandInterface public function getId(); /** - * Returns an hash of the command using the provided algorithm against the - * key (used to calculate the distribution of keys with client-side sharding). + * Set the hash for the command. + * + * @param int $hash Calculated hash. + */ + public function setHash($hash); + + /** + * Returns the hash of the command. * - * @param HashGeneratorInterface $hasher Distribution algorithm. * @return int */ - public function getHash(HashGeneratorInterface $hasher); + public function getHash(); /** * Sets the arguments of the command. diff --git a/lib/Predis/Command/ConnectionAuth.php b/lib/Predis/Command/ConnectionAuth.php index c12f3e2c..de0d3ee8 100644 --- a/lib/Predis/Command/ConnectionAuth.php +++ b/lib/Predis/Command/ConnectionAuth.php @@ -24,12 +24,4 @@ class ConnectionAuth extends AbstractCommand { return 'AUTH'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/ConnectionEcho.php b/lib/Predis/Command/ConnectionEcho.php index ed504953..dd9d072a 100644 --- a/lib/Predis/Command/ConnectionEcho.php +++ b/lib/Predis/Command/ConnectionEcho.php @@ -24,12 +24,4 @@ class ConnectionEcho extends AbstractCommand { return 'ECHO'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/ConnectionPing.php b/lib/Predis/Command/ConnectionPing.php index f011594c..cf29416a 100644 --- a/lib/Predis/Command/ConnectionPing.php +++ b/lib/Predis/Command/ConnectionPing.php @@ -25,14 +25,6 @@ class ConnectionPing extends AbstractCommand return 'PING'; } - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } - /** * {@inheritdoc} */ diff --git a/lib/Predis/Command/ConnectionQuit.php b/lib/Predis/Command/ConnectionQuit.php index 905ede5b..641cc356 100644 --- a/lib/Predis/Command/ConnectionQuit.php +++ b/lib/Predis/Command/ConnectionQuit.php @@ -24,12 +24,4 @@ class ConnectionQuit extends AbstractCommand { return 'QUIT'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/ConnectionSelect.php b/lib/Predis/Command/ConnectionSelect.php index b784b230..51b96b77 100644 --- a/lib/Predis/Command/ConnectionSelect.php +++ b/lib/Predis/Command/ConnectionSelect.php @@ -24,12 +24,4 @@ class ConnectionSelect extends AbstractCommand { return 'SELECT'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/Hash/CommandHashStrategy.php b/lib/Predis/Command/Hash/CommandHashStrategy.php new file mode 100644 index 00000000..fc436694 --- /dev/null +++ b/lib/Predis/Command/Hash/CommandHashStrategy.php @@ -0,0 +1,303 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Hash; + +use Predis\Command\CommandInterface; +use Predis\Distribution\HashGeneratorInterface; + +/** + * Default class used by Predis for client-side sharding to calculate + * hashes out of keys of supported commands. + * + * @author Daniele Alessandri + */ +class CommandHashStrategy implements CommandHashStrategyInterface +{ + private $commands; + + /** + * + */ + public function __construct() + { + $this->commands = $this->getDefaultCommands(); + } + + /** + * Returns the default map of supported commands with their handlers. + * + * @return array + */ + protected function getDefaultCommands() + { + $keyIsFirstArgument = array($this, 'getKeyFromFirstArgument'); + + return array( + /* commands operating on the key space */ + 'EXISTS' => $keyIsFirstArgument, + 'DEL' => array($this, 'getKeyFromAllArguments'), + 'TYPE' => $keyIsFirstArgument, + 'EXPIRE' => $keyIsFirstArgument, + 'EXPIREAT' => $keyIsFirstArgument, + 'PERSIST' => $keyIsFirstArgument, + 'PEXPIRE' => $keyIsFirstArgument, + 'PEXPIREAT' => $keyIsFirstArgument, + 'TTL' => $keyIsFirstArgument, + 'PTTL' => $keyIsFirstArgument, + 'SORT' => $keyIsFirstArgument, // TODO + + /* commands operating on string values */ + 'APPEND' => $keyIsFirstArgument, + 'DECR' => $keyIsFirstArgument, + 'DECRBY' => $keyIsFirstArgument, + 'GET' => $keyIsFirstArgument, + 'GETBIT' => $keyIsFirstArgument, + 'MGET' => array($this, 'getKeyFromAllArguments'), + 'SET' => $keyIsFirstArgument, + 'GETRANGE' => $keyIsFirstArgument, + 'GETSET' => $keyIsFirstArgument, + 'INCR' => $keyIsFirstArgument, + 'INCRBY' => $keyIsFirstArgument, + 'SETBIT' => $keyIsFirstArgument, + 'SETEX' => $keyIsFirstArgument, + 'MSET' => array($this, 'getKeyFromInterleavedArguments'), + 'MSETNX' => array($this, 'getKeyFromInterleavedArguments'), + 'SETNX' => $keyIsFirstArgument, + 'SETRANGE' => $keyIsFirstArgument, + 'STRLEN' => $keyIsFirstArgument, + 'SUBSTR' => $keyIsFirstArgument, + + /* commands operating on lists */ + 'LINSERT' => $keyIsFirstArgument, + 'LINDEX' => $keyIsFirstArgument, + 'LLEN' => $keyIsFirstArgument, + 'LPOP' => $keyIsFirstArgument, + 'RPOP' => $keyIsFirstArgument, + 'RPOPLPUSH' => array($this, 'getKeyFromAllArguments'), + 'BLPOP' => array($this, 'getKeyFromBlockingListCommands'), + 'BRPOP' => array($this, 'getKeyFromBlockingListCommands'), + 'BRPOPLPUSH' => array($this, 'getKeyFromBlockingListCommands'), + 'LPUSH' => $keyIsFirstArgument, + 'LPUSHX' => $keyIsFirstArgument, + 'RPUSH' => $keyIsFirstArgument, + 'RPUSHX' => $keyIsFirstArgument, + 'LRANGE' => $keyIsFirstArgument, + 'LREM' => $keyIsFirstArgument, + 'LSET' => $keyIsFirstArgument, + 'LTRIM' => $keyIsFirstArgument, + + /* commands operating on sets */ + 'SADD' => $keyIsFirstArgument, + 'SCARD' => $keyIsFirstArgument, + 'SDIFF' => array($this, 'getKeyFromAllArguments'), + 'SDIFFSTORE' => array($this, 'getKeyFromAllArguments'), + 'SINTER' => array($this, 'getKeyFromAllArguments'), + 'SINTERSTORE' => array($this, 'getKeyFromAllArguments'), + 'SUNION' => array($this, 'getKeyFromAllArguments'), + 'SUNIONSTORE' => array($this, 'getKeyFromAllArguments'), + 'SISMEMBER' => $keyIsFirstArgument, + 'SMEMBERS' => $keyIsFirstArgument, + 'SPOP' => $keyIsFirstArgument, + 'SRANDMEMBER' => $keyIsFirstArgument, + 'SREM' => $keyIsFirstArgument, + + /* commands operating on sorted sets */ + 'ZADD' => $keyIsFirstArgument, + 'ZCARD' => $keyIsFirstArgument, + 'ZCOUNT' => $keyIsFirstArgument, + 'ZINCRBY' => $keyIsFirstArgument, + 'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'), + 'ZRANGE' => $keyIsFirstArgument, + 'ZRANGEBYSCORE' => $keyIsFirstArgument, + 'ZRANK' => $keyIsFirstArgument, + 'ZREM' => $keyIsFirstArgument, + 'ZREMRANGEBYRANK' => $keyIsFirstArgument, + 'ZREMRANGEBYSCORE' => $keyIsFirstArgument, + 'ZREVRANGE' => $keyIsFirstArgument, + 'ZREVRANGEBYSCORE' => $keyIsFirstArgument, + 'ZREVRANK' => $keyIsFirstArgument, + 'ZSCORE' => $keyIsFirstArgument, + 'ZUNIONSTORE' => array($this, 'getKeyFromZsetAggregationCommands'), + + /* commands operating on hashes */ + 'HDEL' => $keyIsFirstArgument, + 'HEXISTS' => $keyIsFirstArgument, + 'HGET' => $keyIsFirstArgument, + 'HGETALL' => $keyIsFirstArgument, + 'HMGET' => $keyIsFirstArgument, + 'HINCRBY' => $keyIsFirstArgument, + 'HINCRBYFLOAT' => $keyIsFirstArgument, + 'HKEYS' => $keyIsFirstArgument, + 'HLEN' => $keyIsFirstArgument, + 'HSET' => $keyIsFirstArgument, + 'HSETNX' => $keyIsFirstArgument, + 'HVALS' => $keyIsFirstArgument, + ); + } + + /** + * Returns the list of IDs for the supported commands. + * + * @return array + */ + public function getSupportedCommands() + { + return array_keys($this->commands); + } + + /** + * Extracts the key from the first argument of a command instance. + * + * @param CommandInterface $command Command instance. + * @return string + */ + protected function getKeyFromFirstArgument(CommandInterface $command) + { + return $command->getArgument(0); + } + + /** + * Extracts the key from a command with multiple keys only when all keys + * in the arguments array produce the same hash. + * + * @param CommandInterface $command Command instance. + * @return string + */ + protected function getKeyFromAllArguments(CommandInterface $command) + { + $arguments = $command->getArguments(); + + if ($this->checkSameHashForKeys($arguments)) { + return $arguments[0]; + } + } + + /** + * Extracts the key from a command with multiple keys only when all keys + * in the arguments array produce the same hash. + * + * @param CommandInterface $command Command instance. + * @return string + */ + protected function getKeyFromInterleavedArguments(CommandInterface $command) + { + $arguments = $command->getArguments(); + $keys = array(); + + for ($i = 0; $i < count($arguments); $i += 2) { + $keys[] = $arguments[$i]; + } + + if ($this->checkSameHashForKeys($keys)) { + return $arguments[0]; + } + } + + /** + * Extracts the key from BLPOP and BRPOP commands. + * + * @param CommandInterface $command Command instance. + * @return string + */ + protected function getKeyFromBlockingListCommands(CommandInterface $command) + { + $arguments = $command->getArguments(); + + if ($this->checkSameHashForKeys(array_slice($arguments, 0, count($arguments) - 1))) { + return $arguments[0]; + } + } + + /** + * Extracts the key from ZINTERSTORE and ZUNIONSTORE commands. + * + * @param CommandInterface $command Command instance. + * @return string + */ + protected function getKeyFromZsetAggregationCommands(CommandInterface $command) + { + $arguments = $command->getArguments(); + $keys = array_merge(array($arguments[0]), array_slice($arguments, 2, $arguments[1])); + + if ($this->checkSameHashForKeys($keys)) { + return $arguments[0]; + } + } + + /** + * {@inheritdoc} + */ + public function getHash(HashGeneratorInterface $hasher, CommandInterface $command) + { + if (isset($this->commands[$cmdID = $command->getId()])) { + if ($key = call_user_func($this->commands[$cmdID], $command)) { + return $this->getKeyHash($hasher, $key); + } + } + } + + /** + * {@inheritdoc} + */ + public function getKeyHash(HashGeneratorInterface $hasher, $key) + { + $key = $this->extractKeyTag($key); + $hash = $hasher->hash($key); + + return $hash; + } + + /** + * Checks if the specified array of keys will generate the same hash. + * + * @param array $keys Array of keys. + * @return Boolean + */ + protected function checkSameHashForKeys(Array $keys) + { + if (($count = count($keys)) === 0) { + return false; + } + + $currentKey = $this->extractKeyTag($keys[0]); + + for ($i = 1; $i < $count; $i++) { + $nextKey = $this->extractKeyTag($keys[$i]); + if ($currentKey !== $nextKey) { + return false; + } + $currentKey = $nextKey; + } + + return true; + } + + /** + * Returns only the hashable part of a key (delimited by "{...}"), or the + * whole key if a key tag is not found in the string. + * + * @param string $key A key. + * @return string + */ + protected function extractKeyTag($key) + { + $start = strpos($key, '{'); + if ($start !== false) { + $end = strpos($key, '}', $start); + if ($end !== false) { + $key = substr($key, ++$start, $end - $start); + } + } + + return $key; + } +} diff --git a/lib/Predis/Command/Hash/CommandHashStrategyInterface.php b/lib/Predis/Command/Hash/CommandHashStrategyInterface.php new file mode 100644 index 00000000..87db66e9 --- /dev/null +++ b/lib/Predis/Command/Hash/CommandHashStrategyInterface.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Hash; + +use Predis\Command\CommandInterface; +use Predis\Distribution\HashGeneratorInterface; + +/** + * Interface for classes defining the strategy used to calculate an hash + * out of keys extracted from supported commands. + * + * This is mostly useful to support clustering via client-side sharding. + * + * @author Daniele Alessandri + */ +interface CommandHashStrategyInterface +{ + /** + * Returns the hash for the given command using the specified algorithm, or null + * if the command cannot be hashed. + * + * @param HashGeneratorInterface $hasher Hash algorithm. + * @param CommandInterface $command Command to be hashed. + * @return int + */ + public function getHash(HashGeneratorInterface $hasher, CommandInterface $command); + + /** + * Returns the hash for the given key using the specified algorithm. + * + * @param HashGeneratorInterface $hasher Hash algorithm. + * @param string $key Key to be hashed. + * @return string + */ + public function getKeyHash(HashGeneratorInterface $hasher, $key); +} diff --git a/lib/Predis/Command/KeyDelete.php b/lib/Predis/Command/KeyDelete.php index e85fa343..d6967848 100644 --- a/lib/Predis/Command/KeyDelete.php +++ b/lib/Predis/Command/KeyDelete.php @@ -42,17 +42,4 @@ class KeyDelete extends AbstractCommand implements PrefixableCommandInterface { PrefixHelpers::all($this, $prefix); } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - $args = $this->getArguments(); - if (count($args) === 1) { - return true; - } - - return $this->checkSameHashForKeys($args); - } } diff --git a/lib/Predis/Command/KeyKeys.php b/lib/Predis/Command/KeyKeys.php index ebf10c23..3b973edd 100644 --- a/lib/Predis/Command/KeyKeys.php +++ b/lib/Predis/Command/KeyKeys.php @@ -24,12 +24,4 @@ class KeyKeys extends PrefixableCommand { return 'KEYS'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/KeyMove.php b/lib/Predis/Command/KeyMove.php index 4c7bf42b..9e2a64da 100644 --- a/lib/Predis/Command/KeyMove.php +++ b/lib/Predis/Command/KeyMove.php @@ -25,14 +25,6 @@ class KeyMove extends PrefixableCommand return 'MOVE'; } - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } - /** * {@inheritdoc} */ diff --git a/lib/Predis/Command/KeyRandom.php b/lib/Predis/Command/KeyRandom.php index 367eac54..520a368c 100644 --- a/lib/Predis/Command/KeyRandom.php +++ b/lib/Predis/Command/KeyRandom.php @@ -25,14 +25,6 @@ class KeyRandom extends AbstractCommand return 'RANDOMKEY'; } - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } - /** * {@inheritdoc} */ diff --git a/lib/Predis/Command/KeyRename.php b/lib/Predis/Command/KeyRename.php index 2f4b63cd..49af1ffe 100644 --- a/lib/Predis/Command/KeyRename.php +++ b/lib/Predis/Command/KeyRename.php @@ -32,12 +32,4 @@ class KeyRename extends AbstractCommand implements PrefixableCommandInterface { PrefixHelpers::all($this, $prefix); } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/ListPopFirstBlocking.php b/lib/Predis/Command/ListPopFirstBlocking.php index 95d52dc5..599a41b8 100644 --- a/lib/Predis/Command/ListPopFirstBlocking.php +++ b/lib/Predis/Command/ListPopFirstBlocking.php @@ -44,14 +44,4 @@ class ListPopFirstBlocking extends AbstractCommand implements PrefixableCommandI { PrefixHelpers::skipLast($this, $prefix); } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return $this->checkSameHashForKeys( - array_slice(($args = $this->getArguments()), 0, count($args) - 1) - ); - } } diff --git a/lib/Predis/Command/ListPopLastPushHead.php b/lib/Predis/Command/ListPopLastPushHead.php index 12052bb2..761b6124 100644 --- a/lib/Predis/Command/ListPopLastPushHead.php +++ b/lib/Predis/Command/ListPopLastPushHead.php @@ -32,12 +32,4 @@ class ListPopLastPushHead extends AbstractCommand implements PrefixableCommandIn { PrefixHelpers::all($this, $prefix); } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return $this->checkSameHashForKeys($this->getArguments()); - } } diff --git a/lib/Predis/Command/ListPopLastPushHeadBlocking.php b/lib/Predis/Command/ListPopLastPushHeadBlocking.php index bc785674..3ef14071 100644 --- a/lib/Predis/Command/ListPopLastPushHeadBlocking.php +++ b/lib/Predis/Command/ListPopLastPushHeadBlocking.php @@ -32,14 +32,4 @@ class ListPopLastPushHeadBlocking extends AbstractCommand implements PrefixableC { PrefixHelpers::skipLast($this, $prefix); } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return $this->checkSameHashForKeys( - array_slice($args = $this->getArguments(), 0, count($args) - 1) - ); - } } diff --git a/lib/Predis/Command/PubSubPublish.php b/lib/Predis/Command/PubSubPublish.php index 0f32d66b..b2ecce51 100644 --- a/lib/Predis/Command/PubSubPublish.php +++ b/lib/Predis/Command/PubSubPublish.php @@ -24,12 +24,4 @@ class PubSubPublish extends PrefixableCommand { return 'PUBLISH'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/PubSubSubscribe.php b/lib/Predis/Command/PubSubSubscribe.php index 18cbe711..bd7d7736 100644 --- a/lib/Predis/Command/PubSubSubscribe.php +++ b/lib/Predis/Command/PubSubSubscribe.php @@ -42,12 +42,4 @@ class PubSubSubscribe extends AbstractCommand implements PrefixableCommandInterf { PrefixHelpers::all($this, $prefix); } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/PubSubUnsubscribe.php b/lib/Predis/Command/PubSubUnsubscribe.php index c041a17a..4bb6661e 100644 --- a/lib/Predis/Command/PubSubUnsubscribe.php +++ b/lib/Predis/Command/PubSubUnsubscribe.php @@ -42,12 +42,4 @@ class PubSubUnsubscribe extends AbstractCommand implements PrefixableCommandInte { PrefixHelpers::all($this, $prefix); } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/ServerBackgroundRewriteAOF.php b/lib/Predis/Command/ServerBackgroundRewriteAOF.php index 7f43291f..c4b40078 100644 --- a/lib/Predis/Command/ServerBackgroundRewriteAOF.php +++ b/lib/Predis/Command/ServerBackgroundRewriteAOF.php @@ -25,14 +25,6 @@ class ServerBackgroundRewriteAOF extends AbstractCommand return 'BGREWRITEAOF'; } - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } - /** * {@inheritdoc} */ diff --git a/lib/Predis/Command/ServerBackgroundSave.php b/lib/Predis/Command/ServerBackgroundSave.php index 23f7b38c..b5f3e065 100644 --- a/lib/Predis/Command/ServerBackgroundSave.php +++ b/lib/Predis/Command/ServerBackgroundSave.php @@ -25,14 +25,6 @@ class ServerBackgroundSave extends AbstractCommand return 'BGSAVE'; } - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } - /** * {@inheritdoc} */ diff --git a/lib/Predis/Command/ServerClient.php b/lib/Predis/Command/ServerClient.php index ab08dc3f..b6650d34 100644 --- a/lib/Predis/Command/ServerClient.php +++ b/lib/Predis/Command/ServerClient.php @@ -25,14 +25,6 @@ class ServerClient extends AbstractCommand return 'CLIENT'; } - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } - /** * {@inheritdoc} */ diff --git a/lib/Predis/Command/ServerConfig.php b/lib/Predis/Command/ServerConfig.php index 22c6e328..2ca7973a 100644 --- a/lib/Predis/Command/ServerConfig.php +++ b/lib/Predis/Command/ServerConfig.php @@ -29,14 +29,6 @@ class ServerConfig extends AbstractCommand return 'CONFIG'; } - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } - /** * {@inheritdoc} */ diff --git a/lib/Predis/Command/ServerDatabaseSize.php b/lib/Predis/Command/ServerDatabaseSize.php index 7a539232..51dcac63 100644 --- a/lib/Predis/Command/ServerDatabaseSize.php +++ b/lib/Predis/Command/ServerDatabaseSize.php @@ -24,12 +24,4 @@ class ServerDatabaseSize extends AbstractCommand { return 'DBSIZE'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/ServerEval.php b/lib/Predis/Command/ServerEval.php index f2d6766c..098fa29c 100644 --- a/lib/Predis/Command/ServerEval.php +++ b/lib/Predis/Command/ServerEval.php @@ -39,14 +39,6 @@ class ServerEval extends AbstractCommand implements PrefixableCommandInterface $this->setRawArguments($arguments); } - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } - /** * Calculates the SHA1 hash of the body of the script. * diff --git a/lib/Predis/Command/ServerFlushAll.php b/lib/Predis/Command/ServerFlushAll.php index 562faf80..a50218c7 100644 --- a/lib/Predis/Command/ServerFlushAll.php +++ b/lib/Predis/Command/ServerFlushAll.php @@ -24,12 +24,4 @@ class ServerFlushAll extends AbstractCommand { return 'FLUSHALL'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/ServerFlushDatabase.php b/lib/Predis/Command/ServerFlushDatabase.php index 686f2977..8b6b6a20 100644 --- a/lib/Predis/Command/ServerFlushDatabase.php +++ b/lib/Predis/Command/ServerFlushDatabase.php @@ -24,12 +24,4 @@ class ServerFlushDatabase extends AbstractCommand { return 'FLUSHDB'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/ServerInfo.php b/lib/Predis/Command/ServerInfo.php index 82422389..67d0b47c 100644 --- a/lib/Predis/Command/ServerInfo.php +++ b/lib/Predis/Command/ServerInfo.php @@ -25,14 +25,6 @@ class ServerInfo extends AbstractCommand return 'INFO'; } - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } - /** * {@inheritdoc} */ diff --git a/lib/Predis/Command/ServerLastSave.php b/lib/Predis/Command/ServerLastSave.php index e138cc29..33a1e93c 100644 --- a/lib/Predis/Command/ServerLastSave.php +++ b/lib/Predis/Command/ServerLastSave.php @@ -24,12 +24,4 @@ class ServerLastSave extends AbstractCommand { return 'LASTSAVE'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/ServerMonitor.php b/lib/Predis/Command/ServerMonitor.php index 4bf389b5..7f4a83fd 100644 --- a/lib/Predis/Command/ServerMonitor.php +++ b/lib/Predis/Command/ServerMonitor.php @@ -24,12 +24,4 @@ class ServerMonitor extends AbstractCommand { return 'MONITOR'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/ServerObject.php b/lib/Predis/Command/ServerObject.php index 69dd432c..c344a4a8 100644 --- a/lib/Predis/Command/ServerObject.php +++ b/lib/Predis/Command/ServerObject.php @@ -26,12 +26,4 @@ class ServerObject extends AbstractCommand { return 'OBJECT'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/ServerSave.php b/lib/Predis/Command/ServerSave.php index 7080da23..6dd3c797 100644 --- a/lib/Predis/Command/ServerSave.php +++ b/lib/Predis/Command/ServerSave.php @@ -24,12 +24,4 @@ class ServerSave extends AbstractCommand { return 'SAVE'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/ServerScript.php b/lib/Predis/Command/ServerScript.php index 008c6ccb..5b668665 100644 --- a/lib/Predis/Command/ServerScript.php +++ b/lib/Predis/Command/ServerScript.php @@ -24,12 +24,4 @@ class ServerScript extends AbstractCommand { return 'SCRIPT'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/ServerShutdown.php b/lib/Predis/Command/ServerShutdown.php index c11c3368..b0278b03 100644 --- a/lib/Predis/Command/ServerShutdown.php +++ b/lib/Predis/Command/ServerShutdown.php @@ -24,12 +24,4 @@ class ServerShutdown extends AbstractCommand { return 'SHUTDOWN'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/ServerSlaveOf.php b/lib/Predis/Command/ServerSlaveOf.php index 4c895aa4..94f9f696 100644 --- a/lib/Predis/Command/ServerSlaveOf.php +++ b/lib/Predis/Command/ServerSlaveOf.php @@ -36,12 +36,4 @@ class ServerSlaveOf extends AbstractCommand return $arguments; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/ServerSlowlog.php b/lib/Predis/Command/ServerSlowlog.php index 8e80f8b1..2520cc92 100644 --- a/lib/Predis/Command/ServerSlowlog.php +++ b/lib/Predis/Command/ServerSlowlog.php @@ -27,14 +27,6 @@ class ServerSlowlog extends AbstractCommand return 'SLOWLOG'; } - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } - /** * {@inheritdoc} */ diff --git a/lib/Predis/Command/SetIntersection.php b/lib/Predis/Command/SetIntersection.php index 3b540788..179473df 100644 --- a/lib/Predis/Command/SetIntersection.php +++ b/lib/Predis/Command/SetIntersection.php @@ -42,12 +42,4 @@ class SetIntersection extends AbstractCommand implements PrefixableCommandInterf { PrefixHelpers::all($this, $prefix); } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return $this->checkSameHashForKeys($this->getArguments()); - } } diff --git a/lib/Predis/Command/SetIntersectionStore.php b/lib/Predis/Command/SetIntersectionStore.php index 59e59e34..4e764e0d 100644 --- a/lib/Predis/Command/SetIntersectionStore.php +++ b/lib/Predis/Command/SetIntersectionStore.php @@ -44,12 +44,4 @@ class SetIntersectionStore extends AbstractCommand implements PrefixableCommandI { PrefixHelpers::all($this, $prefix); } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return $this->checkSameHashForKeys($this->getArguments()); - } } diff --git a/lib/Predis/Command/SetMove.php b/lib/Predis/Command/SetMove.php index 343d4eff..01678ec7 100644 --- a/lib/Predis/Command/SetMove.php +++ b/lib/Predis/Command/SetMove.php @@ -33,14 +33,6 @@ class SetMove extends AbstractCommand implements PrefixableCommandInterface PrefixHelpers::skipLast($this, $prefix); } - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } - /** * {@inheritdoc} */ diff --git a/lib/Predis/Command/StringGetMultiple.php b/lib/Predis/Command/StringGetMultiple.php index 49dcf08e..9a4646a9 100644 --- a/lib/Predis/Command/StringGetMultiple.php +++ b/lib/Predis/Command/StringGetMultiple.php @@ -42,12 +42,4 @@ class StringGetMultiple extends AbstractCommand implements PrefixableCommandInte { PrefixHelpers::all($this, $prefix); } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return $this->checkSameHashForKeys($this->getArguments()); - } } diff --git a/lib/Predis/Command/StringSetMultiple.php b/lib/Predis/Command/StringSetMultiple.php index e7ee533b..cda5157e 100644 --- a/lib/Predis/Command/StringSetMultiple.php +++ b/lib/Predis/Command/StringSetMultiple.php @@ -52,19 +52,4 @@ class StringSetMultiple extends AbstractCommand implements PrefixableCommandInte { PrefixHelpers::interleaved($this, $prefix); } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - $args = $this->getArguments(); - $keys = array(); - - for ($i = 0; $i < count($args); $i += 2) { - $keys[] = $args[$i]; - } - - return $this->checkSameHashForKeys($keys); - } } diff --git a/lib/Predis/Command/TransactionDiscard.php b/lib/Predis/Command/TransactionDiscard.php index e08f7419..67010b07 100644 --- a/lib/Predis/Command/TransactionDiscard.php +++ b/lib/Predis/Command/TransactionDiscard.php @@ -24,12 +24,4 @@ class TransactionDiscard extends AbstractCommand { return 'DISCARD'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/TransactionExec.php b/lib/Predis/Command/TransactionExec.php index 5e2d5bbc..5141454f 100644 --- a/lib/Predis/Command/TransactionExec.php +++ b/lib/Predis/Command/TransactionExec.php @@ -24,12 +24,4 @@ class TransactionExec extends AbstractCommand { return 'EXEC'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/TransactionMulti.php b/lib/Predis/Command/TransactionMulti.php index 8febe751..8f4ccf18 100644 --- a/lib/Predis/Command/TransactionMulti.php +++ b/lib/Predis/Command/TransactionMulti.php @@ -24,12 +24,4 @@ class TransactionMulti extends AbstractCommand { return 'MULTI'; } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } } diff --git a/lib/Predis/Command/TransactionUnwatch.php b/lib/Predis/Command/TransactionUnwatch.php index e7b17b65..697e09f9 100644 --- a/lib/Predis/Command/TransactionUnwatch.php +++ b/lib/Predis/Command/TransactionUnwatch.php @@ -25,14 +25,6 @@ class TransactionUnwatch extends AbstractCommand return 'UNWATCH'; } - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } - /** * {@inheritdoc} */ diff --git a/lib/Predis/Command/TransactionWatch.php b/lib/Predis/Command/TransactionWatch.php index 7be684a1..a1dd9a2a 100644 --- a/lib/Predis/Command/TransactionWatch.php +++ b/lib/Predis/Command/TransactionWatch.php @@ -45,14 +45,6 @@ class TransactionWatch extends AbstractCommand implements PrefixableCommandInter PrefixHelpers::all($this, $prefix); } - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - return false; - } - /** * {@inheritdoc} */ diff --git a/lib/Predis/Command/ZSetUnionStore.php b/lib/Predis/Command/ZSetUnionStore.php index cf400302..93fa5955 100644 --- a/lib/Predis/Command/ZSetUnionStore.php +++ b/lib/Predis/Command/ZSetUnionStore.php @@ -89,16 +89,4 @@ class ZSetUnionStore extends PrefixableCommand $this->setRawArguments($arguments); } - - /** - * {@inheritdoc} - */ - protected function canBeHashed() - { - $args = $this->getArguments(); - - return $this->checkSameHashForKeys( - array_merge(array($args[0]), array_slice($args, 2, $args[1])) - ); - } } diff --git a/lib/Predis/Connection/PredisCluster.php b/lib/Predis/Connection/PredisCluster.php index 13e97fe0..0fa95bdf 100644 --- a/lib/Predis/Connection/PredisCluster.php +++ b/lib/Predis/Connection/PredisCluster.php @@ -12,8 +12,8 @@ namespace Predis\Connection; use Predis\Command\CommandInterface; +use Predis\Command\Hash\CommandHashStrategy; use Predis\Distribution\DistributionStrategyInterface; -use Predis\Helpers; use Predis\ClientException; use Predis\NotSupportedException; use Predis\Distribution\HashRing; @@ -29,6 +29,7 @@ class PredisCluster implements ClusterConnectionInterface, \IteratorAggregate, \ { private $pool; private $distributor; + private $cmdHasher; /** * @param DistributionStrategyInterface $distributor Distribution strategy used by the cluster. @@ -36,6 +37,7 @@ class PredisCluster implements ClusterConnectionInterface, \IteratorAggregate, \ public function __construct(DistributionStrategyInterface $distributor = null) { $this->pool = array(); + $this->cmdHasher = new CommandHashStrategy(); $this->distributor = $distributor ?: new HashRing(); } @@ -126,14 +128,18 @@ class PredisCluster implements ClusterConnectionInterface, \IteratorAggregate, \ */ public function getConnection(CommandInterface $command) { - $cmdHash = $command->getHash($this->distributor); + $hash = $command->getHash(); - if (isset($cmdHash)) { - return $this->distributor->get($cmdHash); + if (isset($hash)) { + return $this->distributor->get($hash); } - $message = sprintf("Cannot send '%s' commands to a cluster of connections", $command->getId()); - throw new NotSupportedException($message); + if ($hash = $this->cmdHasher->getHash($this->distributor, $command)) { + $command->setHash($hash); + return $this->distributor->get($hash); + } + + throw new NotSupportedException("Cannot send {$command->getId()} to a cluster of connections"); } /** @@ -155,10 +161,10 @@ class PredisCluster implements ClusterConnectionInterface, \IteratorAggregate, \ */ public function getConnectionByKey($key) { - $hashablePart = Helpers::extractKeyTag($key); - $keyHash = $this->distributor->hash($hashablePart); + $hash = $this->cmdHasher->getKeyHash($this->distributor, $key); + $node = $this->distributor->get($hash); - return $this->distributor->get($keyHash); + return $node; } /** @@ -210,6 +216,7 @@ class PredisCluster implements ClusterConnectionInterface, \IteratorAggregate, \ public function executeCommandOnNodes(CommandInterface $command) { $replies = array(); + foreach ($this->pool as $connection) { $replies[] = $connection->executeCommand($command); } diff --git a/lib/Predis/Helpers.php b/lib/Predis/Helpers.php index 4c07eb3d..c9be0e6b 100644 --- a/lib/Predis/Helpers.php +++ b/lib/Predis/Helpers.php @@ -91,24 +91,4 @@ class Helpers return $arguments; } - - /** - * Returns only the hashable part of a key (delimited by "{...}"), or the - * whole key if a key tag is not found in the string. - * - * @param string $key A key. - * @return string - */ - public static function extractKeyTag($key) - { - $start = strpos($key, '{'); - if ($start !== false) { - $end = strpos($key, '}', $start); - if ($end !== false) { - $key = substr($key, ++$start, $end - $start); - } - } - - return $key; - } } diff --git a/tests/Predis/Command/CommandTest.php b/tests/Predis/Command/CommandTest.php index 4441bdf1..a27d29d0 100644 --- a/tests/Predis/Command/CommandTest.php +++ b/tests/Predis/Command/CommandTest.php @@ -95,91 +95,18 @@ class CommandTest extends StandardTestCase /** * @group disconnected - * @protected */ - public function testCheckSameHashForKeys() + public function testSetAndGetHash() { + $hash = "key-hash"; + $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand'); - - $checkSameHashForKeys = new \ReflectionMethod($command, 'checkSameHashForKeys'); - $checkSameHashForKeys->setAccessible(true); - - $this->assertTrue($checkSameHashForKeys->invoke($command, array('foo', '{foo}:bar'))); - $this->assertFalse($checkSameHashForKeys->invoke($command, array('foo', '{foo}:bar', 'foo:bar'))); - } - - /** - * @group disconnected - * @protected - */ - public function testCanBeHashed() - { - $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand'); - - $canBeHashed = new \ReflectionMethod($command, 'canBeHashed'); - $canBeHashed->setAccessible(true); - - $this->assertFalse($canBeHashed->invoke($command)); - $command->setRawArguments(array('key')); - $this->assertTrue($canBeHashed->invoke($command)); - } - /** - * @group disconnected - */ - public function testDoesNotReturnAnHashByDefault() - { - $distributor = $this->getMock('Predis\Distribution\HashGeneratorInterface'); - $distributor->expects($this->never())->method('hash'); + $this->assertNull($command->getHash()); - $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand'); - - $command->getHash($distributor); - } - - /** - * @group disconnected - */ - public function testReturnAnHashWhenCanBeHashedAndCachesIt() - { - $key = 'key'; - $hash = "$key-hash"; - - $distributor = $this->getMock('Predis\Distribution\HashGeneratorInterface'); - $distributor->expects($this->once()) - ->method('hash') - ->with($key) - ->will($this->returnValue($hash)); - - $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand'); - $command->setRawArguments(array($key)); - - $this->assertEquals($hash, $command->getHash($distributor)); - - $this->assertEquals($hash, $command->getHash($distributor)); - $this->assertEquals($hash, $command->getHash($distributor)); - } - - /** - * @group disconnected - */ - public function testExtractsKeyTagsBeforeHashing() - { - $tag = 'key'; - $key = "{{$tag}}:ignore"; - $hash = "$tag-hash"; - - $distributor = $this->getMock('Predis\Distribution\HashGeneratorInterface'); - $distributor->expects($this->once()) - ->method('hash') - ->with($tag) - ->will($this->returnValue($hash)); - - $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand'); - $command->setRawArguments(array($key)); - - $this->assertEquals($hash, $command->getHash($distributor)); + $command->setHash($hash); + $this->assertSame($hash, $command->getHash()); } /** diff --git a/tests/Predis/Command/Hash/CommandHashStrategyTest.php b/tests/Predis/Command/Hash/CommandHashStrategyTest.php new file mode 100644 index 00000000..c735f5e3 --- /dev/null +++ b/tests/Predis/Command/Hash/CommandHashStrategyTest.php @@ -0,0 +1,265 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command\Hash; + +use \PHPUnit_Framework_TestCase as StandardTestCase; +use Predis\Profile\ServerProfile; +use Predis\Distribution\HashRing; + +/** + * + */ +class CommandHashStrategyTest extends StandardTestCase +{ + /** + * @group disconnected + */ + public function testSupportsKeyTags() + { + $expected = -1938594527; + $distribution = new HashRing(); + $hashstrategy = new CommandHashStrategy(); + + $this->assertSame($expected, $hashstrategy->getKeyHash($distribution, '{foo}')); + $this->assertSame($expected, $hashstrategy->getKeyHash($distribution, '{foo}:bar')); + $this->assertSame($expected, $hashstrategy->getKeyHash($distribution, '{foo}:baz')); + $this->assertSame($expected, $hashstrategy->getKeyHash($distribution, 'bar:{foo}:bar')); + + $this->assertSame(0, $hashstrategy->getKeyHash($distribution, '')); + $this->assertSame(0, $hashstrategy->getKeyHash($distribution, '{}')); + } + + /** + * @group disconnected + */ + public function testSupportedCommands() + { + $hashstrategy = new CommandHashStrategy(); + + $this->assertSame($this->getExpectedCommands(), $hashstrategy->getSupportedCommands()); + } + + /** + * @group disconnected + */ + public function testReturnsNullOnUnsupportedCommand() + { + $distribution = new HashRing(); + $hashstrategy = new CommandHashStrategy(); + $command = ServerProfile::getDevelopment()->createCommand('ping'); + + $this->assertNull($hashstrategy->getHash($distribution, $command)); + } + + /** + * @group disconnected + */ + public function testFirstKeyCommands() + { + $distribution = new HashRing(); + $hashstrategy = new CommandHashStrategy(); + $profile = ServerProfile::getDevelopment(); + $arguments = array('key'); + + foreach ($this->getExpectedCommands('keys-first') as $commandID) { + $command = $profile->createCommand($commandID, $arguments); + $this->assertNotNull($hashstrategy->getHash($distribution, $command), $commandID); + } + } + + /** + * @group disconnected + */ + public function testAllKeysCommands() + { + $distribution = new HashRing(); + $hashstrategy = new CommandHashStrategy(); + $profile = ServerProfile::getDevelopment(); + $arguments = array('{key}:1', '{key}:2', '{key}:3', '{key}:4'); + + foreach ($this->getExpectedCommands('keys-all') as $commandID) { + $command = $profile->createCommand($commandID, $arguments); + $this->assertNotNull($hashstrategy->getHash($distribution, $command), $commandID); + } + } + + /** + * @group disconnected + */ + public function testInterleavedKeysCommands() + { + $distribution = new HashRing(); + $hashstrategy = new CommandHashStrategy(); + $profile = ServerProfile::getDevelopment(); + $arguments = array('{key}:1', 'value1', '{key}:2', 'value2'); + + foreach ($this->getExpectedCommands('keys-interleaved') as $commandID) { + $command = $profile->createCommand($commandID, $arguments); + $this->assertNotNull($hashstrategy->getHash($distribution, $command), $commandID); + } + } + + /** + * @group disconnected + */ + public function testKeysForBlockingListCommands() + { + $distribution = new HashRing(); + $hashstrategy = new CommandHashStrategy(); + $profile = ServerProfile::getDevelopment(); + $arguments = array('{key}:1', '{key}:2', 10); + + foreach ($this->getExpectedCommands('keys-blockinglist') as $commandID) { + $command = $profile->createCommand($commandID, $arguments); + $this->assertNotNull($hashstrategy->getHash($distribution, $command), $commandID); + } + } + + /** + * @group disconnected + */ + public function testKeysForZsetAggregationCommands() + { + $distribution = new HashRing(); + $hashstrategy = new CommandHashStrategy(); + $profile = ServerProfile::getDevelopment(); + $arguments = array('{key}:destination', 2, '{key}:1', '{key}:1', array('aggregate' => 'SUM')); + + foreach ($this->getExpectedCommands('keys-zaggregated') as $commandID) { + $command = $profile->createCommand($commandID, $arguments); + $this->assertNotNull($hashstrategy->getHash($distribution, $command), $commandID); + } + } + + // ******************************************************************** // + // ---- 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 key space */ + 'EXISTS' => 'keys-first', + 'DEL' => 'keys-all', + 'TYPE' => 'keys-first', + 'EXPIRE' => 'keys-first', + 'EXPIREAT' => 'keys-first', + 'PERSIST' => 'keys-first', + 'PEXPIRE' => 'keys-first', + 'PEXPIREAT' => 'keys-first', + 'TTL' => 'keys-first', + 'PTTL' => 'keys-first', + 'SORT' => 'keys-first', // TODO + + /* commands operating on string values */ + 'APPEND' => 'keys-first', + 'DECR' => 'keys-first', + 'DECRBY' => 'keys-first', + 'GET' => 'keys-first', + 'GETBIT' => 'keys-first', + 'MGET' => 'keys-all', + 'SET' => 'keys-first', + 'GETRANGE' => 'keys-first', + 'GETSET' => 'keys-first', + 'INCR' => 'keys-first', + 'INCRBY' => 'keys-first', + 'SETBIT' => 'keys-first', + 'SETEX' => 'keys-first', + 'MSET' => 'keys-interleaved', + 'MSETNX' => 'keys-interleaved', + 'SETNX' => 'keys-first', + 'SETRANGE' => 'keys-first', + 'STRLEN' => 'keys-first', + 'SUBSTR' => 'keys-first', + + /* commands operating on lists */ + 'LINSERT' => 'keys-first', + 'LINDEX' => 'keys-first', + 'LLEN' => 'keys-first', + 'LPOP' => 'keys-first', + 'RPOP' => 'keys-first', + 'RPOPLPUSH' => 'keys-all', + 'BLPOP' => 'keys-blockinglist', + 'BRPOP' => 'keys-blockinglist', + 'BRPOPLPUSH' => 'keys-blockinglist', + 'LPUSH' => 'keys-first', + 'LPUSHX' => 'keys-first', + 'RPUSH' => 'keys-first', + 'RPUSHX' => 'keys-first', + 'LRANGE' => 'keys-first', + 'LREM' => 'keys-first', + 'LSET' => 'keys-first', + 'LTRIM' => 'keys-first', + + /* commands operating on sets */ + 'SADD' => 'keys-first', + 'SCARD' => 'keys-first', + 'SDIFF' => 'keys-all', + 'SDIFFSTORE' => 'keys-all', + 'SINTER' => 'keys-all', + 'SINTERSTORE' => 'keys-all', + 'SUNION' => 'keys-all', + 'SUNIONSTORE' => 'keys-all', + 'SISMEMBER' => 'keys-first', + 'SMEMBERS' => 'keys-first', + 'SPOP' => 'keys-first', + 'SRANDMEMBER' => 'keys-first', + 'SREM' => 'keys-first', + + /* commands operating on sorted sets */ + 'ZADD' => 'keys-first', + 'ZCARD' => 'keys-first', + 'ZCOUNT' => 'keys-first', + 'ZINCRBY' => 'keys-first', + 'ZINTERSTORE' => 'keys-zaggregated', + 'ZRANGE' => 'keys-first', + 'ZRANGEBYSCORE' => 'keys-first', + 'ZRANK' => 'keys-first', + 'ZREM' => 'keys-first', + 'ZREMRANGEBYRANK' => 'keys-first', + 'ZREMRANGEBYSCORE' => 'keys-first', + 'ZREVRANGE' => 'keys-first', + 'ZREVRANGEBYSCORE' => 'keys-first', + 'ZREVRANK' => 'keys-first', + 'ZSCORE' => 'keys-first', + 'ZUNIONSTORE' => 'keys-zaggregated', + + /* commands operating on hashes */ + 'HDEL' => 'keys-first', + 'HEXISTS' => 'keys-first', + 'HGET' => 'keys-first', + 'HGETALL' => 'keys-first', + 'HMGET' => 'keys-first', + 'HINCRBY' => 'keys-first', + 'HINCRBYFLOAT' => 'keys-first', + 'HKEYS' => 'keys-first', + 'HLEN' => 'keys-first', + 'HSET' => 'keys-first', + 'HSETNX' => 'keys-first', + 'HVALS' => 'keys-first', + ); + + if (isset($type)) { + $commands = array_filter($commands, function($expectedType) use($type) { + return $expectedType === $type; + }); + } + + return array_keys($commands); + } +} diff --git a/tests/Predis/Connection/PredisClusterTest.php b/tests/Predis/Connection/PredisClusterTest.php index 7bc158a0..63ee640e 100644 --- a/tests/Predis/Connection/PredisClusterTest.php +++ b/tests/Predis/Connection/PredisClusterTest.php @@ -249,7 +249,7 @@ class PredisClusterTest extends StandardTestCase /** * @group disconnected * @expectedException Predis\NotSupportedException - * @expectedExceptionMessage Cannot send 'PING' commands to a cluster of connections + * @expectedExceptionMessage Cannot send PING to a cluster of connections */ public function testThrowsExceptionOnNonShardableCommand() { diff --git a/tests/Predis/HelpersTest.php b/tests/Predis/HelpersTest.php index f36f3320..db5a131d 100644 --- a/tests/Predis/HelpersTest.php +++ b/tests/Predis/HelpersTest.php @@ -79,18 +79,4 @@ class HelpersTest extends StandardTestCase $arguments = array(new \stdClass()); $this->assertSame($arguments, Helpers::filterArrayArguments($arguments)); } - - /** - * @group disconnected - */ - public function testExtractKeyTag() - { - $this->assertEquals('foo:bar', Helpers::extractKeyTag('foo:bar')); - $this->assertEquals('foo:', Helpers::extractKeyTag('{foo:}bar')); - $this->assertEquals('bar', Helpers::extractKeyTag('foo:{bar}')); - $this->assertEquals('foo:bar', Helpers::extractKeyTag('{foo:bar}')); - $this->assertEquals('', Helpers::extractKeyTag('foo{}:bar')); - $this->assertEquals('', Helpers::extractKeyTag('')); - $this->assertEquals('', Helpers::extractKeyTag('{}')); - } }