diff --git a/src/ClientContextInterface.php b/src/ClientContextInterface.php index edc6f3b4..a4100d4b 100644 --- a/src/ClientContextInterface.php +++ b/src/ClientContextInterface.php @@ -38,6 +38,7 @@ use Predis\Command\CommandInterface; * @method $this append($key, $value) * @method $this bitcount($key, $start = null, $end = null) * @method $this bitop($operation, $destkey, $key) + * @method $this bitfield($key, ...) * @method $this decr($key) * @method $this decrby($key, $decrement) * @method $this get($key) @@ -98,7 +99,7 @@ use Predis\Command\CommandInterface; * @method $this sismember($key, $member) * @method $this smembers($key) * @method $this smove($source, $destination, $member) - * @method $this spop($key) + * @method $this spop($key, $count = null) * @method $this srandmember($key, $count = null) * @method $this srem($key, $member) * @method $this sscan($key, $cursor, array $options = null) @@ -156,6 +157,12 @@ use Predis\Command\CommandInterface; * @method $this slowlog($subcommand, $argument = null) * @method $this time() * @method $this command() + * @method $this geoadd($key, $longitude, $latitude, $member) + * @method $this geohash($key, array $members) + * @method $this geopos($key, array $members) + * @method $this geodist($key, $member1, $member2, $unit = null) + * @method $this georadius($key, $longitude, $latitude, $radius, $unit, array $options = null) + * @method $this georadiusbymember($key, $member, $radius, $unit, array $options = null) * * @author Daniele Alessandri */ diff --git a/src/ClientInterface.php b/src/ClientInterface.php index effd7c09..440d6daf 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -46,6 +46,7 @@ use Predis\Profile\ProfileInterface; * @method int append($key, $value) * @method int bitcount($key, $start = null, $end = null) * @method int bitop($operation, $destkey, $key) + * @method array bitfield($key, ...) * @method int decr($key) * @method int decrby($key, $decrement) * @method string get($key) @@ -106,7 +107,7 @@ use Predis\Profile\ProfileInterface; * @method int sismember($key, $member) * @method array smembers($key) * @method int smove($source, $destination, $member) - * @method string spop($key) + * @method string spop($key, $count = null) * @method string srandmember($key, $count = null) * @method int srem($key, $member) * @method array sscan($key, $cursor, array $options = null) @@ -164,6 +165,12 @@ use Predis\Profile\ProfileInterface; * @method mixed slowlog($subcommand, $argument = null) * @method array time() * @method array command() + * @method int geoadd($key, $longitude, $latitude, $member) + * @method array geohash($key, array $members) + * @method array geopos($key, array $members) + * @method string geodist($key, $member1, $member2, $unit = null) + * @method array georadius($key, $longitude, $latitude, $radius, $unit, array $options = null) + * @method array georadiusbymember($key, $member, $radius, $unit, array $options = null) * * @author Daniele Alessandri */ diff --git a/src/Cluster/ClusterStrategy.php b/src/Cluster/ClusterStrategy.php index 9ef6c81d..16f30521 100644 --- a/src/Cluster/ClusterStrategy.php +++ b/src/Cluster/ClusterStrategy.php @@ -80,6 +80,7 @@ abstract class ClusterStrategy implements StrategyInterface 'SUBSTR' => $getKeyFromFirstArgument, 'BITOP' => array($this, 'getKeyFromBitOp'), 'BITCOUNT' => $getKeyFromFirstArgument, + 'BITFIELD' => $getKeyFromFirstArgument, /* commands operating on lists */ 'LINSERT' => $getKeyFromFirstArgument, @@ -164,6 +165,14 @@ abstract class ClusterStrategy implements StrategyInterface /* scripting */ 'EVAL' => array($this, 'getKeyFromScriptingCommands'), 'EVALSHA' => array($this, 'getKeyFromScriptingCommands'), + + /* commands performing geospatial operations */ + 'GEOADD' => $getKeyFromFirstArgument, + 'GEOHASH' => $getKeyFromFirstArgument, + 'GEOPOS' => $getKeyFromFirstArgument, + 'GEODIST' => $getKeyFromFirstArgument, + 'GEORADIUS' => array($this, 'getKeyFromGeoradiusCommands'), + 'GEORADIUSBYMEMBER' => array($this, 'getKeyFromGeoradiusCommands'), ); } @@ -322,6 +331,39 @@ abstract class ClusterStrategy implements StrategyInterface } } + /** + * Extracts the key from GEORADIUS and GEORADIUSBYMEMBER commands. + * + * @param CommandInterface $command Command instance. + * + * @return string|null + */ + protected function getKeyFromGeoradiusCommands(CommandInterface $command) + { + $arguments = $command->getArguments(); + $argc = count($arguments); + $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4; + + if ($argc > $startIndex) { + $keys = array($arguments[0]); + + for ($i = $startIndex; $i < $argc; $i++) { + $argument = strtoupper($arguments[$i]); + if ($argument === 'STORE' || $argument === 'STOREDIST') { + $keys[] = $arguments[++$i]; + } + } + + if ($this->checkSameSlotForKeys($keys)) { + return $arguments[0]; + } else { + return null; + } + } + + return $arguments[0]; + } + /** * Extracts the key from ZINTERSTORE and ZUNIONSTORE commands. * diff --git a/src/Command/GeospatialGeoAdd.php b/src/Command/GeospatialGeoAdd.php new file mode 100644 index 00000000..adca2ca5 --- /dev/null +++ b/src/Command/GeospatialGeoAdd.php @@ -0,0 +1,42 @@ + + * + * 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/geoadd + * + * @author Daniele Alessandri + */ +class GeospatialGeoAdd extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GEOADD'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[1])) { + foreach (array_pop($arguments) as $item) { + $arguments = array_merge($arguments, $item); + } + } + + return $arguments; + } +} diff --git a/src/Command/GeospatialGeoDist.php b/src/Command/GeospatialGeoDist.php new file mode 100644 index 00000000..17c5f549 --- /dev/null +++ b/src/Command/GeospatialGeoDist.php @@ -0,0 +1,28 @@ + + * + * 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/geodist + * + * @author Daniele Alessandri + */ +class GeospatialGeoDist extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GEODIST'; + } +} diff --git a/src/Command/GeospatialGeoHash.php b/src/Command/GeospatialGeoHash.php new file mode 100644 index 00000000..2eccaf4f --- /dev/null +++ b/src/Command/GeospatialGeoHash.php @@ -0,0 +1,41 @@ + + * + * 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/geohash + * + * @author Daniele Alessandri + */ +class GeospatialGeoHash extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GEOHASH'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[1])) { + $members = array_pop($arguments); + $arguments = array_merge($arguments, $members); + } + + return $arguments; + } +} diff --git a/src/Command/GeospatialGeoPos.php b/src/Command/GeospatialGeoPos.php new file mode 100644 index 00000000..6b7a9a3d --- /dev/null +++ b/src/Command/GeospatialGeoPos.php @@ -0,0 +1,41 @@ + + * + * 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/geopos + * + * @author Daniele Alessandri + */ +class GeospatialGeoPos extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GEOPOS'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if (count($arguments) === 2 && is_array($arguments[1])) { + $members = array_pop($arguments); + $arguments = array_merge($arguments, $members); + } + + return $arguments; + } +} diff --git a/src/Command/GeospatialGeoRadius.php b/src/Command/GeospatialGeoRadius.php new file mode 100644 index 00000000..f2052148 --- /dev/null +++ b/src/Command/GeospatialGeoRadius.php @@ -0,0 +1,71 @@ + + * + * 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/georadius + * + * @author Daniele Alessandri + */ +class GeospatialGeoRadius extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GEORADIUS'; + } + + /** + * {@inheritdoc} + */ + protected function filterArguments(array $arguments) + { + if ($arguments && is_array(end($arguments))) { + $options = array_change_key_case(array_pop($arguments), CASE_UPPER); + + if (isset($options['WITHCOORD']) && $options['WITHCOORD'] == true) { + $arguments[] = 'WITHCOORD'; + } + + if (isset($options['WITHDIST']) && $options['WITHDIST'] == true) { + $arguments[] = 'WITHDIST'; + } + + if (isset($options['WITHHASH']) && $options['WITHHASH'] == true) { + $arguments[] = 'WITHHASH'; + } + + if (isset($options['COUNT'])) { + $arguments[] = 'COUNT'; + $arguments[] = $options['COUNT']; + } + + if (isset($options['SORT'])) { + $arguments[] = strtoupper($options['SORT']); + } + + if (isset($options['STORE'])) { + $arguments[] = 'STORE'; + $arguments[] = $options['STORE']; + } + + if (isset($options['STOREDIST'])) { + $arguments[] = 'STOREDIST'; + $arguments[] = $options['STOREDIST']; + } + } + + return $arguments; + } +} diff --git a/src/Command/GeospatialGeoRadiusByMember.php b/src/Command/GeospatialGeoRadiusByMember.php new file mode 100644 index 00000000..abfff7b4 --- /dev/null +++ b/src/Command/GeospatialGeoRadiusByMember.php @@ -0,0 +1,28 @@ + + * + * 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/georadiusbymember + * + * @author Daniele Alessandri + */ +class GeospatialGeoRadiusByMember extends GeospatialGeoRadius +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'GEORADIUSBYMEMBER'; + } +} diff --git a/src/Command/Processor/KeyPrefixProcessor.php b/src/Command/Processor/KeyPrefixProcessor.php index 0edb2142..75201276 100644 --- a/src/Command/Processor/KeyPrefixProcessor.php +++ b/src/Command/Processor/KeyPrefixProcessor.php @@ -159,6 +159,13 @@ class KeyPrefixProcessor implements ProcessorInterface 'BITPOS' => 'static::first', /* ---------------- Redis 3.2 ---------------- */ 'HSTRLEN' => 'static::first', + 'BITFIELD' => 'static::first', + 'GEOADD' => 'static::first', + 'GEOHASH' => 'static::first', + 'GEOPOS' => 'static::first', + 'GEODIST' => 'static::first', + 'GEORADIUS' => 'static::georadius', + 'GEORADIUSBYMEMBER' => 'static::georadius', ); } @@ -412,4 +419,32 @@ class KeyPrefixProcessor implements ProcessorInterface $command->setRawArguments($arguments); } } + + /** + * Applies the specified prefix to the key of a GEORADIUS command. + * + * @param CommandInterface $command Command instance. + * @param string $prefix Prefix string. + */ + public static function georadius(CommandInterface $command, $prefix) + { + if ($arguments = $command->getArguments()) { + $arguments[0] = "$prefix{$arguments[0]}"; + $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4; + + if (($count = count($arguments)) > $startIndex) { + for ($i = $startIndex; $i < $count; ++$i) { + switch (strtoupper($arguments[$i])) { + case 'STORE': + case 'STOREDIST': + $arguments[$i] = "$prefix{$arguments[++$i]}"; + break; + + } + } + } + + $command->setRawArguments($arguments); + } + } } diff --git a/src/Command/StringBitField.php b/src/Command/StringBitField.php new file mode 100644 index 00000000..9f4deaa6 --- /dev/null +++ b/src/Command/StringBitField.php @@ -0,0 +1,28 @@ + + * + * 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/bitfield + * + * @author Daniele Alessandri + */ +class StringBitField extends Command +{ + /** + * {@inheritdoc} + */ + public function getId() + { + return 'BITFIELD'; + } +} diff --git a/src/Profile/Factory.php b/src/Profile/Factory.php index bcee3c2c..260fee42 100644 --- a/src/Profile/Factory.php +++ b/src/Profile/Factory.php @@ -27,6 +27,7 @@ final class Factory '2.6' => 'Predis\Profile\RedisVersion260', '2.8' => 'Predis\Profile\RedisVersion280', '3.0' => 'Predis\Profile\RedisVersion300', + '3.2' => 'Predis\Profile\RedisVersion320', 'dev' => 'Predis\Profile\RedisUnstable', 'default' => 'Predis\Profile\RedisVersion300', ); diff --git a/src/Profile/RedisUnstable.php b/src/Profile/RedisUnstable.php index cf6174fe..573cc9e6 100644 --- a/src/Profile/RedisUnstable.php +++ b/src/Profile/RedisUnstable.php @@ -16,7 +16,7 @@ namespace Predis\Profile; * * @author Daniele Alessandri */ -class RedisUnstable extends RedisVersion300 +class RedisUnstable extends RedisVersion320 { /** * {@inheritdoc} @@ -32,10 +32,7 @@ class RedisUnstable extends RedisVersion300 public function getSupportedCommands() { return array_merge(parent::getSupportedCommands(), array( - /* ---------------- Redis 3.2 ---------------- */ - - /* commands operating on hashes */ - 'HSTRLEN' => 'Predis\Command\HashStringLength', + // EMPTY )); } } diff --git a/src/Profile/RedisVersion320.php b/src/Profile/RedisVersion320.php new file mode 100644 index 00000000..7de79573 --- /dev/null +++ b/src/Profile/RedisVersion320.php @@ -0,0 +1,281 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +/** + * Server profile for Redis 3.0. + * + * @author Daniele Alessandri + */ +class RedisVersion320 extends RedisProfile +{ + /** + * {@inheritdoc} + */ + public function getVersion() + { + return '3.2'; + } + + /** + * {@inheritdoc} + */ + public function getSupportedCommands() + { + return array( + /* ---------------- Redis 1.2 ---------------- */ + + /* commands operating on the key space */ + 'EXISTS' => 'Predis\Command\KeyExists', + 'DEL' => 'Predis\Command\KeyDelete', + 'TYPE' => 'Predis\Command\KeyType', + 'KEYS' => 'Predis\Command\KeyKeys', + 'RANDOMKEY' => 'Predis\Command\KeyRandom', + 'RENAME' => 'Predis\Command\KeyRename', + 'RENAMENX' => 'Predis\Command\KeyRenamePreserve', + 'EXPIRE' => 'Predis\Command\KeyExpire', + 'EXPIREAT' => 'Predis\Command\KeyExpireAt', + 'TTL' => 'Predis\Command\KeyTimeToLive', + 'MOVE' => 'Predis\Command\KeyMove', + 'SORT' => 'Predis\Command\KeySort', + 'DUMP' => 'Predis\Command\KeyDump', + 'RESTORE' => 'Predis\Command\KeyRestore', + + /* commands operating on string values */ + 'SET' => 'Predis\Command\StringSet', + 'SETNX' => 'Predis\Command\StringSetPreserve', + 'MSET' => 'Predis\Command\StringSetMultiple', + 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve', + 'GET' => 'Predis\Command\StringGet', + 'MGET' => 'Predis\Command\StringGetMultiple', + 'GETSET' => 'Predis\Command\StringGetSet', + 'INCR' => 'Predis\Command\StringIncrement', + 'INCRBY' => 'Predis\Command\StringIncrementBy', + 'DECR' => 'Predis\Command\StringDecrement', + 'DECRBY' => 'Predis\Command\StringDecrementBy', + + /* commands operating on lists */ + 'RPUSH' => 'Predis\Command\ListPushTail', + 'LPUSH' => 'Predis\Command\ListPushHead', + 'LLEN' => 'Predis\Command\ListLength', + 'LRANGE' => 'Predis\Command\ListRange', + 'LTRIM' => 'Predis\Command\ListTrim', + 'LINDEX' => 'Predis\Command\ListIndex', + 'LSET' => 'Predis\Command\ListSet', + 'LREM' => 'Predis\Command\ListRemove', + 'LPOP' => 'Predis\Command\ListPopFirst', + 'RPOP' => 'Predis\Command\ListPopLast', + 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead', + + /* commands operating on sets */ + 'SADD' => 'Predis\Command\SetAdd', + 'SREM' => 'Predis\Command\SetRemove', + 'SPOP' => 'Predis\Command\SetPop', + 'SMOVE' => 'Predis\Command\SetMove', + 'SCARD' => 'Predis\Command\SetCardinality', + 'SISMEMBER' => 'Predis\Command\SetIsMember', + 'SINTER' => 'Predis\Command\SetIntersection', + 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore', + 'SUNION' => 'Predis\Command\SetUnion', + 'SUNIONSTORE' => 'Predis\Command\SetUnionStore', + 'SDIFF' => 'Predis\Command\SetDifference', + 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore', + 'SMEMBERS' => 'Predis\Command\SetMembers', + 'SRANDMEMBER' => 'Predis\Command\SetRandomMember', + + /* commands operating on sorted sets */ + 'ZADD' => 'Predis\Command\ZSetAdd', + 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy', + 'ZREM' => 'Predis\Command\ZSetRemove', + 'ZRANGE' => 'Predis\Command\ZSetRange', + 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange', + 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore', + 'ZCARD' => 'Predis\Command\ZSetCardinality', + 'ZSCORE' => 'Predis\Command\ZSetScore', + 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore', + + /* connection related commands */ + 'PING' => 'Predis\Command\ConnectionPing', + 'AUTH' => 'Predis\Command\ConnectionAuth', + 'SELECT' => 'Predis\Command\ConnectionSelect', + 'ECHO' => 'Predis\Command\ConnectionEcho', + 'QUIT' => 'Predis\Command\ConnectionQuit', + + /* remote server control commands */ + 'INFO' => 'Predis\Command\ServerInfoV26x', + 'SLAVEOF' => 'Predis\Command\ServerSlaveOf', + 'MONITOR' => 'Predis\Command\ServerMonitor', + 'DBSIZE' => 'Predis\Command\ServerDatabaseSize', + 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase', + 'FLUSHALL' => 'Predis\Command\ServerFlushAll', + 'SAVE' => 'Predis\Command\ServerSave', + 'BGSAVE' => 'Predis\Command\ServerBackgroundSave', + 'LASTSAVE' => 'Predis\Command\ServerLastSave', + 'SHUTDOWN' => 'Predis\Command\ServerShutdown', + 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF', + + /* ---------------- Redis 2.0 ---------------- */ + + /* commands operating on string values */ + 'SETEX' => 'Predis\Command\StringSetExpire', + 'APPEND' => 'Predis\Command\StringAppend', + 'SUBSTR' => 'Predis\Command\StringSubstr', + + /* commands operating on lists */ + 'BLPOP' => 'Predis\Command\ListPopFirstBlocking', + 'BRPOP' => 'Predis\Command\ListPopLastBlocking', + + /* commands operating on sorted sets */ + 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore', + 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore', + 'ZCOUNT' => 'Predis\Command\ZSetCount', + 'ZRANK' => 'Predis\Command\ZSetRank', + 'ZREVRANK' => 'Predis\Command\ZSetReverseRank', + 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank', + + /* commands operating on hashes */ + 'HSET' => 'Predis\Command\HashSet', + 'HSETNX' => 'Predis\Command\HashSetPreserve', + 'HMSET' => 'Predis\Command\HashSetMultiple', + 'HINCRBY' => 'Predis\Command\HashIncrementBy', + 'HGET' => 'Predis\Command\HashGet', + 'HMGET' => 'Predis\Command\HashGetMultiple', + 'HDEL' => 'Predis\Command\HashDelete', + 'HEXISTS' => 'Predis\Command\HashExists', + 'HLEN' => 'Predis\Command\HashLength', + 'HKEYS' => 'Predis\Command\HashKeys', + 'HVALS' => 'Predis\Command\HashValues', + 'HGETALL' => 'Predis\Command\HashGetAll', + + /* transactions */ + 'MULTI' => 'Predis\Command\TransactionMulti', + 'EXEC' => 'Predis\Command\TransactionExec', + 'DISCARD' => 'Predis\Command\TransactionDiscard', + + /* publish - subscribe */ + 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe', + 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe', + 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern', + 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern', + 'PUBLISH' => 'Predis\Command\PubSubPublish', + + /* remote server control commands */ + 'CONFIG' => 'Predis\Command\ServerConfig', + + /* ---------------- Redis 2.2 ---------------- */ + + /* commands operating on the key space */ + 'PERSIST' => 'Predis\Command\KeyPersist', + + /* commands operating on string values */ + 'STRLEN' => 'Predis\Command\StringStrlen', + 'SETRANGE' => 'Predis\Command\StringSetRange', + 'GETRANGE' => 'Predis\Command\StringGetRange', + 'SETBIT' => 'Predis\Command\StringSetBit', + 'GETBIT' => 'Predis\Command\StringGetBit', + + /* commands operating on lists */ + 'RPUSHX' => 'Predis\Command\ListPushTailX', + 'LPUSHX' => 'Predis\Command\ListPushHeadX', + 'LINSERT' => 'Predis\Command\ListInsert', + 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking', + + /* commands operating on sorted sets */ + 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore', + + /* transactions */ + 'WATCH' => 'Predis\Command\TransactionWatch', + 'UNWATCH' => 'Predis\Command\TransactionUnwatch', + + /* remote server control commands */ + 'OBJECT' => 'Predis\Command\ServerObject', + 'SLOWLOG' => 'Predis\Command\ServerSlowlog', + + /* ---------------- Redis 2.4 ---------------- */ + + /* remote server control commands */ + 'CLIENT' => 'Predis\Command\ServerClient', + + /* ---------------- Redis 2.6 ---------------- */ + + /* commands operating on the key space */ + 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive', + 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire', + 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt', + 'MIGRATE' => 'Predis\Command\KeyMigrate', + + /* 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', + + /* scripting */ + 'EVAL' => 'Predis\Command\ServerEval', + 'EVALSHA' => 'Predis\Command\ServerEvalSHA', + 'SCRIPT' => 'Predis\Command\ServerScript', + + /* remote server control commands */ + 'TIME' => 'Predis\Command\ServerTime', + 'SENTINEL' => 'Predis\Command\ServerSentinel', + + /* ---------------- Redis 2.8 ---------------- */ + + /* commands operating on the key space */ + 'SCAN' => 'Predis\Command\KeyScan', + + /* commands operating on string values */ + 'BITPOS' => 'Predis\Command\StringBitPos', + + /* commands operating on sets */ + 'SSCAN' => 'Predis\Command\SetScan', + + /* commands operating on sorted sets */ + 'ZSCAN' => 'Predis\Command\ZSetScan', + 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount', + 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex', + 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex', + 'ZREVRANGEBYLEX' => 'Predis\Command\ZSetReverseRangeByLex', + + /* commands operating on hashes */ + 'HSCAN' => 'Predis\Command\HashScan', + + /* publish - subscribe */ + 'PUBSUB' => 'Predis\Command\PubSubPubsub', + + /* commands operating on HyperLogLog */ + 'PFADD' => 'Predis\Command\HyperLogLogAdd', + 'PFCOUNT' => 'Predis\Command\HyperLogLogCount', + 'PFMERGE' => 'Predis\Command\HyperLogLogMerge', + + /* remote server control commands */ + 'COMMAND' => 'Predis\Command\ServerCommand', + + /* ---------------- Redis 3.2 ---------------- */ + + /* commands operating on hashes */ + 'HSTRLEN' => 'Predis\Command\HashStringLength', + 'BITFIELD' => 'Predis\Command\StringBitField', + + /* commands performing geospatial operations */ + 'GEOADD' => 'Predis\Command\GeospatialGeoAdd', + 'GEOHASH' => 'Predis\Command\GeospatialGeoHash', + 'GEOPOS' => 'Predis\Command\GeospatialGeoPos', + 'GEODIST' => 'Predis\Command\GeospatialGeoDist', + 'GEORADIUS' => 'Predis\Command\GeospatialGeoRadius', + 'GEORADIUSBYMEMBER' => 'Predis\Command\GeospatialGeoRadiusByMember', + ); + } +} diff --git a/src/Replication/ReplicationStrategy.php b/src/Replication/ReplicationStrategy.php index 454f1caa..72be1564 100644 --- a/src/Replication/ReplicationStrategy.php +++ b/src/Replication/ReplicationStrategy.php @@ -114,6 +114,57 @@ class ReplicationStrategy return true; } + /** + * Checks if BITFIELD performs a read-only operation by looking for certain + * SET and INCRYBY modifiers in the arguments array of the command. + * + * @param CommandInterface $command Command instance. + * + * @return bool + */ + protected function isBitfieldReadOnly(CommandInterface $command) + { + $arguments = $command->getArguments(); + $argc = count($arguments); + + if ($argc >= 2) { + for ($i = 1; $i < $argc; $i++) { + $argument = strtoupper($arguments[$i]); + if ($argument === 'SET' || $argument === 'INCRBY') { + return false; + } + } + } + + return true; + } + + /** + * Checks if a GEORADIUS command is a readable operation by parsing the + * arguments array of the specified commad instance. + * + * @param CommandInterface $command Command instance. + * + * @return bool + */ + protected function isGeoradiusReadOnly(CommandInterface $command) + { + $arguments = $command->getArguments(); + $argc = count($arguments); + $startIndex = $command->getId() === 'GEORADIUS' ? 5 : 4; + + if ($argc > $startIndex) { + for ($i = $startIndex; $i < $argc; $i++) { + $argument = strtoupper($arguments[$i]); + if ($argument === 'STORE' || $argument === 'STOREDIST') { + return false; + } + } + } + + return true; + } + /** * Marks a command as a read-only operation. * @@ -242,6 +293,12 @@ class ReplicationStrategy 'TIME' => true, 'PFCOUNT' => true, 'SORT' => array($this, 'isSortReadOnly'), + 'BITFIELD' => array($this, 'isBitfieldReadOnly'), + 'GEOHASH' => true, + 'GEOPOS' => true, + 'GEODIST' => true, + 'GEORADIUS' => array($this, 'isGeoradiusReadOnly'), + 'GEORADIUSBYMEMBER' => array($this, 'isGeoradiusReadOnly'), ); } } diff --git a/tests/Predis/Cluster/PredisStrategyTest.php b/tests/Predis/Cluster/PredisStrategyTest.php index 5fec3eba..0faa7324 100644 --- a/tests/Predis/Cluster/PredisStrategyTest.php +++ b/tests/Predis/Cluster/PredisStrategyTest.php @@ -170,6 +170,40 @@ class PredisStrategyTest extends PredisTestCase } } + /** + * @group disconnected + */ + public function testKeysForGeoradiusCommand() + { + $strategy = $this->getClusterStrategy(); + $profile = Profile\Factory::getDevelopment(); + + $commandID = 'GEORADIUS'; + + $command = $profile->createCommand($commandID, array('{key}:1', 10, 10, 1, 'km')); + $this->assertNotNull($strategy->getSlot($command), $commandID); + + $command = $profile->createCommand($commandID, array('{key}:1', 10, 10, 1, 'km', 'store', '{key}:2', 'storedist', '{key}:3')); + $this->assertNotNull($strategy->getSlot($command), $commandID); + } + + /** + * @group disconnected + */ + public function testKeysForGeoradiusByMemberCommand() + { + $strategy = $this->getClusterStrategy(); + $profile = Profile\Factory::getDevelopment(); + + $commandID = 'GEORADIUSBYMEMBER'; + + $command = $profile->createCommand($commandID, array('{key}:1', 'member', 1, 'km')); + $this->assertNotNull($strategy->getSlot($command), $commandID); + + $command = $profile->createCommand($commandID, array('{key}:1', 'member', 1, 'km', 'store', '{key}:2', 'storedist', '{key}:3')); + $this->assertNotNull($strategy->getSlot($command), $commandID); + } + /** * @group disconnected */ @@ -310,6 +344,7 @@ class PredisStrategyTest extends PredisTestCase 'SUBSTR' => 'keys-first', 'BITOP' => 'keys-bitop', 'BITCOUNT' => 'keys-first', + 'BITFIELD' => 'keys-first', /* commands operating on lists */ 'LINSERT' => 'keys-first', @@ -394,6 +429,14 @@ class PredisStrategyTest extends PredisTestCase /* scripting */ 'EVAL' => 'keys-script', 'EVALSHA' => 'keys-script', + + /* commands performing geospatial operations */ + 'GEOADD' => 'keys-first', + 'GEOHASH' => 'keys-first', + 'GEOPOS' => 'keys-first', + 'GEODIST' => 'keys-first', + 'GEORADIUS' => 'keys-georadius', + 'GEORADIUSBYMEMBER' => 'keys-georadius', ); if (isset($type)) { diff --git a/tests/Predis/Cluster/RedisStrategyTest.php b/tests/Predis/Cluster/RedisStrategyTest.php index a2ecf5ad..7b6fe116 100644 --- a/tests/Predis/Cluster/RedisStrategyTest.php +++ b/tests/Predis/Cluster/RedisStrategyTest.php @@ -183,6 +183,40 @@ class RedisStrategyTest extends PredisTestCase } } + /** + * @group disconnected + */ + public function testKeysForGeoradiusCommand() + { + $strategy = $this->getClusterStrategy(); + $profile = Profile\Factory::getDevelopment(); + + $commandID = 'GEORADIUS'; + + $command = $profile->createCommand($commandID, array('{key}:1', 10, 10, 1, 'km')); + $this->assertNotNull($strategy->getSlot($command), $commandID); + + $command = $profile->createCommand($commandID, array('{key}:1', 10, 10, 1, 'km', 'store', '{key}:2', 'storedist', '{key}:3')); + $this->assertNotNull($strategy->getSlot($command), $commandID); + } + + /** + * @group disconnected + */ + public function testKeysForGeoradiusByMemberCommand() + { + $strategy = $this->getClusterStrategy(); + $profile = Profile\Factory::getDevelopment(); + + $commandID = 'GEORADIUSBYMEMBER'; + + $command = $profile->createCommand($commandID, array('{key}:1', 'member', 1, 'km')); + $this->assertNotNull($strategy->getSlot($command), $commandID); + + $command = $profile->createCommand($commandID, array('{key}:1', 'member', 1, 'km', 'store', '{key}:2', 'storedist', '{key}:3')); + $this->assertNotNull($strategy->getSlot($command), $commandID); + } + /** * @group disconnected */ @@ -320,6 +354,7 @@ class RedisStrategyTest extends PredisTestCase 'SUBSTR' => 'keys-first', 'BITOP' => 'keys-bitop', 'BITCOUNT' => 'keys-first', + 'BITFIELD' => 'keys-first', /* commands operating on lists */ 'LINSERT' => 'keys-first', @@ -404,6 +439,14 @@ class RedisStrategyTest extends PredisTestCase /* scripting */ 'EVAL' => 'keys-script', 'EVALSHA' => 'keys-script', + + /* commands performing geospatial operations */ + 'GEOADD' => 'keys-first', + 'GEOHASH' => 'keys-first', + 'GEOPOS' => 'keys-first', + 'GEODIST' => 'keys-first', + 'GEORADIUS' => 'keys-georadius', + 'GEORADIUSBYMEMBER' => 'keys-georadius', ); if (isset($type)) { diff --git a/tests/Predis/Command/GeospatialGeoAddTest.php b/tests/Predis/Command/GeospatialGeoAddTest.php new file mode 100644 index 00000000..d2be244a --- /dev/null +++ b/tests/Predis/Command/GeospatialGeoAddTest.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @group commands + * @group realm-geospatial + */ +class GeospatialGeoAddTest extends PredisCommandTestCase +{ + /** + * {@inheritdoc} + */ + protected function getExpectedCommand() + { + return 'Predis\Command\GeospatialGeoAdd'; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedId() + { + return 'GEOADD'; + } + + /** + * @group disconnected + */ + public function testFilterArguments() + { + $arguments = array('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania'); + $expected = array('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania'); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testFilterArgumentsWithMembersAsSingleArray() + { + $arguments = array('Sicily', array( + array('13.361389', '38.115556', 'Palermo'), + array('15.087269', '37.502669', 'Catania'), + )); + + $expected = array('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania'); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse() + { + $raw = 1; + $expected = 1; + + $command = $this->getCommand(); + + $this->assertSame($expected, $command->parseResponse($raw)); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + */ + public function testCommandFillsSortedSet() + { + $redis = $this->getClient(); + + $redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo'); + $this->assertSame(array('Palermo' => '3479099956230698'), $redis->zrange('Sicily', 0, -1, 'WITHSCORES')); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + * @expectedException \Predis\Response\ServerException + * @expectedExceptionMessage Operation against a key holding the wrong kind of value + */ + public function testThrowsExceptionOnWrongType() + { + $redis = $this->getClient(); + + $redis->lpush('Sicily', 'Palermo'); + $redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo'); + } +} diff --git a/tests/Predis/Command/GeospatialGeoDistTest.php b/tests/Predis/Command/GeospatialGeoDistTest.php new file mode 100644 index 00000000..33250a3c --- /dev/null +++ b/tests/Predis/Command/GeospatialGeoDistTest.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @group commands + * @group realm-geospatial + */ +class GeospatialGeoDistTest extends PredisCommandTestCase +{ + /** + * {@inheritdoc} + */ + protected function getExpectedCommand() + { + return 'Predis\Command\GeospatialGeoDist'; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedId() + { + return 'GEODIST'; + } + + /** + * @group disconnected + */ + public function testFilterArguments() + { + $arguments = array('key', 'member:1', 'member:2', 'km'); + $expected = array('key', 'member:1', 'member:2', 'km'); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse() + { + $raw = array('103.31822459492736'); + $expected = array('103.31822459492736'); + + $command = $this->getCommand(); + + $this->assertSame($expected, $command->parseResponse($raw)); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + */ + public function testCommandReturnsGeoDistance() + { + $redis = $this->getClient(); + + $redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania'); + $this->assertSame('166.2742', $redis->geodist('Sicily', 'Palermo', 'Catania', 'km')); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + * @expectedException \Predis\Response\ServerException + * @expectedExceptionMessage Operation against a key holding the wrong kind of value + */ + public function testThrowsExceptionOnWrongType() + { + $redis = $this->getClient(); + + $redis->lpush('Sicily', 'Palermo'); + $redis->geodist('Sicily', 'Palermo', 'Catania'); + } +} diff --git a/tests/Predis/Command/GeospatialGeoHashTest.php b/tests/Predis/Command/GeospatialGeoHashTest.php new file mode 100644 index 00000000..bd354146 --- /dev/null +++ b/tests/Predis/Command/GeospatialGeoHashTest.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @group commands + * @group realm-geospatial + */ +class GeospatialGeoHashTest extends PredisCommandTestCase +{ + /** + * {@inheritdoc} + */ + protected function getExpectedCommand() + { + return 'Predis\Command\GeospatialGeoHash'; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedId() + { + return 'GEOHASH'; + } + + /** + * @group disconnected + */ + public function testFilterArguments() + { + $arguments = array('key', 'member:1', 'member:2'); + $expected = array('key', 'member:1', 'member:2'); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testFilterArgumentsWithMembersAsSingleArray() + { + $arguments = array('key', array('member:1', 'member:2')); + $expected = array('key', 'member:1', 'member:2'); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse() + { + $raw = array('sqc8b49rny0', 'sqdtr74hyu0'); + $expected = array('sqc8b49rny0', 'sqdtr74hyu0'); + + $command = $this->getCommand(); + + $this->assertSame($expected, $command->parseResponse($raw)); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + */ + public function testCommandReturnsGeoHashes() + { + $redis = $this->getClient(); + + $redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania'); + $this->assertSame(array('sqc8b49rny0', 'sqdtr74hyu0'), $redis->geohash('Sicily', 'Palermo', 'Catania')); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + * @expectedException \Predis\Response\ServerException + * @expectedExceptionMessage Operation against a key holding the wrong kind of value + */ + public function testThrowsExceptionOnWrongType() + { + $redis = $this->getClient(); + + $redis->lpush('Sicily', 'Palermo'); + $redis->geohash('Sicily', 'Palermo'); + } +} diff --git a/tests/Predis/Command/GeospatialGeoPosTest.php b/tests/Predis/Command/GeospatialGeoPosTest.php new file mode 100644 index 00000000..18c87638 --- /dev/null +++ b/tests/Predis/Command/GeospatialGeoPosTest.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @group commands + * @group realm-geospatial + */ +class GeospatialGeoPosTest extends PredisCommandTestCase +{ + /** + * {@inheritdoc} + */ + protected function getExpectedCommand() + { + return 'Predis\Command\GeospatialGeoPos'; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedId() + { + return 'GEOPOS'; + } + + /** + * @group disconnected + */ + public function testFilterArguments() + { + $arguments = array('key', 'member:1', 'member:2'); + $expected = array('key', 'member:1', 'member:2'); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testFilterArgumentsWithMembersAsSingleArray() + { + $arguments = array('key', array('member:1', 'member:2')); + $expected = array('key', 'member:1', 'member:2'); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse() + { + $raw = array( + array("13.361389338970184", "38.115556395496299"), + array("15.087267458438873", "37.50266842333162"), + ); + + $expected = array( + array("13.361389338970184", "38.115556395496299"), + array("15.087267458438873", "37.50266842333162"), + ); + + $command = $this->getCommand(); + + $this->assertSame($expected, $command->parseResponse($raw)); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + */ + public function testCommandReturnsGeoPositions() + { + $redis = $this->getClient(); + + $redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania'); + $this->assertEquals(array( + array("13.361389338970184", "38.115556395496299"), + array("15.087267458438873", "37.50266842333162"), + ), $redis->geopos('Sicily', 'Palermo', 'Catania')); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + * @expectedException \Predis\Response\ServerException + * @expectedExceptionMessage Operation against a key holding the wrong kind of value + */ + public function testThrowsExceptionOnWrongType() + { + $redis = $this->getClient(); + + $redis->lpush('Sicily', 'Palermo'); + $redis->geopos('Sicily', 'Palermo'); + } +} diff --git a/tests/Predis/Command/GeospatialGeoRadiusByMemberTest.php b/tests/Predis/Command/GeospatialGeoRadiusByMemberTest.php new file mode 100644 index 00000000..b5523495 --- /dev/null +++ b/tests/Predis/Command/GeospatialGeoRadiusByMemberTest.php @@ -0,0 +1,168 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @group commands + * @group realm-geospatial + */ +class GeospatialGeoRadiusByMemberTest extends PredisCommandTestCase +{ + /** + * {@inheritdoc} + */ + protected function getExpectedCommand() + { + return 'Predis\Command\GeospatialGeoRadiusByMember'; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedId() + { + return 'GEORADIUSBYMEMBER'; + } + + /** + * @group disconnected + */ + public function testFilterArguments() + { + $arguments = array( + 'Sicily', 'Agrigento', 100, 'km', + 'WITHCOORD', 'WITHDIST', 'WITHHASH', 'COUNT', 1, 'ASC', 'STORE', 'key:store', 'STOREDIST', 'key:storedist' + ); + + $expected = array( + 'Sicily', 'Agrigento', 100, 'km', + 'WITHCOORD', 'WITHDIST', 'WITHHASH', 'COUNT', 1, 'ASC', 'STORE', 'key:store', 'STOREDIST', 'key:storedist' + ); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testFilterArgumentsWithComplexOptions() + { + $arguments = array( + 'Sicily', 'Agrigento', 100, 'km', array( + 'store' => 'key:store', + 'storedist' => 'key:storedist', + 'withdist' => true, + 'withcoord' => true, + 'withhash' => true, + 'count' => 1, + 'sort' => 'asc', + ), + ); + + $expected = array( + 'Sicily', 'Agrigento', 100, 'km', + 'WITHCOORD', 'WITHDIST', 'WITHHASH', 'COUNT', 1, 'ASC', 'STORE', 'key:store', 'STOREDIST', 'key:storedist' + ); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testFilterArgumentsWithSpecificOptionsSetToFalse() + { + $arguments = array( + 'Sicily', 'Agrigento', 100, 'km', array( + 'store' => 'key:store', + 'storedist' => 'key:storedist', + 'withdist' => false, + 'withcoord' => false, + 'withhash' => false, + 'count' => 1, + 'sort' => 'asc', + ), + ); + + $expected = array('Sicily', 'Agrigento', 100, 'km', 'COUNT', 1, 'ASC', 'STORE', 'key:store', 'STOREDIST', 'key:storedist'); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponseWithNoOptions() + { + $raw = array( + array('Agrigento', 'Palermo'), + ); + + $expected = array( + array('Agrigento', 'Palermo'), + ); + + $command = $this->getCommand(); + + $this->assertSame($expected, $command->parseResponse($raw)); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + */ + public function testCommandReturnsGeoRadiusInfoWithNoOptions() + { + $redis = $this->getClient(); + + $redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania', '13.583333', '37.316667', 'Agrigento'); + $this->assertEquals(array('Agrigento', 'Palermo'), $redis->georadiusbymember('Sicily', 'Agrigento', 100, 'km')); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + */ + public function testCommandReturnsGeoRadiusInfoWithOptions() + { + $redis = $this->getClient(); + + $redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania', '13.583333', '37.316667', 'Agrigento'); + $this->assertEquals(array( + array('Agrigento', '0.0000', array('13.5833314061164856', '37.31666804993816555')), + array('Palermo', '90.9778', array('13.361389338970184', '38.115556395496299')), + ), $redis->georadiusbymember('Sicily', 'Agrigento', 100, 'km', 'WITHDIST', 'WITHCOORD')); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + * @expectedException \Predis\Response\ServerException + * @expectedExceptionMessage Operation against a key holding the wrong kind of value + */ + public function testThrowsExceptionOnWrongType() + { + $redis = $this->getClient(); + + $redis->lpush('Sicily', 'Palermo'); + $redis->georadiusbymember('Sicily', 'Agrigento', 200, 'km'); + } +} diff --git a/tests/Predis/Command/GeospatialGeoRadiusTest.php b/tests/Predis/Command/GeospatialGeoRadiusTest.php new file mode 100644 index 00000000..83d74bed --- /dev/null +++ b/tests/Predis/Command/GeospatialGeoRadiusTest.php @@ -0,0 +1,170 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @group commands + * @group realm-geospatial + */ +class GeospatialGeoRadiusTest extends PredisCommandTestCase +{ + /** + * {@inheritdoc} + */ + protected function getExpectedCommand() + { + return 'Predis\Command\GeospatialGeoRadius'; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedId() + { + return 'GEORADIUS'; + } + + /** + * @group disconnected + */ + public function testFilterArguments() + { + $arguments = array( + 'Sicily', 15, 37, 200, 'km', + 'WITHCOORD', 'WITHDIST', 'WITHHASH', 'COUNT', 1, 'ASC', 'STORE', 'key:store', 'STOREDIST', 'key:storedist' + ); + + $expected = array( + 'Sicily', 15, 37, 200, 'km', + 'WITHCOORD', 'WITHDIST', 'WITHHASH', 'COUNT', 1, 'ASC', 'STORE', 'key:store', 'STOREDIST', 'key:storedist' + ); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testFilterArgumentsWithComplexOptions() + { + $arguments = array( + 'Sicily', 15, 37, 200, 'km', array( + 'store' => 'key:store', + 'storedist' => 'key:storedist', + 'withdist' => true, + 'withcoord' => true, + 'withhash' => true, + 'count' => 1, + 'sort' => 'asc', + ), + ); + + $expected = array( + 'Sicily', 15, 37, 200, 'km', + 'WITHCOORD', 'WITHDIST', 'WITHHASH', 'COUNT', 1, 'ASC', 'STORE', 'key:store', 'STOREDIST', 'key:storedist' + ); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testFilterArgumentsWithSpecificOptionsSetToFalse() + { + $arguments = array( + 'Sicily', 15, 37, 200, 'km', array( + 'store' => 'key:store', + 'storedist' => 'key:storedist', + 'withdist' => false, + 'withcoord' => false, + 'withhash' => false, + 'count' => 1, + 'sort' => 'asc', + ), + ); + + $expected = array('Sicily', 15, 37, 200, 'km', 'COUNT', 1, 'ASC', 'STORE', 'key:store', 'STOREDIST', 'key:storedist'); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponseWithNoOptions() + { + $raw = array( + array('Palermo', '190.4424'), + array('Catania', '56.4413'), + ); + + $expected = array( + array('Palermo', '190.4424'), + array('Catania', '56.4413'), + ); + + $command = $this->getCommand(); + + $this->assertSame($expected, $command->parseResponse($raw)); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + */ + public function testCommandReturnsGeoRadiusInfoWithNoOptions() + { + $redis = $this->getClient(); + + $redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania'); + $this->assertEquals(array('Palermo', 'Catania'), $redis->georadius('Sicily', 15, 37, 200, 'km')); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + */ + public function testCommandReturnsGeoRadiusInfoWithOptions() + { + $redis = $this->getClient(); + + $redis->geoadd('Sicily', '13.361389', '38.115556', 'Palermo', '15.087269', '37.502669', 'Catania'); + $this->assertEquals(array( + array('Palermo', '190.4424', array('13.361389338970184', '38.115556395496299')), + array('Catania', '56.4413', array('15.087267458438873', '37.50266842333162')), + ), $redis->georadius('Sicily', 15, 37, 200, 'km', 'WITHDIST', 'WITHCOORD')); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + * @expectedException \Predis\Response\ServerException + * @expectedExceptionMessage Operation against a key holding the wrong kind of value + */ + public function testThrowsExceptionOnWrongType() + { + $redis = $this->getClient(); + + $redis->lpush('Sicily', 'Palermo'); + $redis->georadius('Sicily', 15, 37, 200, 'km'); + } +} diff --git a/tests/Predis/Command/Processor/KeyPrefixProcessorTest.php b/tests/Predis/Command/Processor/KeyPrefixProcessorTest.php index 422218a6..c3f84e8b 100644 --- a/tests/Predis/Command/Processor/KeyPrefixProcessorTest.php +++ b/tests/Predis/Command/Processor/KeyPrefixProcessorTest.php @@ -873,6 +873,42 @@ class KeyPrefixProcessorTest extends PredisTestCase array('key', 'field'), array('prefix:key', 'field'), ), + array('BITFIELD', + array('key', 'GET', 'u8', '0', 'SET', 'u8', '0', '1'), + array('prefix:key', 'GET', 'u8', '0', 'SET', 'u8', '0', '1'), + ), + array('GEOADD', + array('key', '13.361389', '38.115556', 'member:1', '15.087269', '37.502669', 'member:2'), + array('prefix:key', '13.361389', '38.115556', 'member:1', '15.087269', '37.502669', 'member:2'), + ), + array('GEOHASH', + array('key', 'member:1', 'member:2'), + array('prefix:key', 'member:1', 'member:2'), + ), + array('GEOPOS', + array('key', 'member:1', 'member:2'), + array('prefix:key', 'member:1', 'member:2'), + ), + array('GEODIST', + array('key', 'member:1', 'member:2', 'km'), + array('prefix:key', 'member:1', 'member:2', 'km'), + ), + array('GEORADIUS', + array('key', '15', '37', '200', 'km'), + array('prefix:key', '15', '37', '200', 'km'), + ), + array('GEORADIUS', + array('key', '15', '37', '200', 'km', 'WITHDIST', 'STORE', 'key:store', 'STOREDIST', 'key:storedist'), + array('prefix:key', '15', '37', '200', 'km', 'WITHDIST', 'STORE', 'prefix:key:store', 'STOREDIST', 'prefix:key:storedist'), + ), + array('GEORADIUSBYMEMBER', + array('key', 'member', '100', 'km'), + array('prefix:key', 'member', '100', 'km'), + ), + array('GEORADIUSBYMEMBER', + array('key', 'member', '100', 'km', 'WITHDIST', 'STORE', 'key:store', 'STOREDIST', 'key:storedist'), + array('prefix:key', 'member', '100', 'km', 'WITHDIST', 'STORE', 'prefix:key:store', 'STOREDIST', 'prefix:key:storedist'), + ), ); } } diff --git a/tests/Predis/Command/SetPopTest.php b/tests/Predis/Command/SetPopTest.php index bb337a02..87b13c26 100644 --- a/tests/Predis/Command/SetPopTest.php +++ b/tests/Predis/Command/SetPopTest.php @@ -38,8 +38,8 @@ class SetPopTest extends PredisCommandTestCase */ public function testFilterArguments() { - $arguments = array('key'); - $expected = array('key'); + $arguments = array('key', 2); + $expected = array('key', 2); $command = $this->getCommand(); $command->setArguments($arguments); @@ -70,6 +70,22 @@ class SetPopTest extends PredisCommandTestCase $this->assertNull($redis->spop('letters')); } + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + */ + public function testPopsMoreRandomMembersFromSet() + { + $redis = $this->getClient(); + + $redis->sadd('letters', 'a', 'b', 'c'); + + $this->assertSameValues(array('a', 'b', 'c'), $redis->spop('letters', 3)); + $this->assertEmpty($redis->spop('letters', 3)); + + $this->assertNull($redis->spop('letters')); + } + /** * @group connected * @expectedException \Predis\Response\ServerException diff --git a/tests/Predis/Command/StringBitFieldTest.php b/tests/Predis/Command/StringBitFieldTest.php new file mode 100644 index 00000000..244ed0e0 --- /dev/null +++ b/tests/Predis/Command/StringBitFieldTest.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Command; + +/** + * @group commands + * @group realm-string + */ +class StringBitFieldTest extends PredisCommandTestCase +{ + /** + * {@inheritdoc} + */ + protected function getExpectedCommand() + { + return 'Predis\Command\StringBitField'; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedId() + { + return 'BITFIELD'; + } + + /** + * @group disconnected + */ + public function testFilterArguments() + { + $arguments = array('key'); + $expected = array('key'); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testFilterMultipleArguments() + { + $arguments = array('key', 'incrby', 'u2', '100', '1', 'OVERFLOW', 'SAT', 'incrby', 'u2', '102', '1', 'GET', 'u2', '100'); + $expected = array('key', 'incrby', 'u2', '100', '1', 'OVERFLOW', 'SAT', 'incrby', 'u2', '102', '1', 'GET', 'u2', '100'); + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSame($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testParseResponse() + { + $raw = array(1); + $expected = array(1); + + $command = $this->getCommand(); + + $this->assertSame($expected, $command->parseResponse($raw)); + } + + /** + * @group disconnected + */ + public function testParseResponseComplex() + { + $raw = array(1, 0, 3); + $expected = array(1, 0, 3); + + $command = $this->getCommand(); + + $this->assertSame($expected, $command->parseResponse($raw)); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + */ + public function testBitfieldWithGetModifier() + { + $redis = $this->getClient(); + + $redis->setbit('string', 0, 1); + $redis->setbit('string', 8, 1); + + $this->assertSame(array(128), $redis->bitfield('string', 'GET', 'u8', 0)); + $this->assertSame(array(128, 1, 128), $redis->bitfield('string', 'GET', 'u8', 0, 'GET', 'u8', 1, 'GET', 'u8', 8)); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + */ + public function testBitfieldWithSetModifier() + { + $redis = $this->getClient(); + + $redis->setbit('string', 0, 1); + $redis->setbit('string', 8, 1); + + $this->assertSame(array(128), $redis->bitfield('string', 'SET', 'u8', 0, 1)); + $this->assertSame(array(1, 128), $redis->bitfield('string', 'SET', 'u8', 0, 128, 'SET', 'u8', 8, 1)); + $this->assertSame(array(1, 128), $redis->bitfield('string', 'SET', 'u8', 8, 128, 'GET', 'u8', 8)); + + $this->assertSame("\x80\x80", $redis->get('string')); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + */ + public function testBitfieldWithIncrbyModifier() + { + $redis = $this->getClient(); + + $redis->setbit('string', 0, 1); + $redis->setbit('string', 8, 1); + + $this->assertSame(array(138), $redis->bitfield('string', 'INCRBY', 'u8', 0, 10)); + $this->assertSame(array(143, 128), $redis->bitfield('string', 'INCRBY', 'u8', 0, 5, 'INCRBY', 'u8', 0, -15)); + + $this->assertSame("\x80\x80", $redis->get('string')); + } + + /** + * @group connected + * @requiresRedisVersion >= 3.2.0 + * @expectedException \Predis\Response\ServerException + * @expectedExceptionMessage Operation against a key holding the wrong kind of value + */ + public function testThrowsExceptionOnWrongType() + { + $this->markTestSkipped('Currently skipped due issues in Redis (see antirez/redis#3259).'); + + $redis = $this->getClient(); + + $redis->lpush('metavars', 'foo'); + $redis->bitfield('metavars', 'SET', 'u4', '0', '1'); + } +} diff --git a/tests/Predis/Profile/RedisUnstableTest.php b/tests/Predis/Profile/RedisUnstableTest.php index 77059386..75ffbf58 100644 --- a/tests/Predis/Profile/RedisUnstableTest.php +++ b/tests/Predis/Profile/RedisUnstableTest.php @@ -190,6 +190,13 @@ class RedisUnstableTest extends PredisProfileTestCase 149 => 'PFMERGE', 150 => 'COMMAND', 151 => 'HSTRLEN', + 152 => 'BITFIELD', + 153 => 'GEOADD', + 154 => 'GEOHASH', + 155 => 'GEOPOS', + 156 => 'GEODIST', + 157 => 'GEORADIUS', + 158 => 'GEORADIUSBYMEMBER', ); } } diff --git a/tests/Predis/Profile/RedisVersion320Test.php b/tests/Predis/Profile/RedisVersion320Test.php new file mode 100644 index 00000000..d68df913 --- /dev/null +++ b/tests/Predis/Profile/RedisVersion320Test.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Predis\Profile; + +/** + * + */ +class RedisVersion320Test extends PredisProfileTestCase +{ + /** + * {@inheritdoc} + */ + public function getProfile($version = null) + { + return new RedisVersion320(); + } + + /** + * {@inheritdoc} + */ + public function getExpectedVersion() + { + return '3.2'; + } + + /** + * {@inheritdoc} + */ + public function getExpectedCommands() + { + return array( + 0 => 'EXISTS', + 1 => 'DEL', + 2 => 'TYPE', + 3 => 'KEYS', + 4 => 'RANDOMKEY', + 5 => 'RENAME', + 6 => 'RENAMENX', + 7 => 'EXPIRE', + 8 => 'EXPIREAT', + 9 => 'TTL', + 10 => 'MOVE', + 11 => 'SORT', + 12 => 'DUMP', + 13 => 'RESTORE', + 14 => 'SET', + 15 => 'SETNX', + 16 => 'MSET', + 17 => 'MSETNX', + 18 => 'GET', + 19 => 'MGET', + 20 => 'GETSET', + 21 => 'INCR', + 22 => 'INCRBY', + 23 => 'DECR', + 24 => 'DECRBY', + 25 => 'RPUSH', + 26 => 'LPUSH', + 27 => 'LLEN', + 28 => 'LRANGE', + 29 => 'LTRIM', + 30 => 'LINDEX', + 31 => 'LSET', + 32 => 'LREM', + 33 => 'LPOP', + 34 => 'RPOP', + 35 => 'RPOPLPUSH', + 36 => 'SADD', + 37 => 'SREM', + 38 => 'SPOP', + 39 => 'SMOVE', + 40 => 'SCARD', + 41 => 'SISMEMBER', + 42 => 'SINTER', + 43 => 'SINTERSTORE', + 44 => 'SUNION', + 45 => 'SUNIONSTORE', + 46 => 'SDIFF', + 47 => 'SDIFFSTORE', + 48 => 'SMEMBERS', + 49 => 'SRANDMEMBER', + 50 => 'ZADD', + 51 => 'ZINCRBY', + 52 => 'ZREM', + 53 => 'ZRANGE', + 54 => 'ZREVRANGE', + 55 => 'ZRANGEBYSCORE', + 56 => 'ZCARD', + 57 => 'ZSCORE', + 58 => 'ZREMRANGEBYSCORE', + 59 => 'PING', + 60 => 'AUTH', + 61 => 'SELECT', + 62 => 'ECHO', + 63 => 'QUIT', + 64 => 'INFO', + 65 => 'SLAVEOF', + 66 => 'MONITOR', + 67 => 'DBSIZE', + 68 => 'FLUSHDB', + 69 => 'FLUSHALL', + 70 => 'SAVE', + 71 => 'BGSAVE', + 72 => 'LASTSAVE', + 73 => 'SHUTDOWN', + 74 => 'BGREWRITEAOF', + 75 => 'SETEX', + 76 => 'APPEND', + 77 => 'SUBSTR', + 78 => 'BLPOP', + 79 => 'BRPOP', + 80 => 'ZUNIONSTORE', + 81 => 'ZINTERSTORE', + 82 => 'ZCOUNT', + 83 => 'ZRANK', + 84 => 'ZREVRANK', + 85 => 'ZREMRANGEBYRANK', + 86 => 'HSET', + 87 => 'HSETNX', + 88 => 'HMSET', + 89 => 'HINCRBY', + 90 => 'HGET', + 91 => 'HMGET', + 92 => 'HDEL', + 93 => 'HEXISTS', + 94 => 'HLEN', + 95 => 'HKEYS', + 96 => 'HVALS', + 97 => 'HGETALL', + 98 => 'MULTI', + 99 => 'EXEC', + 100 => 'DISCARD', + 101 => 'SUBSCRIBE', + 102 => 'UNSUBSCRIBE', + 103 => 'PSUBSCRIBE', + 104 => 'PUNSUBSCRIBE', + 105 => 'PUBLISH', + 106 => 'CONFIG', + 107 => 'PERSIST', + 108 => 'STRLEN', + 109 => 'SETRANGE', + 110 => 'GETRANGE', + 111 => 'SETBIT', + 112 => 'GETBIT', + 113 => 'RPUSHX', + 114 => 'LPUSHX', + 115 => 'LINSERT', + 116 => 'BRPOPLPUSH', + 117 => 'ZREVRANGEBYSCORE', + 118 => 'WATCH', + 119 => 'UNWATCH', + 120 => 'OBJECT', + 121 => 'SLOWLOG', + 122 => 'CLIENT', + 123 => 'PTTL', + 124 => 'PEXPIRE', + 125 => 'PEXPIREAT', + 126 => 'MIGRATE', + 127 => 'PSETEX', + 128 => 'INCRBYFLOAT', + 129 => 'BITOP', + 130 => 'BITCOUNT', + 131 => 'HINCRBYFLOAT', + 132 => 'EVAL', + 133 => 'EVALSHA', + 134 => 'SCRIPT', + 135 => 'TIME', + 136 => 'SENTINEL', + 137 => 'SCAN', + 138 => 'BITPOS', + 139 => 'SSCAN', + 140 => 'ZSCAN', + 141 => 'ZLEXCOUNT', + 142 => 'ZRANGEBYLEX', + 143 => 'ZREMRANGEBYLEX', + 144 => 'ZREVRANGEBYLEX', + 145 => 'HSCAN', + 146 => 'PUBSUB', + 147 => 'PFADD', + 148 => 'PFCOUNT', + 149 => 'PFMERGE', + 150 => 'COMMAND', + 151 => 'HSTRLEN', + 152 => 'BITFIELD', + 153 => 'GEOADD', + 154 => 'GEOHASH', + 155 => 'GEOPOS', + 156 => 'GEODIST', + 157 => 'GEORADIUS', + 158 => 'GEORADIUSBYMEMBER', + ); + } +} diff --git a/tests/Predis/Replication/ReplicationStrategyTest.php b/tests/Predis/Replication/ReplicationStrategyTest.php index 02d00472..b1237c17 100644 --- a/tests/Predis/Replication/ReplicationStrategyTest.php +++ b/tests/Predis/Replication/ReplicationStrategyTest.php @@ -94,6 +94,105 @@ class ReplicationStrategyTest extends PredisTestCase ); } + /** + * @group disconnected + */ + public function testBitFieldCommand() + { + $profile = Profile\Factory::getDevelopment(); + $strategy = new ReplicationStrategy(); + + $command = $profile->createCommand('BITFIELD', array('key')); + $this->assertTrue( + $strategy->isReadOperation($command), + 'BITFIELD with no modifiers is expected to be a read operation.' + ); + + $command = $profile->createCommand('BITFIELD', array('key', 'GET', 'u4', '0')); + $this->assertTrue( + $strategy->isReadOperation($command), + 'BITFIELD with GET only is expected to be a read operation.' + ); + + $command = $profile->createCommand('BITFIELD', array('key', 'SET', 'u4', '0', 1)); + $this->assertFalse( + $strategy->isReadOperation($command), + 'BITFIELD with SET is expected to be a write operation.' + ); + + $command = $profile->createCommand('BITFIELD', array('key', 'INCRBY', 'u4', '0', 1)); + $this->assertFalse( + $strategy->isReadOperation($command), + 'BITFIELD with INCRBY is expected to be a write operation.' + ); + + $command = $profile->createCommand('BITFIELD', array('key', 'GET', 'u4', '0', 'INCRBY', 'u4', '0', 1)); + $this->assertFalse( + $strategy->isReadOperation($command), + 'BITFIELD with GET and INCRBY is expected to be a write operation.' + ); + + $command = $profile->createCommand('BITFIELD', array('key', 'GET', 'u4', '0', 'SET', 'u4', '0', 1)); + $this->assertFalse( + $strategy->isReadOperation($command), + 'BITFIELD with GET and SET is expected to be a write operation.' + ); + } + + /** + * @group disconnected + */ + public function testGeoradiusCommand() + { + $profile = Profile\Factory::getDevelopment(); + $strategy = new ReplicationStrategy(); + + $command = $profile->createCommand('GEORADIUS', array('key:geo', 15, 37, 200, 'km')); + $this->assertTrue( + $strategy->isReadOperation($command), + 'GEORADIUS is expected to be a read operation.' + ); + + $command = $profile->createCommand('GEORADIUS', array('key:geo', 15, 37, 200, 'km', 'store', 'key:store')); + $this->assertFalse( + $strategy->isReadOperation($command), + 'GEORADIUS with STORE is expected to be a write operation.' + ); + + $command = $profile->createCommand('GEORADIUS', array('key:geo', 15, 37, 200, 'km', 'storedist', 'key:storedist')); + $this->assertFalse( + $strategy->isReadOperation($command), + 'GEORADIUS with STOREDIST is expected to be a write operation.' + ); + } + + /** + * @group disconnected + */ + public function testGeoradiusByMemberCommand() + { + $profile = Profile\Factory::getDevelopment(); + $strategy = new ReplicationStrategy(); + + $command = $profile->createCommand('GEORADIUSBYMEMBER', array('key:geo', 15, 37, 200, 'km')); + $this->assertTrue( + $strategy->isReadOperation($command), + 'GEORADIUSBYMEMBER is expected to be a read operation.' + ); + + $command = $profile->createCommand('GEORADIUSBYMEMBER', array('key:geo', 15, 37, 200, 'km', 'store', 'key:store')); + $this->assertFalse( + $strategy->isReadOperation($command), + 'GEORADIUSBYMEMBER with STORE is expected to be a write operation.' + ); + + $command = $profile->createCommand('GEORADIUSBYMEMBER', array('key:geo', 15, 37, 200, 'km', 'storedist', 'key:storedist')); + $this->assertFalse( + $strategy->isReadOperation($command), + 'GEORADIUSBYMEMBER with STOREDIST is expected to be a write operation.' + ); + } + /** * @group disconnected * @expectedException \Predis\NotSupportedException @@ -316,6 +415,7 @@ class ReplicationStrategyTest extends PredisTestCase 'SETRANGE' => 'write', 'STRLEN' => 'read', 'SUBSTR' => 'read', + 'BITFIELD' => 'variable', /* commands operating on lists */ 'LINSERT' => 'write', @@ -392,6 +492,14 @@ class ReplicationStrategyTest extends PredisTestCase /* scripting */ 'EVAL' => 'write', 'EVALSHA' => 'write', + + /* commands performing geospatial operations */ + 'GEOADD' => 'write', + 'GEOHASH' => 'read', + 'GEOPOS' => 'read', + 'GEODIST' => 'read', + 'GEORADIUS' => 'variable', + 'GEORADIUSBYMEMBER' => 'variable', ); if (isset($type)) {