package.xml0000644000175000000120000004574413223116600013762 0ustar pyatsukhnenkowheel redis pecl.php.net PHP extension for interfacing with Redis This extension provides an API for communicating with Redis servers. Nicolas Favre-Felix nff n.favrefelix@gmail.com yes Michael Grunder mgrunder michael.grunder@gmail.com yes Pavlo Yatsukhnenko yatsukhnenko p.yatsukhnenko@gmail.com yes 2018-01-03 3.1.6 3.1.6 stable stable PHP phpredis 3.1.6 This release conains only fix of RedisArray distributor hashing function which was broken in 3.1.4. Huge thanks to @rexchen123 5.3.0 7.9.99 1.4.0b1 redis stable stable 3.1.6 3.1.6 2018-01-03 phpredis 3.1.6 This release conains only fix of RedisArray distributor hashing function which was broken in 3.1.4. Huge thanks to @rexchen123 stable stable 3.1.5 3.1.5 2017-12-20 phpredis 3.1.5 This is interim release which contains only bug fixes. * Fix segfault when extending Redis class in PHP 5 [d23eff] (Pavlo Yatsukhnenko) * Fix RedisCluster constructor with PHP 7 strict scalar type [5c21d7] (Pavlo Yatsukhnenko) * Allow to use empty string as persistant_id [344de5] (Pavlo Yatsukhnenko) * Fix cluster_init_seeds. [db1347] (@adlagares) * Fix z_seeds may be a reference [42581a] (@janic716) * PHP >=7.3 uses zend_string for php_url elements [b566fb] (@fmk) stable stable 3.1.4 3.1.4 2017-09-27 phpredis 3.1.4 The primary new feature phpredis 3.1.4 is the ability to send MULTI .. EXEC blocks in pipeline mode. There are also many bugfixes and minor improvements to the api, listed below: * Allow mixing MULTI and PIPELINE modes (experimental)! [5874b0] (Pavlo Yatsukhnenko) * Added integration for coverty static analysis and fixed several warnings [faac8b0, eff7398, 4766c25, 0438ab4, 1e0b065, 733732a, 26eeda5, 735025, 42f1c9, af71d4] (Pavlo Yatsukhnenko) * Added arginfo inrospection structures [81a0303, d5609fc, e5660be, 3c60e1f, 50dcb15, 6c2c6fa, 212e323, e23be2c, 682593d, f8de702, 4ef3acd, f116be9, 5c111dd, 9caa029, 0d69650, 6859828, 024e593, 3643ab6, f576fab, 122d41f, a09d0e6] (Tyson Andre, Pavlo Yatsukhnenko) * Fixed link to redis cluster documentation [3b0b06] (Pavlo Yatsukhnenko) * Remove unused PHP_RINIT and PHP_RSHUTDOWN functions [c760bf] (Pavlo Yatsukhnenko) * Removed duplicate HGET in redis array hash table, formatting [d0b9c5] (Pavlo Yatsukhnenko) * Treat NULL bulk as success for session read [659450] (Pavlo Yatsukhnenko) * Refactor redis_send_discard [ea15ce] (Pavlo Yatsukhnenko) * Updated runtime exception handling [8dcaa4, 7c1407] (Pavlo Yatsukhnenko) * Added a github issue template [61aba9] (Pavlo Yatsukhnenko) * Initialize gc member of zend_string [37f569) (Pavlo Yatsukhnenko) * Fix valgrind warnings [471ce07, 1ab89e1, b624a8b] (Pavlo Yatsukhnenko) * Fix php5/php7 compatibility layer [1ab89e, 4e3225] (Pavlo Yatsukhnenko) * Fix typo in README.markdown [e47e44] (Mark Shehata) * Improve redis array rehash [577a91] (Pavlo Yatsukhnenko) * Change redis array pure_cmds from zval to hashtable [a56ed7] (Pavlo Yatsukhnenko) * Don't try to set TCP_NODELAY on a unix socket and don't warn on multiple calls to pipeline [d11798, 77aeba] (Michael Grunder) * Use zend_string rather than char* for various context fields (err, prefix, etc) [2bf7b2] (Pavlo Yatsukhnenko) * Various other library fixes [142b51, 4452f6, e672f4, 658ee3, c9df77, 4a0a46] (Pavlo Yatsukhnenko) stable stable 3.1.3 3.1.3 2017-07-15 phpredis 3.1.3 This release contains two big improvements: 1. Adding a new printf like command construction function with additionaly format specifiers specific to phpredis. 2. Implementation of custom objects for Redis and RedisArray wich eliminates double hash lookup. Also many small improvements and bug fixes were made. * A printf like method to construct a Redis RESP command [a4a0ed, d75081, bdd287, 0eaeae, b3d00d] (Michael Grunder) * Use custom objects instead of zend_list for storing Redis/RedisArray [a765f8, 8fa85a] (Pavlo Yatsukhnenko) * Make sure redisCluster members are all initialized on (re)creation [162d88] (Michael Grunder) * Fix Null Bulk String response parsing in cluster library [058753] (Alberto Fernández) * Add hStrLen command [c52077, fb88e1] (Pavlo Yatsukhnenko) * Add optional COUNT argument to sPop [d2e203] (Michael Grunder) * Allow sInterStore to take one arg [26aec4, 4cd06b] (Michael Grunder) * Allow MIGRATE to accept multiple keys [9aa3db] (Michael Grunder) * Allow using numeric string in zInter command [ba0070] (Pavlo Yatsukhnenko) * Use crc32 table from PHP distro [f81694] (Pavlo Yatsukhnenko) * Use ZVAL_DEREF macros for dereference input variables [ad4596] (Pavlo Yatsukhnenko) * Add configureoption tag to package.xml [750963] (Pavlo Yatsukhnenko) * Fix read_timeout [18149e, b56dc4] (Pavlo Yatsukhnenko) * Fix zval_get_string impl for PHP5 [4e56ba] (Pavlo Yatsukhnenko) * Fix Redis/RedisArray segfaults [be5c1f, 635c3a, 1f8dde, 43e1e0] (Pavlo Yatsukhnenko) * Fix memory leak and potential segfault [aa6ff7, 88efaa] (Michael Grunder) * Throw exception for all non recoverable errors [e37239] (Pavlo Yatsukhnenko) * Assume "NULL bulk" reply as success (empty session data) [4a81e1] (Pavlo Yatsukhnenko) * Increase read buffers size [520e06] (Pavlo Yatsukhnenko) * Better documentation [f0c25a, c5991f, 9ec9ae] (Michael Grunder) * Better TravisCI integration [e37c08] (Pavlo Yatsukhnenko) * Refactoring (Pavlo Yatsukhnenko, Michael Grunder) stable stable 3.1.2 3.1.2 2017-03-16 phpredis 3.1.2 * RedisArray segfault fix [564ce3] (Pavlo Yatsukhnenko) * Small memory leak fix [645888b] (Mike Grunder) * Segfault fix when recreating RedisCluster objects [abf7d4] (Michael Grunder) * Fix for RedisCluster bulk response parsing [4121c4] (Alberto Fernández) * Re allow single array for sInterStore [6ef0c2, d01966] (Michael Grunder) * Better TravisCI integration [4fd2f6] (Pavlo Yatsukhnenko) beta beta 3.1.1RC2 3.1.1RC2 2017-01-16 phpredis 3.1.1RC2 * Additional test updates for 32 bit systems (@remicollet) * ARM rounding issue in tests (@remicollet) * Use new zend_list_close instead of zend_list_delete when reconnecting. * Refactoring of redis_boolean_response_impl and redis_sock_write (@yatsukhnenko) phpredis 3.1.1.RC1 This release contains mostly fixes for issues introduced when merging the php 5 and 7 codebase into a single branch. * Fixed a segfault in igbinary serialization (@yatsukhnenko) * Restore 2.2.8/3.0.0 functionality to distinguish between an error and simply empty session data. (@remicollet) * Fix double to string conversion function (@yatsukhnenko) * Use PHP_FE_END definition when available (@remicollet) * Fixed various 'static function declared but not used' warnings * Fixes to various calls which were typecasting pointers to the wrong size. (@remicollet) * Added php session unit test (@yatsukhnenko) * Added explicit module dependancy for igbinary (@remicollet) * Added phpinfo serialization information (@remicollet) stable stable 3.1.0 3.1.0 2016-12-14 phpredis 3.1.0 In this version of phpredis codebase was unified to work with all versions of php \o/ Also many bug fixes and some improvements has been made. --- Improvements --- * Support the client to Redis Cluster just having one master (andyli) [892e5646] * Allow both long and strings that are longs for zrangebyscore offset/limit (Michael Grunder) [bdcdd2aa] * Process NX|XX, CH and INCR options in zAdd command (Pavlo Yatsukhnenko) [71c9f7c8] --- Fixes --- * Fix incrby/decrby for large integers (Michael Grunder) [3a12758a] * Use static declarations for spl_ce_RuntimeException decl (Jeremy Mikola) [a9857d69] * Fixed method call problem causes session handler to display two times (ZiHang Gao) [24f86c49] * psetex method returns '+OK' on success, not true (sitri@ndxbn) [afcd8445] * Fix integer overflow for long (>32bit) increments in hIncrBy (iyesin) [58e1d799] * Move zend_object handler to the end (Michael Grunder) [34107966] * Using setOption on redis array causes immediate connection (Pavlo Yatsukhnenko) [f1a85b38] stable stable 2.2.8 2.2.8 2016-06-02 phpredis 2.2.8 The main improvement in this version of phpredis is support for Redis Cluster. This version of phpredis is intended for versions of php older than 7. In addition there have been many bug fixes and improvements to non cluster related commands, which are listed below. I've attempted to include everyone who contribued to the project in each fix description and have included names or github user ids. Thanks to everyone for submitting bug reports and pull requests. A special thanks to Remi Collet for helping with any and all packaging related issues \o/ --- Improvements --- * Added randomization to our seed nodes to balance which instance is used to map the keyspace (Vitaliy Stepanyuk) [32eb1c5f] * Added support for IPv6 addresses --- Fixes --- * PHP liveness checking workaround (Shafreeck Sea) [c18d58b9] * Various documentation and code formatting and style fixes (ares333, sanpili, Bryan Nelson, linfangrong, Romero Malaquias, Viktor Szépe) * Fix scan reply processing to use long instead of int to avoid overflow (mixiaojiong). * Fix potential segfault in Redis Cluster session storage (Sergei Lomakov) [cc15aae] * Fixed memory leak in discard function [17b1f427] * Sanity check for igbinary unserialization (Maurus Cuelenaere) [3266b222, 5528297a] * Fix segfault occuring from unclosed socket connection for Redis Cluster (CatKang) [04196aee] * Case insensitive zRangeByScore options * Fixed dreaded size_t vs long long compiler warning stable stable 2.2.7 2.2.7 2015-03-03 phpredis 2.2.7 -- Improvements --- * Implemented PFADD, PFMERGE, and PFCOUNT command handling * Implemented ZRANGEBYLEX command (holding off on ZREVRANGEBYLEX as that won't be out until 3.0) * Implemented getMode() so clients can detect whether we're in ATOMIC/MULTI/PIPELINE mode. * Implemented rawCommand() so clients can send arbitrary things to the redis server * Implemented DEBUG OBJECT (@michael-grunder, @isage) * Added/abide by connect timeout for RedisArray * Select to the last selected DB when phpredis reconnects -- Fixes --- * Fix a possible invalid free in _serialize * Added SAVE and BGSAVE to "distributable" commands for RedisArray * @welting -- Fixed invalid "argc" calculation re HLL commands * Allow clients to break out of the subscribe loop and return context. * Fixes a memory leak in SCAN when OPT_SCAN_RETRY id. * @remicollet -- Fix possible segfault when igbinary is enabled. * Add a couple of cases where we throw on an error (LOADING/NOAUTH/MASTERDOWN) * Fix several issues with serialization NARY * @itcom -- Fix missing TSRMLS_CC and a TSRMLS_DC/TSRMLS_CC typo stable stable 2.2.5 2.2.5 2014-03-15 phpredis 2.2.5 This is a minor release with several bug fixes as well as additions to support new commands that have been introduced to Redis since our last release. A special thanks to everyone who helps the project by commenting on issues and submitting pull requests! :) [NEW] Support for the BITPOS command [NEW] Connection timeout option for RedisArray (@MikeToString) [NEW] A _serialize method, to complement our existing _unserialize method [NEW] Support for the PUBSUB command [NEW] Support for SCAN, SSCAN, HSCAN, and ZSCAN [NEW] Support for the WAIT command [FIX] Handle the COPY and REPLACE arguments for the MIGRATE command [DOC] Fix syntax error in documentation for the SET command (@mithunsatheesh) [DOC] Homebrew documentation instructions (@mathias) stable stable 2.2.4 2.2.4 2013-09-01 ** ** Features / Improvements ** * Randomized reconnect delay for RedisArray @mobli This feature adds an optional parameter when constructing a RedisArray object such that a random delay will be introduced if reconnections are made, mitigating any 'thundering herd' type problems. * Lazy connections to RedisArray servers @mobli By default, RedisArray will attempt to connect to each server you pass in the ring on construction. This feature lets you specify that you would rather have RedisArray only attempt a connection when it needs to get data from a particular node (throughput/performance improvement). * Allow LONG and STRING keys in MGET/MSET * Extended SET options for Redis >= 2.6.12 * Persistent connections and UNIX SOCKET support for RedisArray * Allow aggregates for ZUNION/ZINTER without weights @mheijkoop * Support for SLOWLOG command * Reworked MGET algorithm to run in linear time regardless of key count. * Reworked ZINTERSTORE/ZUNIONSTORE algorithm to run in linear time ** ** Bug fixes ** * C99 Compliance (or rather lack thereof) fix @mobli * Added ZEND_ACC_CTOR and ZEND_ACC_DTOR @euskadi31 * Stop throwing and clearing an exception on connect failure @matmoi * Fix a false positive unit test failure having to do with TTL returns stable stable 2.2.3 2.2.3 2013-04-29 First release to PECL redis-3.1.6/tests/RedisArrayTest.php0000644000175000000120000004024213223116600020162 0ustar pyatsukhnenkowheel\d+)_\w+#", $str, $out)) { return $out['facebook_id']; } return $str; } function parseHostPort($str, &$host, &$port) { $pos = strrpos($str, ':'); $host = substr($str, 0, $pos); $port = substr($str, $pos+1); } class Redis_Array_Test extends TestSuite { private $strings; public $ra = NULL; private $data = NULL; public function setUp() { // initialize strings. $n = REDIS_ARRAY_DATA_SIZE; $this->strings = array(); for($i = 0; $i < $n; $i++) { $this->strings['key-'.$i] = 'val-'.$i; } global $newRing, $oldRing, $useIndex; $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex)); } public function testMSet() { // run mset $this->assertTrue(TRUE === $this->ra->mset($this->strings)); // check each key individually using the array foreach($this->strings as $k => $v) { $this->assertTrue($v === $this->ra->get($k)); } // check each key individually using a new connection foreach($this->strings as $k => $v) { parseHostPort($this->ra->_target($k), $host, $port); $target = $this->ra->_target($k); $pos = strrpos($target, ':'); $host = substr($target, 0, $pos); $port = substr($target, $pos+1); $r = new Redis; $r->pconnect($host, (int)$port); $this->assertTrue($v === $r->get($k)); } } public function testMGet() { $this->assertTrue(array_values($this->strings) === $this->ra->mget(array_keys($this->strings))); } private function addData($commonString) { $this->data = array(); for($i = 0; $i < REDIS_ARRAY_DATA_SIZE; $i++) { $k = rand().'_'.$commonString.'_'.rand(); $this->data[$k] = rand(); } $this->ra->mset($this->data); } private function checkCommonLocality() { // check that they're all on the same node. $lastNode = NULL; foreach($this->data as $k => $v) { $node = $this->ra->_target($k); if($lastNode) { $this->assertTrue($node === $lastNode); } $this->assertTrue($this->ra->get($k) == $v); $lastNode = $node; } } public function testKeyLocality() { // basic key locality with default hash $this->addData('{hashed part of the key}'); $this->checkCommonLocality(); // with common hashing function global $newRing, $oldRing, $useIndex; $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex, 'function' => 'custom_hash')); // basic key locality with custom hash $this->addData('fb'.rand()); $this->checkCommonLocality(); } public function customDistributor($key) { $a = unpack("N*", md5($key, true)); global $newRing; $pos = abs($a[1]) % count($newRing); return $pos; } public function testKeyDistributor() { global $newRing, $useIndex; $this->ra = new RedisArray($newRing, array( 'index' => $useIndex, 'function' => 'custom_hash', 'distributor' => array($this, "customDistributor"))); // custom key distribution function. $this->addData('fb'.rand()); // check that they're all on the expected node. $lastNode = NULL; foreach($this->data as $k => $v) { $node = $this->ra->_target($k); $pos = $this->customDistributor($k); $this->assertTrue($node === $newRing[$pos]); } } } class Redis_Rehashing_Test extends TestSuite { public $ra = NULL; private $useIndex; // data private $strings; private $sets; private $lists; private $hashes; private $zsets; public function setUp() { // initialize strings. $n = REDIS_ARRAY_DATA_SIZE; $this->strings = array(); for($i = 0; $i < $n; $i++) { $this->strings['key-'.$i] = 'val-'.$i; } // initialize sets for($i = 0; $i < $n; $i++) { // each set has 20 elements $this->sets['set-'.$i] = range($i, $i+20); } // initialize lists for($i = 0; $i < $n; $i++) { // each list has 20 elements $this->lists['list-'.$i] = range($i, $i+20); } // initialize hashes for($i = 0; $i < $n; $i++) { // each hash has 5 keys $this->hashes['hash-'.$i] = array('A' => $i, 'B' => $i+1, 'C' => $i+2, 'D' => $i+3, 'E' => $i+4); } // initialize sorted sets for($i = 0; $i < $n; $i++) { // each sorted sets has 5 elements $this->zsets['zset-'.$i] = array($i, 'A', $i+1, 'B', $i+2, 'C', $i+3, 'D', $i+4, 'E'); } global $newRing, $oldRing, $useIndex; // create array $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex)); } public function testFlush() { // flush all servers first. global $serverList; foreach($serverList as $s) { parseHostPort($s, $host, $port); $r = new Redis(); $r->pconnect($host, (int)$port, 0); $r->flushdb(); } } private function distributeKeys() { // strings foreach($this->strings as $k => $v) { $this->ra->set($k, $v); } // sets foreach($this->sets as $k => $v) { call_user_func_array(array($this->ra, 'sadd'), array_merge(array($k), $v)); } // lists foreach($this->lists as $k => $v) { call_user_func_array(array($this->ra, 'rpush'), array_merge(array($k), $v)); } // hashes foreach($this->hashes as $k => $v) { $this->ra->hmset($k, $v); } // sorted sets foreach($this->zsets as $k => $v) { call_user_func_array(array($this->ra, 'zadd'), array_merge(array($k), $v)); } } public function testDistribution() { $this->distributeKeys(); } public function testSimpleRead() { $this->readAllvalues(); } private function readAllvalues() { // strings foreach($this->strings as $k => $v) { $this->assertTrue($this->ra->get($k) === $v); } // sets foreach($this->sets as $k => $v) { $ret = $this->ra->smembers($k); // get values // sort sets sort($v); sort($ret); $this->assertTrue($ret == $v); } // lists foreach($this->lists as $k => $v) { $ret = $this->ra->lrange($k, 0, -1); $this->assertTrue($ret == $v); } // hashes foreach($this->hashes as $k => $v) { $ret = $this->ra->hgetall($k); // get values $this->assertTrue($ret == $v); } // sorted sets foreach($this->zsets as $k => $v) { $ret = $this->ra->zrange($k, 0, -1, TRUE); // get values with scores // create assoc array from local dataset $tmp = array(); for($i = 0; $i < count($v); $i += 2) { $tmp[$v[$i+1]] = $v[$i]; } // compare to RA value $this->assertTrue($ret == $tmp); } } // add a new node. public function testCreateSecondRing() { global $newRing, $oldRing, $serverList; $oldRing = $newRing; // back up the original. $newRing = $serverList; // add a new node to the main ring. } public function testReadUsingFallbackMechanism() { $this->readAllvalues(); // some of the reads will fail and will go to another target node. } public function testRehash() { $this->ra->_rehash(); // this will redistribute the keys } public function testRehashWithCallback() { $total = 0; $this->ra->_rehash(function ($host, $count) use (&$total) { $total += $count; }); $this->assertTrue($total > 0); } public function testReadRedistributedKeys() { $this->readAllvalues(); // we shouldn't have any missed reads now. } } // Test auto-migration of keys class Redis_Auto_Rehashing_Test extends TestSuite { public $ra = NULL; // data private $strings; public function setUp() { // initialize strings. $n = REDIS_ARRAY_DATA_SIZE; $this->strings = array(); for($i = 0; $i < $n; $i++) { $this->strings['key-'.$i] = 'val-'.$i; } global $newRing, $oldRing, $useIndex; // create array $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex, 'autorehash' => TRUE)); } public function testDistribute() { // strings foreach($this->strings as $k => $v) { $this->ra->set($k, $v); } } private function readAllvalues() { foreach($this->strings as $k => $v) { $this->assertTrue($this->ra->get($k) === $v); } } public function testReadAll() { $this->readAllvalues(); } // add a new node. public function testCreateSecondRing() { global $newRing, $oldRing, $serverList; $oldRing = $newRing; // back up the original. $newRing = $serverList; // add a new node to the main ring. } // Read and migrate keys on fallback, causing the whole ring to be rehashed. public function testReadAndMigrateAll() { $this->readAllvalues(); } // Read and migrate keys on fallback, causing the whole ring to be rehashed. public function testAllKeysHaveBeenMigrated() { foreach($this->strings as $k => $v) { parseHostPort($this->ra->_target($k), $host, $port); $r = new Redis; $r->pconnect($host, $port); $this->assertTrue($v === $r->get($k)); // check that the key has actually been migrated to the new node. } } } // Test node-specific multi/exec class Redis_Multi_Exec_Test extends TestSuite { public $ra = NULL; public function setUp() { global $newRing, $oldRing, $useIndex; // create array $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex)); } public function testInit() { $this->ra->set('{groups}:managers', 2); $this->ra->set('{groups}:executives', 3); $this->ra->set('1_{employee:joe}_name', 'joe'); $this->ra->set('1_{employee:joe}_group', 2); $this->ra->set('1_{employee:joe}_salary', 2000); } public function testKeyDistribution() { // check that all of joe's keys are on the same instance $lastNode = NULL; foreach(array('name', 'group', 'salary') as $field) { $node = $this->ra->_target('1_{employee:joe}_'.$field); if($lastNode) { $this->assertTrue($node === $lastNode); } $lastNode = $node; } } public function testMultiExec() { // Joe gets a promotion $newGroup = $this->ra->get('{groups}:executives'); $newSalary = 4000; // change both in a transaction. $host = $this->ra->_target('{employee:joe}'); // transactions are per-node, so we need a reference to it. $tr = $this->ra->multi($host) ->set('1_{employee:joe}_group', $newGroup) ->set('1_{employee:joe}_salary', $newSalary) ->exec(); // check that the group and salary have been changed $this->assertTrue($this->ra->get('1_{employee:joe}_group') === $newGroup); $this->assertTrue($this->ra->get('1_{employee:joe}_salary') == $newSalary); } public function testMultiExecMSet() { global $newGroup, $newSalary; $newGroup = 1; $newSalary = 10000; // test MSET, making Joe a top-level executive $out = $this->ra->multi($this->ra->_target('{employee:joe}')) ->mset(array('1_{employee:joe}_group' => $newGroup, '1_{employee:joe}_salary' => $newSalary)) ->exec(); $this->assertTrue($out[0] === TRUE); } public function testMultiExecMGet() { global $newGroup, $newSalary; // test MGET $out = $this->ra->multi($this->ra->_target('{employee:joe}')) ->mget(array('1_{employee:joe}_group', '1_{employee:joe}_salary')) ->exec(); $this->assertTrue($out[0][0] == $newGroup); $this->assertTrue($out[0][1] == $newSalary); } public function testMultiExecDel() { // test DEL $out = $this->ra->multi($this->ra->_target('{employee:joe}')) ->del('1_{employee:joe}_group', '1_{employee:joe}_salary') ->exec(); $this->assertTrue($out[0] === 2); $this->assertTrue($this->ra->exists('1_{employee:joe}_group') === FALSE); $this->assertTrue($this->ra->exists('1_{employee:joe}_salary') === FALSE); } public function testDiscard() { /* phpredis issue #87 */ $key = 'test_err'; $this->assertTrue($this->ra->set($key, 'test')); $this->assertTrue('test' === $this->ra->get($key)); $this->ra->watch($key); // After watch, same $this->assertTrue('test' === $this->ra->get($key)); // change in a multi/exec block. $ret = $this->ra->multi($this->ra->_target($key))->set($key, 'test1')->exec(); $this->assertTrue($ret === array(true)); // Get after exec, 'test1': $this->assertTrue($this->ra->get($key) === 'test1'); $this->ra->watch($key); // After second watch, still test1. $this->assertTrue($this->ra->get($key) === 'test1'); $ret = $this->ra->multi($this->ra->_target($key))->set($key, 'test2')->discard(); // Ret after discard: NULL"; $this->assertTrue($ret === NULL); // Get after discard, unchanged: $this->assertTrue($this->ra->get($key) === 'test1'); } } // Test custom distribution function class Redis_Distributor_Test extends TestSuite { public $ra = NULL; public function setUp() { global $newRing, $oldRing, $useIndex; // create array $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex, 'distributor' => array($this, 'distribute'))); } public function testInit() { $this->ra->set('{uk}test', 'joe'); $this->ra->set('{us}test', 'bob'); } public function distribute($key) { $matches = array(); if (preg_match('/{([^}]+)}.*/', $key, $matches) == 1) { $countries = array('uk' => 0, 'us' => 1); if (array_key_exists($matches[1], $countries)) { return $countries[$matches[1]]; } } return 2; // default server } public function testDistribution() { $ukServer = $this->ra->_target('{uk}test'); $usServer = $this->ra->_target('{us}test'); $deServer = $this->ra->_target('{de}test'); $defaultServer = $this->ra->_target('unknown'); $nodes = $this->ra->_hosts(); $this->assertTrue($ukServer === $nodes[0]); $this->assertTrue($usServer === $nodes[1]); $this->assertTrue($deServer === $nodes[2]); $this->assertTrue($defaultServer === $nodes[2]); } } function run_tests($className, $str_filter, $str_host) { // reset rings global $newRing, $oldRing, $serverList; $newRing = Array("$str_host:6379", "$str_host:6380", "$str_host:6381"); $oldRing = Array(); $serverList = Array("$str_host:6379", "$str_host:6380", "$str_host:6381", "$str_host:6382"); // run return TestSuite::run($className, $str_filter); } ?> redis-3.1.6/tests/RedisClusterTest.php0000644000175000000120000005300213223116600020523 0ustar pyatsukhnenkowheelmarkTestSkipped(); } public function testSortDesc() { return $this->markTestSkipped(); } public function testWait() { return $this->markTestSkipped(); } public function testSelect() { return $this->markTestSkipped(); } public function testReconnectSelect() { return $this->markTestSkipped(); } public function testMultipleConnect() { return $this->markTestSkipped(); } public function testDoublePipeNoOp() { return $this->markTestSkipped(); } /* Load our seeds on construction */ public function __construct() { $str_nodemap_file = dirname($_SERVER['PHP_SELF']) . '/nodes/nodemap'; if (!file_exists($str_nodemap_file)) { fprintf(STDERR, "Error: Can't find nodemap file for seeds!\n"); exit(1); } /* Store our node map */ if (!self::$_arr_node_map) { self::$_arr_node_map = array_filter( explode("\n", file_get_contents($str_nodemap_file) )); } } /* Override setUp to get info from a specific node */ public function setUp() { $this->redis = $this->newInstance(); $info = $this->redis->info(uniqid()); $this->version = (isset($info['redis_version'])?$info['redis_version']:'0.0.0'); } /* Override newInstance as we want a RedisCluster object */ protected function newInstance() { return new RedisCluster(NULL, self::$_arr_node_map); } /* Overrides for RedisTest where the function signature is different. This * is only true for a few commands, which by definition have to be directed * at a specific node */ public function testPing() { for ($i = 0; $i < 100; $i++) { $this->assertTrue($this->redis->ping("key:$i")); } } public function testRandomKey() { /* Ensure some keys are present to test */ for ($i = 0; $i < 1000; $i++) { if (rand(1, 2) == 1) { $this->redis->set("key:$i", "val:$i"); } } for ($i = 0; $i < 1000; $i++) { $k = $this->redis->randomKey("key:$i"); $this->assertTrue($this->redis->exists($k)); } } public function testEcho() { $this->assertEquals($this->redis->echo('k1', 'hello'), 'hello'); $this->assertEquals($this->redis->echo('k2', 'world'), 'world'); $this->assertEquals($this->redis->echo('k3', " 0123 "), " 0123 "); } public function testSortPrefix() { $this->redis->setOption(Redis::OPT_PREFIX, 'some-prefix:'); $this->redis->del('some-item'); $this->redis->sadd('some-item', 1); $this->redis->sadd('some-item', 2); $this->redis->sadd('some-item', 3); $this->assertEquals(array('1','2','3'), $this->redis->sort('some-item')); // Kill our set/prefix $this->redis->del('some-item'); $this->redis->setOption(Redis::OPT_PREFIX, ''); } public function testDBSize() { for ($i = 0; $i < 10; $i++) { $str_key = "key:$i"; $this->assertTrue($this->redis->flushdb($str_key)); $this->redis->set($str_key, "val:$i"); $this->assertEquals(1, $this->redis->dbsize($str_key)); } } public function testInfo() { $arr_check_keys = Array( "redis_version", "arch_bits", "uptime_in_seconds", "uptime_in_days", "connected_clients", "connected_slaves", "used_memory", "total_connections_received", "total_commands_processed", "role" ); for ($i = 0; $i < 3; $i++) { $arr_info = $this->redis->info("k:$i"); foreach ($arr_check_keys as $str_check_key) { $this->assertTrue(isset($arr_info[$str_check_key])); } } } public function testClient() { $str_key = 'key-' . rand(1,100); $this->assertTrue($this->redis->client($str_key, 'setname', 'cluster_tests')); $arr_clients = $this->redis->client($str_key, 'list'); $this->assertTrue(is_array($arr_clients)); /* Find us in the list */ $str_addr = NULL; foreach ($arr_clients as $arr_client) { if ($arr_client['name'] == 'cluster_tests') { $str_addr = $arr_client['addr']; break; } } /* We should be in there */ $this->assertFalse(empty($str_addr)); /* Kill our own client! */ $this->assertTrue($this->redis->client($str_key, 'kill', $str_addr)); } public function testTime() { $time_arr = $this->redis->time("k:" . rand(1,100)); $this->assertTrue(is_array($time_arr) && count($time_arr) == 2 && strval(intval($time_arr[0])) === strval($time_arr[0]) && strval(intval($time_arr[1])) === strval($time_arr[1])); } public function testScan() { $i_key_count = 0; $i_scan_count = 0; /* Have scan retry for us */ $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); /* Iterate over our masters, scanning each one */ foreach ($this->redis->_masters() as $arr_master) { /* Grab the number of keys we have */ $i_key_count += $this->redis->dbsize($arr_master); /* Scan the keys here */ $it = NULL; while ($arr_keys = $this->redis->scan($it, $arr_master)) { $i_scan_count += count($arr_keys); } } /* Our total key count should match */ $this->assertEquals($i_scan_count, $i_key_count); } // Run some simple tests against the PUBSUB command. This is problematic, as we // can't be sure what's going on in the instance, but we can do some things. public function testPubSub() { // PUBSUB CHANNELS ... $result = $this->redis->pubsub("somekey", "channels", "*"); $this->assertTrue(is_array($result)); $result = $this->redis->pubsub("somekey", "channels"); $this->assertTrue(is_array($result)); // PUBSUB NUMSUB $c1 = '{pubsub}-' . rand(1,100); $c2 = '{pubsub}-' . rand(1,100); $result = $this->redis->pubsub("{pubsub}", "numsub", $c1, $c2); // Should get an array back, with two elements $this->assertTrue(is_array($result)); $this->assertEquals(count($result), 4); $arr_zipped = Array(); for ($i = 0; $i <= count($result) / 2; $i+=2) { $arr_zipped[$result[$i]] = $result[$i+1]; } $result = $arr_zipped; // Make sure the elements are correct, and have zero counts foreach(Array($c1,$c2) as $channel) { $this->assertTrue(isset($result[$channel])); $this->assertEquals($result[$channel], 0); } // PUBSUB NUMPAT $result = $this->redis->pubsub("somekey", "numpat"); $this->assertTrue(is_int($result)); // Invalid call $this->assertFalse($this->redis->pubsub("somekey", "notacommand")); } /* Unlike Redis proper, MsetNX won't always totally fail if all keys can't * be set, but rather will only fail per-node when that is the case */ public function testMSetNX() { /* All of these keys should get set */ $this->redis->del('x','y','z'); $ret = $this->redis->msetnx(Array('x'=>'a','y'=>'b','z'=>'c')); $this->assertTrue(is_array($ret)); $this->assertEquals(array_sum($ret),count($ret)); /* Delete one key */ $this->redis->del('x'); $ret = $this->redis->msetnx(Array('x'=>'a','y'=>'b','z'=>'c')); $this->assertTrue(is_array($ret)); $this->assertEquals(array_sum($ret),1); $this->assertFalse($this->redis->msetnx(array())); // set ø → FALSE } /* Slowlog needs to take a key or Array(ip, port), to direct it to a node */ public function testSlowlog() { $str_key = uniqid() . '-' . rand(1, 1000); $this->assertTrue(is_array($this->redis->slowlog($str_key, 'get'))); $this->assertTrue(is_array($this->redis->slowlog($str_key, 'get', 10))); $this->assertTrue(is_int($this->redis->slowlog($str_key, 'len'))); $this->assertTrue($this->redis->slowlog($str_key, 'reset')); $this->assertFalse($this->redis->slowlog($str_key, 'notvalid')); } /* INFO COMMANDSTATS requires a key or ip:port for node direction */ public function testInfoCommandStats() { $str_key = uniqid() . '-' . rand(1,1000); $arr_info = $this->redis->info($str_key, "COMMANDSTATS"); $this->assertTrue(is_array($arr_info)); if (is_array($arr_info)) { foreach($arr_info as $k => $str_value) { $this->assertTrue(strpos($k, 'cmdstat_') !== false); } } } /* RedisCluster will always respond with an array, even if transactions * failed, because the commands could be coming from multiple nodes */ public function testFailedTransactions() { $this->redis->set('x', 42); // failed transaction $this->redis->watch('x'); $r = $this->newInstance(); // new instance, modifying `x'. $r->incr('x'); // This transaction should fail because the other client changed 'x' $ret = $this->redis->multi()->get('x')->exec(); $this->assertTrue($ret === Array(FALSE)); // watch and unwatch $this->redis->watch('x'); $r->incr('x'); // other instance $this->redis->unwatch('x'); // cancel transaction watch // This should succeed as the watch has been cancelled $ret = $this->redis->multi()->get('x')->exec(); $this->assertTrue($ret === array('44')); } /* RedisCluster::script() is a 'raw' command, which requires a key such that * we can direct it to a given node */ public function testScript() { $str_key = uniqid() . '-' . rand(1,1000); // Flush any scripts we have $this->assertTrue($this->redis->script($str_key, 'flush')); // Silly scripts to test against $s1_src = 'return 1'; $s1_sha = sha1($s1_src); $s2_src = 'return 2'; $s2_sha = sha1($s2_src); $s3_src = 'return 3'; $s3_sha = sha1($s3_src); // None should exist $result = $this->redis->script($str_key, 'exists', $s1_sha, $s2_sha, $s3_sha); $this->assertTrue(is_array($result) && count($result) == 3); $this->assertTrue(is_array($result) && count(array_filter($result)) == 0); // Load them up $this->assertTrue($this->redis->script($str_key, 'load', $s1_src) == $s1_sha); $this->assertTrue($this->redis->script($str_key, 'load', $s2_src) == $s2_sha); $this->assertTrue($this->redis->script($str_key, 'load', $s3_src) == $s3_sha); // They should all exist $result = $this->redis->script($str_key, 'exists', $s1_sha, $s2_sha, $s3_sha); $this->assertTrue(is_array($result) && count(array_filter($result)) == 3); } /* RedisCluster::EVALSHA needs a 'key' to let us know which node we want to * direct the command at */ public function testEvalSHA() { $str_key = uniqid() . '-' . rand(1,1000); // Flush any loaded scripts $this->redis->script($str_key, 'flush'); // Non existant script (but proper sha1), and a random (not) sha1 string $this->assertFalse($this->redis->evalsha(sha1(uniqid()),Array($str_key), 1)); $this->assertFalse($this->redis->evalsha('some-random-data'),Array($str_key), 1); // Load a script $cb = uniqid(); // To ensure the script is new $scr = "local cb='$cb' return 1"; $sha = sha1($scr); // Run it when it doesn't exist, run it with eval, and then run it with sha1 $this->assertTrue(false === $this->redis->evalsha($scr,Array($str_key), 1)); $this->assertTrue(1 === $this->redis->eval($scr,Array($str_key), 1)); $this->assertTrue(1 === $this->redis->evalsha($sha,Array($str_key), 1)); } public function testEvalBulkResponse() { $str_key1 = uniqid() . '-' . rand(1,1000) . '{hash}'; $str_key2 = uniqid() . '-' . rand(1,1000) . '{hash}'; $this->redis->script($str_key1, 'flush'); $this->redis->script($str_key2, 'flush'); $scr = "return {KEYS[1],KEYS[2]}"; $result = $this->redis->eval($scr,Array($str_key1, $str_key2), 2); $this->assertTrue($str_key1 === $result[0]); $this->assertTrue($str_key2 === $result[1]); } public function testEvalBulkResponseMulti() { $str_key1 = uniqid() . '-' . rand(1,1000) . '{hash}'; $str_key2 = uniqid() . '-' . rand(1,1000) . '{hash}'; $this->redis->script($str_key1, 'flush'); $this->redis->script($str_key2, 'flush'); $scr = "return {KEYS[1],KEYS[2]}"; $this->redis->multi(); $this->redis->eval($scr,Array($str_key1, $str_key2), 2); $result = $this->redis->exec(); $this->assertTrue($str_key1 === $result[0][0]); $this->assertTrue($str_key2 === $result[0][1]); } public function testEvalBulkEmptyResponse() { $str_key1 = uniqid() . '-' . rand(1,1000) . '{hash}'; $str_key2 = uniqid() . '-' . rand(1,1000) . '{hash}'; $this->redis->script($str_key1, 'flush'); $this->redis->script($str_key2, 'flush'); $scr = "for _,key in ipairs(KEYS) do redis.call('SET', key, 'value') end"; $result = $this->redis->eval($scr,Array($str_key1, $str_key2), 2); $this->assertTrue(null === $result); } public function testEvalBulkEmptyResponseMulti() { $str_key1 = uniqid() . '-' . rand(1,1000) . '{hash}'; $str_key2 = uniqid() . '-' . rand(1,1000) . '{hash}'; $this->redis->script($str_key1, 'flush'); $this->redis->script($str_key2, 'flush'); $scr = "for _,key in ipairs(KEYS) do redis.call('SET', key, 'value') end"; $this->redis->multi(); $this->redis->eval($scr,Array($str_key1, $str_key2), 2); $result = $this->redis->exec(); $this->assertTrue(null === $result[0]); } /* Cluster specific introspection stuff */ public function testIntrospection() { $arr_masters = $this->redis->_masters(); $this->assertTrue(is_array($arr_masters)); foreach ($arr_masters as $arr_info) { $this->assertTrue(is_array($arr_info)); $this->assertTrue(is_string($arr_info[0])); $this->assertTrue(is_long($arr_info[1])); } } protected function genKeyName($i_key_idx, $i_type) { switch ($i_type) { case Redis::REDIS_STRING: return "string-$i_key_idx"; case Redis::REDIS_SET: return "set-$i_key_idx"; case Redis::REDIS_LIST: return "list-$i_key_idx"; case Redis::REDIS_ZSET: return "zset-$i_key_idx"; case Redis::REDIS_HASH: return "hash-$i_key_idx"; default: return "unknown-$i_key_idx"; } } protected function setKeyVals($i_key_idx, $i_type, &$arr_ref) { $str_key = $this->genKeyName($i_key_idx, $i_type); $this->redis->del($str_key); switch ($i_type) { case Redis::REDIS_STRING: $value = "$str_key-value"; $this->redis->set($str_key, $value); break; case Redis::REDIS_SET: $value = Array( $str_key . '-mem1', $str_key . '-mem2', $str_key . '-mem3', $str_key . '-mem4', $str_key . '-mem5', $str_key . '-mem6' ); $arr_args = $value; array_unshift($arr_args, $str_key); call_user_func_array(Array($this->redis, 'sadd'), $arr_args); break; case Redis::REDIS_HASH: $value = Array( $str_key . '-mem1' => $str_key . '-val1', $str_key . '-mem2' => $str_key . '-val2', $str_key . '-mem3' => $str_key . '-val3' ); $this->redis->hmset($str_key, $value); break; case Redis::REDIS_LIST: $value = Array( $str_key . '-ele1', $str_key . '-ele2', $str_key . '-ele3', $str_key . '-ele4', $str_key . '-ele5', $str_key . '-ele6' ); $arr_args = $value; array_unshift($arr_args, $str_key); call_user_func_array(Array($this->redis, 'rpush'), $arr_args); break; case Redis::REDIS_ZSET: $i_score = 1; $value = Array( $str_key . '-mem1' => 1, $str_key . '-mem2' => 2, $str_key . '-mem3' => 3, $str_key . '-mem3' => 3 ); foreach ($value as $str_mem => $i_score) { $this->redis->zadd($str_key, $i_score, $str_mem); } break; } /* Update our reference array so we can verify values */ $arr_ref[$str_key] = $value; return $str_key; } /* Verify that our ZSET values are identical */ protected function checkZSetEquality($a, $b) { /* If the count is off, the array keys are different or the sums are * different, we know there is something off */ $boo_diff = count($a) != count($b) || count(array_diff(array_keys($a), array_keys($b))) != 0 || array_sum($a) != array_sum($b); if ($boo_diff) { $this->assertEquals($a,$b); return; } } protected function checkKeyValue($str_key, $i_type, $value) { switch ($i_type) { case Redis::REDIS_STRING: $this->assertEquals($value, $this->redis->get($str_key)); break; case Redis::REDIS_SET: $arr_r_values = $this->redis->sMembers($str_key); $arr_l_values = $value; sort($arr_r_values); sort($arr_l_values); $this->assertEquals($arr_r_values, $arr_l_values); break; case Redis::REDIS_LIST: $this->assertEquals($value, $this->redis->lrange($str_key,0,-1)); break; case Redis::REDIS_HASH: $this->assertEquals($value, $this->redis->hgetall($str_key)); break; case Redis::REDIS_ZSET: $this->checkZSetEquality($value, $this->redis->zrange($str_key,0,-1,true)); break; default: throw new Exception("Unknown type " . $i_type); } } /* Test automatic load distributor */ public function testFailOver() { $arr_value_ref = Array(); $arr_type_ref = Array(); /* Set a bunch of keys of various redis types*/ for ($i = 0; $i < 200; $i++) { foreach ($this->_arr_redis_types as $i_type) { $str_key = $this->setKeyVals($i, $i_type, $arr_value_ref); $arr_type_ref[$str_key] = $i_type; } } /* Iterate over failover options */ foreach ($this->_arr_failover_types as $i_opt) { $this->redis->setOption(RedisCluster::OPT_SLAVE_FAILOVER, $i_opt); foreach ($arr_value_ref as $str_key => $value) { $this->checkKeyValue($str_key, $arr_type_ref[$str_key], $value); } break; } } /* Test a 'raw' command */ public function testRawCommand() { $this->redis->rawCommand('mykey', 'set', 'mykey', 'my-value'); $this->assertEquals($this->redis->get('mykey'), 'my-value'); $this->redis->del('mylist'); $this->redis->rpush('mylist', 'A','B','C','D'); $this->assertEquals($this->redis->lrange('mylist', 0, -1), Array('A','B','C','D')); } protected function rawCommandArray($key, $args) { array_unshift($args, $key); return call_user_func_array(Array($this->redis, 'rawCommand'), $args); } public function testSession() { ini_set('session.save_handler', 'rediscluster'); ini_set('session.save_path', implode('&', array_map(function ($seed) { return 'seed[]=' . $seed; }, self::$_arr_node_map)) . '&failover=error'); if (!@session_start()) { return $this->markTestSkipped(); } session_write_close(); $this->assertTrue($this->redis->exists('PHPREDIS_CLUSTER_SESSION:' . session_id())); } } ?> redis-3.1.6/tests/RedisTest.php0000644000175000000120000062652713223116600017203 0ustar pyatsukhnenkowheel Array(-121.837478, 39.728494), 'Sacramento' => Array(-121.494400, 38.581572), 'Gridley' => Array(-121.693583, 39.363777), 'Marysville' => Array(-121.591355, 39.145725), 'Cupertino' => Array(-122.032182, 37.322998) ); /** * @var Redis */ public $redis; public function setUp() { $this->redis = $this->newInstance(); $info = $this->redis->info(); $this->version = (isset($info['redis_version'])?$info['redis_version']:'0.0.0'); } protected function minVersionCheck($version) { return version_compare($this->version, $version, "ge"); } protected function newInstance() { $r = new Redis(); $r->connect($this->getHost(), self::PORT); if(self::AUTH) { $this->assertTrue($r->auth(self::AUTH)); } return $r; } public function tearDown() { if($this->redis) { $this->redis->close(); } } public function reset() { $this->setUp(); $this->tearDown(); } /* Helper function to determine if the clsas has pipeline support */ protected function havePipeline() { $str_constant = get_class($this->redis) . '::PIPELINE'; return defined($str_constant); } public function testMinimumVersion() { // Minimum server version required for tests $this->assertTrue(version_compare($this->version, "2.4.0", "ge")); } public function testPing() { $this->assertEquals('+PONG', $this->redis->ping()); $count = 1000; while($count --) { $this->assertEquals('+PONG', $this->redis->ping()); } } public function testPipelinePublish() { if (!$this->havePipeline()) { $this->markTestSkipped(); } $ret = $this->redis->pipeline() ->publish('chan', 'msg') ->exec(); $this->assertTrue(is_array($ret) && count($ret) === 1 && $ret[0] >= 0); } // Run some simple tests against the PUBSUB command. This is problematic, as we // can't be sure what's going on in the instance, but we can do some things. public function testPubSub() { // Only available since 2.8.0 if(version_compare($this->version, "2.8.0", "lt")) { $this->markTestSkipped(); return; } // PUBSUB CHANNELS ... $result = $this->redis->pubsub("channels", "*"); $this->assertTrue(is_array($result)); $result = $this->redis->pubsub("channels"); $this->assertTrue(is_array($result)); // PUBSUB NUMSUB $c1 = uniqid() . '-' . rand(1,100); $c2 = uniqid() . '-' . rand(1,100); $result = $this->redis->pubsub("numsub", Array($c1, $c2)); // Should get an array back, with two elements $this->assertTrue(is_array($result)); $this->assertEquals(count($result), 2); // Make sure the elements are correct, and have zero counts foreach(Array($c1,$c2) as $channel) { $this->assertTrue(isset($result[$channel])); $this->assertEquals($result[$channel], 0); } // PUBSUB NUMPAT $result = $this->redis->pubsub("numpat"); $this->assertTrue(is_int($result)); // Invalid calls $this->assertFalse($this->redis->pubsub("notacommand")); $this->assertFalse($this->redis->pubsub("numsub", "not-an-array")); } public function testBitsets() { $this->redis->del('key'); $this->assertEquals(0, $this->redis->getBit('key', 0)); $this->assertEquals(FALSE, $this->redis->getBit('key', -1)); $this->assertEquals(0, $this->redis->getBit('key', 100000)); $this->redis->set('key', "\xff"); for($i = 0; $i < 8; $i++) { $this->assertEquals(1, $this->redis->getBit('key', $i)); } $this->assertEquals(0, $this->redis->getBit('key', 8)); // change bit 0 $this->assertEquals(1, $this->redis->setBit('key', 0, 0)); $this->assertEquals(0, $this->redis->setBit('key', 0, 0)); $this->assertEquals(0, $this->redis->getBit('key', 0)); $this->assertEquals("\x7f", $this->redis->get('key')); // change bit 1 $this->assertEquals(1, $this->redis->setBit('key', 1, 0)); $this->assertEquals(0, $this->redis->setBit('key', 1, 0)); $this->assertEquals(0, $this->redis->getBit('key', 1)); $this->assertEquals("\x3f", $this->redis->get('key')); // change bit > 1 $this->assertEquals(1, $this->redis->setBit('key', 2, 0)); $this->assertEquals(0, $this->redis->setBit('key', 2, 0)); $this->assertEquals(0, $this->redis->getBit('key', 2)); $this->assertEquals("\x1f", $this->redis->get('key')); // values above 1 are changed to 1 but don't overflow on bits to the right. $this->assertEquals(0, $this->redis->setBit('key', 0, 0xff)); $this->assertEquals("\x9f", $this->redis->get('key')); // Verify valid offset ranges $this->assertFalse($this->redis->getBit('key', -1)); $this->redis->setBit('key', 0x7fffffff, 1); $this->assertEquals(1, $this->redis->getBit('key', 0x7fffffff)); } public function testBitPos() { if(version_compare($this->version, "2.8.7", "lt")) { $this->MarkTestSkipped(); return; } $this->redis->del('bpkey'); $this->redis->set('bpkey', "\xff\xf0\x00"); $this->assertEquals($this->redis->bitpos('bpkey', 0), 12); $this->redis->set('bpkey', "\x00\xff\xf0"); $this->assertEquals($this->redis->bitpos('bpkey', 1, 0), 8); $this->assertEquals($this->redis->bitpos('bpkey', 1, 1), 8); $this->redis->set('bpkey', "\x00\x00\x00"); $this->assertEquals($this->redis->bitpos('bpkey', 1), -1); } public function test1000() { $s = str_repeat('A', 1000); $this->redis->set('x', $s); $this->assertEquals($s, $this->redis->get('x')); $s = str_repeat('A', 1000000); $this->redis->set('x', $s); $this->assertEquals($s, $this->redis->get('x')); } public function testEcho() { $this->assertEquals($this->redis->echo("hello"), "hello"); $this->assertEquals($this->redis->echo(""), ""); $this->assertEquals($this->redis->echo(" 0123 "), " 0123 "); } public function testErr() { $this->redis->set('x', '-ERR'); $this->assertEquals($this->redis->get('x'), '-ERR'); } public function testSet() { $this->assertEquals(TRUE, $this->redis->set('key', 'nil')); $this->assertEquals('nil', $this->redis->get('key')); $this->assertEquals(TRUE, $this->redis->set('key', 'val')); $this->assertEquals('val', $this->redis->get('key')); $this->assertEquals('val', $this->redis->get('key')); $this->redis->del('keyNotExist'); $this->assertEquals(FALSE, $this->redis->get('keyNotExist')); $this->redis->set('key2', 'val'); $this->assertEquals('val', $this->redis->get('key2')); $value = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; $this->redis->set('key2', $value); $this->assertEquals($value, $this->redis->get('key2')); $this->assertEquals($value, $this->redis->get('key2')); $this->redis->del('key'); $this->redis->del('key2'); $i = 66000; $value2 = 'X'; while($i--) { $value2 .= 'A'; } $value2 .= 'X'; $this->redis->set('key', $value2); $this->assertEquals($value2, $this->redis->get('key')); $this->redis->del('key'); $this->assertEquals(False, $this->redis->get('key')); $data = gzcompress('42'); $this->assertEquals(True, $this->redis->set('key', $data)); $this->assertEquals('42', gzuncompress($this->redis->get('key'))); $this->redis->del('key'); $data = gzcompress('value1'); $this->assertEquals(True, $this->redis->set('key', $data)); $this->assertEquals('value1', gzuncompress($this->redis->get('key'))); $this->redis->del('key'); $this->assertEquals(TRUE, $this->redis->set('key', 0)); $this->assertEquals('0', $this->redis->get('key')); $this->assertEquals(TRUE, $this->redis->set('key', 1)); $this->assertEquals('1', $this->redis->get('key')); $this->assertEquals(TRUE, $this->redis->set('key', 0.1)); $this->assertEquals('0.1', $this->redis->get('key')); $this->assertEquals(TRUE, $this->redis->set('key', '0.1')); $this->assertEquals('0.1', $this->redis->get('key')); $this->assertEquals(TRUE, $this->redis->set('key', TRUE)); $this->assertEquals('1', $this->redis->get('key')); $this->assertEquals(True, $this->redis->set('key', '')); $this->assertEquals('', $this->redis->get('key')); $this->assertEquals(True, $this->redis->set('key', NULL)); $this->assertEquals('', $this->redis->get('key')); $this->assertEquals(True, $this->redis->set('key', gzcompress('42'))); $this->assertEquals('42', gzuncompress($this->redis->get('key'))); } /* Extended SET options for Redis >= 2.6.12 */ public function testExtendedSet() { // Skip the test if we don't have a new enough version of Redis if(version_compare($this->version, '2.6.12', 'lt')) { $this->markTestSkipped(); return; } /* Legacy SETEX redirection */ $this->redis->del('foo'); $this->assertTrue($this->redis->set('foo','bar', 20)); $this->assertEquals($this->redis->get('foo'), 'bar'); $this->assertEquals($this->redis->ttl('foo'), 20); /* Invalid third arguments */ $this->assertFalse($this->redis->set('foo','bar','baz')); $this->assertFalse($this->redis->set('foo','bar',new StdClass())); /* Set if not exist */ $this->redis->del('foo'); $this->assertTrue($this->redis->set('foo','bar',Array('nx'))); $this->assertEquals($this->redis->get('foo'), 'bar'); $this->assertFalse($this->redis->set('foo','bar',Array('nx'))); /* Set if exists */ $this->assertTrue($this->redis->set('foo','bar',Array('xx'))); $this->assertEquals($this->redis->get('foo'), 'bar'); $this->redis->del('foo'); $this->assertFalse($this->redis->set('foo','bar',Array('xx'))); /* Set with a TTL */ $this->assertTrue($this->redis->set('foo','bar',Array('ex'=>100))); $this->assertEquals($this->redis->ttl('foo'), 100); /* Set with a PTTL */ $this->assertTrue($this->redis->set('foo','bar',Array('px'=>100000))); $this->assertTrue(100000 - $this->redis->pttl('foo') < 1000); /* Set if exists, with a TTL */ $this->assertTrue($this->redis->set('foo','bar',Array('xx','ex'=>105))); $this->assertEquals($this->redis->ttl('foo'), 105); $this->assertEquals($this->redis->get('foo'), 'bar'); /* Set if not exists, with a TTL */ $this->redis->del('foo'); $this->assertTrue($this->redis->set('foo','bar', Array('nx', 'ex'=>110))); $this->assertEquals($this->redis->ttl('foo'), 110); $this->assertEquals($this->redis->get('foo'), 'bar'); $this->assertFalse($this->redis->set('foo','bar', Array('nx', 'ex'=>110))); /* Throw some nonsense into the array, and check that the TTL came through */ $this->redis->del('foo'); $this->assertTrue($this->redis->set('foo','barbaz', Array('not-valid','nx','invalid','ex'=>200))); $this->assertEquals($this->redis->ttl('foo'), 200); $this->assertEquals($this->redis->get('foo'), 'barbaz'); /* Pass NULL as the optional arguments which should be ignored */ $this->redis->del('foo'); $this->redis->set('foo','bar', NULL); $this->assertEquals($this->redis->get('foo'), 'bar'); $this->assertTrue($this->redis->ttl('foo')<0); } public function testGetSet() { $this->redis->del('key'); $this->assertTrue($this->redis->getSet('key', '42') === FALSE); $this->assertTrue($this->redis->getSet('key', '123') === '42'); $this->assertTrue($this->redis->getSet('key', '123') === '123'); } public function testRandomKey() { for($i = 0; $i < 1000; $i++) { $k = $this->redis->randomKey(); $this->assertTrue($this->redis->exists($k)); } } public function testRename() { // strings $this->redis->del('{key}0'); $this->redis->set('{key}0', 'val0'); $this->redis->rename('{key}0', '{key}1'); $this->assertEquals(FALSE, $this->redis->get('{key}0')); $this->assertEquals('val0', $this->redis->get('{key}1')); } public function testRenameNx() { // strings $this->redis->del('{key}0', '{key}1'); $this->redis->set('{key}0', 'val0'); $this->redis->set('{key}1', 'val1'); $this->assertTrue($this->redis->renameNx('{key}0', '{key}1') === FALSE); $this->assertTrue($this->redis->get('{key}0') === 'val0'); $this->assertTrue($this->redis->get('{key}1') === 'val1'); // lists $this->redis->del('{key}0'); $this->redis->del('{key}1'); $this->redis->lPush('{key}0', 'val0'); $this->redis->lPush('{key}0', 'val1'); $this->redis->lPush('{key}1', 'val1-0'); $this->redis->lPush('{key}1', 'val1-1'); $this->assertTrue($this->redis->renameNx('{key}0', '{key}1') === FALSE); $this->assertTrue($this->redis->lRange('{key}0', 0, -1) === array('val1', 'val0')); $this->assertTrue($this->redis->lRange('{key}1', 0, -1) === array('val1-1', 'val1-0')); $this->redis->del('{key}2'); $this->assertTrue($this->redis->renameNx('{key}0', '{key}2') === TRUE); $this->assertTrue($this->redis->lRange('{key}0', 0, -1) === array()); $this->assertTrue($this->redis->lRange('{key}2', 0, -1) === array('val1', 'val0')); } public function testMultiple() { $this->redis->del('k1'); $this->redis->del('k2'); $this->redis->del('k3'); $this->redis->set('k1', 'v1'); $this->redis->set('k2', 'v2'); $this->redis->set('k3', 'v3'); $this->redis->set(1, 'test'); $this->assertEquals(array('v1'), $this->redis->mget(array('k1'))); $this->assertEquals(array('v1', 'v3', false), $this->redis->mget(array('k1', 'k3', 'NoKey'))); $this->assertEquals(array('v1', 'v2', 'v3'), $this->redis->mget(array('k1', 'k2', 'k3'))); $this->assertEquals(array('v1', 'v2', 'v3'), $this->redis->mget(array('k1', 'k2', 'k3'))); $this->redis->set('k5', '$1111111111'); $this->assertEquals(array(0 => '$1111111111'), $this->redis->mget(array('k5'))); $this->assertEquals(array(0 => 'test'), $this->redis->mget(array(1))); // non-string } public function testMultipleBin() { $this->redis->del('k1'); $this->redis->del('k2'); $this->redis->del('k3'); $this->redis->set('k1', gzcompress('v1')); $this->redis->set('k2', gzcompress('v2')); $this->redis->set('k3', gzcompress('v3')); $this->assertEquals(array(gzcompress('v1'), gzcompress('v2'), gzcompress('v3')), $this->redis->mget(array('k1', 'k2', 'k3'))); $this->assertEquals(array(gzcompress('v1'), gzcompress('v2'), gzcompress('v3')), $this->redis->mget(array('k1', 'k2', 'k3'))); } public function testSetTimeout() { $this->redis->del('key'); $this->redis->set('key', 'value'); $this->assertEquals('value', $this->redis->get('key')); $this->redis->expire('key', 1); $this->assertEquals('value', $this->redis->get('key')); sleep(2); $this->assertEquals(False, $this->redis->get('key')); } public function testExpireAt() { $this->redis->del('key'); $this->redis->set('key', 'value'); $now = time(NULL); $this->redis->expireAt('key', $now + 1); $this->assertEquals('value', $this->redis->get('key')); sleep(2); $this->assertEquals(FALSE, $this->redis->get('key')); } public function testSetEx() { $this->redis->del('key'); $this->assertTrue($this->redis->setex('key', 7, 'val') === TRUE); $this->assertTrue($this->redis->ttl('key') ===7); $this->assertTrue($this->redis->get('key') === 'val'); } public function testPSetEx() { $this->redis->del('key'); $this->assertTrue($this->redis->psetex('key', 7 * 1000, 'val') === TRUE); $this->assertTrue($this->redis->ttl('key') ===7); $this->assertTrue($this->redis->get('key') === 'val'); } public function testSetNX() { $this->redis->set('key', 42); $this->assertTrue($this->redis->setnx('key', 'err') === FALSE); $this->assertTrue($this->redis->get('key') === '42'); $this->redis->del('key'); $this->assertTrue($this->redis->setnx('key', '42') === TRUE); $this->assertTrue($this->redis->get('key') === '42'); } public function testExpireAtWithLong() { if (PHP_INT_SIZE != 8) { $this->markTestSkipped('64 bits only'); } $longExpiryTimeExceedingInt = 3153600000; $this->redis->del('key'); $this->assertTrue($this->redis->setex('key', $longExpiryTimeExceedingInt, 'val') === TRUE); $this->assertTrue($this->redis->ttl('key') === $longExpiryTimeExceedingInt); } public function testIncr() { $this->redis->set('key', 0); $this->redis->incr('key'); $this->assertEquals(1, (int)$this->redis->get('key')); $this->redis->incr('key'); $this->assertEquals(2, (int)$this->redis->get('key')); $this->redis->incrBy('key', 3); $this->assertEquals(5, (int)$this->redis->get('key')); $this->redis->incrBy('key', 1); $this->assertEquals(6, (int)$this->redis->get('key')); $this->redis->incrBy('key', -1); $this->assertEquals(5, (int)$this->redis->get('key')); $this->redis->incr('key', 5); $this->assertEquals(10, (int)$this->redis->get('key')); $this->redis->del('key'); $this->redis->set('key', 'abc'); $this->redis->incr('key'); $this->assertTrue("abc" === $this->redis->get('key')); $this->redis->incr('key'); $this->assertTrue("abc" === $this->redis->get('key')); $this->redis->set('key', 0); $this->assertEquals(PHP_INT_MAX, $this->redis->incrby('key', PHP_INT_MAX)); } public function testIncrByFloat() { // incrbyfloat is new in 2.6.0 if (version_compare($this->version, "2.5.0", "lt")) { $this->markTestSkipped(); } $this->redis->del('key'); $this->redis->set('key', 0); $this->redis->incrbyfloat('key', 1.5); $this->assertEquals('1.5', $this->redis->get('key')); $this->redis->incrbyfloat('key', 2.25); $this->assertEquals('3.75', $this->redis->get('key')); $this->redis->incrbyfloat('key', -2.25); $this->assertEquals('1.5', $this->redis->get('key')); $this->redis->set('key', 'abc'); $this->redis->incrbyfloat('key', 1.5); $this->assertTrue("abc" === $this->redis->get('key')); $this->redis->incrbyfloat('key', -1.5); $this->assertTrue("abc" === $this->redis->get('key')); // Test with prefixing $this->redis->setOption(Redis::OPT_PREFIX, 'someprefix:'); $this->redis->del('key'); $this->redis->incrbyfloat('key',1.8); $this->assertEquals(1.8, floatval($this->redis->get('key'))); // convert to float to avoid rounding issue on arm $this->redis->setOption(Redis::OPT_PREFIX, ''); $this->assertTrue($this->redis->exists('someprefix:key')); $this->redis->del('someprefix:key'); } public function testDecr() { $this->redis->set('key', 5); $this->redis->decr('key'); $this->assertEquals(4, (int)$this->redis->get('key')); $this->redis->decr('key'); $this->assertEquals(3, (int)$this->redis->get('key')); $this->redis->decrBy('key', 2); $this->assertEquals(1, (int)$this->redis->get('key')); $this->redis->decrBy('key', 1); $this->assertEquals(0, (int)$this->redis->get('key')); $this->redis->decrBy('key', -10); $this->assertEquals(10, (int)$this->redis->get('key')); $this->redis->decr('key', 10); $this->assertEquals(0, (int)$this->redis->get('key')); } public function testExists() { $this->redis->del('key'); $this->assertFalse($this->redis->exists('key')); $this->redis->set('key', 'val'); $this->assertEquals(True, $this->redis->exists('key')); } public function testGetKeys() { $pattern = 'getKeys-test-'; for($i = 1; $i < 10; $i++) { $this->redis->set($pattern.$i, $i); } $this->redis->del($pattern.'3'); $keys = $this->redis->keys($pattern.'*'); $this->redis->set($pattern.'3', 'something'); $keys2 = $this->redis->keys($pattern.'*'); $this->assertEquals((count($keys) + 1), count($keys2)); // empty array when no key matches $this->assertEquals(array(), $this->redis->keys(rand().rand().rand().'*')); } public function testDelete() { $key = 'key' . rand(); $this->redis->set($key, 'val'); $this->assertEquals('val', $this->redis->get($key)); $this->assertEquals(1, $this->redis->del($key)); $this->assertEquals(false, $this->redis->get($key)); // multiple, all existing $this->redis->set('x', 0); $this->redis->set('y', 1); $this->redis->set('z', 2); $this->assertEquals(3, $this->redis->del('x', 'y', 'z')); $this->assertEquals(false, $this->redis->get('x')); $this->assertEquals(false, $this->redis->get('y')); $this->assertEquals(false, $this->redis->get('z')); // multiple, none existing $this->assertEquals(0, $this->redis->del('x', 'y', 'z')); $this->assertEquals(false, $this->redis->get('x')); $this->assertEquals(false, $this->redis->get('y')); $this->assertEquals(false, $this->redis->get('z')); // multiple, some existing $this->redis->set('y', 1); $this->assertEquals(1, $this->redis->del('x', 'y', 'z')); $this->assertEquals(false, $this->redis->get('y')); $this->redis->set('x', 0); $this->redis->set('y', 1); $this->assertEquals(2, $this->redis->del(array('x', 'y'))); } public function testType() { // 0 => none, (key didn't exist) // 1=> string, // 2 => set, // 3 => list, // 4 => zset, // 5 => hash // string $this->redis->set('key', 'val'); $this->assertEquals(Redis::REDIS_STRING, $this->redis->type('key')); // list $this->redis->lPush('keyList', 'val0'); $this->redis->lPush('keyList', 'val1'); $this->assertEquals(Redis::REDIS_LIST, $this->redis->type('keyList')); // set $this->redis->del('keySet'); $this->redis->sAdd('keySet', 'val0'); $this->redis->sAdd('keySet', 'val1'); $this->assertEquals(Redis::REDIS_SET, $this->redis->type('keySet')); // sadd with numeric key $this->redis->del(123); $this->assertTrue(1 === $this->redis->sAdd(123, 'val0')); $this->assertTrue(array('val0') === $this->redis->sMembers(123)); // zset $this->redis->del('keyZSet'); $this->redis->zAdd('keyZSet', 0, 'val0'); $this->redis->zAdd('keyZSet', 1, 'val1'); $this->assertEquals(Redis::REDIS_ZSET, $this->redis->type('keyZSet')); // hash $this->redis->del('keyHash'); $this->redis->hSet('keyHash', 'key0', 'val0'); $this->redis->hSet('keyHash', 'key1', 'val1'); $this->assertEquals(Redis::REDIS_HASH, $this->redis->type('keyHash')); //None $this->assertEquals(Redis::REDIS_NOT_FOUND, $this->redis->type('keyNotExists')); } public function testStr() { $this->redis->set('key', 'val1'); $this->assertTrue($this->redis->append('key', 'val2') === 8); $this->assertTrue($this->redis->get('key') === 'val1val2'); $this->redis->del('keyNotExist'); $this->assertTrue($this->redis->append('keyNotExist', 'value') === 5); $this->assertTrue($this->redis->get('keyNotExist') === 'value'); $this->redis->set('key', 'This is a string') ; $this->assertTrue($this->redis->getRange('key', 0, 3) === 'This'); $this->assertTrue($this->redis->getRange('key', -6, -1) === 'string'); $this->assertTrue($this->redis->getRange('key', -6, 100000) === 'string'); $this->assertTrue($this->redis->get('key') === 'This is a string'); $this->redis->set('key', 'This is a string') ; $this->assertTrue($this->redis->strlen('key') === 16); $this->redis->set('key', 10) ; $this->assertTrue($this->redis->strlen('key') === 2); $this->redis->set('key', '') ; $this->assertTrue($this->redis->strlen('key') === 0); $this->redis->set('key', '000') ; $this->assertTrue($this->redis->strlen('key') === 3); } // PUSH, POP : LPUSH, LPOP public function testlPop() { // rpush => tail // lpush => head $this->redis->del('list'); $this->redis->lPush('list', 'val'); $this->redis->lPush('list', 'val2'); $this->redis->rPush('list', 'val3'); // 'list' = [ 'val2', 'val', 'val3'] $this->assertEquals('val2', $this->redis->lPop('list')); $this->assertEquals('val', $this->redis->lPop('list')); $this->assertEquals('val3', $this->redis->lPop('list')); $this->assertEquals(FALSE, $this->redis->lPop('list')); // testing binary data $this->redis->del('list'); $this->assertEquals(1, $this->redis->lPush('list', gzcompress('val1'))); $this->assertEquals(2, $this->redis->lPush('list', gzcompress('val2'))); $this->assertEquals(3, $this->redis->lPush('list', gzcompress('val3'))); $this->assertEquals('val3', gzuncompress($this->redis->lPop('list'))); $this->assertEquals('val2', gzuncompress($this->redis->lPop('list'))); $this->assertEquals('val1', gzuncompress($this->redis->lPop('list'))); } // PUSH, POP : RPUSH, RPOP public function testrPop() { // rpush => tail // lpush => head $this->redis->del('list'); $this->redis->rPush('list', 'val'); $this->redis->rPush('list', 'val2'); $this->redis->lPush('list', 'val3'); // 'list' = [ 'val3', 'val', 'val2'] $this->assertEquals('val2', $this->redis->rPop('list')); $this->assertEquals('val', $this->redis->rPop('list')); $this->assertEquals('val3', $this->redis->rPop('list')); $this->assertEquals(FALSE, $this->redis->rPop('list')); // testing binary data $this->redis->del('list'); $this->assertEquals(1, $this->redis->rPush('list', gzcompress('val1'))); $this->assertEquals(2, $this->redis->rPush('list', gzcompress('val2'))); $this->assertEquals(3, $this->redis->rPush('list', gzcompress('val3'))); $this->assertEquals('val3', gzuncompress($this->redis->rPop('list'))); $this->assertEquals('val2', gzuncompress($this->redis->rPop('list'))); $this->assertEquals('val1', gzuncompress($this->redis->rPop('list'))); } public function testblockingPop() { // non blocking blPop, brPop $this->redis->del('list'); $this->redis->lPush('list', 'val1'); $this->redis->lPush('list', 'val2'); $this->assertTrue($this->redis->blPop(array('list'), 2) === array('list', 'val2')); $this->assertTrue($this->redis->blPop(array('list'), 2) === array('list', 'val1')); $this->redis->del('list'); $this->redis->lPush('list', 'val1'); $this->redis->lPush('list', 'val2'); $this->assertTrue($this->redis->brPop(array('list'), 1) === array('list', 'val1')); $this->assertTrue($this->redis->brPop(array('list'), 1) === array('list', 'val2')); // blocking blpop, brpop $this->redis->del('list'); $this->assertTrue($this->redis->blPop(array('list'), 1) === array()); $this->assertTrue($this->redis->brPop(array('list'), 1) === array()); } public function testllen() { $this->redis->del('list'); $this->redis->lPush('list', 'val'); $this->assertEquals(1, $this->redis->llen('list')); $this->redis->lPush('list', 'val2'); $this->assertEquals(2, $this->redis->llen('list')); $this->assertEquals('val2', $this->redis->lPop('list')); $this->assertEquals(1, $this->redis->llen('list')); $this->assertEquals('val', $this->redis->lPop('list')); $this->assertEquals(0, $this->redis->llen('list')); $this->assertEquals(FALSE, $this->redis->lPop('list')); $this->assertEquals(0, $this->redis->llen('list')); // empty returns 0 $this->redis->del('list'); $this->assertEquals(0, $this->redis->llen('list')); // non-existent returns 0 $this->redis->set('list', 'actually not a list'); $this->assertEquals(FALSE, $this->redis->llen('list'));// not a list returns FALSE } //lInsert, lPopx, rPopx public function testlPopx() { //test lPushx/rPushx $this->redis->del('keyNotExists'); $this->assertTrue($this->redis->lPushx('keyNotExists', 'value') === 0); $this->assertTrue($this->redis->rPushx('keyNotExists', 'value') === 0); $this->redis->del('key'); $this->redis->lPush('key', 'val0'); $this->assertTrue($this->redis->lPushx('key', 'val1') === 2); $this->assertTrue($this->redis->rPushx('key', 'val2') === 3); $this->assertTrue($this->redis->lrange('key', 0, -1) === array('val1', 'val0', 'val2')); //test linsert $this->redis->del('key'); $this->redis->lPush('key', 'val0'); $this->assertTrue($this->redis->lInsert('keyNotExists', Redis::AFTER, 'val1', 'val2') === 0); $this->assertTrue($this->redis->lInsert('key', Redis::BEFORE, 'valX', 'val2') === -1); $this->assertTrue($this->redis->lInsert('key', Redis::AFTER, 'val0', 'val1') === 2); $this->assertTrue($this->redis->lInsert('key', Redis::BEFORE, 'val0', 'val2') === 3); $this->assertTrue($this->redis->lrange('key', 0, -1) === array('val2', 'val0', 'val1')); } // ltrim, lsize, lpop public function testltrim() { $this->redis->del('list'); $this->redis->lPush('list', 'val'); $this->redis->lPush('list', 'val2'); $this->redis->lPush('list', 'val3'); $this->redis->lPush('list', 'val4'); $this->assertEquals(TRUE, $this->redis->ltrim('list', 0, 2)); $this->assertEquals(3, $this->redis->llen('list')); $this->redis->ltrim('list', 0, 0); $this->assertEquals(1, $this->redis->llen('list')); $this->assertEquals('val4', $this->redis->lPop('list')); $this->assertEquals(TRUE, $this->redis->ltrim('list', 10, 10000)); $this->assertEquals(TRUE, $this->redis->ltrim('list', 10000, 10)); // test invalid type $this->redis->set('list', 'not a list...'); $this->assertEquals(FALSE, $this->redis->ltrim('list', 0, 2)); } public function setupSort() { // people with name, age, salary $this->redis->set('person:name_1', 'Alice'); $this->redis->set('person:age_1', 27); $this->redis->set('person:salary_1', 2500); $this->redis->set('person:name_2', 'Bob'); $this->redis->set('person:age_2', 34); $this->redis->set('person:salary_2', 2000); $this->redis->set('person:name_3', 'Carol'); $this->redis->set('person:age_3', 25); $this->redis->set('person:salary_3', 2800); $this->redis->set('person:name_4', 'Dave'); $this->redis->set('person:age_4', 41); $this->redis->set('person:salary_4', 3100); // set-up $this->redis->del('person:id'); foreach(array(1,2,3,4) as $id) { $this->redis->lPush('person:id', $id); } } public function testSortPrefix() { // Make sure that sorting works with a prefix $this->redis->setOption(Redis::OPT_PREFIX, 'some-prefix:'); $this->redis->del('some-item'); $this->redis->sadd('some-item', 1); $this->redis->sadd('some-item', 2); $this->redis->sadd('some-item', 3); $this->assertEquals(array('1','2','3'), $this->redis->sortAsc('some-item')); $this->assertEquals(array('3','2','1'), $this->redis->sortDesc('some-item')); $this->assertEquals(array('1','2','3'), $this->redis->sort('some-item')); // Kill our set/prefix $this->redis->del('some-item'); $this->redis->setOption(Redis::OPT_PREFIX, ''); } public function testSortAsc() { $this->setupSort(); $this->assertTrue(FALSE === $this->redis->sortAsc(NULL)); // sort by age and get IDs $byAgeAsc = array('3','1','2','4'); $this->assertEquals($byAgeAsc, $this->redis->sortAsc('person:id', 'person:age_*')); $this->assertEquals($byAgeAsc, $this->redis->sort('person:id', array('by' => 'person:age_*', 'sort' => 'asc'))); $this->assertEquals(array('1', '2', '3', '4'), $this->redis->sortAsc('person:id', NULL)); // check that NULL works. $this->assertEquals(array('1', '2', '3', '4'), $this->redis->sortAsc('person:id', NULL, NULL)); // for all fields. $this->assertEquals(array('1', '2', '3', '4'), $this->redis->sort('person:id', array('sort' => 'asc'))); // sort by age and get names $byAgeAsc = array('Carol','Alice','Bob','Dave'); $this->assertEquals($byAgeAsc, $this->redis->sortAsc('person:id', 'person:age_*', 'person:name_*')); $this->assertEquals($byAgeAsc, $this->redis->sort('person:id', array('by' => 'person:age_*', 'get' => 'person:name_*', 'sort' => 'asc'))); $this->assertEquals(array_slice($byAgeAsc, 0, 2), $this->redis->sortAsc('person:id', 'person:age_*', 'person:name_*', 0, 2)); $this->assertEquals(array_slice($byAgeAsc, 0, 2), $this->redis->sort('person:id', array('by' => 'person:age_*', 'get' => 'person:name_*', 'limit' => array(0, 2), 'sort' => 'asc'))); $this->assertEquals(array_slice($byAgeAsc, 1, 2), $this->redis->sortAsc('person:id', 'person:age_*', 'person:name_*', 1, 2)); $this->assertEquals(array_slice($byAgeAsc, 1, 2), $this->redis->sort('person:id', array('by' => 'person:age_*', 'get' => 'person:name_*', 'limit' => array(1, 2), 'sort' => 'asc'))); $this->assertEquals(array_slice($byAgeAsc, 0, 3), $this->redis->sortAsc('person:id', 'person:age_*', 'person:name_*', NULL, 3)); // NULL is transformed to 0 if there is something after it. $this->assertEquals($byAgeAsc, $this->redis->sortAsc('person:id', 'person:age_*', 'person:name_*', 0, 4)); $this->assertEquals($byAgeAsc, $this->redis->sort('person:id', array('by' => 'person:age_*', 'get' => 'person:name_*', 'limit' => array(0, 4)))); $this->assertEquals($byAgeAsc, $this->redis->sort('person:id', array('by' => 'person:age_*', 'get' => 'person:name_*', 'limit' => array(0, "4")))); // with strings $this->assertEquals($byAgeAsc, $this->redis->sort('person:id', array('by' => 'person:age_*', 'get' => 'person:name_*', 'limit' => array("0", 4)))); $this->assertEquals(array(), $this->redis->sortAsc('person:id', 'person:age_*', 'person:name_*', NULL, NULL)); // NULL, NULL is the same as (0,0). That returns no element. // sort by salary and get ages $agesBySalaryAsc = array('34', '27', '25', '41'); $this->assertEquals($agesBySalaryAsc, $this->redis->sortAsc('person:id', 'person:salary_*', 'person:age_*')); $this->assertEquals($agesBySalaryAsc, $this->redis->sort('person:id', array('by' => 'person:salary_*', 'get' => 'person:age_*', 'sort' => 'asc'))); $agesAndSalaries = $this->redis->sort('person:id', array('by' => 'person:salary_*', 'get' => array('person:age_*', 'person:salary_*'), 'sort' => 'asc')); $this->assertEquals(array('34', '2000', '27', '2500', '25', '2800', '41', '3100'), $agesAndSalaries); // sort non-alpha doesn't change all-string lists // list → [ghi, def, abc] $list = array('abc', 'def', 'ghi'); $this->redis->del('list'); foreach($list as $i) { $this->redis->lPush('list', $i); } // SORT list → [ghi, def, abc] if (version_compare($this->version, "2.5.0", "lt")) { $this->assertEquals(array_reverse($list), $this->redis->sortAsc('list')); $this->assertEquals(array_reverse($list), $this->redis->sort('list', array('sort' => 'asc'))); } else { // TODO rewrite, from 2.6.0 release notes: // SORT now will refuse to sort in numerical mode elements that can't be parsed // as numbers } // SORT list ALPHA → [abc, def, ghi] $this->assertEquals($list, $this->redis->sortAscAlpha('list')); $this->assertEquals($list, $this->redis->sort('list', array('sort' => 'asc', 'alpha' => TRUE))); } public function testSortDesc() { $this->setupSort(); // sort by age and get IDs $byAgeDesc = array('4','2','1','3'); $this->assertEquals($byAgeDesc, $this->redis->sortDesc('person:id', 'person:age_*')); // sort by age and get names $byAgeDesc = array('Dave', 'Bob', 'Alice', 'Carol'); $this->assertEquals($byAgeDesc, $this->redis->sortDesc('person:id', 'person:age_*', 'person:name_*')); $this->assertEquals(array_slice($byAgeDesc, 0, 2), $this->redis->sortDesc('person:id', 'person:age_*', 'person:name_*', 0, 2)); $this->assertEquals(array_slice($byAgeDesc, 1, 2), $this->redis->sortDesc('person:id', 'person:age_*', 'person:name_*', 1, 2)); // sort by salary and get ages $agesBySalaryDesc = array('41', '25', '27', '34'); $this->assertEquals($agesBySalaryDesc, $this->redis->sortDesc('person:id', 'person:salary_*', 'person:age_*')); // sort non-alpha doesn't change all-string lists $list = array('def', 'abc', 'ghi'); $this->redis->del('list'); foreach($list as $i) { $this->redis->lPush('list', $i); } // SORT list → [ghi, abc, def] if (version_compare($this->version, "2.5.0", "lt")) { $this->assertEquals(array_reverse($list), $this->redis->sortDesc('list')); } else { // TODO rewrite, from 2.6.0 release notes: // SORT now will refuse to sort in numerical mode elements that can't be parsed // as numbers } // SORT list ALPHA → [abc, def, ghi] $this->assertEquals(array('ghi', 'def', 'abc'), $this->redis->sortDescAlpha('list')); } // LINDEX public function testlGet() { $this->redis->del('list'); $this->redis->lPush('list', 'val'); $this->redis->lPush('list', 'val2'); $this->redis->lPush('list', 'val3'); $this->assertEquals('val3', $this->redis->lGet('list', 0)); $this->assertEquals('val2', $this->redis->lGet('list', 1)); $this->assertEquals('val', $this->redis->lGet('list', 2)); $this->assertEquals('val', $this->redis->lGet('list', -1)); $this->assertEquals('val2', $this->redis->lGet('list', -2)); $this->assertEquals('val3', $this->redis->lGet('list', -3)); $this->assertEquals(FALSE, $this->redis->lGet('list', -4)); $this->redis->rPush('list', 'val4'); $this->assertEquals('val4', $this->redis->lGet('list', 3)); $this->assertEquals('val4', $this->redis->lGet('list', -1)); } // lRem testing public function testlrem() { $this->redis->del('list'); $this->redis->lPush('list', 'a'); $this->redis->lPush('list', 'b'); $this->redis->lPush('list', 'c'); $this->redis->lPush('list', 'c'); $this->redis->lPush('list', 'b'); $this->redis->lPush('list', 'c'); // ['c', 'b', 'c', 'c', 'b', 'a'] $return = $this->redis->lrem('list', 'b', 2); // ['c', 'c', 'c', 'a'] $this->assertEquals(2, $return); $this->assertEquals('c', $this->redis->lGET('list', 0)); $this->assertEquals('c', $this->redis->lGET('list', 1)); $this->assertEquals('c', $this->redis->lGET('list', 2)); $this->assertEquals('a', $this->redis->lGET('list', 3)); $this->redis->del('list'); $this->redis->lPush('list', 'a'); $this->redis->lPush('list', 'b'); $this->redis->lPush('list', 'c'); $this->redis->lPush('list', 'c'); $this->redis->lPush('list', 'b'); $this->redis->lPush('list', 'c'); // ['c', 'b', 'c', 'c', 'b', 'a'] $this->redis->lrem('list', 'c', -2); // ['c', 'b', 'b', 'a'] $this->assertEquals(2, $return); $this->assertEquals('c', $this->redis->lGET('list', 0)); $this->assertEquals('b', $this->redis->lGET('list', 1)); $this->assertEquals('b', $this->redis->lGET('list', 2)); $this->assertEquals('a', $this->redis->lGET('list', 3)); // remove each element $this->assertEquals(1, $this->redis->lrem('list', 'a', 0)); $this->assertEquals(0, $this->redis->lrem('list', 'x', 0)); $this->assertEquals(2, $this->redis->lrem('list', 'b', 0)); $this->assertEquals(1, $this->redis->lrem('list', 'c', 0)); $this->assertEquals(FALSE, $this->redis->get('list')); $this->redis->set('list', 'actually not a list'); $this->assertEquals(FALSE, $this->redis->lrem('list', 'x')); } public function testsAdd() { $this->redis->del('set'); $this->assertEquals(1, $this->redis->sAdd('set', 'val')); $this->assertEquals(0, $this->redis->sAdd('set', 'val')); $this->assertTrue($this->redis->sismember('set', 'val')); $this->assertFalse($this->redis->sismember('set', 'val2')); $this->assertEquals(1, $this->redis->sAdd('set', 'val2')); $this->assertTrue($this->redis->sismember('set', 'val2')); } public function testscard() { $this->redis->del('set'); $this->assertEquals(1, $this->redis->sAdd('set', 'val')); $this->assertEquals(1, $this->redis->scard('set')); $this->assertEquals(1, $this->redis->sAdd('set', 'val2')); $this->assertEquals(2, $this->redis->scard('set')); } public function testsrem() { $this->redis->del('set'); $this->redis->sAdd('set', 'val'); $this->redis->sAdd('set', 'val2'); $this->redis->srem('set', 'val'); $this->assertEquals(1, $this->redis->scard('set')); $this->redis->srem('set', 'val2'); $this->assertEquals(0, $this->redis->scard('set')); } public function testsMove() { $this->redis->del('{set}0'); $this->redis->del('{set}1'); $this->redis->sAdd('{set}0', 'val'); $this->redis->sAdd('{set}0', 'val2'); $this->assertTrue($this->redis->sMove('{set}0', '{set}1', 'val')); $this->assertFalse($this->redis->sMove('{set}0', '{set}1', 'val')); $this->assertFalse($this->redis->sMove('{set}0', '{set}1', 'val-what')); $this->assertEquals(1, $this->redis->scard('{set}0')); $this->assertEquals(1, $this->redis->scard('{set}1')); $this->assertEquals(array('val2'), $this->redis->smembers('{set}0')); $this->assertEquals(array('val'), $this->redis->smembers('{set}1')); } public function testsPop() { $this->redis->del('set0'); $this->assertTrue($this->redis->sPop('set0') === FALSE); $this->redis->sAdd('set0', 'val'); $this->redis->sAdd('set0', 'val2'); $v0 = $this->redis->sPop('set0'); $this->assertTrue(1 === $this->redis->scard('set0')); $this->assertTrue($v0 === 'val' || $v0 === 'val2'); $v1 = $this->redis->sPop('set0'); $this->assertTrue(0 === $this->redis->scard('set0')); $this->assertTrue(($v0 === 'val' && $v1 === 'val2') || ($v1 === 'val' && $v0 === 'val2')); $this->assertTrue($this->redis->sPop('set0') === FALSE); } public function testsPopWithCount() { if (!$this->minVersionCheck("3.2")) { return $this->markTestSkipped(); } $set = 'set0'; $prefix = 'member'; $count = 5; /* Add a few members */ $this->redis->del($set); for ($i = 0; $i < $count; $i++) { $this->redis->sadd($set, $prefix.$i); } /* Pop them all */ $ret = $this->redis->sPop($set, $i); /* Make sure we got an arary and the count is right */ if ($this->assertTrue(is_array($ret)) && $this->assertTrue(count($ret) == $count)) { /* Probably overkill but validate the actual returned members */ for ($i = 0; $i < $count; $i++) { $this->assertTrue(in_array($prefix.$i, $ret)); } } } public function testsRandMember() { $this->redis->del('set0'); $this->assertTrue($this->redis->sRandMember('set0') === FALSE); $this->redis->sAdd('set0', 'val'); $this->redis->sAdd('set0', 'val2'); $got = array(); while(true) { $v = $this->redis->sRandMember('set0'); $this->assertTrue(2 === $this->redis->scard('set0')); // no change. $this->assertTrue($v === 'val' || $v === 'val2'); $got[$v] = $v; if(count($got) == 2) { break; } } // // With and without count, while serializing // $this->redis->del('set0'); $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); for($i=0;$i<5;$i++) { $member = "member:$i"; $this->redis->sAdd('set0', $member); $mems[] = $member; } $member = $this->redis->srandmember('set0'); $this->assertTrue(in_array($member, $mems)); $rmembers = $this->redis->srandmember('set0', $i); foreach($rmembers as $reply_mem) { $this->assertTrue(in_array($reply_mem, $mems)); } $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); } public function testSRandMemberWithCount() { // Make sure the set is nuked $this->redis->del('set0'); // Run with a count (positive and negative) on an empty set $ret_pos = $this->redis->sRandMember('set0', 10); $ret_neg = $this->redis->sRandMember('set0', -10); // Should both be empty arrays $this->assertTrue(is_array($ret_pos) && empty($ret_pos)); $this->assertTrue(is_array($ret_neg) && empty($ret_neg)); // Add a few items to the set for($i=0;$i<100;$i++) { $this->redis->sadd('set0', "member$i"); } // Get less than the size of the list $ret_slice = $this->redis->srandmember('set0', 20); // Should be an array with 20 items $this->assertTrue(is_array($ret_slice) && count($ret_slice) == 20); // Ask for more items than are in the list (but with a positive count) $ret_slice = $this->redis->srandmember('set0', 200); // Should be an array, should be however big the set is, exactly $this->assertTrue(is_array($ret_slice) && count($ret_slice) == $i); // Now ask for too many items but negative $ret_slice = $this->redis->srandmember('set0', -200); // Should be an array, should have exactly the # of items we asked for (will be dups) $this->assertTrue(is_array($ret_slice) && count($ret_slice) == 200); // // Test in a pipeline // if ($this->havePipeline()) { $pipe = $this->redis->pipeline(); $pipe->srandmember('set0', 20); $pipe->srandmember('set0', 200); $pipe->srandmember('set0', -200); $ret = $this->redis->exec(); $this->assertTrue(is_array($ret[0]) && count($ret[0]) == 20); $this->assertTrue(is_array($ret[1]) && count($ret[1]) == $i); $this->assertTrue(is_array($ret[2]) && count($ret[2]) == 200); // Kill the set $this->redis->del('set0'); } } public function testsismember() { $this->redis->del('set'); $this->redis->sAdd('set', 'val'); $this->assertTrue($this->redis->sismember('set', 'val')); $this->assertFalse($this->redis->sismember('set', 'val2')); } public function testsmembers() { $this->redis->del('set'); $this->redis->sAdd('set', 'val'); $this->redis->sAdd('set', 'val2'); $this->redis->sAdd('set', 'val3'); $array = array('val', 'val2', 'val3'); $smembers = $this->redis->smembers('set'); sort($smembers); $this->assertEquals($array, $smembers); $sMembers = $this->redis->sMembers('set'); sort($sMembers); $this->assertEquals($array, $sMembers); // test alias } public function testlSet() { $this->redis->del('list'); $this->redis->lPush('list', 'val'); $this->redis->lPush('list', 'val2'); $this->redis->lPush('list', 'val3'); $this->assertEquals($this->redis->lGet('list', 0), 'val3'); $this->assertEquals($this->redis->lGet('list', 1), 'val2'); $this->assertEquals($this->redis->lGet('list', 2), 'val'); $this->assertEquals(TRUE, $this->redis->lSet('list', 1, 'valx')); $this->assertEquals($this->redis->lGet('list', 0), 'val3'); $this->assertEquals($this->redis->lGet('list', 1), 'valx'); $this->assertEquals($this->redis->lGet('list', 2), 'val'); } public function testsInter() { $this->redis->del('{set}odd'); // set of odd numbers $this->redis->del('{set}prime'); // set of prime numbers $this->redis->del('{set}square'); // set of squares $this->redis->del('{set}seq'); // set of numbers of the form n^2 - 1 $x = array(1,3,5,7,9,11,13,15,17,19,21,23,25); foreach($x as $i) { $this->redis->sAdd('{set}odd', $i); } $y = array(1,2,3,5,7,11,13,17,19,23); foreach($y as $i) { $this->redis->sAdd('{set}prime', $i); } $z = array(1,4,9,16,25); foreach($z as $i) { $this->redis->sAdd('{set}square', $i); } $t = array(2,5,10,17,26); foreach($t as $i) { $this->redis->sAdd('{set}seq', $i); } $xy = $this->redis->sInter('{set}odd', '{set}prime'); // odd prime numbers foreach($xy as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_intersect($x, $y))); } $xy = $this->redis->sInter(array('{set}odd', '{set}prime')); // odd prime numbers, as array. foreach($xy as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_intersect($x, $y))); } $yz = $this->redis->sInter('{set}prime', '{set}square'); // set of prime squares foreach($yz as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_intersect($y, $z))); } $yz = $this->redis->sInter(array('{set}prime', '{set}square')); // set of odd squares, as array foreach($yz as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_intersect($y, $z))); } $zt = $this->redis->sInter('{set}square', '{set}seq'); // prime squares $this->assertTrue($zt === array()); $zt = $this->redis->sInter(array('{set}square', '{set}seq')); // prime squares, as array $this->assertTrue($zt === array()); $xyz = $this->redis->sInter('{set}odd', '{set}prime', '{set}square');// odd prime squares $this->assertTrue($xyz === array('1')); $xyz = $this->redis->sInter(array('{set}odd', '{set}prime', '{set}square'));// odd prime squares, with an array as a parameter $this->assertTrue($xyz === array('1')); $nil = $this->redis->sInter(array()); $this->assertTrue($nil === FALSE); } public function testsInterStore() { $this->redis->del('{set}x'); // set of odd numbers $this->redis->del('{set}y'); // set of prime numbers $this->redis->del('{set}z'); // set of squares $this->redis->del('{set}t'); // set of numbers of the form n^2 - 1 $x = array(1,3,5,7,9,11,13,15,17,19,21,23,25); foreach($x as $i) { $this->redis->sAdd('{set}x', $i); } $y = array(1,2,3,5,7,11,13,17,19,23); foreach($y as $i) { $this->redis->sAdd('{set}y', $i); } $z = array(1,4,9,16,25); foreach($z as $i) { $this->redis->sAdd('{set}z', $i); } $t = array(2,5,10,17,26); foreach($t as $i) { $this->redis->sAdd('{set}t', $i); } /* Regression test for passing a single array */ $this->assertEquals($this->redis->sInterStore(Array('{set}k', '{set}x', '{set}y')), count(array_intersect($x,$y))); $count = $this->redis->sInterStore('{set}k', '{set}x', '{set}y'); // odd prime numbers $this->assertEquals($count, $this->redis->scard('{set}k')); foreach(array_intersect($x, $y) as $i) { $this->assertTrue($this->redis->sismember('{set}k', $i)); } $count = $this->redis->sInterStore('{set}k', '{set}y', '{set}z'); // set of odd squares $this->assertEquals($count, $this->redis->scard('{set}k')); foreach(array_intersect($y, $z) as $i) { $this->assertTrue($this->redis->sismember('{set}k', $i)); } $count = $this->redis->sInterStore('{set}k', '{set}z', '{set}t'); // squares of the form n^2 + 1 $this->assertEquals($count, 0); $this->assertEquals($count, $this->redis->scard('{set}k')); $this->redis->del('{set}z'); $xyz = $this->redis->sInterStore('{set}k', '{set}x', '{set}y', '{set}z'); // only z missing, expect 0. $this->assertTrue($xyz === 0); $this->redis->del('{set}y'); $xyz = $this->redis->sInterStore('{set}k', '{set}x', '{set}y', '{set}z'); // y and z missing, expect 0. $this->assertTrue($xyz === 0); $this->redis->del('{set}x'); $xyz = $this->redis->sInterStore('{set}k', '{set}x', '{set}y', '{set}z'); // x y and z ALL missing, expect 0. $this->assertTrue($xyz === 0); } public function testsUnion() { $this->redis->del('{set}x'); // set of odd numbers $this->redis->del('{set}y'); // set of prime numbers $this->redis->del('{set}z'); // set of squares $this->redis->del('{set}t'); // set of numbers of the form n^2 - 1 $x = array(1,3,5,7,9,11,13,15,17,19,21,23,25); foreach($x as $i) { $this->redis->sAdd('{set}x', $i); } $y = array(1,2,3,5,7,11,13,17,19,23); foreach($y as $i) { $this->redis->sAdd('{set}y', $i); } $z = array(1,4,9,16,25); foreach($z as $i) { $this->redis->sAdd('{set}z', $i); } $t = array(2,5,10,17,26); foreach($t as $i) { $this->redis->sAdd('{set}t', $i); } $xy = $this->redis->sUnion('{set}x', '{set}y'); // x U y foreach($xy as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_merge($x, $y))); } $yz = $this->redis->sUnion('{set}y', '{set}z'); // y U Z foreach($yz as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_merge($y, $z))); } $zt = $this->redis->sUnion('{set}z', '{set}t'); // z U t foreach($zt as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_merge($z, $t))); } $xyz = $this->redis->sUnion('{set}x', '{set}y', '{set}z'); // x U y U z foreach($xyz as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_merge($x, $y, $z))); } } public function testsUnionStore() { $this->redis->del('{set}x'); // set of odd numbers $this->redis->del('{set}y'); // set of prime numbers $this->redis->del('{set}z'); // set of squares $this->redis->del('{set}t'); // set of numbers of the form n^2 - 1 $x = array(1,3,5,7,9,11,13,15,17,19,21,23,25); foreach($x as $i) { $this->redis->sAdd('{set}x', $i); } $y = array(1,2,3,5,7,11,13,17,19,23); foreach($y as $i) { $this->redis->sAdd('{set}y', $i); } $z = array(1,4,9,16,25); foreach($z as $i) { $this->redis->sAdd('{set}z', $i); } $t = array(2,5,10,17,26); foreach($t as $i) { $this->redis->sAdd('{set}t', $i); } $count = $this->redis->sUnionStore('{set}k', '{set}x', '{set}y'); // x U y $xy = array_unique(array_merge($x, $y)); $this->assertEquals($count, count($xy)); foreach($xy as $i) { $i = (int)$i; $this->assertTrue($this->redis->sismember('{set}k', $i)); } $count = $this->redis->sUnionStore('{set}k', '{set}y', '{set}z'); // y U z $yz = array_unique(array_merge($y, $z)); $this->assertEquals($count, count($yz)); foreach($yz as $i) { $i = (int)$i; $this->assertTrue($this->redis->sismember('{set}k', $i)); } $count = $this->redis->sUnionStore('{set}k', '{set}z', '{set}t'); // z U t $zt = array_unique(array_merge($z, $t)); $this->assertEquals($count, count($zt)); foreach($zt as $i) { $i = (int)$i; $this->assertTrue($this->redis->sismember('{set}k', $i)); } $count = $this->redis->sUnionStore('{set}k', '{set}x', '{set}y', '{set}z'); // x U y U z $xyz = array_unique(array_merge($x, $y, $z)); $this->assertEquals($count, count($xyz)); foreach($xyz as $i) { $i = (int)$i; $this->assertTrue($this->redis->sismember('{set}k', $i)); } $this->redis->del('{set}x'); // x missing now $count = $this->redis->sUnionStore('{set}k', '{set}x', '{set}y', '{set}z'); // x U y U z $this->assertTrue($count === count(array_unique(array_merge($y, $z)))); $this->redis->del('{set}y'); // x and y missing $count = $this->redis->sUnionStore('{set}k', '{set}x', '{set}y', '{set}z'); // x U y U z $this->assertTrue($count === count(array_unique($z))); $this->redis->del('{set}z'); // x, y, and z ALL missing $count = $this->redis->sUnionStore('{set}k', '{set}x', '{set}y', '{set}z'); // x U y U z $this->assertTrue($count === 0); } public function testsDiff() { $this->redis->del('{set}x'); // set of odd numbers $this->redis->del('{set}y'); // set of prime numbers $this->redis->del('{set}z'); // set of squares $this->redis->del('{set}t'); // set of numbers of the form n^2 - 1 $x = array(1,3,5,7,9,11,13,15,17,19,21,23,25); foreach($x as $i) { $this->redis->sAdd('{set}x', $i); } $y = array(1,2,3,5,7,11,13,17,19,23); foreach($y as $i) { $this->redis->sAdd('{set}y', $i); } $z = array(1,4,9,16,25); foreach($z as $i) { $this->redis->sAdd('{set}z', $i); } $t = array(2,5,10,17,26); foreach($t as $i) { $this->redis->sAdd('{set}t', $i); } $xy = $this->redis->sDiff('{set}x', '{set}y'); // x U y foreach($xy as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_diff($x, $y))); } $yz = $this->redis->sDiff('{set}y', '{set}z'); // y U Z foreach($yz as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_diff($y, $z))); } $zt = $this->redis->sDiff('{set}z', '{set}t'); // z U t foreach($zt as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_diff($z, $t))); } $xyz = $this->redis->sDiff('{set}x', '{set}y', '{set}z'); // x U y U z foreach($xyz as $i) { $i = (int)$i; $this->assertTrue(in_array($i, array_diff($x, $y, $z))); } } public function testsDiffStore() { $this->redis->del('{set}x'); // set of odd numbers $this->redis->del('{set}y'); // set of prime numbers $this->redis->del('{set}z'); // set of squares $this->redis->del('{set}t'); // set of numbers of the form n^2 - 1 $x = array(1,3,5,7,9,11,13,15,17,19,21,23,25); foreach($x as $i) { $this->redis->sAdd('{set}x', $i); } $y = array(1,2,3,5,7,11,13,17,19,23); foreach($y as $i) { $this->redis->sAdd('{set}y', $i); } $z = array(1,4,9,16,25); foreach($z as $i) { $this->redis->sAdd('{set}z', $i); } $t = array(2,5,10,17,26); foreach($t as $i) { $this->redis->sAdd('{set}t', $i); } $count = $this->redis->sDiffStore('{set}k', '{set}x', '{set}y'); // x - y $xy = array_unique(array_diff($x, $y)); $this->assertEquals($count, count($xy)); foreach($xy as $i) { $i = (int)$i; $this->assertTrue($this->redis->sismember('{set}k', $i)); } $count = $this->redis->sDiffStore('{set}k', '{set}y', '{set}z'); // y - z $yz = array_unique(array_diff($y, $z)); $this->assertEquals($count, count($yz)); foreach($yz as $i) { $i = (int)$i; $this->assertTrue($this->redis->sismember('{set}k', $i)); } $count = $this->redis->sDiffStore('{set}k', '{set}z', '{set}t'); // z - t $zt = array_unique(array_diff($z, $t)); $this->assertEquals($count, count($zt)); foreach($zt as $i) { $i = (int)$i; $this->assertTrue($this->redis->sismember('{set}k', $i)); } $count = $this->redis->sDiffStore('{set}k', '{set}x', '{set}y', '{set}z'); // x - y - z $xyz = array_unique(array_diff($x, $y, $z)); $this->assertEquals($count, count($xyz)); foreach($xyz as $i) { $i = (int)$i; $this->assertTrue($this->redis->sismember('{set}k', $i)); } $this->redis->del('{set}x'); // x missing now $count = $this->redis->sDiffStore('{set}k', '{set}x', '{set}y', '{set}z'); // x - y - z $this->assertTrue($count === 0); $this->redis->del('{set}y'); // x and y missing $count = $this->redis->sDiffStore('{set}k', '{set}x', '{set}y', '{set}z'); // x - y - z $this->assertTrue($count === 0); $this->redis->del('{set}z'); // x, y, and z ALL missing $count = $this->redis->sDiffStore('{set}k', '{set}x', '{set}y', '{set}z'); // x - y - z $this->assertTrue($count === 0); } public function testlrange() { $this->redis->del('list'); $this->redis->lPush('list', 'val'); $this->redis->lPush('list', 'val2'); $this->redis->lPush('list', 'val3'); // pos : 0 1 2 // pos : -3 -2 -1 // list: [val3, val2, val] $this->assertEquals($this->redis->lrange('list', 0, 0), array('val3')); $this->assertEquals($this->redis->lrange('list', 0, 1), array('val3', 'val2')); $this->assertEquals($this->redis->lrange('list', 0, 2), array('val3', 'val2', 'val')); $this->assertEquals($this->redis->lrange('list', 0, 3), array('val3', 'val2', 'val')); $this->assertEquals($this->redis->lrange('list', 0, -1), array('val3', 'val2', 'val')); $this->assertEquals($this->redis->lrange('list', 0, -2), array('val3', 'val2')); $this->assertEquals($this->redis->lrange('list', -2, -1), array('val2', 'val')); $this->redis->del('list'); $this->assertEquals($this->redis->lrange('list', 0, -1), array()); } public function testdbSize() { $this->assertTrue($this->redis->flushDB()); $this->redis->set('x', 'y'); $this->assertTrue($this->redis->dbSize() === 1); } public function testttl() { $this->redis->set('x', 'y'); $this->redis->expire('x', 5); for($i = 5; $i > 0; $i--) { $this->assertEquals($i, $this->redis->ttl('x')); sleep(1); } // A key with no TTL $this->redis->del('x'); $this->redis->set('x', 'bar'); $this->assertEquals($this->redis->ttl('x'), -1); // A key that doesn't exist (> 2.8 will return -2) if(version_compare($this->version, "2.8.0", "gte")) { $this->redis->del('x'); $this->assertEquals($this->redis->ttl('x'), -2); } } public function testPersist() { $this->redis->set('x', 'y'); $this->redis->expire('x', 100); $this->assertTrue(TRUE === $this->redis->persist('x')); // true if there is a timeout $this->assertTrue(-1 === $this->redis->ttl('x')); // -1: timeout has been removed. $this->assertTrue(FALSE === $this->redis->persist('x')); // false if there is no timeout $this->redis->del('x'); $this->assertTrue(FALSE === $this->redis->persist('x')); // false if the key doesn’t exist. } public function testClient() { /* CLIENT SETNAME */ $this->assertTrue($this->redis->client('setname', 'phpredis_unit_tests')); /* CLIENT LIST */ $arr_clients = $this->redis->client('list'); $this->assertTrue(is_array($arr_clients)); // Figure out which ip:port is us! $str_addr = NULL; foreach($arr_clients as $arr_client) { if($arr_client['name'] == 'phpredis_unit_tests') { $str_addr = $arr_client['addr']; } } // We should have found our connection $this->assertFalse(empty($str_addr)); /* CLIENT GETNAME */ $this->assertTrue($this->redis->client('getname'), 'phpredis_unit_tests'); /* CLIENT KILL -- phpredis will reconnect, so we can do this */ $this->assertTrue($this->redis->client('kill', $str_addr)); } public function testSlowlog() { // We don't really know what's going to be in the slowlog, but make sure // the command returns proper types when called in various ways $this->assertTrue(is_array($this->redis->slowlog('get'))); $this->assertTrue(is_array($this->redis->slowlog('get', 10))); $this->assertTrue(is_int($this->redis->slowlog('len'))); $this->assertTrue($this->redis->slowlog('reset')); $this->assertFalse($this->redis->slowlog('notvalid')); } public function testWait() { // Closest we can check based on redis commmit history if(version_compare($this->version, '2.9.11', 'lt')) { $this->markTestSkipped(); return; } // We could have slaves here, so determine that $arr_slaves = $this->redis->info(); $i_slaves = $arr_slaves['connected_slaves']; // Send a couple commands $this->redis->set('wait-foo', 'over9000'); $this->redis->set('wait-bar', 'revo9000'); // Make sure we get the right replication count $this->assertEquals($this->redis->wait($i_slaves, 100), $i_slaves); // Pass more slaves than are connected $this->redis->set('wait-foo','over9000'); $this->redis->set('wait-bar','revo9000'); $this->assertTrue($this->redis->wait($i_slaves+1, 100) < $i_slaves+1); // Make sure when we pass with bad arguments we just get back false $this->assertFalse($this->redis->wait(-1, -1)); $this->assertFalse($this->redis->wait(-1, 20)); } public function testInfo() { $info = $this->redis->info(); $keys = array( "redis_version", "arch_bits", "uptime_in_seconds", "uptime_in_days", "connected_clients", "connected_slaves", "used_memory", "total_connections_received", "total_commands_processed", "role" ); if (version_compare($this->version, "2.5.0", "lt")) { array_push($keys, "changes_since_last_save", "bgsave_in_progress", "last_save_time" ); } else { array_push($keys, "rdb_changes_since_last_save", "rdb_bgsave_in_progress", "rdb_last_save_time" ); } foreach($keys as $k) { $this->assertTrue(in_array($k, array_keys($info))); } } public function testInfoCommandStats() { // INFO COMMANDSTATS is new in 2.6.0 if (version_compare($this->version, "2.5.0", "lt")) { $this->markTestSkipped(); } $info = $this->redis->info("COMMANDSTATS"); $this->assertTrue(is_array($info)); if (is_array($info)) { foreach($info as $k => $value) { $this->assertTrue(strpos($k, 'cmdstat_') !== false); } } } public function testSelect() { $this->assertFalse($this->redis->select(-1)); $this->assertTrue($this->redis->select(0)); } public function testMset() { $this->redis->del('x', 'y', 'z'); // remove x y z $this->assertTrue($this->redis->mset(array('x' => 'a', 'y' => 'b', 'z' => 'c'))); // set x y z $this->assertEquals($this->redis->mget(array('x', 'y', 'z')), array('a', 'b', 'c')); // check x y z $this->redis->del('x'); // delete just x $this->assertTrue($this->redis->mset(array('x' => 'a', 'y' => 'b', 'z' => 'c'))); // set x y z $this->assertEquals($this->redis->mget(array('x', 'y', 'z')), array('a', 'b', 'c')); // check x y z $this->assertFalse($this->redis->mset(array())); // set ø → FALSE /* * Integer keys */ // No prefix $set_array = Array(-1 => 'neg1', -2 => 'neg2', -3 => 'neg3', 1 => 'one', 2 => 'two', '3' => 'three'); $this->redis->del(array_keys($set_array)); $this->assertTrue($this->redis->mset($set_array)); $this->assertEquals($this->redis->mget(array_keys($set_array)), array_values($set_array)); $this->redis->del(array_keys($set_array)); // With a prefix $this->redis->setOption(Redis::OPT_PREFIX, 'pfx:'); $this->redis->del(array_keys($set_array)); $this->assertTrue($this->redis->mset($set_array)); $this->assertEquals($this->redis->mget(array_keys($set_array)), array_values($set_array)); $this->redis->del(array_keys($set_array)); $this->redis->setOption(Redis::OPT_PREFIX, ''); } public function testMsetNX() { $this->redis->del('x', 'y', 'z'); // remove x y z $this->assertTrue(TRUE === $this->redis->msetnx(array('x' => 'a', 'y' => 'b', 'z' => 'c'))); // set x y z $this->assertEquals($this->redis->mget(array('x', 'y', 'z')), array('a', 'b', 'c')); // check x y z $this->redis->del('x'); // delete just x $this->assertTrue(FALSE === $this->redis->msetnx(array('x' => 'A', 'y' => 'B', 'z' => 'C'))); // set x y z $this->assertEquals($this->redis->mget(array('x', 'y', 'z')), array(FALSE, 'b', 'c')); // check x y z $this->assertFalse($this->redis->msetnx(array())); // set ø → FALSE } public function testRpopLpush() { // standard case. $this->redis->del('{list}x', '{list}y'); $this->redis->lpush('{list}x', 'abc'); $this->redis->lpush('{list}x', 'def'); // x = [def, abc] $this->redis->lpush('{list}y', '123'); $this->redis->lpush('{list}y', '456'); // y = [456, 123] $this->assertEquals($this->redis->rpoplpush('{list}x', '{list}y'), 'abc'); // we RPOP x, yielding abc. $this->assertEquals($this->redis->lrange('{list}x', 0, -1), array('def')); // only def remains in x. $this->assertEquals($this->redis->lrange('{list}y', 0, -1), array('abc', '456', '123')); // abc has been lpushed to y. // with an empty source, expecting no change. $this->redis->del('{list}x', '{list}y'); $this->assertTrue(FALSE === $this->redis->rpoplpush('{list}x', '{list}y')); $this->assertTrue(array() === $this->redis->lrange('{list}x', 0, -1)); $this->assertTrue(array() === $this->redis->lrange('{list}y', 0, -1)); } public function testBRpopLpush() { // standard case. $this->redis->del('{list}x', '{list}y'); $this->redis->lpush('{list}x', 'abc'); $this->redis->lpush('{list}x', 'def'); // x = [def, abc] $this->redis->lpush('{list}y', '123'); $this->redis->lpush('{list}y', '456'); // y = [456, 123] $this->assertEquals($this->redis->brpoplpush('{list}x', '{list}y', 1), 'abc'); // we RPOP x, yielding abc. $this->assertEquals($this->redis->lrange('{list}x', 0, -1), array('def')); // only def remains in x. $this->assertEquals($this->redis->lrange('{list}y', 0, -1), array('abc', '456', '123')); // abc has been lpushed to y. // with an empty source, expecting no change. $this->redis->del('{list}x', '{list}y'); $this->assertTrue(FALSE === $this->redis->brpoplpush('{list}x', '{list}y', 1)); $this->assertTrue(array() === $this->redis->lrange('{list}x', 0, -1)); $this->assertTrue(array() === $this->redis->lrange('{list}y', 0, -1)); } public function testZAddFirstArg() { $this->redis->del('key'); $zsetName = 100; // not a string! $this->assertTrue(1 === $this->redis->zAdd($zsetName, 0, 'val0')); $this->assertTrue(1 === $this->redis->zAdd($zsetName, 1, 'val1')); $this->assertTrue(array('val0', 'val1') === $this->redis->zRange($zsetName, 0, -1)); } public function testZX() { $this->redis->del('key'); $this->assertTrue(array() === $this->redis->zRange('key', 0, -1)); $this->assertTrue(array() === $this->redis->zRange('key', 0, -1, true)); $this->assertTrue(1 === $this->redis->zAdd('key', 0, 'val0')); $this->assertTrue(1 === $this->redis->zAdd('key', 2, 'val2')); $this->assertTrue(2 === $this->redis->zAdd('key', 4, 'val4', 5, 'val5')); // multiple parameters if (version_compare($this->version, "3.0.2", "lt")) { $this->assertTrue(1 === $this->redis->zAdd('key', 1, 'val1')); $this->assertTrue(1 === $this->redis->zAdd('key', 3, 'val3')); } else { $this->assertTrue(1 === $this->redis->zAdd('key', array(), 1, 'val1')); // empty options $this->assertTrue(1 === $this->redis->zAdd('key', array('nx'), 3, 'val3')); // nx option $this->assertTrue(0 === $this->redis->zAdd('key', array('xx'), 3, 'val3')); // xx option } $this->assertTrue(array('val0', 'val1', 'val2', 'val3', 'val4', 'val5') === $this->redis->zRange('key', 0, -1)); // withscores $ret = $this->redis->zRange('key', 0, -1, true); $this->assertTrue(count($ret) == 6); $this->assertTrue($ret['val0'] == 0); $this->assertTrue($ret['val1'] == 1); $this->assertTrue($ret['val2'] == 2); $this->assertTrue($ret['val3'] == 3); $this->assertTrue($ret['val4'] == 4); $this->assertTrue($ret['val5'] == 5); $this->assertTrue(0 === $this->redis->zRem('key', 'valX')); $this->assertTrue(1 === $this->redis->zRem('key', 'val3')); $this->assertTrue(1 === $this->redis->zRem('key', 'val4')); $this->assertTrue(1 === $this->redis->zRem('key', 'val5')); $this->assertTrue(array('val0', 'val1', 'val2') === $this->redis->zRange('key', 0, -1)); // zGetReverseRange $this->assertTrue(1 === $this->redis->zAdd('key', 3, 'val3')); $this->assertTrue(1 === $this->redis->zAdd('key', 3, 'aal3')); $zero_to_three = $this->redis->zRangeByScore('key', 0, 3); $this->assertTrue(array('val0', 'val1', 'val2', 'aal3', 'val3') === $zero_to_three || array('val0', 'val1', 'val2', 'val3', 'aal3') === $zero_to_three); $three_to_zero = $this->redis->zRevRangeByScore('key', 3, 0); $this->assertTrue(array_reverse(array('val0', 'val1', 'val2', 'aal3', 'val3')) === $three_to_zero || array_reverse(array('val0', 'val1', 'val2', 'val3', 'aal3')) === $three_to_zero); $this->assertTrue(5 === $this->redis->zCount('key', 0, 3)); // withscores $this->redis->zRem('key', 'aal3'); $zero_to_three = $this->redis->zRangeByScore('key', 0, 3, array('withscores' => TRUE)); $this->assertTrue(array('val0' => 0, 'val1' => 1, 'val2' => 2, 'val3' => 3) == $zero_to_three); $this->assertTrue(4 === $this->redis->zCount('key', 0, 3)); // limit $this->assertTrue(array('val0') === $this->redis->zRangeByScore('key', 0, 3, array('limit' => array(0, 1)))); $this->assertTrue(array('val0', 'val1') === $this->redis->zRangeByScore('key', 0, 3, array('limit' => array(0, 2)))); $this->assertTrue(array('val1', 'val2') === $this->redis->zRangeByScore('key', 0, 3, array('limit' => array(1, 2)))); $this->assertTrue(array('val0', 'val1') === $this->redis->zRangeByScore('key', 0, 1, array('limit' => array(0, 100)))); // limits as references $limit = array(0, 100); foreach ($limit as &$val) {} $this->assertTrue(array('val0', 'val1') === $this->redis->zRangeByScore('key', 0, 1, array('limit' => $limit))); $this->assertTrue(array('val3') === $this->redis->zRevRangeByScore('key', 3, 0, array('limit' => array(0, 1)))); $this->assertTrue(array('val3', 'val2') === $this->redis->zRevRangeByScore('key', 3, 0, array('limit' => array(0, 2)))); $this->assertTrue(array('val2', 'val1') === $this->redis->zRevRangeByScore('key', 3, 0, array('limit' => array(1, 2)))); $this->assertTrue(array('val1', 'val0') === $this->redis->zRevRangeByScore('key', 1, 0, array('limit' => array(0, 100)))); $this->assertTrue(4 === $this->redis->zCard('key')); $this->assertTrue(1.0 === $this->redis->zScore('key', 'val1')); $this->assertFalse($this->redis->zScore('key', 'val')); $this->assertFalse($this->redis->zScore(3, 2)); // with () and +inf, -inf $this->redis->del('zset'); $this->redis->zAdd('zset', 1, 'foo'); $this->redis->zAdd('zset', 2, 'bar'); $this->redis->zAdd('zset', 3, 'biz'); $this->redis->zAdd('zset', 4, 'foz'); $this->assertTrue(array('foo' => 1, 'bar' => 2, 'biz' => 3, 'foz' => 4) == $this->redis->zRangeByScore('zset', '-inf', '+inf', array('withscores' => TRUE))); $this->assertTrue(array('foo' => 1, 'bar' => 2) == $this->redis->zRangeByScore('zset', 1, 2, array('withscores' => TRUE))); $this->assertTrue(array('bar' => 2) == $this->redis->zRangeByScore('zset', '(1', 2, array('withscores' => TRUE))); $this->assertTrue(array() == $this->redis->zRangeByScore('zset', '(1', '(2', array('withscores' => TRUE))); $this->assertTrue(4 == $this->redis->zCount('zset', '-inf', '+inf')); $this->assertTrue(2 == $this->redis->zCount('zset', 1, 2)); $this->assertTrue(1 == $this->redis->zCount('zset', '(1', 2)); $this->assertTrue(0 == $this->redis->zCount('zset', '(1', '(2')); // zincrby $this->redis->del('key'); $this->assertTrue(1.0 === $this->redis->zIncrBy('key', 1, 'val1')); $this->assertTrue(1.0 === $this->redis->zScore('key', 'val1')); $this->assertTrue(2.5 === $this->redis->zIncrBy('key', 1.5, 'val1')); $this->assertTrue(2.5 === $this->redis->zScore('key', 'val1')); // zUnionStore $this->redis->del('{zset}1'); $this->redis->del('{zset}2'); $this->redis->del('{zset}3'); $this->redis->del('{zset}U'); $this->redis->zAdd('{zset}1', 0, 'val0'); $this->redis->zAdd('{zset}1', 1, 'val1'); $this->redis->zAdd('{zset}2', 2, 'val2'); $this->redis->zAdd('{zset}2', 3, 'val3'); $this->redis->zAdd('{zset}3', 4, 'val4'); $this->redis->zAdd('{zset}3', 5, 'val5'); $this->assertTrue(4 === $this->redis->zUnionStore('{zset}U', array('{zset}1', '{zset}3'))); $this->assertTrue(array('val0', 'val1', 'val4', 'val5') === $this->redis->zRange('{zset}U', 0, -1)); // Union on non existing keys $this->redis->del('{zset}U'); $this->assertTrue(0 === $this->redis->zUnionStore('{zset}U', array('{zset}X', '{zset}Y'))); $this->assertTrue(array() === $this->redis->zRange('{zset}U', 0, -1)); // !Exist U Exist → copy of existing zset. $this->redis->del('{zset}U', 'X'); $this->assertTrue(2 === $this->redis->zUnionStore('{zset}U', array('{zset}1', '{zset}X'))); // test weighted zUnion $this->redis->del('{zset}Z'); $this->assertTrue(4 === $this->redis->zUnionStore('{zset}Z', array('{zset}1', '{zset}2'), array(1, 1))); $this->assertTrue(array('val0', 'val1', 'val2', 'val3') === $this->redis->zRange('{zset}Z', 0, -1)); $this->redis->zRemRangeByScore('{zset}Z', 0, 10); $this->assertTrue(4 === $this->redis->zUnionStore('{zset}Z', array('{zset}1', '{zset}2'), array(5, 1))); $this->assertTrue(array('val0', 'val2', 'val3', 'val1') === $this->redis->zRange('{zset}Z', 0, -1)); $this->redis->del('{zset}1'); $this->redis->del('{zset}2'); $this->redis->del('{zset}3'); //test zUnion with weights and aggegration function $this->redis->zadd('{zset}1', 1, 'duplicate'); $this->redis->zadd('{zset}2', 2, 'duplicate'); $this->redis->zUnionStore('{zset}U', array('{zset}1','{zset}2'), array(1,1), 'MIN'); $this->assertTrue($this->redis->zScore('{zset}U', 'duplicate')===1.0); $this->redis->del('{zset}U'); //now test zUnion *without* weights but with aggregrate function $this->redis->zUnionStore('{zset}U', array('{zset}1','{zset}2'), null, 'MIN'); $this->assertTrue($this->redis->zScore('{zset}U', 'duplicate')===1.0); $this->redis->del('{zset}U', '{zset}1', '{zset}2'); // test integer and float weights (GitHub issue #109). $this->redis->del('{zset}1', '{zset}2', '{zset}3'); $this->redis->zadd('{zset}1', 1, 'one'); $this->redis->zadd('{zset}1', 2, 'two'); $this->redis->zadd('{zset}2', 1, 'one'); $this->redis->zadd('{zset}2', 2, 'two'); $this->redis->zadd('{zset}2', 3, 'three'); $this->assertTrue($this->redis->zUnionStore('{zset}3', array('{zset}1', '{zset}2'), array(2, 3.0)) === 3); $this->redis->del('{zset}1'); $this->redis->del('{zset}2'); $this->redis->del('{zset}3'); // Test 'inf', '-inf', and '+inf' weights (GitHub issue #336) $this->redis->zadd('{zset}1', 1, 'one', 2, 'two', 3, 'three'); $this->redis->zadd('{zset}2', 3, 'three', 4, 'four', 5, 'five'); // Make sure phpredis handles these weights $this->assertTrue($this->redis->zUnionStore('{zset}3', array('{zset}1','{zset}2'), array(1, 'inf')) === 5); $this->assertTrue($this->redis->zUnionStore('{zset}3', array('{zset}1','{zset}2'), array(1, '-inf')) === 5); $this->assertTrue($this->redis->zUnionStore('{zset}3', array('{zset}1','{zset}2'), array(1, '+inf')) === 5); // Now, confirm that they're being sent, and that it works $arr_weights = Array('inf','-inf','+inf'); foreach($arr_weights as $str_weight) { $r = $this->redis->zUnionStore('{zset}3', array('{zset}1','{zset}2'), array(1,$str_weight)); $this->assertTrue($r===5); $r = $this->redis->zrangebyscore('{zset}3', '(-inf', '(inf',array('withscores'=>true)); $this->assertTrue(count($r)===2); $this->assertTrue(isset($r['one'])); $this->assertTrue(isset($r['two'])); } $this->redis->del('{zset}1','{zset}2','{zset}3'); $this->redis->zadd('{zset}1', 2000.1, 'one'); $this->redis->zadd('{zset}1', 3000.1, 'two'); $this->redis->zadd('{zset}1', 4000.1, 'three'); $ret = $this->redis->zRange('{zset}1', 0, -1, TRUE); $this->assertTrue(count($ret) === 3); $retValues = array_keys($ret); $this->assertTrue(array('one', 'two', 'three') === $retValues); // + 0 converts from string to float OR integer $this->assertTrue(is_float($ret['one'] + 0)); $this->assertTrue(is_float($ret['two'] + 0)); $this->assertTrue(is_float($ret['three'] + 0)); $this->redis->del('{zset}1'); // ZREMRANGEBYRANK $this->redis->zAdd('{zset}1', 1, 'one'); $this->redis->zAdd('{zset}1', 2, 'two'); $this->redis->zAdd('{zset}1', 3, 'three'); $this->assertTrue(2 === $this->redis->zremrangebyrank('{zset}1', 0, 1)); $this->assertTrue(array('three' => 3) == $this->redis->zRange('{zset}1', 0, -1, TRUE)); $this->redis->del('{zset}1'); // zInter $this->redis->zAdd('{zset}1', 0, 'val0'); $this->redis->zAdd('{zset}1', 1, 'val1'); $this->redis->zAdd('{zset}1', 3, 'val3'); $this->redis->zAdd('{zset}2', 2, 'val1'); $this->redis->zAdd('{zset}2', 3, 'val3'); $this->redis->zAdd('{zset}3', 4, 'val3'); $this->redis->zAdd('{zset}3', 5, 'val5'); $this->redis->del('{zset}I'); $this->assertTrue(2 === $this->redis->zInterStore('{zset}I', array('{zset}1', '{zset}2'))); $this->assertTrue(array('val1', 'val3') === $this->redis->zRange('{zset}I', 0, -1)); // Union on non existing keys $this->assertTrue(0 === $this->redis->zInterStore('{zset}X', array('{zset}X', '{zset}Y'))); $this->assertTrue(array() === $this->redis->zRange('{zset}X', 0, -1)); // !Exist U Exist $this->assertTrue(0 === $this->redis->zInterStore('{zset}Y', array('{zset}1', '{zset}X'))); $this->assertTrue(array() === $this->redis->zRange('keyY', 0, -1)); // test weighted zInter $this->redis->del('{zset}1'); $this->redis->del('{zset}2'); $this->redis->del('{zset}3'); $this->redis->zAdd('{zset}1', 0, 'val0'); $this->redis->zAdd('{zset}1', 1, 'val1'); $this->redis->zAdd('{zset}1', 3, 'val3'); $this->redis->zAdd('{zset}2', 2, 'val1'); $this->redis->zAdd('{zset}2', 1, 'val3'); $this->redis->zAdd('{zset}3', 7, 'val1'); $this->redis->zAdd('{zset}3', 3, 'val3'); $this->redis->del('{zset}I'); $this->assertTrue(2 === $this->redis->zInterStore('{zset}I', array('{zset}1', '{zset}2'), array(1, 1))); $this->assertTrue(array('val1', 'val3') === $this->redis->zRange('{zset}I', 0, -1)); $this->redis->del('{zset}I'); $this->assertTrue( 2 === $this->redis->zInterStore('{zset}I', array('{zset}1', '{zset}2', '{zset}3'), array(1, 5, 1), 'min')); $this->assertTrue(array('val1', 'val3') === $this->redis->zRange('{zset}I', 0, -1)); $this->redis->del('{zset}I'); $this->assertTrue( 2 === $this->redis->zInterStore('{zset}I', array('{zset}1', '{zset}2', '{zset}3'), array(1, 5, 1), 'max')); $this->assertTrue(array('val3', 'val1') === $this->redis->zRange('{zset}I', 0, -1)); $this->redis->del('{zset}I'); $this->assertTrue(2 === $this->redis->zInterStore('{zset}I', array('{zset}1', '{zset}2', '{zset}3'), null, 'max')); $this->assertTrue($this->redis->zScore('{zset}I', 'val1') === floatval(7)); // zrank, zrevrank $this->redis->del('z'); $this->redis->zadd('z', 1, 'one'); $this->redis->zadd('z', 2, 'two'); $this->redis->zadd('z', 5, 'five'); $this->assertTrue(0 === $this->redis->zRank('z', 'one')); $this->assertTrue(1 === $this->redis->zRank('z', 'two')); $this->assertTrue(2 === $this->redis->zRank('z', 'five')); $this->assertTrue(2 === $this->redis->zRevRank('z', 'one')); $this->assertTrue(1 === $this->redis->zRevRank('z', 'two')); $this->assertTrue(0 === $this->redis->zRevRank('z', 'five')); } public function testHashes() { $this->redis->del('h', 'key'); $this->assertTrue(0 === $this->redis->hLen('h')); $this->assertTrue(1 === $this->redis->hSet('h', 'a', 'a-value')); $this->assertTrue(1 === $this->redis->hLen('h')); $this->assertTrue(1 === $this->redis->hSet('h', 'b', 'b-value')); $this->assertTrue(2 === $this->redis->hLen('h')); $this->assertTrue('a-value' === $this->redis->hGet('h', 'a')); // simple get $this->assertTrue('b-value' === $this->redis->hGet('h', 'b')); // simple get $this->assertTrue(0 === $this->redis->hSet('h', 'a', 'another-value')); // replacement $this->assertTrue('another-value' === $this->redis->hGet('h', 'a')); // get the new value $this->assertTrue('b-value' === $this->redis->hGet('h', 'b')); // simple get $this->assertTrue(FALSE === $this->redis->hGet('h', 'c')); // unknown hash member $this->assertTrue(FALSE === $this->redis->hGet('key', 'c')); // unknownkey // hDel $this->assertTrue(1 === $this->redis->hDel('h', 'a')); // 1 on success $this->assertTrue(0 === $this->redis->hDel('h', 'a')); // 0 on failure $this->redis->del('h'); $this->redis->hSet('h', 'x', 'a'); $this->redis->hSet('h', 'y', 'b'); $this->assertTrue(2 === $this->redis->hDel('h', 'x', 'y')); // variadic // hsetnx $this->redis->del('h'); $this->assertTrue(TRUE === $this->redis->hSetNx('h', 'x', 'a')); $this->assertTrue(TRUE === $this->redis->hSetNx('h', 'y', 'b')); $this->assertTrue(FALSE === $this->redis->hSetNx('h', 'x', '?')); $this->assertTrue(FALSE === $this->redis->hSetNx('h', 'y', '?')); $this->assertTrue('a' === $this->redis->hGet('h', 'x')); $this->assertTrue('b' === $this->redis->hGet('h', 'y')); // keys $keys = $this->redis->hKeys('h'); $this->assertTrue($keys === array('x', 'y') || $keys === array('y', 'x')); // values $values = $this->redis->hVals('h'); $this->assertTrue($values === array('a', 'b') || $values === array('b', 'a')); // keys + values $all = $this->redis->hGetAll('h'); $this->assertTrue($all === array('x' => 'a', 'y' => 'b') || $all === array('y' => 'b', 'x' => 'a')); // hExists $this->assertTrue(TRUE === $this->redis->hExists('h', 'x')); $this->assertTrue(TRUE === $this->redis->hExists('h', 'y')); $this->assertTrue(FALSE === $this->redis->hExists('h', 'w')); $this->redis->del('h'); $this->assertTrue(FALSE === $this->redis->hExists('h', 'x')); // hIncrBy $this->redis->del('h'); $this->assertTrue(2 === $this->redis->hIncrBy('h', 'x', 2)); $this->assertTrue(3 === $this->redis->hIncrBy('h', 'x', 1)); $this->assertTrue(2 === $this->redis->hIncrBy('h', 'x', -1)); $this->assertTrue("2" === $this->redis->hGet('h', 'x')); $this->assertTrue(PHP_INT_MAX === $this->redis->hIncrBy('h', 'x', PHP_INT_MAX-2)); $this->assertTrue("".PHP_INT_MAX === $this->redis->hGet('h', 'x')); $this->redis->hSet('h', 'y', 'not-a-number'); $this->assertTrue(FALSE === $this->redis->hIncrBy('h', 'y', 1)); if (version_compare($this->version, "2.5.0", "ge")) { // hIncrByFloat $this->redis->del('h'); $this->assertTrue(1.5 === $this->redis->hIncrByFloat('h','x', 1.5)); $this->assertTrue(3.0 === $this->redis->hincrByFloat('h','x', 1.5)); $this->assertTrue(1.5 === $this->redis->hincrByFloat('h','x', -1.5)); $this->assertTrue(1000000000001.5 === $this->redis->hincrByFloat('h','x', 1000000000000)); $this->redis->hset('h','y','not-a-number'); $this->assertTrue(FALSE === $this->redis->hIncrByFloat('h', 'y', 1.5)); } // hmset $this->redis->del('h'); $this->assertTrue(TRUE === $this->redis->hMset('h', array('x' => 123, 'y' => 456, 'z' => 'abc'))); $this->assertTrue('123' === $this->redis->hGet('h', 'x')); $this->assertTrue('456' === $this->redis->hGet('h', 'y')); $this->assertTrue('abc' === $this->redis->hGet('h', 'z')); $this->assertTrue(FALSE === $this->redis->hGet('h', 't')); // hmget $this->assertTrue(array('x' => '123', 'y' => '456') === $this->redis->hMget('h', array('x', 'y'))); $this->assertTrue(array('z' => 'abc') === $this->redis->hMget('h', array('z'))); $this->assertTrue(array('x' => '123', 't' => FALSE, 'y' => '456') === $this->redis->hMget('h', array('x', 't', 'y'))); $this->assertFalse(array(123 => 'x') === $this->redis->hMget('h', array(123))); $this->assertTrue(array(123 => FALSE) === $this->redis->hMget('h', array(123))); // Test with an array populated with things we can't use as keys $this->assertTrue($this->redis->hmget('h', Array(false,NULL,false)) === FALSE); // Test with some invalid keys mixed in (which should just be ignored) $this->assertTrue(array('x'=>'123','y'=>'456','z'=>'abc') === $this->redis->hMget('h',Array('x',null,'y','','z',false))); // hmget/hmset with numeric fields $this->redis->del('h'); $this->assertTrue(TRUE === $this->redis->hMset('h', array(123 => 'x', 'y' => 456))); $this->assertTrue('x' === $this->redis->hGet('h', 123)); $this->assertTrue('x' === $this->redis->hGet('h', '123')); $this->assertTrue('456' === $this->redis->hGet('h', 'y')); $this->assertTrue(array(123 => 'x', 'y' => '456') === $this->redis->hMget('h', array('123', 'y'))); // references $keys = array(123, 'y'); foreach ($keys as &$key) {} $this->assertTrue(array(123 => 'x', 'y' => '456') === $this->redis->hMget('h', $keys)); // check non-string types. $this->redis->del('h1'); $this->assertTrue(TRUE === $this->redis->hMSet('h1', array('x' => 0, 'y' => array(), 'z' => new stdclass(), 't' => NULL))); $h1 = $this->redis->hGetAll('h1'); $this->assertTrue('0' === $h1['x']); $this->assertTrue('Array' === $h1['y']); $this->assertTrue('Object' === $h1['z']); $this->assertTrue('' === $h1['t']); // hstrlen if (version_compare($this->version, '3.2.0', 'ge')) { $this->redis->del('h'); $this->assertTrue(0 === $this->redis->hStrLen('h', 'x')); // key doesn't exist $this->redis->hSet('h', 'foo', 'bar'); $this->assertTrue(0 === $this->redis->hStrLen('h', 'x')); // field is not present in the hash $this->assertTrue(3 === $this->redis->hStrLen('h', 'foo')); } } public function testSetRange() { $this->redis->del('key'); $this->redis->set('key', 'hello world'); $this->redis->setRange('key', 6, 'redis'); $this->assertTrue('hello redis' === $this->redis->get('key')); $this->redis->setRange('key', 6, 'you'); // don't cut off the end $this->assertTrue('hello youis' === $this->redis->get('key')); $this->redis->set('key', 'hello world'); // $this->assertTrue(11 === $this->redis->setRange('key', -6, 'redis')); // works with negative offsets too! (disabled because not all versions support this) // $this->assertTrue('hello redis' === $this->redis->get('key')); // fill with zeros if needed $this->redis->del('key'); $this->redis->setRange('key', 6, 'foo'); $this->assertTrue("\x00\x00\x00\x00\x00\x00foo" === $this->redis->get('key')); } public function testObject() { /* Version 3.0.0 (represented as >= 2.9.0 in redis info) and moving * forward uses "embstr" instead of "raw" for small string values */ if (version_compare($this->version, "2.9.0", "lt")) { $str_small_encoding = "raw"; } else { $str_small_encoding = "embstr"; } $this->redis->del('key'); $this->assertTrue($this->redis->object('encoding', 'key') === FALSE); $this->assertTrue($this->redis->object('refcount', 'key') === FALSE); $this->assertTrue($this->redis->object('idletime', 'key') === FALSE); $this->redis->set('key', 'value'); $this->assertTrue($this->redis->object('encoding', 'key') === $str_small_encoding); $this->assertTrue($this->redis->object('refcount', 'key') === 1); $this->assertTrue($this->redis->object('idletime', 'key') === 0); $this->redis->del('key'); $this->redis->lpush('key', 'value'); /* Newer versions of redis are going to encode lists as 'quicklists', * so 'quicklist' or 'ziplist' is valid here */ $str_encoding = $this->redis->object('encoding', 'key'); $this->assertTrue($str_encoding === "ziplist" || $str_encoding === 'quicklist'); $this->assertTrue($this->redis->object('refcount', 'key') === 1); $this->assertTrue($this->redis->object('idletime', 'key') === 0); $this->redis->del('key'); $this->redis->sadd('key', 'value'); $this->assertTrue($this->redis->object('encoding', 'key') === "hashtable"); $this->assertTrue($this->redis->object('refcount', 'key') === 1); $this->assertTrue($this->redis->object('idletime', 'key') === 0); $this->redis->del('key'); $this->redis->sadd('key', 42); $this->redis->sadd('key', 1729); $this->assertTrue($this->redis->object('encoding', 'key') === "intset"); $this->assertTrue($this->redis->object('refcount', 'key') === 1); $this->assertTrue($this->redis->object('idletime', 'key') === 0); $this->redis->del('key'); $this->redis->lpush('key', str_repeat('A', pow(10,6))); // 1M elements, too big for a ziplist. $str_encoding = $this->redis->object('encoding', 'key'); $this->assertTrue($str_encoding === "linkedlist" || $str_encoding == "quicklist"); $this->assertTrue($this->redis->object('refcount', 'key') === 1); $this->assertTrue($this->redis->object('idletime', 'key') === 0); } public function testMultiExec() { $this->sequence(Redis::MULTI); $this->differentType(Redis::MULTI); // with prefix as well $this->redis->setOption(Redis::OPT_PREFIX, "test:"); $this->sequence(Redis::MULTI); $this->differentType(Redis::MULTI); $this->redis->setOption(Redis::OPT_PREFIX, ""); $this->redis->set('x', '42'); $this->assertTrue(TRUE === $this->redis->watch('x')); $ret = $this->redis->multi()->get('x')->exec(); // successful transaction $this->assertTrue($ret === array('42')); } public function testFailedTransactions() { $this->redis->set('x', 42); // failed transaction $this->redis->watch('x'); $r = $this->newInstance(); // new instance, modifying `x'. $r->incr('x'); $ret = $this->redis->multi()->get('x')->exec(); $this->assertTrue($ret === FALSE); // failed because another client changed our watched key between WATCH and EXEC. // watch and unwatch $this->redis->watch('x'); $r->incr('x'); // other instance $this->redis->unwatch(); // cancel transaction watch $ret = $this->redis->multi()->get('x')->exec(); $this->assertTrue($ret === array('44')); // succeeded since we've cancel the WATCH command. } public function testPipeline() { if (!$this->havePipeline()) { $this->markTestSkipped(); } $this->sequence(Redis::PIPELINE); $this->differentType(Redis::PIPELINE); // with prefix as well $this->redis->setOption(Redis::OPT_PREFIX, "test:"); $this->sequence(Redis::PIPELINE); $this->differentType(Redis::PIPELINE); $this->redis->setOption(Redis::OPT_PREFIX, ""); } public function testPipelineMultiExec() { if (!$this->havePipeline()) { $this->markTestSkipped(); } $ret = $this->redis->pipeline()->multi()->exec()->exec(); $this->assertTrue(is_array($ret)); $this->assertEquals(1, count($ret)); // empty transaction $ret = $this->redis->pipeline() ->ping() ->multi()->set('x', 42)->incr('x')->exec() ->ping() ->multi()->get('x')->del('x')->exec() ->ping() ->exec(); $this->assertTrue(is_array($ret)); $this->assertEquals(5, count($ret)); // should be 5 atomic operations } /* Github issue #1211 (ignore redundant calls to pipeline or multi) */ public function testDoublePipeNoOp() { /* Only the first pipeline should be honored */ for ($i = 0; $i < 6; $i++) { $this->redis->pipeline(); } /* Set and get in our pipeline */ $this->redis->set('pipecount','over9000')->get('pipecount'); $data = $this->redis->exec(); $this->assertEquals(Array(true,'over9000'), $data); /* Only the first MULTI should be honored */ for ($i = 0; $i < 6; $i++) { $this->redis->multi(); } /* Set and get in our MULTI block */ $this->redis->set('multicount', 'over9000')->get('multicount'); $data = $this->redis->exec(); $this->assertEquals(Array(true, 'over9000'), $data); } protected function sequence($mode) { $ret = $this->redis->multi($mode) ->set('x', 42) ->type('x') ->get('x') ->exec(); $this->assertTrue(is_array($ret)); $i = 0; $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] === Redis::REDIS_STRING); $this->assertTrue($ret[$i] === '42' || $ret[$i] === 42); $serializer = $this->redis->getOption(Redis::OPT_SERIALIZER); $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); // testing incr, which doesn't work with the serializer $ret = $this->redis->multi($mode) ->del('{key}1') ->set('{key}1', 'value1') ->get('{key}1') ->getSet('{key}1', 'value2') ->get('{key}1') ->set('{key}2', 4) ->incr('{key}2') ->get('{key}2') ->decr('{key}2') ->get('{key}2') ->rename('{key}2', '{key}3') ->get('{key}3') ->renameNx('{key}3', '{key}1') ->rename('{key}3', '{key}2') ->incrby('{key}2', 5) ->get('{key}2') ->decrby('{key}2', 5) ->get('{key}2') ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i++])); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 'value1'); $this->assertTrue($ret[$i++] == 'value1'); $this->assertTrue($ret[$i++] == 'value2'); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 5); $this->assertTrue($ret[$i++] == 5); $this->assertTrue($ret[$i++] == 4); $this->assertTrue($ret[$i++] == 4); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 4); $this->assertTrue($ret[$i++] == FALSE); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 9); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 4); $this->assertTrue(count($ret) == $i); $this->redis->setOption(Redis::OPT_SERIALIZER, $serializer); $ret = $this->redis->multi($mode) ->del('{key}1') ->del('{key}2') ->set('{key}1', 'val1') ->setnx('{key}1', 'valX') ->setnx('{key}2', 'valX') ->exists('{key}1') ->exists('{key}3') ->exec(); $this->assertTrue(is_array($ret)); $this->assertTrue($ret[0] == TRUE); $this->assertTrue($ret[1] == TRUE); $this->assertTrue($ret[2] == TRUE); $this->assertTrue($ret[3] == FALSE); $this->assertTrue($ret[4] == TRUE); $this->assertTrue($ret[5] == TRUE); $this->assertTrue($ret[6] == FALSE); // ttl, mget, mset, msetnx, expire, expireAt $this->redis->del('key'); $ret = $this->redis->multi($mode) ->ttl('key') ->mget(array('{key}1', '{key}2', '{key}3')) ->mset(array('{key}3' => 'value3', 'key4' => 'value4')) ->set('key', 'value') ->expire('key', 5) ->ttl('key') ->expireAt('key', '0000') ->exec(); $this->assertTrue(is_array($ret)); $i = 0; $ttl = $ret[$i++]; $this->assertTrue($ttl === -1 || $ttl === -2); $this->assertTrue($ret[$i++] === array('val1', 'valX', FALSE)); // mget $this->assertTrue($ret[$i++] === TRUE); // mset $this->assertTrue($ret[$i++] === TRUE); // set $this->assertTrue($ret[$i++] === TRUE); // expire $this->assertTrue($ret[$i++] === 5); // ttl $this->assertTrue($ret[$i++] === TRUE); // expireAt $this->assertTrue(count($ret) == $i); $ret = $this->redis->multi($mode) ->set('{list}lkey', 'x') ->set('{list}lDest', 'y') ->del('{list}lkey', '{list}lDest') ->rpush('{list}lkey', 'lvalue') ->lpush('{list}lkey', 'lvalue') ->lpush('{list}lkey', 'lvalue') ->lpush('{list}lkey', 'lvalue') ->lpush('{list}lkey', 'lvalue') ->lpush('{list}lkey', 'lvalue') ->rpoplpush('{list}lkey', '{list}lDest') ->lrange('{list}lDest', 0, -1) ->lpop('{list}lkey') ->llen('{list}lkey') ->lrem('{list}lkey', 'lvalue', 3) ->llen('{list}lkey') ->lget('{list}lkey', 0) ->lrange('{list}lkey', 0, -1) ->lSet('{list}lkey', 1, "newValue") // check errors on key not exists ->lrange('{list}lkey', 0, -1) ->llen('{list}lkey') ->exec(); $this->assertTrue(is_array($ret)); $i = 0; $this->assertTrue($ret[$i++] === TRUE); // SET $this->assertTrue($ret[$i++] === TRUE); // SET $this->assertTrue($ret[$i++] === 2); // deleting 2 keys $this->assertTrue($ret[$i++] === 1); // rpush, now 1 element $this->assertTrue($ret[$i++] === 2); // lpush, now 2 elements $this->assertTrue($ret[$i++] === 3); // lpush, now 3 elements $this->assertTrue($ret[$i++] === 4); // lpush, now 4 elements $this->assertTrue($ret[$i++] === 5); // lpush, now 5 elements $this->assertTrue($ret[$i++] === 6); // lpush, now 6 elements $this->assertTrue($ret[$i++] === 'lvalue'); // rpoplpush returns the element: "lvalue" $this->assertTrue($ret[$i++] === array('lvalue')); // lDest contains only that one element. $this->assertTrue($ret[$i++] === 'lvalue'); // removing a second element from lkey, now 4 elements left ↓ $this->assertTrue($ret[$i++] === 4); // 4 elements left, after 2 pops. $this->assertTrue($ret[$i++] === 3); // removing 3 elements, now 1 left. $this->assertTrue($ret[$i++] === 1); // 1 element left $this->assertTrue($ret[$i++] === "lvalue"); // this is the current head. $this->assertTrue($ret[$i++] === array("lvalue")); // this is the current list. $this->assertTrue($ret[$i++] === FALSE); // updating a non-existent element fails. $this->assertTrue($ret[$i++] === array("lvalue")); // this is the current list. $this->assertTrue($ret[$i++] === 1); // 1 element left $this->assertTrue(count($ret) == $i); $ret = $this->redis->multi($mode) ->del('{list}lkey', '{list}lDest') ->rpush('{list}lkey', 'lvalue') ->lpush('{list}lkey', 'lvalue') ->lpush('{list}lkey', 'lvalue') ->rpoplpush('{list}lkey', '{list}lDest') ->lrange('{list}lDest', 0, -1) ->lpop('{list}lkey') ->exec(); $this->assertTrue(is_array($ret)); $i = 0; $this->assertTrue($ret[$i++] <= 2); // deleted 0, 1, or 2 items $this->assertTrue($ret[$i++] === 1); // 1 element in the list $this->assertTrue($ret[$i++] === 2); // 2 elements in the list $this->assertTrue($ret[$i++] === 3); // 3 elements in the list $this->assertTrue($ret[$i++] === 'lvalue'); // rpoplpush returns the element: "lvalue" $this->assertTrue($ret[$i++] === array('lvalue')); // rpoplpush returns the element: "lvalue" $this->assertTrue($ret[$i++] === 'lvalue'); // pop returns the front element: "lvalue" $this->assertTrue(count($ret) == $i); $serializer = $this->redis->getOption(Redis::OPT_SERIALIZER); $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); // testing incr, which doesn't work with the serializer $ret = $this->redis->multi($mode) ->del('{key}1') ->set('{key}1', 'value1') ->get('{key}1') ->getSet('{key}1', 'value2') ->get('{key}1') ->set('{key}2', 4) ->incr('{key}2') ->get('{key}2') ->decr('{key}2') ->get('{key}2') ->rename('{key}2', '{key}3') ->get('{key}3') ->renameNx('{key}3', '{key}1') ->rename('{key}3', '{key}2') ->incrby('{key}2', 5) ->get('{key}2') ->decrby('{key}2', 5) ->get('{key}2') ->set('{key}3', 'value3') ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i]) && $ret[$i] <= 1); $i++; $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 'value1'); $this->assertTrue($ret[$i++] == 'value1'); $this->assertTrue($ret[$i++] == 'value2'); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 5); $this->assertTrue($ret[$i++] == 5); $this->assertTrue($ret[$i++] == 4); $this->assertTrue($ret[$i++] == 4); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 4); $this->assertTrue($ret[$i++] == FALSE); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 9); $this->assertTrue($ret[$i++] == TRUE); $this->assertTrue($ret[$i++] == 4); $this->assertTrue($ret[$i++]); $this->redis->setOption(Redis::OPT_SERIALIZER, $serializer); $ret = $this->redis->multi($mode) ->del('{key}1') ->del('{key}2') ->del('{key}3') ->set('{key}1', 'val1') ->setnx('{key}1', 'valX') ->setnx('{key}2', 'valX') ->exists('{key}1') ->exists('{key}3') ->exec(); $this->assertTrue(is_array($ret)); $this->assertTrue($ret[0] == TRUE); $this->assertTrue($ret[1] == TRUE); $this->assertTrue($ret[2] == TRUE); $this->assertTrue($ret[3] == TRUE); $this->assertTrue($ret[4] == FALSE); $this->assertTrue($ret[5] == TRUE); $this->assertTrue($ret[6] == TRUE); $this->assertTrue($ret[7] == FALSE); // ttl, mget, mset, msetnx, expire, expireAt $ret = $this->redis->multi($mode) ->ttl('key') ->mget(array('{key}1', '{key}2', '{key}3')) ->mset(array('{key}3' => 'value3', '{key}4' => 'value4')) ->set('key', 'value') ->expire('key', 5) ->ttl('key') ->expireAt('key', '0000') ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i++])); $this->assertTrue(is_array($ret[$i]) && count($ret[$i]) === 3); // mget $i++; $this->assertTrue($ret[$i++] === TRUE); // mset always returns TRUE $this->assertTrue($ret[$i++] === TRUE); // set always returns TRUE $this->assertTrue($ret[$i++] === TRUE); // expire always returns TRUE $this->assertTrue($ret[$i++] === 5); // TTL was just set. $this->assertTrue($ret[$i++] === TRUE); // expireAt returns TRUE for an existing key $this->assertTrue(count($ret) === $i); // lists $ret = $this->redis->multi($mode) ->del('{l}key', '{l}Dest') ->rpush('{l}key', 'lvalue') ->lpush('{l}key', 'lvalue') ->lpush('{l}key', 'lvalue') ->lpush('{l}key', 'lvalue') ->lpush('{l}key', 'lvalue') ->lpush('{l}key', 'lvalue') ->rpoplpush('{l}key', '{l}Dest') ->lrange('{l}Dest', 0, -1) ->lpop('{l}key') ->llen('{l}key') ->lrem('{l}key', 'lvalue', 3) ->llen('{l}key') ->lget('{l}key', 0) ->lrange('{l}key', 0, -1) ->lSet('{l}key', 1, "newValue") // check errors on missing key ->lrange('{l}key', 0, -1) ->llen('{l}key') ->exec(); $this->assertTrue(is_array($ret)); $i = 0; $this->assertTrue($ret[$i] >= 0 && $ret[$i] <= 2); // delete $i++; $this->assertTrue($ret[$i++] === 1); // 1 value $this->assertTrue($ret[$i++] === 2); // 2 values $this->assertTrue($ret[$i++] === 3); // 3 values $this->assertTrue($ret[$i++] === 4); // 4 values $this->assertTrue($ret[$i++] === 5); // 5 values $this->assertTrue($ret[$i++] === 6); // 6 values $this->assertTrue($ret[$i++] === 'lvalue'); $this->assertTrue($ret[$i++] === array('lvalue')); // 1 value only in lDest $this->assertTrue($ret[$i++] === 'lvalue'); // now 4 values left $this->assertTrue($ret[$i++] === 4); $this->assertTrue($ret[$i++] === 3); // removing 3 elements. $this->assertTrue($ret[$i++] === 1); // length is now 1 $this->assertTrue($ret[$i++] === 'lvalue'); // this is the head $this->assertTrue($ret[$i++] === array('lvalue')); // 1 value only in lkey $this->assertTrue($ret[$i++] === FALSE); // can't set list[1] if we only have a single value in it. $this->assertTrue($ret[$i++] === array('lvalue')); // the previous error didn't touch anything. $this->assertTrue($ret[$i++] === 1); // the previous error didn't change the length $this->assertTrue(count($ret) === $i); // sets $ret = $this->redis->multi($mode) ->del('{s}key1', '{s}key2', '{s}keydest', '{s}keyUnion', '{s}DiffDest') ->sadd('{s}key1', 'sValue1') ->sadd('{s}key1', 'sValue2') ->sadd('{s}key1', 'sValue3') ->sadd('{s}key1', 'sValue4') ->sadd('{s}key2', 'sValue1') ->sadd('{s}key2', 'sValue2') ->scard('{s}key1') ->srem('{s}key1', 'sValue2') ->scard('{s}key1') ->sMove('{s}key1', '{s}key2', 'sValue4') ->scard('{s}key2') ->sismember('{s}key2', 'sValue4') ->sMembers('{s}key1') ->sMembers('{s}key2') ->sInter('{s}key1', '{s}key2') ->sInterStore('{s}keydest', '{s}key1', '{s}key2') ->sMembers('{s}keydest') ->sUnion('{s}key2', '{s}keydest') ->sUnionStore('{s}keyUnion', '{s}key2', '{s}keydest') ->sMembers('{s}keyUnion') ->sDiff('{s}key1', '{s}key2') ->sDiffStore('{s}DiffDest', '{s}key1', '{s}key2') ->sMembers('{s}DiffDest') ->sPop('{s}key2') ->scard('{s}key2') ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i]) && $ret[$i] >= 0 && $ret[$i] <= 5); $i++; // deleted at most 5 values. $this->assertTrue($ret[$i++] === 1); // skey1 now has 1 element. $this->assertTrue($ret[$i++] === 1); // skey1 now has 2 elements. $this->assertTrue($ret[$i++] === 1); // skey1 now has 3 elements. $this->assertTrue($ret[$i++] === 1); // skey1 now has 4 elements. $this->assertTrue($ret[$i++] === 1); // skey2 now has 1 element. $this->assertTrue($ret[$i++] === 1); // skey2 now has 2 elements. $this->assertTrue($ret[$i++] === 4); $this->assertTrue($ret[$i++] === 1); // we did remove that value. $this->assertTrue($ret[$i++] === 3); // now 3 values only. $this->assertTrue($ret[$i++] === TRUE); // the move did succeed. $this->assertTrue($ret[$i++] === 3); // sKey2 now has 3 values. $this->assertTrue($ret[$i++] === TRUE); // sKey2 does contain sValue4. foreach(array('sValue1', 'sValue3') as $k) { // sKey1 contains sValue1 and sValue3. $this->assertTrue(in_array($k, $ret[$i])); } $this->assertTrue(count($ret[$i++]) === 2); foreach(array('sValue1', 'sValue2', 'sValue4') as $k) { // sKey2 contains sValue1, sValue2, and sValue4. $this->assertTrue(in_array($k, $ret[$i])); } $this->assertTrue(count($ret[$i++]) === 3); $this->assertTrue($ret[$i++] === array('sValue1')); // intersection $this->assertTrue($ret[$i++] === 1); // intersection + store → 1 value in the destination set. $this->assertTrue($ret[$i++] === array('sValue1')); // sinterstore destination contents foreach(array('sValue1', 'sValue2', 'sValue4') as $k) { // (skeydest U sKey2) contains sValue1, sValue2, and sValue4. $this->assertTrue(in_array($k, $ret[$i])); } $this->assertTrue(count($ret[$i++]) === 3); // union size $this->assertTrue($ret[$i++] === 3); // unionstore size foreach(array('sValue1', 'sValue2', 'sValue4') as $k) { // (skeyUnion) contains sValue1, sValue2, and sValue4. $this->assertTrue(in_array($k, $ret[$i])); } $this->assertTrue(count($ret[$i++]) === 3); // skeyUnion size $this->assertTrue($ret[$i++] === array('sValue3')); // diff skey1, skey2 : only sValue3 is not shared. $this->assertTrue($ret[$i++] === 1); // sdiffstore size == 1 $this->assertTrue($ret[$i++] === array('sValue3')); // contents of sDiffDest $this->assertTrue(in_array($ret[$i++], array('sValue1', 'sValue2', 'sValue4'))); // we removed an element from sKey2 $this->assertTrue($ret[$i++] === 2); // sKey2 now has 2 elements only. $this->assertTrue(count($ret) === $i); // sorted sets $ret = $this->redis->multi($mode) ->del('{z}key1', '{z}key2', '{z}key5', '{z}Inter', '{z}Union') ->zadd('{z}key1', 1, 'zValue1') ->zadd('{z}key1', 5, 'zValue5') ->zadd('{z}key1', 2, 'zValue2') ->zRange('{z}key1', 0, -1) ->zRem('{z}key1', 'zValue2') ->zRange('{z}key1', 0, -1) ->zadd('{z}key1', 11, 'zValue11') ->zadd('{z}key1', 12, 'zValue12') ->zadd('{z}key1', 13, 'zValue13') ->zadd('{z}key1', 14, 'zValue14') ->zadd('{z}key1', 15, 'zValue15') ->zRemRangeByScore('{z}key1', 11, 13) ->zrange('{z}key1', 0, -1) ->zRevRange('{z}key1', 0, -1) ->zRangeByScore('{z}key1', 1, 6) ->zCard('{z}key1') ->zScore('{z}key1', 'zValue15') ->zadd('{z}key2', 5, 'zValue5') ->zadd('{z}key2', 2, 'zValue2') ->zInterStore('{z}Inter', array('{z}key1', '{z}key2')) ->zRange('{z}key1', 0, -1) ->zRange('{z}key2', 0, -1) ->zRange('{z}Inter', 0, -1) ->zUnionStore('{z}Union', array('{z}key1', '{z}key2')) ->zRange('{z}Union', 0, -1) ->zadd('{z}key5', 5, 'zValue5') ->zIncrBy('{z}key5', 3, 'zValue5') // fix this ->zScore('{z}key5', 'zValue5') ->zScore('{z}key5', 'unknown') ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i]) && $ret[$i] >= 0 && $ret[$i] <= 5); $i++; // deleting at most 5 keys $this->assertTrue($ret[$i++] === 1); $this->assertTrue($ret[$i++] === 1); $this->assertTrue($ret[$i++] === 1); $this->assertTrue($ret[$i++] === array('zValue1', 'zValue2', 'zValue5')); $this->assertTrue($ret[$i++] === 1); $this->assertTrue($ret[$i++] === array('zValue1', 'zValue5')); $this->assertTrue($ret[$i++] === 1); // adding zValue11 $this->assertTrue($ret[$i++] === 1); // adding zValue12 $this->assertTrue($ret[$i++] === 1); // adding zValue13 $this->assertTrue($ret[$i++] === 1); // adding zValue14 $this->assertTrue($ret[$i++] === 1); // adding zValue15 $this->assertTrue($ret[$i++] === 3); // deleted zValue11, zValue12, zValue13 $this->assertTrue($ret[$i++] === array('zValue1', 'zValue5', 'zValue14', 'zValue15')); $this->assertTrue($ret[$i++] === array('zValue15', 'zValue14', 'zValue5', 'zValue1')); $this->assertTrue($ret[$i++] === array('zValue1', 'zValue5')); $this->assertTrue($ret[$i++] === 4); // 4 elements $this->assertTrue($ret[$i++] === 15.0); $this->assertTrue($ret[$i++] === 1); // added value $this->assertTrue($ret[$i++] === 1); // added value $this->assertTrue($ret[$i++] === 1); // zinter only has 1 value $this->assertTrue($ret[$i++] === array('zValue1', 'zValue5', 'zValue14', 'zValue15')); // {z}key1 contents $this->assertTrue($ret[$i++] === array('zValue2', 'zValue5')); // {z}key2 contents $this->assertTrue($ret[$i++] === array('zValue5')); // {z}inter contents $this->assertTrue($ret[$i++] === 5); // {z}Union has 5 values (1,2,5,14,15) $this->assertTrue($ret[$i++] === array('zValue1', 'zValue2', 'zValue5', 'zValue14', 'zValue15')); // {z}Union contents $this->assertTrue($ret[$i++] === 1); // added value to {z}key5, with score 5 $this->assertTrue($ret[$i++] === 8.0); // incremented score by 3 → it is now 8. $this->assertTrue($ret[$i++] === 8.0); // current score is 8. $this->assertTrue($ret[$i++] === FALSE); // score for unknown element. $this->assertTrue(count($ret) === $i); // hash $ret = $this->redis->multi($mode) ->del('hkey1') ->hset('hkey1', 'key1', 'value1') ->hset('hkey1', 'key2', 'value2') ->hset('hkey1', 'key3', 'value3') ->hmget('hkey1', array('key1', 'key2', 'key3')) ->hget('hkey1', 'key1') ->hlen('hkey1') ->hdel('hkey1', 'key2') ->hdel('hkey1', 'key2') ->hexists('hkey1', 'key2') ->hkeys('hkey1') ->hvals('hkey1') ->hgetall('hkey1') ->hset('hkey1', 'valn', 1) ->hset('hkey1', 'val-fail', 'non-string') ->hget('hkey1', 'val-fail') ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue($ret[$i++] <= 1); // delete $this->assertTrue($ret[$i++] === 1); // added 1 element $this->assertTrue($ret[$i++] === 1); // added 1 element $this->assertTrue($ret[$i++] === 1); // added 1 element $this->assertTrue($ret[$i++] === array('key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3')); // hmget, 3 elements $this->assertTrue($ret[$i++] === 'value1'); // hget $this->assertTrue($ret[$i++] === 3); // hlen $this->assertTrue($ret[$i++] === 1); // hdel succeeded $this->assertTrue($ret[$i++] === 0); // hdel failed $this->assertTrue($ret[$i++] === FALSE); // hexists didn't find the deleted key $this->assertTrue($ret[$i] === array('key1', 'key3') || $ret[$i] === array('key3', 'key1')); $i++; // hkeys $this->assertTrue($ret[$i] === array('value1', 'value3') || $ret[$i] === array('value3', 'value1')); $i++; // hvals $this->assertTrue($ret[$i] === array('key1' => 'value1', 'key3' => 'value3') || $ret[$i] === array('key3' => 'value3', 'key1' => 'value1')); $i++; // hgetall $this->assertTrue($ret[$i++] === 1); // added 1 element $this->assertTrue($ret[$i++] === 1); // added the element, so 1. $this->assertTrue($ret[$i++] === 'non-string'); // hset succeeded $this->assertTrue(count($ret) === $i); $ret = $this->redis->multi($mode) // default to MULTI, not PIPELINE. ->del('test') ->set('test', 'xyz') ->get('test') ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue($ret[$i++] <= 1); // delete $this->assertTrue($ret[$i++] === TRUE); // added 1 element $this->assertTrue($ret[$i++] === 'xyz'); $this->assertTrue(count($ret) === $i); // GitHub issue 78 $this->redis->del('test'); for($i = 1; $i <= 5; $i++) $this->redis->zadd('test', $i, (string)$i); $result = $this->redis->multi($mode) ->zscore('test', "1") ->zscore('test', "6") ->zscore('test', "8") ->zscore('test', "2") ->exec(); $this->assertTrue($result === array(1.0, FALSE, FALSE, 2.0)); } protected function differentType($mode) { // string $key = '{hash}string'; $dkey = '{hash}' . __FUNCTION__; $ret = $this->redis->multi($mode) ->del($key) ->set($key, 'value') // lists I/F ->rPush($key, 'lvalue') ->lPush($key, 'lvalue') ->lLen($key) ->lPop($key) ->lrange($key, 0, -1) ->lTrim($key, 0, 1) ->lGet($key, 0) ->lSet($key, 0, "newValue") ->lrem($key, 'lvalue', 1) ->lPop($key) ->rPop($key) ->rPoplPush($key, $dkey . 'lkey1') // sets I/F ->sAdd($key, 'sValue1') ->srem($key, 'sValue1') ->sPop($key) ->sMove($key, $dkey . 'skey1', 'sValue1') ->scard($key) ->sismember($key, 'sValue1') ->sInter($key, $dkey . 'skey2') ->sUnion($key, $dkey . 'skey4') ->sDiff($key, $dkey . 'skey7') ->sMembers($key) ->sRandMember($key) // sorted sets I/F ->zAdd($key, 1, 'zValue1') ->zRem($key, 'zValue1') ->zIncrBy($key, 1, 'zValue1') ->zRank($key, 'zValue1') ->zRevRank($key, 'zValue1') ->zRange($key, 0, -1) ->zRevRange($key, 0, -1) ->zRangeByScore($key, 1, 2) ->zCount($key, 0, -1) ->zCard($key) ->zScore($key, 'zValue1') ->zRemRangeByRank($key, 1, 2) ->zRemRangeByScore($key, 1, 2) // hash I/F ->hSet($key, 'key1', 'value1') ->hGet($key, 'key1') ->hMGet($key, array('key1')) ->hMSet($key, array('key1' => 'value1')) ->hIncrBy($key, 'key2', 1) ->hExists($key, 'key2') ->hDel($key, 'key2') ->hLen($key) ->hKeys($key) ->hVals($key) ->hGetAll($key) ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i++])); // delete $this->assertTrue($ret[$i++] === TRUE); // set $this->assertTrue($ret[$i++] === FALSE); // rpush $this->assertTrue($ret[$i++] === FALSE); // lpush $this->assertTrue($ret[$i++] === FALSE); // llen $this->assertTrue($ret[$i++] === FALSE); // lpop $this->assertTrue($ret[$i++] === FALSE); // lgetrange $this->assertTrue($ret[$i++] === FALSE); // ltrim $this->assertTrue($ret[$i++] === FALSE); // lget $this->assertTrue($ret[$i++] === FALSE); // lset $this->assertTrue($ret[$i++] === FALSE); // lremove $this->assertTrue($ret[$i++] === FALSE); // lpop $this->assertTrue($ret[$i++] === FALSE); // rpop $this->assertTrue($ret[$i++] === FALSE); // rpoplush $this->assertTrue($ret[$i++] === FALSE); // sadd $this->assertTrue($ret[$i++] === FALSE); // sremove $this->assertTrue($ret[$i++] === FALSE); // spop $this->assertTrue($ret[$i++] === FALSE); // smove $this->assertTrue($ret[$i++] === FALSE); // ssize $this->assertTrue($ret[$i++] === FALSE); // scontains $this->assertTrue($ret[$i++] === FALSE); // sinter $this->assertTrue($ret[$i++] === FALSE); // sunion $this->assertTrue($ret[$i++] === FALSE); // sdiff $this->assertTrue($ret[$i++] === FALSE); // smembers $this->assertTrue($ret[$i++] === FALSE); // srandmember $this->assertTrue($ret[$i++] === FALSE); // zadd $this->assertTrue($ret[$i++] === FALSE); // zdelete $this->assertTrue($ret[$i++] === FALSE); // zincrby $this->assertTrue($ret[$i++] === FALSE); // zrank $this->assertTrue($ret[$i++] === FALSE); // zrevrank $this->assertTrue($ret[$i++] === FALSE); // zrange $this->assertTrue($ret[$i++] === FALSE); // zreverserange $this->assertTrue($ret[$i++] === FALSE); // zrangebyscore $this->assertTrue($ret[$i++] === FALSE); // zcount $this->assertTrue($ret[$i++] === FALSE); // zcard $this->assertTrue($ret[$i++] === FALSE); // zscore $this->assertTrue($ret[$i++] === FALSE); // zdeleterangebyrank $this->assertTrue($ret[$i++] === FALSE); // zdeleterangebyscore $this->assertTrue($ret[$i++] === FALSE); // hset $this->assertTrue($ret[$i++] === FALSE); // hget $this->assertTrue($ret[$i++] === FALSE); // hmget $this->assertTrue($ret[$i++] === FALSE); // hmset $this->assertTrue($ret[$i++] === FALSE); // hincrby $this->assertTrue($ret[$i++] === FALSE); // hexists $this->assertTrue($ret[$i++] === FALSE); // hdel $this->assertTrue($ret[$i++] === FALSE); // hlen $this->assertTrue($ret[$i++] === FALSE); // hkeys $this->assertTrue($ret[$i++] === FALSE); // hvals $this->assertTrue($ret[$i++] === FALSE); // hgetall $this->assertEquals($i, count($ret)); // list $key = '{hash}list'; $dkey = '{hash}' . __FUNCTION__; $ret = $this->redis->multi($mode) ->del($key) ->lpush($key, 'lvalue') // string I/F ->get($key) ->getset($key, 'value2') ->append($key, 'append') ->getRange($key, 0, 8) ->mget(array($key)) ->incr($key) ->incrBy($key, 1) ->decr($key) ->decrBy($key, 1) // sets I/F ->sAdd($key, 'sValue1') ->srem($key, 'sValue1') ->sPop($key) ->sMove($key, $dkey . 'skey1', 'sValue1') ->scard($key) ->sismember($key, 'sValue1') ->sInter($key, $dkey . 'skey2') ->sUnion($key, $dkey . 'skey4') ->sDiff($key, $dkey . 'skey7') ->sMembers($key) ->sRandMember($key) // sorted sets I/F ->zAdd($key, 1, 'zValue1') ->zRem($key, 'zValue1') ->zIncrBy($key, 1, 'zValue1') ->zRank($key, 'zValue1') ->zRevRank($key, 'zValue1') ->zRange($key, 0, -1) ->zRevRange($key, 0, -1) ->zRangeByScore($key, 1, 2) ->zCount($key, 0, -1) ->zCard($key) ->zScore($key, 'zValue1') ->zRemRangeByRank($key, 1, 2) ->zRemRangeByScore($key, 1, 2) // hash I/F ->hSet($key, 'key1', 'value1') ->hGet($key, 'key1') ->hMGet($key, array('key1')) ->hMSet($key, array('key1' => 'value1')) ->hIncrBy($key, 'key2', 1) ->hExists($key, 'key2') ->hDel($key, 'key2') ->hLen($key) ->hKeys($key) ->hVals($key) ->hGetAll($key) ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i++])); // delete $this->assertTrue($ret[$i++] === 1); // lpush $this->assertTrue($ret[$i++] === FALSE); // get $this->assertTrue($ret[$i++] === FALSE); // getset $this->assertTrue($ret[$i++] === FALSE); // append $this->assertTrue($ret[$i++] === FALSE); // getRange $this->assertTrue(is_array($ret[$i]) && count($ret[$i]) === 1 && $ret[$i][0] === FALSE); // mget $i++; $this->assertTrue($ret[$i++] === FALSE); // incr $this->assertTrue($ret[$i++] === FALSE); // incrBy $this->assertTrue($ret[$i++] === FALSE); // decr $this->assertTrue($ret[$i++] === FALSE); // decrBy $this->assertTrue($ret[$i++] === FALSE); // sadd $this->assertTrue($ret[$i++] === FALSE); // sremove $this->assertTrue($ret[$i++] === FALSE); // spop $this->assertTrue($ret[$i++] === FALSE); // smove $this->assertTrue($ret[$i++] === FALSE); // ssize $this->assertTrue($ret[$i++] === FALSE); // scontains $this->assertTrue($ret[$i++] === FALSE); // sinter $this->assertTrue($ret[$i++] === FALSE); // sunion $this->assertTrue($ret[$i++] === FALSE); // sdiff $this->assertTrue($ret[$i++] === FALSE); // smembers $this->assertTrue($ret[$i++] === FALSE); // srandmember $this->assertTrue($ret[$i++] === FALSE); // zadd $this->assertTrue($ret[$i++] === FALSE); // zdelete $this->assertTrue($ret[$i++] === FALSE); // zincrby $this->assertTrue($ret[$i++] === FALSE); // zrank $this->assertTrue($ret[$i++] === FALSE); // zrevrank $this->assertTrue($ret[$i++] === FALSE); // zrange $this->assertTrue($ret[$i++] === FALSE); // zreverserange $this->assertTrue($ret[$i++] === FALSE); // zrangebyscore $this->assertTrue($ret[$i++] === FALSE); // zcount $this->assertTrue($ret[$i++] === FALSE); // zcard $this->assertTrue($ret[$i++] === FALSE); // zscore $this->assertTrue($ret[$i++] === FALSE); // zdeleterangebyrank $this->assertTrue($ret[$i++] === FALSE); // zdeleterangebyscore $this->assertTrue($ret[$i++] === FALSE); // hset $this->assertTrue($ret[$i++] === FALSE); // hget $this->assertTrue($ret[$i++] === FALSE); // hmget $this->assertTrue($ret[$i++] === FALSE); // hmset $this->assertTrue($ret[$i++] === FALSE); // hincrby $this->assertTrue($ret[$i++] === FALSE); // hexists $this->assertTrue($ret[$i++] === FALSE); // hdel $this->assertTrue($ret[$i++] === FALSE); // hlen $this->assertTrue($ret[$i++] === FALSE); // hkeys $this->assertTrue($ret[$i++] === FALSE); // hvals $this->assertTrue($ret[$i++] === FALSE); // hgetall $this->assertEquals($i, count($ret)); // set $key = '{hash}set'; $dkey = '{hash}' . __FUNCTION__; $ret = $this->redis->multi($mode) ->del($key) ->sAdd($key, 'sValue') // string I/F ->get($key) ->getset($key, 'value2') ->append($key, 'append') ->getRange($key, 0, 8) ->mget(array($key)) ->incr($key) ->incrBy($key, 1) ->decr($key) ->decrBy($key, 1) // lists I/F ->rPush($key, 'lvalue') ->lPush($key, 'lvalue') ->lLen($key) ->lPop($key) ->lrange($key, 0, -1) ->lTrim($key, 0, 1) ->lGet($key, 0) ->lSet($key, 0, "newValue") ->lrem($key, 'lvalue', 1) ->lPop($key) ->rPop($key) ->rPoplPush($key, $dkey . 'lkey1') // sorted sets I/F ->zAdd($key, 1, 'zValue1') ->zRem($key, 'zValue1') ->zIncrBy($key, 1, 'zValue1') ->zRank($key, 'zValue1') ->zRevRank($key, 'zValue1') ->zRange($key, 0, -1) ->zRevRange($key, 0, -1) ->zRangeByScore($key, 1, 2) ->zCount($key, 0, -1) ->zCard($key) ->zScore($key, 'zValue1') ->zRemRangeByRank($key, 1, 2) ->zRemRangeByScore($key, 1, 2) // hash I/F ->hSet($key, 'key1', 'value1') ->hGet($key, 'key1') ->hMGet($key, array('key1')) ->hMSet($key, array('key1' => 'value1')) ->hIncrBy($key, 'key2', 1) ->hExists($key, 'key2') ->hDel($key, 'key2') ->hLen($key) ->hKeys($key) ->hVals($key) ->hGetAll($key) ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i++])); // delete $this->assertTrue($ret[$i++] === 1); // zadd $this->assertTrue($ret[$i++] === FALSE); // get $this->assertTrue($ret[$i++] === FALSE); // getset $this->assertTrue($ret[$i++] === FALSE); // append $this->assertTrue($ret[$i++] === FALSE); // getRange $this->assertTrue(is_array($ret[$i]) && count($ret[$i]) === 1 && $ret[$i][0] === FALSE); // mget $i++; $this->assertTrue($ret[$i++] === FALSE); // incr $this->assertTrue($ret[$i++] === FALSE); // incrBy $this->assertTrue($ret[$i++] === FALSE); // decr $this->assertTrue($ret[$i++] === FALSE); // decrBy $this->assertTrue($ret[$i++] === FALSE); // rpush $this->assertTrue($ret[$i++] === FALSE); // lpush $this->assertTrue($ret[$i++] === FALSE); // llen $this->assertTrue($ret[$i++] === FALSE); // lpop $this->assertTrue($ret[$i++] === FALSE); // lgetrange $this->assertTrue($ret[$i++] === FALSE); // ltrim $this->assertTrue($ret[$i++] === FALSE); // lget $this->assertTrue($ret[$i++] === FALSE); // lset $this->assertTrue($ret[$i++] === FALSE); // lremove $this->assertTrue($ret[$i++] === FALSE); // lpop $this->assertTrue($ret[$i++] === FALSE); // rpop $this->assertTrue($ret[$i++] === FALSE); // rpoplush $this->assertTrue($ret[$i++] === FALSE); // zadd $this->assertTrue($ret[$i++] === FALSE); // zdelete $this->assertTrue($ret[$i++] === FALSE); // zincrby $this->assertTrue($ret[$i++] === FALSE); // zrank $this->assertTrue($ret[$i++] === FALSE); // zrevrank $this->assertTrue($ret[$i++] === FALSE); // zrange $this->assertTrue($ret[$i++] === FALSE); // zreverserange $this->assertTrue($ret[$i++] === FALSE); // zrangebyscore $this->assertTrue($ret[$i++] === FALSE); // zcount $this->assertTrue($ret[$i++] === FALSE); // zcard $this->assertTrue($ret[$i++] === FALSE); // zscore $this->assertTrue($ret[$i++] === FALSE); // zdeleterangebyrank $this->assertTrue($ret[$i++] === FALSE); // zdeleterangebyscore $this->assertTrue($ret[$i++] === FALSE); // hset $this->assertTrue($ret[$i++] === FALSE); // hget $this->assertTrue($ret[$i++] === FALSE); // hmget $this->assertTrue($ret[$i++] === FALSE); // hmset $this->assertTrue($ret[$i++] === FALSE); // hincrby $this->assertTrue($ret[$i++] === FALSE); // hexists $this->assertTrue($ret[$i++] === FALSE); // hdel $this->assertTrue($ret[$i++] === FALSE); // hlen $this->assertTrue($ret[$i++] === FALSE); // hkeys $this->assertTrue($ret[$i++] === FALSE); // hvals $this->assertTrue($ret[$i++] === FALSE); // hgetall $this->assertEquals($i, count($ret)); // sorted set $key = '{hash}sortedset'; $dkey = '{hash}' . __FUNCTION__; $ret = $this->redis->multi($mode) ->del($key) ->zAdd($key, 0, 'zValue') // string I/F ->get($key) ->getset($key, 'value2') ->append($key, 'append') ->getRange($key, 0, 8) ->mget(array($key)) ->incr($key) ->incrBy($key, 1) ->decr($key) ->decrBy($key, 1) // lists I/F ->rPush($key, 'lvalue') ->lPush($key, 'lvalue') ->lLen($key) ->lPop($key) ->lrange($key, 0, -1) ->lTrim($key, 0, 1) ->lGet($key, 0) ->lSet($key, 0, "newValue") ->lrem($key, 'lvalue', 1) ->lPop($key) ->rPop($key) ->rPoplPush($key, $dkey . 'lkey1') // sets I/F ->sAdd($key, 'sValue1') ->srem($key, 'sValue1') ->sPop($key) ->sMove($key, $dkey . 'skey1', 'sValue1') ->scard($key) ->sismember($key, 'sValue1') ->sInter($key, $dkey . 'skey2') ->sUnion($key, $dkey . 'skey4') ->sDiff($key, $dkey . 'skey7') ->sMembers($key) ->sRandMember($key) // hash I/F ->hSet($key, 'key1', 'value1') ->hGet($key, 'key1') ->hMGet($key, array('key1')) ->hMSet($key, array('key1' => 'value1')) ->hIncrBy($key, 'key2', 1) ->hExists($key, 'key2') ->hDel($key, 'key2') ->hLen($key) ->hKeys($key) ->hVals($key) ->hGetAll($key) ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i++])); // delete $this->assertTrue($ret[$i++] === 1); // zadd $this->assertTrue($ret[$i++] === FALSE); // get $this->assertTrue($ret[$i++] === FALSE); // getset $this->assertTrue($ret[$i++] === FALSE); // append $this->assertTrue($ret[$i++] === FALSE); // getRange $this->assertTrue(is_array($ret[$i]) && count($ret[$i]) === 1 && $ret[$i][0] === FALSE); // mget $i++; $this->assertTrue($ret[$i++] === FALSE); // incr $this->assertTrue($ret[$i++] === FALSE); // incrBy $this->assertTrue($ret[$i++] === FALSE); // decr $this->assertTrue($ret[$i++] === FALSE); // decrBy $this->assertTrue($ret[$i++] === FALSE); // rpush $this->assertTrue($ret[$i++] === FALSE); // lpush $this->assertTrue($ret[$i++] === FALSE); // llen $this->assertTrue($ret[$i++] === FALSE); // lpop $this->assertTrue($ret[$i++] === FALSE); // lgetrange $this->assertTrue($ret[$i++] === FALSE); // ltrim $this->assertTrue($ret[$i++] === FALSE); // lget $this->assertTrue($ret[$i++] === FALSE); // lset $this->assertTrue($ret[$i++] === FALSE); // lremove $this->assertTrue($ret[$i++] === FALSE); // lpop $this->assertTrue($ret[$i++] === FALSE); // rpop $this->assertTrue($ret[$i++] === FALSE); // rpoplush $this->assertTrue($ret[$i++] === FALSE); // sadd $this->assertTrue($ret[$i++] === FALSE); // sremove $this->assertTrue($ret[$i++] === FALSE); // spop $this->assertTrue($ret[$i++] === FALSE); // smove $this->assertTrue($ret[$i++] === FALSE); // ssize $this->assertTrue($ret[$i++] === FALSE); // scontains $this->assertTrue($ret[$i++] === FALSE); // sinter $this->assertTrue($ret[$i++] === FALSE); // sunion $this->assertTrue($ret[$i++] === FALSE); // sdiff $this->assertTrue($ret[$i++] === FALSE); // smembers $this->assertTrue($ret[$i++] === FALSE); // srandmember $this->assertTrue($ret[$i++] === FALSE); // hset $this->assertTrue($ret[$i++] === FALSE); // hget $this->assertTrue($ret[$i++] === FALSE); // hmget $this->assertTrue($ret[$i++] === FALSE); // hmset $this->assertTrue($ret[$i++] === FALSE); // hincrby $this->assertTrue($ret[$i++] === FALSE); // hexists $this->assertTrue($ret[$i++] === FALSE); // hdel $this->assertTrue($ret[$i++] === FALSE); // hlen $this->assertTrue($ret[$i++] === FALSE); // hkeys $this->assertTrue($ret[$i++] === FALSE); // hvals $this->assertTrue($ret[$i++] === FALSE); // hgetall $this->assertEquals($i, count($ret)); // hash $key = '{hash}hash'; $dkey = '{hash}' . __FUNCTION__; $ret = $this->redis->multi($mode) ->del($key) ->hset($key, 'key1', 'hValue') // string I/F ->get($key) ->getset($key, 'value2') ->append($key, 'append') ->getRange($key, 0, 8) ->mget(array($key)) ->incr($key) ->incrBy($key, 1) ->decr($key) ->decrBy($key, 1) // lists I/F ->rPush($key, 'lvalue') ->lPush($key, 'lvalue') ->lLen($key) ->lPop($key) ->lrange($key, 0, -1) ->lTrim($key, 0, 1) ->lGet($key, 0) ->lSet($key, 0, "newValue") ->lrem($key, 'lvalue', 1) ->lPop($key) ->rPop($key) ->rPoplPush($key, $dkey . 'lkey1') // sets I/F ->sAdd($key, 'sValue1') ->srem($key, 'sValue1') ->sPop($key) ->sMove($key, $dkey . 'skey1', 'sValue1') ->scard($key) ->sismember($key, 'sValue1') ->sInter($key, $dkey . 'skey2') ->sUnion($key, $dkey . 'skey4') ->sDiff($key, $dkey . 'skey7') ->sMembers($key) ->sRandMember($key) // sorted sets I/F ->zAdd($key, 1, 'zValue1') ->zRem($key, 'zValue1') ->zIncrBy($key, 1, 'zValue1') ->zRank($key, 'zValue1') ->zRevRank($key, 'zValue1') ->zRange($key, 0, -1) ->zRevRange($key, 0, -1) ->zRangeByScore($key, 1, 2) ->zCount($key, 0, -1) ->zCard($key) ->zScore($key, 'zValue1') ->zRemRangeByRank($key, 1, 2) ->zRemRangeByScore($key, 1, 2) ->exec(); $i = 0; $this->assertTrue(is_array($ret)); $this->assertTrue(is_long($ret[$i++])); // delete $this->assertTrue($ret[$i++] === 1); // hset $this->assertTrue($ret[$i++] === FALSE); // get $this->assertTrue($ret[$i++] === FALSE); // getset $this->assertTrue($ret[$i++] === FALSE); // append $this->assertTrue($ret[$i++] === FALSE); // getRange $this->assertTrue(is_array($ret[$i]) && count($ret[$i]) === 1 && $ret[$i][0] === FALSE); // mget $i++; $this->assertTrue($ret[$i++] === FALSE); // incr $this->assertTrue($ret[$i++] === FALSE); // incrBy $this->assertTrue($ret[$i++] === FALSE); // decr $this->assertTrue($ret[$i++] === FALSE); // decrBy $this->assertTrue($ret[$i++] === FALSE); // rpush $this->assertTrue($ret[$i++] === FALSE); // lpush $this->assertTrue($ret[$i++] === FALSE); // llen $this->assertTrue($ret[$i++] === FALSE); // lpop $this->assertTrue($ret[$i++] === FALSE); // lgetrange $this->assertTrue($ret[$i++] === FALSE); // ltrim $this->assertTrue($ret[$i++] === FALSE); // lget $this->assertTrue($ret[$i++] === FALSE); // lset $this->assertTrue($ret[$i++] === FALSE); // lremove $this->assertTrue($ret[$i++] === FALSE); // lpop $this->assertTrue($ret[$i++] === FALSE); // rpop $this->assertTrue($ret[$i++] === FALSE); // rpoplush $this->assertTrue($ret[$i++] === FALSE); // sadd $this->assertTrue($ret[$i++] === FALSE); // sremove $this->assertTrue($ret[$i++] === FALSE); // spop $this->assertTrue($ret[$i++] === FALSE); // smove $this->assertTrue($ret[$i++] === FALSE); // ssize $this->assertTrue($ret[$i++] === FALSE); // scontains $this->assertTrue($ret[$i++] === FALSE); // sinter $this->assertTrue($ret[$i++] === FALSE); // sunion $this->assertTrue($ret[$i++] === FALSE); // sdiff $this->assertTrue($ret[$i++] === FALSE); // smembers $this->assertTrue($ret[$i++] === FALSE); // srandmember $this->assertTrue($ret[$i++] === FALSE); // zadd $this->assertTrue($ret[$i++] === FALSE); // zdelete $this->assertTrue($ret[$i++] === FALSE); // zincrby $this->assertTrue($ret[$i++] === FALSE); // zrank $this->assertTrue($ret[$i++] === FALSE); // zrevrank $this->assertTrue($ret[$i++] === FALSE); // zrange $this->assertTrue($ret[$i++] === FALSE); // zreverserange $this->assertTrue($ret[$i++] === FALSE); // zrangebyscore $this->assertTrue($ret[$i++] === FALSE); // zcount $this->assertTrue($ret[$i++] === FALSE); // zcard $this->assertTrue($ret[$i++] === FALSE); // zscore $this->assertTrue($ret[$i++] === FALSE); // zdeleterangebyrank $this->assertTrue($ret[$i++] === FALSE); // zdeleterangebyscore $this->assertEquals($i, count($ret)); } public function testDifferentTypeString() { $key = '{hash}string'; $dkey = '{hash}' . __FUNCTION__; $this->redis->del($key); $this->assertEquals(TRUE, $this->redis->set($key, 'value')); // lists I/F $this->assertEquals(FALSE, $this->redis->rPush($key, 'lvalue')); $this->assertEquals(FALSE, $this->redis->lPush($key, 'lvalue')); $this->assertEquals(FALSE, $this->redis->lLen($key)); $this->assertEquals(FALSE, $this->redis->lPop($key)); $this->assertEquals(FALSE, $this->redis->lrange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->lTrim($key, 0, 1)); $this->assertEquals(FALSE, $this->redis->lGet($key, 0)); $this->assertEquals(FALSE, $this->redis->lSet($key, 0, "newValue")); $this->assertEquals(FALSE, $this->redis->lrem($key, 'lvalue', 1)); $this->assertEquals(FALSE, $this->redis->lPop($key)); $this->assertEquals(FALSE, $this->redis->rPop($key)); $this->assertEquals(FALSE, $this->redis->rPoplPush($key, $dkey . 'lkey1')); // sets I/F $this->assertEquals(FALSE, $this->redis->sAdd($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->srem($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sPop($key)); $this->assertEquals(FALSE, $this->redis->sMove($key, $dkey . 'skey1', 'sValue1')); $this->assertEquals(FALSE, $this->redis->scard($key)); $this->assertEquals(FALSE, $this->redis->sismember($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sInter($key, $dkey. 'skey2')); $this->assertEquals(FALSE, $this->redis->sUnion($key, $dkey . 'skey4')); $this->assertEquals(FALSE, $this->redis->sDiff($key, $dkey . 'skey7')); $this->assertEquals(FALSE, $this->redis->sMembers($key)); $this->assertEquals(FALSE, $this->redis->sRandMember($key)); // sorted sets I/F $this->assertEquals(FALSE, $this->redis->zAdd($key, 1, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRem($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zIncrBy($key, 1, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRank($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRevRank($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zRevRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zRangeByScore($key, 1, 2)); $this->assertEquals(FALSE, $this->redis->zCount($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zCard($key)); $this->assertEquals(FALSE, $this->redis->zScore($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRemRangeByRank($key, 1, 2)); $this->assertEquals(FALSE, $this->redis->zRemRangeByScore($key, 1, 2)); // hash I/F $this->assertEquals(FALSE, $this->redis->hSet($key, 'key1', 'value1')); $this->assertEquals(FALSE, $this->redis->hGet($key, 'key1')); $this->assertEquals(FALSE, $this->redis->hMGet($key, array('key1'))); $this->assertEquals(FALSE, $this->redis->hMSet($key, array('key1' => 'value1'))); $this->assertEquals(FALSE, $this->redis->hIncrBy($key, 'key2', 1)); $this->assertEquals(FALSE, $this->redis->hExists($key, 'key2')); $this->assertEquals(FALSE, $this->redis->hDel($key, 'key2')); $this->assertEquals(FALSE, $this->redis->hLen($key)); $this->assertEquals(FALSE, $this->redis->hKeys($key)); $this->assertEquals(FALSE, $this->redis->hVals($key)); $this->assertEquals(FALSE, $this->redis->hGetAll($key)); } public function testDifferentTypeList() { $key = '{hash}list'; $dkey = '{hash}' . __FUNCTION__; $this->redis->del($key); $this->assertEquals(1, $this->redis->lPush($key, 'value')); // string I/F $this->assertEquals(FALSE, $this->redis->get($key)); $this->assertEquals(FALSE, $this->redis->getset($key, 'value2')); $this->assertEquals(FALSE, $this->redis->append($key, 'append')); $this->assertEquals(FALSE, $this->redis->getRange($key, 0, 8)); $this->assertEquals(array(FALSE), $this->redis->mget(array($key))); $this->assertEquals(FALSE, $this->redis->incr($key)); $this->assertEquals(FALSE, $this->redis->incrBy($key, 1)); $this->assertEquals(FALSE, $this->redis->decr($key)); $this->assertEquals(FALSE, $this->redis->decrBy($key, 1)); // sets I/F $this->assertEquals(FALSE, $this->redis->sAdd($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->srem($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sPop($key)); $this->assertEquals(FALSE, $this->redis->sMove($key, $dkey . 'skey1', 'sValue1')); $this->assertEquals(FALSE, $this->redis->scard($key)); $this->assertEquals(FALSE, $this->redis->sismember($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sInter($key, $dkey . 'skey2')); $this->assertEquals(FALSE, $this->redis->sUnion($key, $dkey . 'skey4')); $this->assertEquals(FALSE, $this->redis->sDiff($key, $dkey . 'skey7')); $this->assertEquals(FALSE, $this->redis->sMembers($key)); $this->assertEquals(FALSE, $this->redis->sRandMember($key)); // sorted sets I/F $this->assertEquals(FALSE, $this->redis->zAdd($key, 1, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRem($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zIncrBy($key, 1, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRank($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRevRank($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zRevRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zRangeByScore($key, 1, 2)); $this->assertEquals(FALSE, $this->redis->zCount($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zCard($key)); $this->assertEquals(FALSE, $this->redis->zScore($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRemRangeByRank($key, 1, 2)); $this->assertEquals(FALSE, $this->redis->zRemRangeByScore($key, 1, 2)); // hash I/F $this->assertEquals(FALSE, $this->redis->hSet($key, 'key1', 'value1')); $this->assertEquals(FALSE, $this->redis->hGet($key, 'key1')); $this->assertEquals(FALSE, $this->redis->hMGet($key, array('key1'))); $this->assertEquals(FALSE, $this->redis->hMSet($key, array('key1' => 'value1'))); $this->assertEquals(FALSE, $this->redis->hIncrBy($key, 'key2', 1)); $this->assertEquals(FALSE, $this->redis->hExists($key, 'key2')); $this->assertEquals(FALSE, $this->redis->hDel($key, 'key2')); $this->assertEquals(FALSE, $this->redis->hLen($key)); $this->assertEquals(FALSE, $this->redis->hKeys($key)); $this->assertEquals(FALSE, $this->redis->hVals($key)); $this->assertEquals(FALSE, $this->redis->hGetAll($key)); } public function testDifferentTypeSet() { $key = '{hash}set'; $dkey = '{hash}' . __FUNCTION__; $this->redis->del($key); $this->assertEquals(1, $this->redis->sAdd($key, 'value')); // string I/F $this->assertEquals(FALSE, $this->redis->get($key)); $this->assertEquals(FALSE, $this->redis->getset($key, 'value2')); $this->assertEquals(FALSE, $this->redis->append($key, 'append')); $this->assertEquals(FALSE, $this->redis->getRange($key, 0, 8)); $this->assertEquals(array(FALSE), $this->redis->mget(array($key))); $this->assertEquals(FALSE, $this->redis->incr($key)); $this->assertEquals(FALSE, $this->redis->incrBy($key, 1)); $this->assertEquals(FALSE, $this->redis->decr($key)); $this->assertEquals(FALSE, $this->redis->decrBy($key, 1)); // lists I/F $this->assertEquals(FALSE, $this->redis->rPush($key, 'lvalue')); $this->assertEquals(FALSE, $this->redis->lPush($key, 'lvalue')); $this->assertEquals(FALSE, $this->redis->lLen($key)); $this->assertEquals(FALSE, $this->redis->lPop($key)); $this->assertEquals(FALSE, $this->redis->lrange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->lTrim($key, 0, 1)); $this->assertEquals(FALSE, $this->redis->lGet($key, 0)); $this->assertEquals(FALSE, $this->redis->lSet($key, 0, "newValue")); $this->assertEquals(FALSE, $this->redis->lrem($key, 'lvalue', 1)); $this->assertEquals(FALSE, $this->redis->lPop($key)); $this->assertEquals(FALSE, $this->redis->rPop($key)); $this->assertEquals(FALSE, $this->redis->rPoplPush($key, $dkey . 'lkey1')); // sorted sets I/F $this->assertEquals(FALSE, $this->redis->zAdd($key, 1, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRem($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zIncrBy($key, 1, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRank($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRevRank($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zRevRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zRangeByScore($key, 1, 2)); $this->assertEquals(FALSE, $this->redis->zCount($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zCard($key)); $this->assertEquals(FALSE, $this->redis->zScore($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRemRangeByRank($key, 1, 2)); $this->assertEquals(FALSE, $this->redis->zRemRangeByScore($key, 1, 2)); // hash I/F $this->assertEquals(FALSE, $this->redis->hSet($key, 'key1', 'value1')); $this->assertEquals(FALSE, $this->redis->hGet($key, 'key1')); $this->assertEquals(FALSE, $this->redis->hMGet($key, array('key1'))); $this->assertEquals(FALSE, $this->redis->hMSet($key, array('key1' => 'value1'))); $this->assertEquals(FALSE, $this->redis->hIncrBy($key, 'key2', 1)); $this->assertEquals(FALSE, $this->redis->hExists($key, 'key2')); $this->assertEquals(FALSE, $this->redis->hDel($key, 'key2')); $this->assertEquals(FALSE, $this->redis->hLen($key)); $this->assertEquals(FALSE, $this->redis->hKeys($key)); $this->assertEquals(FALSE, $this->redis->hVals($key)); $this->assertEquals(FALSE, $this->redis->hGetAll($key)); } public function testDifferentTypeSortedSet() { $key = '{hash}sortedset'; $dkey = '{hash}' . __FUNCTION__; $this->redis->del($key); $this->assertEquals(1, $this->redis->zAdd($key, 0, 'value')); // string I/F $this->assertEquals(FALSE, $this->redis->get($key)); $this->assertEquals(FALSE, $this->redis->getset($key, 'value2')); $this->assertEquals(FALSE, $this->redis->append($key, 'append')); $this->assertEquals(FALSE, $this->redis->getRange($key, 0, 8)); $this->assertEquals(array(FALSE), $this->redis->mget(array($key))); $this->assertEquals(FALSE, $this->redis->incr($key)); $this->assertEquals(FALSE, $this->redis->incrBy($key, 1)); $this->assertEquals(FALSE, $this->redis->decr($key)); $this->assertEquals(FALSE, $this->redis->decrBy($key, 1)); // lists I/F $this->assertEquals(FALSE, $this->redis->rPush($key, 'lvalue')); $this->assertEquals(FALSE, $this->redis->lPush($key, 'lvalue')); $this->assertEquals(FALSE, $this->redis->lLen($key)); $this->assertEquals(FALSE, $this->redis->lPop($key)); $this->assertEquals(FALSE, $this->redis->lrange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->lTrim($key, 0, 1)); $this->assertEquals(FALSE, $this->redis->lGet($key, 0)); $this->assertEquals(FALSE, $this->redis->lSet($key, 0, "newValue")); $this->assertEquals(FALSE, $this->redis->lrem($key, 'lvalue', 1)); $this->assertEquals(FALSE, $this->redis->lPop($key)); $this->assertEquals(FALSE, $this->redis->rPop($key)); $this->assertEquals(FALSE, $this->redis->rPoplPush($key, $dkey . 'lkey1')); // sets I/F $this->assertEquals(FALSE, $this->redis->sAdd($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->srem($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sPop($key)); $this->assertEquals(FALSE, $this->redis->sMove($key, $dkey . 'skey1', 'sValue1')); $this->assertEquals(FALSE, $this->redis->scard($key)); $this->assertEquals(FALSE, $this->redis->sismember($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sInter($key, $dkey . 'skey2')); $this->assertEquals(FALSE, $this->redis->sUnion($key, $dkey . 'skey4')); $this->assertEquals(FALSE, $this->redis->sDiff($key, $dkey . 'skey7')); $this->assertEquals(FALSE, $this->redis->sMembers($key)); $this->assertEquals(FALSE, $this->redis->sRandMember($key)); // hash I/F $this->assertEquals(FALSE, $this->redis->hSet($key, 'key1', 'value1')); $this->assertEquals(FALSE, $this->redis->hGet($key, 'key1')); $this->assertEquals(FALSE, $this->redis->hMGet($key, array('key1'))); $this->assertEquals(FALSE, $this->redis->hMSet($key, array('key1' => 'value1'))); $this->assertEquals(FALSE, $this->redis->hIncrBy($key, 'key2', 1)); $this->assertEquals(FALSE, $this->redis->hExists($key, 'key2')); $this->assertEquals(FALSE, $this->redis->hDel($key, 'key2')); $this->assertEquals(FALSE, $this->redis->hLen($key)); $this->assertEquals(FALSE, $this->redis->hKeys($key)); $this->assertEquals(FALSE, $this->redis->hVals($key)); $this->assertEquals(FALSE, $this->redis->hGetAll($key)); } public function testDifferentTypeHash() { $key = '{hash}hash'; $dkey = '{hash}hash'; $this->redis->del($key); $this->assertEquals(1, $this->redis->hSet($key, 'key', 'value')); // string I/F $this->assertEquals(FALSE, $this->redis->get($key)); $this->assertEquals(FALSE, $this->redis->getset($key, 'value2')); $this->assertEquals(FALSE, $this->redis->append($key, 'append')); $this->assertEquals(FALSE, $this->redis->getRange($key, 0, 8)); $this->assertEquals(array(FALSE), $this->redis->mget(array($key))); $this->assertEquals(FALSE, $this->redis->incr($key)); $this->assertEquals(FALSE, $this->redis->incrBy($key, 1)); $this->assertEquals(FALSE, $this->redis->decr($key)); $this->assertEquals(FALSE, $this->redis->decrBy($key, 1)); // lists I/F $this->assertEquals(FALSE, $this->redis->rPush($key, 'lvalue')); $this->assertEquals(FALSE, $this->redis->lPush($key, 'lvalue')); $this->assertEquals(FALSE, $this->redis->lLen($key)); $this->assertEquals(FALSE, $this->redis->lPop($key)); $this->assertEquals(FALSE, $this->redis->lrange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->lTrim($key, 0, 1)); $this->assertEquals(FALSE, $this->redis->lGet($key, 0)); $this->assertEquals(FALSE, $this->redis->lSet($key, 0, "newValue")); $this->assertEquals(FALSE, $this->redis->lrem($key, 'lvalue', 1)); $this->assertEquals(FALSE, $this->redis->lPop($key)); $this->assertEquals(FALSE, $this->redis->rPop($key)); $this->assertEquals(FALSE, $this->redis->rPoplPush($key, $dkey . 'lkey1')); // sets I/F $this->assertEquals(FALSE, $this->redis->sAdd($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->srem($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sPop($key)); $this->assertEquals(FALSE, $this->redis->sMove($key, $dkey . 'skey1', 'sValue1')); $this->assertEquals(FALSE, $this->redis->scard($key)); $this->assertEquals(FALSE, $this->redis->sismember($key, 'sValue1')); $this->assertEquals(FALSE, $this->redis->sInter($key, $dkey . 'skey2')); $this->assertEquals(FALSE, $this->redis->sUnion($key, $dkey . 'skey4')); $this->assertEquals(FALSE, $this->redis->sDiff($key, $dkey . 'skey7')); $this->assertEquals(FALSE, $this->redis->sMembers($key)); $this->assertEquals(FALSE, $this->redis->sRandMember($key)); // sorted sets I/F $this->assertEquals(FALSE, $this->redis->zAdd($key, 1, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRem($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zIncrBy($key, 1, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRank($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRevRank($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zRevRange($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zRangeByScore($key, 1, 2)); $this->assertEquals(FALSE, $this->redis->zCount($key, 0, -1)); $this->assertEquals(FALSE, $this->redis->zCard($key)); $this->assertEquals(FALSE, $this->redis->zScore($key, 'zValue1')); $this->assertEquals(FALSE, $this->redis->zRemRangeByRank($key, 1, 2)); $this->assertEquals(FALSE, $this->redis->zRemRangeByScore($key, 1, 2)); } public function testSerializerPHP() { $this->checkSerializer(Redis::SERIALIZER_PHP); // with prefix $this->redis->setOption(Redis::OPT_PREFIX, "test:"); $this->checkSerializer(Redis::SERIALIZER_PHP); $this->redis->setOption(Redis::OPT_PREFIX, ""); } public function testSerializerIGBinary() { if(defined('Redis::SERIALIZER_IGBINARY')) { $this->checkSerializer(Redis::SERIALIZER_IGBINARY); // with prefix $this->redis->setOption(Redis::OPT_PREFIX, "test:"); $this->checkSerializer(Redis::SERIALIZER_IGBINARY); $this->redis->setOption(Redis::OPT_PREFIX, ""); } } private function checkSerializer($mode) { $this->redis->del('key'); $this->assertTrue($this->redis->getOption(Redis::OPT_SERIALIZER) === Redis::SERIALIZER_NONE); // default $this->assertTrue($this->redis->setOption(Redis::OPT_SERIALIZER, $mode) === TRUE); // set ok $this->assertTrue($this->redis->getOption(Redis::OPT_SERIALIZER) === $mode); // get ok // lPush, rPush $a = array('hello world', 42, TRUE, array('' => 1729)); $this->redis->del('key'); $this->redis->lPush('key', $a[0]); $this->redis->rPush('key', $a[1]); $this->redis->rPush('key', $a[2]); $this->redis->rPush('key', $a[3]); // lrange $this->assertTrue($a === $this->redis->lrange('key', 0, -1)); // lGet $this->assertTrue($a[0] === $this->redis->lGet('key', 0)); $this->assertTrue($a[1] === $this->redis->lGet('key', 1)); $this->assertTrue($a[2] === $this->redis->lGet('key', 2)); $this->assertTrue($a[3] === $this->redis->lGet('key', 3)); // lrem $this->assertTrue($this->redis->lrem('key', $a[3]) === 1); $this->assertTrue(array_slice($a, 0, 3) === $this->redis->lrange('key', 0, -1)); // lSet $a[0] = array('k' => 'v'); // update $this->assertTrue(TRUE === $this->redis->lSet('key', 0, $a[0])); $this->assertTrue($a[0] === $this->redis->lGet('key', 0)); // lInsert $this->assertTrue($this->redis->lInsert('key', Redis::BEFORE, $a[0], array(1,2,3)) === 4); $this->assertTrue($this->redis->lInsert('key', Redis::AFTER, $a[0], array(4,5,6)) === 5); $a = array(array(1,2,3), $a[0], array(4,5,6), $a[1], $a[2]); $this->assertTrue($a === $this->redis->lrange('key', 0, -1)); // sAdd $this->redis->del('{set}key'); $s = array(1,'a', array(1,2,3), array('k' => 'v')); $this->assertTrue(1 === $this->redis->sAdd('{set}key', $s[0])); $this->assertTrue(1 === $this->redis->sAdd('{set}key', $s[1])); $this->assertTrue(1 === $this->redis->sAdd('{set}key', $s[2])); $this->assertTrue(1 === $this->redis->sAdd('{set}key', $s[3])); // variadic sAdd $this->redis->del('k'); $this->assertTrue(3 === $this->redis->sAdd('k', 'a', 'b', 'c')); $this->assertTrue(1 === $this->redis->sAdd('k', 'a', 'b', 'c', 'd')); // srem $this->assertTrue(1 === $this->redis->srem('{set}key', $s[3])); $this->assertTrue(0 === $this->redis->srem('{set}key', $s[3])); // variadic $this->redis->del('k'); $this->redis->sAdd('k', 'a', 'b', 'c', 'd'); $this->assertTrue(2 === $this->redis->sRem('k', 'a', 'd')); $this->assertTrue(2 === $this->redis->sRem('k', 'b', 'c', 'e')); $this->assertTrue(FALSE === $this->redis->exists('k')); // sismember $this->assertTrue(TRUE === $this->redis->sismember('{set}key', $s[0])); $this->assertTrue(TRUE === $this->redis->sismember('{set}key', $s[1])); $this->assertTrue(TRUE === $this->redis->sismember('{set}key', $s[2])); $this->assertTrue(FALSE === $this->redis->sismember('{set}key', $s[3])); unset($s[3]); // sMove $this->redis->del('{set}tmp'); $this->redis->sMove('{set}key', '{set}tmp', $s[0]); $this->assertTrue(FALSE === $this->redis->sismember('{set}key', $s[0])); $this->assertTrue(TRUE === $this->redis->sismember('{set}tmp', $s[0])); unset($s[0]); // sorted sets $z = array('z0', array('k' => 'v'), FALSE, NULL); $this->redis->del('key'); // zAdd $this->assertTrue(1 === $this->redis->zAdd('key', 0, $z[0])); $this->assertTrue(1 === $this->redis->zAdd('key', 1, $z[1])); $this->assertTrue(1 === $this->redis->zAdd('key', 2, $z[2])); $this->assertTrue(1 === $this->redis->zAdd('key', 3, $z[3])); // zRem $this->assertTrue(1 === $this->redis->zRem('key', $z[3])); $this->assertTrue(0 === $this->redis->zRem('key', $z[3])); unset($z[3]); // check that zRem doesn't crash with a missing parameter (GitHub issue #102): $this->assertTrue(FALSE === @$this->redis->zRem('key')); // variadic $this->redis->del('k'); $this->redis->zAdd('k', 0, 'a'); $this->redis->zAdd('k', 1, 'b'); $this->redis->zAdd('k', 2, 'c'); $this->assertTrue(2 === $this->redis->zRem('k', 'a', 'c')); $this->assertTrue(1.0 === $this->redis->zScore('k', 'b')); $this->assertTrue($this->redis->zRange('k', 0, -1, true) == array('b' => 1.0)); // zRange $this->assertTrue($z === $this->redis->zRange('key', 0, -1)); // zScore $this->assertTrue(0.0 === $this->redis->zScore('key', $z[0])); $this->assertTrue(1.0 === $this->redis->zScore('key', $z[1])); $this->assertTrue(2.0 === $this->redis->zScore('key', $z[2])); // zRank $this->assertTrue(0 === $this->redis->zRank('key', $z[0])); $this->assertTrue(1 === $this->redis->zRank('key', $z[1])); $this->assertTrue(2 === $this->redis->zRank('key', $z[2])); // zRevRank $this->assertTrue(2 === $this->redis->zRevRank('key', $z[0])); $this->assertTrue(1 === $this->redis->zRevRank('key', $z[1])); $this->assertTrue(0 === $this->redis->zRevRank('key', $z[2])); // zIncrBy $this->assertTrue(3.0 === $this->redis->zIncrBy('key', 1.0, $z[2])); $this->assertTrue(3.0 === $this->redis->zScore('key', $z[2])); $this->assertTrue(5.0 === $this->redis->zIncrBy('key', 2.0, $z[2])); $this->assertTrue(5.0 === $this->redis->zScore('key', $z[2])); $this->assertTrue(2.0 === $this->redis->zIncrBy('key', -3.0, $z[2])); $this->assertTrue(2.0 === $this->redis->zScore('key', $z[2])); // mset $a = array('k0' => 1, 'k1' => 42, 'k2' => NULL, 'k3' => FALSE, 'k4' => array('a' => 'b')); $this->assertTrue(TRUE === $this->redis->mset($a)); foreach($a as $k => $v) { $this->assertTrue($this->redis->get($k) === $v); } $a = array('k0' => 1, 'k1' => 42, 'k2' => NULL, 'k3' => FALSE, 'k4' => array('a' => 'b')); // hSet $this->redis->del('key'); foreach($a as $k => $v) { $this->assertTrue(1 === $this->redis->hSet('key', $k, $v)); } // hGet foreach($a as $k => $v) { $this->assertTrue($v === $this->redis->hGet('key', $k)); } // hGetAll $this->assertTrue($a === $this->redis->hGetAll('key')); $this->assertTrue(TRUE === $this->redis->hExists('key', 'k0')); $this->assertTrue(TRUE === $this->redis->hExists('key', 'k1')); $this->assertTrue(TRUE === $this->redis->hExists('key', 'k2')); $this->assertTrue(TRUE === $this->redis->hExists('key', 'k3')); $this->assertTrue(TRUE === $this->redis->hExists('key', 'k4')); // hMSet $this->redis->del('key'); $this->redis->hMSet('key', $a); foreach($a as $k => $v) { $this->assertTrue($v === $this->redis->hGet('key', $k)); } // hMget $hmget = $this->redis->hMget('key', array_keys($a)); foreach($hmget as $k => $v) { $this->assertTrue($v === $a[$k]); } // getMultiple $this->redis->set('a', NULL); $this->redis->set('b', FALSE); $this->redis->set('c', 42); $this->redis->set('d', array('x' => 'y')); $this->assertTrue(array(NULL, FALSE, 42, array('x' => 'y')) === $this->redis->mGet(array('a', 'b', 'c', 'd'))); // pipeline if ($this->havePipeline()) { $this->sequence(Redis::PIPELINE); } // multi-exec $this->sequence(Redis::MULTI); // keys $this->assertTrue(is_array($this->redis->keys('*'))); // issue #62, hgetall $this->redis->del('hash1'); $this->redis->hSet('hash1','data', 'test 1'); $this->redis->hSet('hash1','session_id', 'test 2'); $data = $this->redis->hGetAll('hash1'); $this->assertTrue($data['data'] === 'test 1'); $this->assertTrue($data['session_id'] === 'test 2'); // issue #145, serializer with objects. $this->redis->set('x', array(new stdClass, new stdClass)); $x = $this->redis->get('x'); $this->assertTrue(is_array($x)); $this->assertTrue(is_object($x[0]) && get_class($x[0]) === 'stdClass'); $this->assertTrue(is_object($x[1]) && get_class($x[1]) === 'stdClass'); // revert $this->assertTrue($this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE) === TRUE); // set ok $this->assertTrue($this->redis->getOption(Redis::OPT_SERIALIZER) === Redis::SERIALIZER_NONE); // get ok } public function testDumpRestore() { if (version_compare($this->version, "2.5.0", "lt")) { $this->markTestSkipped(); } $this->redis->del('foo'); $this->redis->del('bar'); $this->redis->set('foo', 'this-is-foo'); $this->redis->set('bar', 'this-is-bar'); $d_foo = $this->redis->dump('foo'); $d_bar = $this->redis->dump('bar'); $this->redis->del('foo'); $this->redis->del('bar'); // Assert returns from restore $this->assertTrue($this->redis->restore('foo', 0, $d_bar)); $this->assertTrue($this->redis->restore('bar', 0, $d_foo)); // Now check that the keys have switched $this->assertTrue($this->redis->get('foo') === 'this-is-bar'); $this->assertTrue($this->redis->get('bar') === 'this-is-foo'); $this->redis->del('foo'); $this->redis->del('bar'); } public function testGetLastError() { // We shouldn't have any errors now $this->assertTrue($this->redis->getLastError() === NULL); // test getLastError with a regular command $this->redis->set('x', 'a'); $this->assertFalse($this->redis->incr('x')); $incrError = $this->redis->getLastError(); $this->assertTrue(strlen($incrError) > 0); // clear error $this->redis->clearLastError(); $this->assertTrue($this->redis->getLastError() === NULL); } // Helper function to compare nested results -- from the php.net array_diff page, I believe private function array_diff_recursive($aArray1, $aArray2) { $aReturn = array(); foreach ($aArray1 as $mKey => $mValue) { if (array_key_exists($mKey, $aArray2)) { if (is_array($mValue)) { $aRecursiveDiff = $this->array_diff_recursive($mValue, $aArray2[$mKey]); if (count($aRecursiveDiff)) { $aReturn[$mKey] = $aRecursiveDiff; } } else { if ($mValue != $aArray2[$mKey]) { $aReturn[$mKey] = $mValue; } } } else { $aReturn[$mKey] = $mValue; } } return $aReturn; } public function testScript() { if (version_compare($this->version, "2.5.0", "lt")) { $this->markTestSkipped(); } // Flush any scripts we have $this->assertTrue($this->redis->script('flush')); // Silly scripts to test against $s1_src = 'return 1'; $s1_sha = sha1($s1_src); $s2_src = 'return 2'; $s2_sha = sha1($s2_src); $s3_src = 'return 3'; $s3_sha = sha1($s3_src); // None should exist $result = $this->redis->script('exists', $s1_sha, $s2_sha, $s3_sha); $this->assertTrue(is_array($result) && count($result) == 3); $this->assertTrue(is_array($result) && count(array_filter($result)) == 0); // Load them up $this->assertTrue($this->redis->script('load', $s1_src) == $s1_sha); $this->assertTrue($this->redis->script('load', $s2_src) == $s2_sha); $this->assertTrue($this->redis->script('load', $s3_src) == $s3_sha); // They should all exist $result = $this->redis->script('exists', $s1_sha, $s2_sha, $s3_sha); $this->assertTrue(is_array($result) && count(array_filter($result)) == 3); } public function testEval() { if (version_compare($this->version, "2.5.0", "lt")) { $this->markTestSkipped(); } // Basic single line response tests $this->assertTrue(1 == $this->redis->eval('return 1')); $this->assertTrue(1.55 == $this->redis->eval("return '1.55'")); $this->assertTrue("hello, world" == $this->redis->eval("return 'hello, world'")); /* * Keys to be incorporated into lua results */ // Make a list $this->redis->del('{eval-key}-list'); $this->redis->rpush('{eval-key}-list', 'a'); $this->redis->rpush('{eval-key}-list', 'b'); $this->redis->rpush('{eval-key}-list', 'c'); // Make a set $this->redis->del('{eval-key}-set'); $this->redis->sadd('{eval-key}-set', 'd'); $this->redis->sadd('{eval-key}-set', 'e'); $this->redis->sadd('{eval-key}-set', 'f'); // Basic keys $this->redis->set('{eval-key}-str1', 'hello, world'); $this->redis->set('{eval-key}-str2', 'hello again!'); // Use a script to return our list, and verify its response $list = $this->redis->eval("return redis.call('lrange', KEYS[1], 0, -1)", Array('{eval-key}-list'), 1); $this->assertTrue($list === Array('a','b','c')); // Use a script to return our set $set = $this->redis->eval("return redis.call('smembers', KEYS[1])", Array('{eval-key}-set'), 1); $this->assertTrue($set == Array('d','e','f')); // Test an empty MULTI BULK response $this->redis->del('{eval-key}-nolist'); $empty_resp = $this->redis->eval("return redis.call('lrange', '{eval-key}-nolist', 0, -1)", Array('{eval-key}-nolist'), 1); $this->assertTrue(is_array($empty_resp) && empty($empty_resp)); // Now test a nested reply $nested_script = " return { 1,2,3, { redis.call('get', '{eval-key}-str1'), redis.call('get', '{eval-key}-str2'), redis.call('lrange', 'not-any-kind-of-list', 0, -1), { redis.call('smembers','{eval-key}-set'), redis.call('lrange', '{eval-key}-list', 0, -1) } } } "; $expected = Array( 1, 2, 3, Array( 'hello, world', 'hello again!', Array(), Array( Array('d','e','f'), Array('a','b','c') ) ) ); // Now run our script, and check our values against each other $eval_result = $this->redis->eval($nested_script, Array('{eval-key}-str1', '{eval-key}-str2', '{eval-key}-set', '{eval-key}-list'), 4); $this->assertTrue(is_array($eval_result) && count($this->array_diff_recursive($eval_result, $expected)) == 0); /* * Nested reply wihin a multi/pipeline block */ $num_scripts = 10; $arr_modes = Array(Redis::MULTI); if ($this->havePipeline()) $arr_modes[] = Redis::PIPELINE; foreach($arr_modes as $mode) { $this->redis->multi($mode); for($i=0;$i<$num_scripts;$i++) { $this->redis->eval($nested_script, Array('{eval-key}-dummy'), 1); } $replies = $this->redis->exec(); foreach($replies as $reply) { $this->assertTrue(is_array($reply) && count($this->array_diff_recursive($reply, $expected)) == 0); } } /* * KEYS/ARGV */ $args_script = "return {KEYS[1],KEYS[2],KEYS[3],ARGV[1],ARGV[2],ARGV[3]}"; $args_args = Array('{k}1','{k}2','{k}3','v1','v2','v3'); $args_result = $this->redis->eval($args_script, $args_args, 3); $this->assertTrue($args_result === $args_args); // turn on key prefixing $this->redis->setOption(Redis::OPT_PREFIX, 'prefix:'); $args_result = $this->redis->eval($args_script, $args_args, 3); // Make sure our first three are prefixed for($i=0;$iassertTrue($args_result[$i] == 'prefix:' . $args_args[$i]); } else { // Should not be prefixed $this->assertTrue($args_result[$i] == $args_args[$i]); } } } public function testEvalSHA() { if (version_compare($this->version, "2.5.0", "lt")) { $this->markTestSkipped(); } // Flush any loaded scripts $this->redis->script('flush'); // Non existant script (but proper sha1), and a random (not) sha1 string $this->assertFalse($this->redis->evalsha(sha1(uniqid()))); $this->assertFalse($this->redis->evalsha('some-random-data')); // Load a script $cb = uniqid(); // To ensure the script is new $scr = "local cb='$cb' return 1"; $sha = sha1($scr); // Run it when it doesn't exist, run it with eval, and then run it with sha1 $this->assertTrue(false === $this->redis->evalsha($scr)); $this->assertTrue(1 === $this->redis->eval($scr)); $this->assertTrue(1 === $this->redis->evalsha($sha)); } public function testSerialize() { $vals = Array(1, 1.5, 'one', Array('here','is','an','array')); // Test with no serialization at all $this->assertTrue($this->redis->_serialize('test') === 'test'); $this->assertTrue($this->redis->_serialize(1) === '1'); $this->assertTrue($this->redis->_serialize(Array()) === 'Array'); $this->assertTrue($this->redis->_serialize(new stdClass) === 'Object'); $arr_serializers = Array(Redis::SERIALIZER_PHP); if(defined('Redis::SERIALIZER_IGBINARY')) { $arr_serializers[] = Redis::SERIALIZER_IGBINARY; } foreach($arr_serializers as $mode) { $arr_enc = Array(); $arr_dec = Array(); foreach($vals as $k => $v) { $enc = $this->redis->_serialize($v); $dec = $this->redis->_unserialize($enc); // They should be the same $this->assertTrue($enc == $dec); } } } public function testUnserialize() { $vals = Array( 1,1.5,'one',Array('this','is','an','array') ); $serializers = Array(Redis::SERIALIZER_PHP); if(defined('Redis::SERIALIZER_IGBINARY')) { $serializers[] = Redis::SERIALIZER_IGBINARY; } foreach($serializers as $mode) { $vals_enc = Array(); // Pass them through redis so they're serialized foreach($vals as $key => $val) { $this->redis->setOption(Redis::OPT_SERIALIZER, $mode); $key = "key" . ++$key; $this->redis->del($key); $this->redis->set($key, $val); // Clear serializer, get serialized value $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); $vals_enc[] = $this->redis->get($key); } // Run through our array comparing values for($i=0;$iredis->setOption(Redis::OPT_SERIALIZER, $mode); $this->assertTrue($vals[$i] == $this->redis->_unserialize($vals_enc[$i])); $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); } } } public function testPrefix() { // no prefix $this->redis->setOption(Redis::OPT_PREFIX, ''); $this->assertTrue('key' == $this->redis->_prefix('key')); // with a prefix $this->redis->setOption(Redis::OPT_PREFIX, 'some-prefix:'); $this->assertTrue('some-prefix:key' == $this->redis->_prefix('key')); // Clear prefix $this->redis->setOption(Redis::OPT_PREFIX, ''); } public function testReconnectSelect() { $key = 'reconnect-select'; $value = 'Has been set!'; $original_cfg = $this->redis->config('GET', 'timeout'); // Make sure the default DB doesn't have the key. $this->redis->select(0); $this->redis->del($key); // Set the key on a different DB. $this->redis->select(5); $this->redis->set($key, $value); // Time out after 1 second. $this->redis->config('SET', 'timeout', '1'); // Wait for the timeout. With Redis 2.4, we have to wait up to 10 s // for the server to close the connection, regardless of the timeout // setting. sleep(11); // Make sure we're still using the same DB. $this->assertEquals($value, $this->redis->get($key)); // Revert the setting. $this->redis->config('SET', 'timeout', $original_cfg['timeout']); } public function testTime() { if (version_compare($this->version, "2.5.0", "lt")) { $this->markTestSkipped(); } $time_arr = $this->redis->time(); $this->assertTrue(is_array($time_arr) && count($time_arr) == 2 && strval(intval($time_arr[0])) === strval($time_arr[0]) && strval(intval($time_arr[1])) === strval($time_arr[1])); } public function testReadTimeoutOption() { $this->assertTrue(defined('Redis::OPT_READ_TIMEOUT')); $this->redis->setOption(Redis::OPT_READ_TIMEOUT, "12.3"); $this->assertEquals(12.3, $this->redis->getOption(Redis::OPT_READ_TIMEOUT)); } public function testIntrospection() { // Simple introspection tests $this->assertTrue($this->redis->getHost() === $this->getHost()); $this->assertTrue($this->redis->getPort() === self::PORT); $this->assertTrue($this->redis->getAuth() === self::AUTH); } /** * Scan and variants */ protected function get_keyspace_count($str_db) { $arr_info = $this->redis->info(); $arr_info = $arr_info[$str_db]; $arr_info = explode(',', $arr_info); $arr_info = explode('=', $arr_info[0]); return $arr_info[1]; } public function testScan() { if(version_compare($this->version, "2.8.0", "lt")) { $this->markTestSkipped(); return; } // Key count $i_key_count = $this->get_keyspace_count('db0'); // Have scan retry $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); // Scan them all $it = NULL; while($arr_keys = $this->redis->scan($it)) { $i_key_count -= count($arr_keys); } // Should have iterated all keys $this->assertEquals(0, $i_key_count); // Unique keys, for pattern matching $str_uniq = uniqid() . '-' . uniqid(); for($i=0;$i<10;$i++) { $this->redis->set($str_uniq . "::$i", "bar::$i"); } // Scan just these keys using a pattern match $it = NULL; while($arr_keys = $this->redis->scan($it, "*$str_uniq*")) { $i -= count($arr_keys); } $this->assertEquals(0, $i); } public function testHScan() { if(version_compare($this->version, "2.8.0", "lt")) { $this->markTestSkipped(); return; } // Never get empty sets $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); $this->redis->del('hash'); $i_foo_mems = 0; for($i=0;$i<100;$i++) { if($i>3) { $this->redis->hset('hash', "member:$i", "value:$i"); } else { $this->redis->hset('hash', "foomember:$i", "value:$i"); $i_foo_mems++; } } // Scan all of them $it = NULL; while($arr_keys = $this->redis->hscan('hash', $it)) { $i -= count($arr_keys); } $this->assertEquals(0, $i); // Scan just *foomem* (should be 4) $it = NULL; while($arr_keys = $this->redis->hscan('hash', $it, '*foomember*')) { $i_foo_mems -= count($arr_keys); foreach($arr_keys as $str_mem => $str_val) { $this->assertTrue(strpos($str_mem, 'member')!==FALSE); $this->assertTrue(strpos($str_val, 'value')!==FALSE); } } $this->assertEquals(0, $i_foo_mems); } public function testSScan() { if(version_compare($this->version, "2.8.0", "lt")) { $this->markTestSkipped(); return; } $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); $this->redis->del('set'); for($i=0;$i<100;$i++) { $this->redis->sadd('set', "member:$i"); } // Scan all of them $it = NULL; while($arr_keys = $this->redis->sscan('set', $it)) { $i -= count($arr_keys); foreach($arr_keys as $str_mem) { $this->assertTrue(strpos($str_mem,'member')!==FALSE); } } $this->assertEquals(0, $i); // Scan just ones with zero in them (0, 10, 20, 30, 40, 50, 60, 70, 80, 90) $it = NULL; $i_w_zero = 0; while($arr_keys = $this->redis->sscan('set', $it, '*0*')) { $i_w_zero += count($arr_keys); } $this->assertEquals(10, $i_w_zero); } public function testZScan() { if(version_compare($this->version, "2.8.0", "lt")) { $this->markTestSkipped(); return; } $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); $this->redis->del('zset'); $i_tot_score = 0; $i_p_score = 0; $i_p_count = 0; for($i=0;$i<2000;$i++) { if($i<10) { $this->redis->zadd('zset', $i, "pmem:$i"); $i_p_score += $i; $i_p_count += 1; } else { $this->redis->zadd('zset', $i, "mem:$i"); } $i_tot_score += $i; } // Scan them all $it = NULL; while($arr_keys = $this->redis->zscan('zset', $it)) { foreach($arr_keys as $str_mem => $f_score) { $i_tot_score -= $f_score; $i--; } } $this->assertEquals(0, $i); $this->assertEquals((float)0, $i_tot_score); // Just scan "pmem" members $it = NULL; $i_p_score_old = $i_p_score; $i_p_count_old = $i_p_count; while($arr_keys = $this->redis->zscan('zset', $it, "*pmem*")) { foreach($arr_keys as $str_mem => $f_score) { $i_p_score -= $f_score; $i_p_count -= 1; } } $this->assertEquals((float)0, $i_p_score); $this->assertEquals(0, $i_p_count); // Turn off retrying and we should get some empty results $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_NORETRY); $i_skips = 0; $i_p_score = $i_p_score_old; $i_p_count = $i_p_count_old; $it = NULL; while(($arr_keys = $this->redis->zscan('zset', $it, "*pmem*")) !== FALSE) { if(count($arr_keys) == 0) $i_skips++; foreach($arr_keys as $str_mem => $f_score) { $i_p_score -= $f_score; $i_p_count -= 1; } } // We should still get all the keys, just with several empty results $this->assertTrue($i_skips > 0); $this->assertEquals((float)0, $i_p_score); $this->assertEquals(0, $i_p_count); } // // HyperLogLog (PF) commands // protected function createPFKey($str_key, $i_count) { $arr_mems = Array(); for($i=0;$i<$i_count;$i++) { $arr_mems[] = uniqid() . '-' . $i; } // Estimation by Redis $this->redis->pfadd($str_key, $i_count); } public function testPFCommands() { // Isn't available until 2.8.9 if(version_compare($this->version, "2.8.9", "lt")) { $this->markTestSkipped(); return; } $str_uniq = uniqid(); $arr_mems = Array(); for($i=0;$i<1000;$i++) { if($i%2 == 0) { $arr_mems[] = $str_uniq . '-' . $i; } else { $arr_mems[] = $i; } } // How many keys to create $i_keys = 10; // Iterate prefixing/serialization options foreach(Array(Redis::SERIALIZER_NONE, Redis::SERIALIZER_PHP) as $str_ser) { foreach(Array('', 'hl-key-prefix:') as $str_prefix) { $arr_keys = Array(); // Now add for each key for($i=0;$i<$i_keys;$i++) { $str_key = "{key}:$i"; $arr_keys[] = $str_key; // Clean up this key $this->redis->del($str_key); // Add to our cardinality set, and confirm we got a valid response $this->assertTrue($this->redis->pfadd($str_key, $arr_mems)); // Grab estimated cardinality $i_card = $this->redis->pfcount($str_key); $this->assertTrue(is_int($i_card)); // Count should be close $this->assertLess(abs($i_card-count($arr_mems)), count($arr_mems) * .1); // The PFCOUNT on this key should be the same as the above returned response $this->assertEquals($this->redis->pfcount($str_key), $i_card); } // Clean up merge key $this->redis->del('pf-merge-{key}'); // Merge the counters $this->assertTrue($this->redis->pfmerge('pf-merge-{key}', $arr_keys)); // Validate our merged count $i_redis_card = $this->redis->pfcount('pf-merge-{key}'); // Merged cardinality should still be roughly 1000 $this->assertLess(abs($i_redis_card-count($arr_mems)), count($arr_mems) * .1); // Clean up merge key $this->redis->del('pf-merge-{key}'); } } } // // GEO* command tests // protected function rawCommandArray($key, $args) { return call_user_func_array(Array($this->redis, 'rawCommand'), $args); } protected function addCities($key) { $this->redis->del($key); foreach ($this->cities as $city => $longlat) { $this->redis->geoadd($key, $longlat[0], $longlat[1], $city); } } /* GEOADD */ public function testGeoAdd() { if (!$this->minVersionCheck("3.2")) { return $this->markTestSkipped(); } $this->redis->del('geokey'); /* Add them one at a time */ foreach ($this->cities as $city => $longlat) { $this->assertEquals($this->redis->geoadd('geokey', $longlat[0], $longlat[1], $city), 1); } /* Add them again, all at once */ $args = Array('geokey'); foreach ($this->cities as $city => $longlat) { $args = array_merge($args, Array($longlat[0], $longlat[1], $city)); } /* They all exist, should be nothing added */ $this->assertEquals(call_user_func_array(Array($this->redis, 'geoadd'), $args), 0); } /* GEORADIUS */ public function genericGeoRadiusTest($cmd) { if (!$this->minVersionCheck("3.2.0")) { return $this->markTestSkipped(); } /* Chico */ $city = 'Chico'; $lng = -121.837478; $lat = 39.728494; $this->addCities('gk'); /* Pre tested with redis-cli. We're just verifying proper delivery of distance and unit */ if ($cmd == 'georadius') { $this->assertEquals($this->redis->georadius('gk', $lng, $lat, 10, 'mi'), Array('Chico')); $this->assertEquals($this->redis->georadius('gk', $lng, $lat, 30, 'mi'), Array('Gridley','Chico')); $this->assertEquals($this->redis->georadius('gk', $lng, $lat, 50, 'km'), Array('Gridley','Chico')); $this->assertEquals($this->redis->georadius('gk', $lng, $lat, 50000, 'm'), Array('Gridley','Chico')); $this->assertEquals($this->redis->georadius('gk', $lng, $lat, 150000, 'ft'), Array('Gridley', 'Chico')); $args = Array('georadius', 'gk', $lng, $lat, 500, 'mi'); } else { $this->assertEquals($this->redis->georadiusbymember('gk', $city, 10, 'mi'), Array('Chico')); $this->assertEquals($this->redis->georadiusbymember('gk', $city, 30, 'mi'), Array('Gridley','Chico')); $this->assertEquals($this->redis->georadiusbymember('gk', $city, 50, 'km'), Array('Gridley','Chico')); $this->assertEquals($this->redis->georadiusbymember('gk', $city, 50000, 'm'), Array('Gridley','Chico')); $this->assertEquals($this->redis->georadiusbymember('gk', $city, 150000, 'ft'), Array('Gridley', 'Chico')); $args = Array('georadiusbymember', 'gk', $city, 500, 'mi'); } /* Options */ $opts = Array('WITHCOORD', 'WITHDIST', 'WITHHASH'); $sortopts = Array('', 'ASC', 'DESC'); for ($i = 0; $i < count($opts); $i++) { $subopts = array_slice($opts, 0, $i); shuffle($subopts); $subargs = $args; foreach ($subopts as $opt) { $subargs[] = $opt; } for ($c = 0; $c < 3; $c++) { /* Add a count if we're past first iteration */ if ($c > 0) { $subopts['count'] = $c; $subargs[] = 'count'; $subargs[] = $c; } /* Adding optional sort */ foreach ($sortopts as $sortopt) { $realargs = $subargs; $realopts = $subopts; if ($sortopt) { $realargs[] = $sortopt; $realopts[] = $sortopt; } $ret1 = $this->rawCommandArray('gk', $realargs); if ($cmd == 'georadius') { $ret2 = $this->redis->$cmd('gk', $lng, $lat, 500, 'mi', $realopts); } else { $ret2 = $this->redis->$cmd('gk', $city, 500, 'mi', $realopts); } $this->assertEquals($ret1, $ret2); } } } } public function testGeoRadius() { if (!$this->minVersionCheck("3.2.0")) { return $this->markTestSkipped(); } $this->genericGeoRadiusTest('georadius'); } public function testGeoRadiusByMember() { if (!$this->minVersionCheck("3.2.0")) { return $this->markTestSkipped(); } $this->genericGeoRadiusTest('georadiusbymember'); } public function testGeoPos() { if (!$this->minVersionCheck("3.2.0")) { return $this->markTestSkipped(); } $this->addCities('gk'); $this->assertEquals($this->redis->geopos('gk', 'Chico', 'Sacramento'), $this->rawCommandArray('gk', Array('geopos', 'gk', 'Chico', 'Sacramento'))); $this->assertEquals($this->redis->geopos('gk', 'Cupertino'), $this->rawCommandArray('gk', Array('geopos', 'gk', 'Cupertino'))); } public function testGeoHash() { if (!$this->minVersionCheck("3.2.0")) { return $this->markTestSkipped(); } $this->addCities('gk'); $this->assertEquals($this->redis->geohash('gk', 'Chico', 'Sacramento'), $this->rawCommandArray('gk', Array('geohash', 'gk', 'Chico', 'Sacramento'))); $this->assertEquals($this->redis->geohash('gk', 'Chico'), $this->rawCommandArray('gk', Array('geohash', 'gk', 'Chico'))); } public function testGeoDist() { if (!$this->minVersionCheck("3.2.0")) { return $this->markTestSkipped(); } $this->addCities('gk'); $r1 = $this->redis->geodist('gk', 'Chico', 'Cupertino'); $r2 = $this->rawCommandArray('gk', Array('geodist', 'gk', 'Chico', 'Cupertino')); $this->assertEquals(round($r1, 8), round($r2, 8)); $r1 = $this->redis->geodist('gk', 'Chico', 'Cupertino', 'km'); $r2 = $this->rawCommandArray('gk', Array('geodist', 'gk', 'Chico', 'Cupertino', 'km')); $this->assertEquals(round($r1, 8), round($r2, 8)); } /* Test a 'raw' command */ public function testRawCommand() { $this->redis->set('mykey','some-value'); $str_result = $this->redis->rawCommand('get', 'mykey'); $this->assertEquals($str_result, 'some-value'); $this->redis->del('mylist'); $this->redis->rpush('mylist', 'A', 'B', 'C', 'D'); $this->assertEquals($this->redis->lrange('mylist', 0, -1), Array('A','B','C','D')); } public function testSession() { ini_set('session.save_handler', 'redis'); ini_set('session.save_path', 'tcp://localhost:6379'); if (!@session_start()) { return $this->markTestSkipped(); } session_write_close(); $this->assertTrue($this->redis->exists('PHPREDIS_SESSION:' . session_id())); } public function testMultipleConnect() { $host = $this->redis->GetHost(); $port = $this->redis->GetPort(); for($i = 0; $i < 5; $i++) { $this->redis->connect($host, $port); $this->assertEquals($this->redis->ping(), "+PONG"); } } } ?> redis-3.1.6/tests/TestRedis.php0000644000175000000120000000467013223116600017170 0ustar pyatsukhnenkowheel redis-3.1.6/tests/TestSuite.php0000644000175000000120000001306313223116600017207 0ustar pyatsukhnenkowheelstr_host = $str_host; } public function getHost() { return $this->str_host; } public static function make_bold($str_msg) { return self::$_boo_colorize ? self::$BOLD_ON . $str_msg . self::$BOLD_OFF : $str_msg; } public static function make_success($str_msg) { return self::$_boo_colorize ? self::$GREEN . $str_msg . self::$BOLD_OFF : $str_msg; } public static function make_fail($str_msg) { return self::$_boo_colorize ? self::$RED . $str_msg . self::$BOLD_OFF : $str_msg; } public static function make_warning($str_msg) { return self::$_boo_colorize ? self::$YELLOW . $str_msg . self::$BOLD_OFF : $str_msg; } protected function assertFalse($bool) { return $this->assertTrue(!$bool); } protected function assertTrue($bool) { if($bool) return true; $bt = debug_backtrace(false); self::$errors []= sprintf("Assertion failed: %s:%d (%s)\n", $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]); return false; } protected function assertLess($a, $b) { if($a < $b) return; $bt = debug_backtrace(false); self::$errors[] = sprintf("Assertion failed (%s >= %s): %s: %d (%s\n", print_r($a, true), print_r($b, true), $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]); } protected function assertEquals($a, $b) { if($a === $b) return; $bt = debug_backtrace(false); self::$errors []= sprintf("Assertion failed (%s !== %s): %s:%d (%s)\n", print_r($a, true), print_r($b, true), $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]); } protected function markTestSkipped($msg='') { $bt = debug_backtrace(false); self::$warnings []= sprintf("Skipped test: %s:%d (%s) %s\n", $bt[0]["file"], $bt[0]["line"], $bt[1]["function"], $msg); throw new Exception($msg); } private static function getMaxTestLen($arr_methods, $str_limit) { $i_result = 0; $str_limit = strtolower($str_limit); foreach ($arr_methods as $obj_method) { $str_name = strtolower($obj_method->name); if (substr($str_name, 0, 4) != 'test') continue; if ($str_limit && !strstr($str_name, $str_limit)) continue; if (strlen($str_name) > $i_result) { $i_result = strlen($str_name); } } return $i_result; } /* Flag colorization */ public static function flagColorization($boo_override) { self::$_boo_colorize = $boo_override && function_exists('posix_isatty') && posix_isatty(STDOUT); } public static function run($className, $str_limit = NULL, $str_host = NULL) { /* Lowercase our limit arg if we're passed one */ $str_limit = $str_limit ? strtolower($str_limit) : $str_limit; $rc = new ReflectionClass($className); $methods = $rc->GetMethods(ReflectionMethod::IS_PUBLIC); $i_max_len = self::getMaxTestLen($methods, $str_limit); foreach($methods as $m) { $name = $m->name; if(substr($name, 0, 4) !== 'test') continue; /* If we're trying to limit to a specific test and can't match the * substring, skip */ if ($str_limit && strstr(strtolower($name), $str_limit)===FALSE) { continue; } $str_out_name = str_pad($name, $i_max_len + 1); echo self::make_bold($str_out_name); $count = count($className::$errors); $rt = new $className($str_host); try { $rt->setUp(); $rt->$name(); if ($count === count($className::$errors)) { $str_msg = self::make_success('PASSED'); } else { $str_msg = self::make_fail('FAILED'); } //echo ($count === count($className::$errors)) ? "." : "F"; } catch (Exception $e) { if ($e instanceof RedisException) { $className::$errors[] = "Uncaught exception '".$e->getMessage()."' ($name)\n"; $str_msg = self::make_fail('FAILED'); } else { $str_msg = self::make_warning('SKIPPED'); } } echo "[" . $str_msg . "]\n"; } echo "\n"; echo implode('', $className::$warnings) . "\n"; if(empty($className::$errors)) { echo "All tests passed. \o/\n"; return 0; } echo implode('', $className::$errors); return 1; } } ?> redis-3.1.6/tests/make-cluster.sh0000755000175000000120000000633513223116600017504 0ustar pyatsukhnenkowheel#!/bin/bash # make-cluster.sh # This is a simple script used to automatically spin up a Redis cluster instance # simplifying the process of running unit tests. # # Usage: # ./make-cluster.sh start [host] # ./make-cluster.sh stop [host] # BASEDIR=`pwd` NODEDIR=$BASEDIR/nodes MAPFILE=$NODEDIR/nodemap # Host, nodes, replicas, ports, etc. Change if you want different values HOST="127.0.0.1" NODES=12 REPLICAS=3 START_PORT=7000 END_PORT=`expr $START_PORT + $NODES` # Helper to determine if we have an executable checkExe() { if ! hash $1 > /dev/null 2>&1; then echo "Error: Must have $1 on the path!" exit 1 fi } # Run a command and output what we're running verboseRun() { echo "Running: $@" $@ } # Spawn a specific redis instance, cluster enabled spawnNode() { # Attempt to spawn the node verboseRun redis-server --cluster-enabled yes --dir $NODEDIR --port $PORT \ --cluster-config-file node-$PORT.conf --daemonize yes --save \'\' \ --bind $HOST --dbfilename node-$PORT.rdb # Abort if we can't spin this instance if [ $? -ne 0 ]; then echo "Error: Can't spawn node at port $PORT." exit 1 fi } # Spawn nodes from start to end port spawnNodes() { for PORT in `seq $START_PORT $END_PORT`; do # Attempt to spawn the node spawnNode $PORT # Add this host:port to our nodemap so the tests can get seeds echo "$HOST:$PORT" >> $MAPFILE done } # Check to see if any nodes are running checkNodes() { echo -n "Checking port availability " for PORT in `seq $START_PORT $END_PORT`; do redis-cli -p $PORT ping > /dev/null 2>&1 if [ $? -eq 0 ]; then echo "FAIL" echo "Error: There appears to be an instance running at port $PORT" exit 1 fi done echo "OK" } # Create our 'node' directory if it doesn't exist and clean out any previous # configuration files from a previous run. cleanConfigInfo() { verboseRun mkdir -p $NODEDIR verboseRun rm -f $NODEDIR/* } # Initialize our cluster with redis-trib.rb initCluster() { TRIBARGS="" for PORT in `seq $START_PORT $END_PORT`; do TRIBARGS="$TRIBARGS $HOST:$PORT" done verboseRun redis-trib.rb create --replicas $REPLICAS $TRIBARGS if [ $? -ne 0 ]; then echo "Error: Couldn't create cluster!" exit 1 fi } # Attempt to spin up our cluster startCluster() { # Make sure none of these nodes are already running checkNodes # Clean out node configuration, etc cleanConfigInfo # Attempt to spawn the nodes spawnNodes # Attempt to initialize the cluster initCluster } # Shut down nodes in our cluster stopCluster() { for PORT in `seq $START_PORT $END_PORT`; do verboseRun redis-cli -p $PORT SHUTDOWN NOSAVE > /dev/null 2>&1 done } # Make sure we have redis-server and redis-trib.rb on the path checkExe redis-server checkExe redis-trib.rb # Override the host if we've got $2 if [[ ! -z "$2" ]]; then HOST=$2 fi # Main entry point to start or stop/kill a cluster case "$1" in start) startCluster ;; stop) stopCluster ;; *) echo "Usage $0 [host]" ;; esac redis-3.1.6/tests/mkring.sh0000755000175000000120000000133313223116600016370 0ustar pyatsukhnenkowheel#!/bin/bash PORTS="6379 6380 6381 6382" REDIS=redis-server function start_node() { P=$1 echo "starting node on port $P"; CONFIG_FILE=`tempfile` cat > $CONFIG_FILE << CONFIG port $P CONFIG $REDIS $CONFIG_FILE > /dev/null 2>/dev/null & sleep 1 rm -f $CONFIG_FILE } function stop_node() { P=$1 PID=$2 redis-cli -h localhost -p $P shutdown kill -9 $PID 2>/dev/null } function stop() { for P in $PORTS; do PID=`lsof -i :$P | tail -1 | cut -f 2 -d " "` if [ "$PID" != "" ]; then stop_node $P $PID fi done } function start() { for P in $PORTS; do start_node $P done } case "$1" in start) start ;; stop) stop ;; restart) stop start ;; *) echo "Usage: $0 [start|stop|restart]" ;; esac redis-3.1.6/README.markdown0000644000175000000120000027730013223116600016112 0ustar pyatsukhnenkowheel# PhpRedis [![Build Status](https://travis-ci.org/phpredis/phpredis.svg?branch=develop)](https://travis-ci.org/phpredis/phpredis) [![Coverity Scan Build Status](https://scan.coverity.com/projects/13205/badge.svg)](https://scan.coverity.com/projects/phpredis-phpredis) The phpredis extension provides an API for communicating with the [Redis](http://redis.io/) key-value store. It is released under the [PHP License, version 3.01](http://www.php.net/license/3_01.txt). This code has been developed and maintained by Owlient from November 2009 to March 2011. You can send comments, patches, questions [here on github](https://github.com/phpredis/phpredis/issues), to n.favrefelix@gmail.com ([@yowgi](https://twitter.com/yowgi)), to michael.grunder@gmail.com ([@grumi78](https://twitter.com/grumi78)) or to p.yatsukhnenko@gmail.com ([@yatsukhnenko](https://twitter.com/yatsukhnenko)). # Table of contents ----- 1. [Installing/Configuring](#installingconfiguring) * [Installation](#installation) * [Installation on OSX](#installation-on-osx) * [Building on Windows](#building-on-windows) * [PHP Session handler](#php-session-handler) * [Distributed Redis Array](#distributed-redis-array) 1. [Classes and methods](#classes-and-methods) * [Usage](#usage) * [Connection](#connection) * [Server](#server) * [Keys and strings](#keys-and-strings) * [Hashes](#hashes) * [Lists](#lists) * [Sets](#sets) * [Sorted sets](#sorted-sets) * [Geocoding](#geocoding) * [Pub/sub](#pubsub) * [Transactions](#transactions) * [Scripting](#scripting) * [Introspection](#introspection) ----- # Installing/Configuring ----- Everything you should need to install PhpRedis on your system. ## Installation ~~~ phpize ./configure [--enable-redis-igbinary] make && make install ~~~ If you would like phpredis to serialize your data using the igbinary library, run configure with `--enable-redis-igbinary`. `make install` copies `redis.so` to an appropriate location, but you still need to enable the module in the PHP config file. To do so, either edit your php.ini or add a redis.ini file in `/etc/php5/conf.d` with the following contents: `extension=redis.so`. You can generate a debian package for PHP5, accessible from Apache 2 by running `./mkdeb-apache2.sh` or with `dpkg-buildpackage` or `svn-buildpackage`. This extension exports a single class, [Redis](#class-redis) (and [RedisException](#class-redisexception) used in case of errors). Check out https://github.com/ukko/phpredis-phpdoc for a PHP stub that you can use in your IDE for code completion. ## Installation on OSX If the install fails on OSX, type the following commands in your shell before trying again: ~~~ MACOSX_DEPLOYMENT_TARGET=10.6 CFLAGS="-arch i386 -arch x86_64 -g -Os -pipe -no-cpp-precomp" CCFLAGS="-arch i386 -arch x86_64 -g -Os -pipe" CXXFLAGS="-arch i386 -arch x86_64 -g -Os -pipe" LDFLAGS="-arch i386 -arch x86_64 -bind_at_load" export CFLAGS CXXFLAGS LDFLAGS CCFLAGS MACOSX_DEPLOYMENT_TARGET ~~~ If that still fails and you are running Zend Server CE, try this right before "make": `./configure CFLAGS="-arch i386"`. Taken from [Compiling phpredis on Zend Server CE/OSX ](http://www.tumblr.com/tagged/phpredis). See also: [Install Redis & PHP Extension PHPRedis with Macports](http://www.lecloud.net/post/3378834922/install-redis-php-extension-phpredis-with-macports). You can install it using Homebrew: - [Get homebrew-php](https://github.com/Homebrew/homebrew-php) - `brew install php55-redis` (or php53-redis, php54-redis) ## PHP Session handler phpredis can be used to store PHP sessions. To do this, configure `session.save_handler` and `session.save_path` in your php.ini to tell phpredis where to store the sessions: ~~~ session.save_handler = redis session.save_path = "tcp://host1:6379?weight=1, tcp://host2:6379?weight=2&timeout=2.5, tcp://host3:6379?weight=2&read_timeout=2.5" ~~~ `session.save_path` can have a simple `host:port` format too, but you need to provide the `tcp://` scheme if you want to use the parameters. The following parameters are available: * weight (integer): the weight of a host is used in comparison with the others in order to customize the session distribution on several hosts. If host A has twice the weight of host B, it will get twice the amount of sessions. In the example, *host1* stores 20% of all the sessions (1/(1+2+2)) while *host2* and *host3* each store 40% (2/1+2+2). The target host is determined once and for all at the start of the session, and doesn't change. The default weight is 1. * timeout (float): the connection timeout to a redis host, expressed in seconds. If the host is unreachable in that amount of time, the session storage will be unavailable for the client. The default timeout is very high (86400 seconds). * persistent (integer, should be 1 or 0): defines if a persistent connection should be used. **(experimental setting)** * prefix (string, defaults to "PHPREDIS_SESSION:"): used as a prefix to the Redis key in which the session is stored. The key is composed of the prefix followed by the session ID. * auth (string, empty by default): used to authenticate with the server prior to sending commands. * database (integer): selects a different database. Sessions have a lifetime expressed in seconds and stored in the INI variable "session.gc_maxlifetime". You can change it with [`ini_set()`](http://php.net/ini_set). The session handler requires a version of Redis with the `SETEX` command (at least 2.0). phpredis can also connect to a unix domain socket: `session.save_path = "unix:///var/run/redis/redis.sock?persistent=1&weight=1&database=0`. ## Building on Windows See [instructions from @char101](https://github.com/phpredis/phpredis/issues/213#issuecomment-11361242) on how to build phpredis on Windows. ## Distributed Redis Array See [dedicated page](https://github.com/phpredis/phpredis/blob/master/arrays.markdown#readme). ## Redis Cluster support See [dedicated page](https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#readme). ## Running the unit tests phpredis uses a small custom unit test suite for testing functionality of the various classes. To run tests, simply do the following: ~~~ # Run tests for Redis class (note this is the default) php tests/TestRedis.php --class Redis # Run tests for RedisArray class tests/mkring.sh start php tests/TestRedis.php --class RedisArray tests/mkring.sh stop # Run tests for the RedisCluster class tests/make-cluster.sh start php tests/TestRedis.php --class RedisCluster tests/make-cluster.sh stop ~~~ Note that it is possible to run only tests which match a substring of the test itself by passing the additional argument '--test ' when invoking. ~~~ # Just run the 'echo' test php tests/TestRedis.php --class Redis --test echo ~~~ # Classes and methods ----- ## Usage 1. [Class Redis](#class-redis) 1. [Class RedisException](#class-redisexception) 1. [Predefined constants](#predefined-constants) ### Class Redis ----- _**Description**_: Creates a Redis client ##### *Example* ~~~ $redis = new Redis(); ~~~ ### Class RedisException ----- phpredis throws a [RedisException](#class-redisexception) object if it can't reach the Redis server. That can happen in case of connectivity issues, if the Redis service is down, or if the redis host is overloaded. In any other problematic case that does not involve an unreachable server (such as a key not existing, an invalid command, etc), phpredis will return `FALSE`. ### Predefined constants ----- _**Description**_: Available Redis Constants Redis data types, as returned by [type](#type) ~~~ Redis::REDIS_STRING - String Redis::REDIS_SET - Set Redis::REDIS_LIST - List Redis::REDIS_ZSET - Sorted set Redis::REDIS_HASH - Hash Redis::REDIS_NOT_FOUND - Not found / other ~~~ @TODO: OPT_SERIALIZER, AFTER, BEFORE,... ## Connection 1. [connect, open](#connect-open) - Connect to a server 1. [pconnect, popen](#pconnect-popen) - Connect to a server (persistent) 1. [auth](#auth) - Authenticate to the server 1. [select](#select) - Change the selected database for the current connection 1. [close](#close) - Close the connection 1. [setOption](#setoption) - Set client option 1. [getOption](#getoption) - Get client option 1. [ping](#ping) - Ping the server 1. [echo](#echo) - Echo the given string ### connect, open ----- _**Description**_: Connects to a Redis instance. ##### *Parameters* *host*: string. can be a host, or the path to a unix domain socket *port*: int, optional *timeout*: float, value in seconds (optional, default is 0 meaning unlimited) *reserved*: should be NULL if retry_interval is specified *retry_interval*: int, value in milliseconds (optional) *read_timeout*: float, value in seconds (optional, default is 0 meaning unlimited) ##### *Return value* *BOOL*: `TRUE` on success, `FALSE` on error. ##### *Example* ~~~ $redis->connect('127.0.0.1', 6379); $redis->connect('127.0.0.1'); // port 6379 by default $redis->connect('127.0.0.1', 6379, 2.5); // 2.5 sec timeout. $redis->connect('/tmp/redis.sock'); // unix domain socket. $redis->connect('127.0.0.1', 6379, 1, NULL, 100); // 1 sec timeout, 100ms delay between reconnection attempts. ~~~ ### pconnect, popen ----- _**Description**_: Connects to a Redis instance or reuse a connection already established with `pconnect`/`popen`. The connection will not be closed on `close` or end of request until the php process ends. So be patient on too many open FD's (specially on redis server side) when using persistent connections on many servers connecting to one redis server. Also more than one persistent connection can be made identified by either host + port + timeout or host + persistent_id or unix socket + timeout. This feature is not available in threaded versions. `pconnect` and `popen` then working like their non persistent equivalents. ##### *Parameters* *host*: string. can be a host, or the path to a unix domain socket *port*: int, optional *timeout*: float, value in seconds (optional, default is 0 meaning unlimited) *persistent_id*: string. identity for the requested persistent connection *retry_interval*: int, value in milliseconds (optional) *read_timeout*: float, value in seconds (optional, default is 0 meaning unlimited) ##### *Return value* *BOOL*: `TRUE` on success, `FALSE` on error. ##### *Example* ~~~ $redis->pconnect('127.0.0.1', 6379); $redis->pconnect('127.0.0.1'); // port 6379 by default - same connection like before. $redis->pconnect('127.0.0.1', 6379, 2.5); // 2.5 sec timeout and would be another connection than the two before. $redis->pconnect('127.0.0.1', 6379, 2.5, 'x'); // x is sent as persistent_id and would be another connection than the three before. $redis->pconnect('/tmp/redis.sock'); // unix domain socket - would be another connection than the four before. ~~~ ### auth ----- _**Description**_: Authenticate the connection using a password. *Warning*: The password is sent in plain-text over the network. ##### *Parameters* *STRING*: password ##### *Return value* *BOOL*: `TRUE` if the connection is authenticated, `FALSE` otherwise. ##### *Example* ~~~ $redis->auth('foobared'); ~~~ ### select ----- _**Description**_: Change the selected database for the current connection. ##### *Parameters* *INTEGER*: dbindex, the database number to switch to. ##### *Return value* `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* See method for example: [move](#move) ### close ----- _**Description**_: Disconnects from the Redis instance, except when `pconnect` is used. ### setOption ----- _**Description**_: Set client option. ##### *Parameters* *parameter name* *parameter value* ##### *Return value* *BOOL*: `TRUE` on success, `FALSE` on error. ##### *Example* ~~~ $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); // don't serialize data $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); // use built-in serialize/unserialize $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY); // use igBinary serialize/unserialize $redis->setOption(Redis::OPT_PREFIX, 'myAppName:'); // use custom prefix on all keys /* Options for the SCAN family of commands, indicating whether to abstract empty results from the user. If set to SCAN_NORETRY (the default), phpredis will just issue one SCAN command at a time, sometimes returning an empty array of results. If set to SCAN_RETRY, phpredis will retry the scan command until keys come back OR Redis returns an iterator of zero */ $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_NORETRY); $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); ~~~ ### getOption ----- _**Description**_: Get client option. ##### *Parameters* *parameter name* ##### *Return value* Parameter value. ##### *Example* ~~~ $redis->getOption(Redis::OPT_SERIALIZER); // return Redis::SERIALIZER_NONE, Redis::SERIALIZER_PHP, or Redis::SERIALIZER_IGBINARY. ~~~ ### ping ----- _**Description**_: Check the current connection status ##### *Parameters* (none) ##### *Return value* *STRING*: `+PONG` on success. Throws a [RedisException](#class-redisexception) object on connectivity error, as described above. ### echo ----- _**Description**_: Sends a string to Redis, which replies with the same string ##### *Parameters* *STRING*: The message to send. ##### *Return value* *STRING*: the same message. ## Server 1. [bgRewriteAOF](#bgrewriteaof) - Asynchronously rewrite the append-only file 1. [bgSave](#bgsave) - Asynchronously save the dataset to disk (in background) 1. [config](#config) - Get or Set the Redis server configuration parameters 1. [dbSize](#dbsize) - Return the number of keys in selected database 1. [flushAll](#flushall) - Remove all keys from all databases 1. [flushDb](#flushdb) - Remove all keys from the current database 1. [info](#info) - Get information and statistics about the server 1. [lastSave](#lastsave) - Get the timestamp of the last disk save 1. [resetStat](#resetstat) - Reset the stats returned by [info](#info) method. 1. [save](#save) - Synchronously save the dataset to disk (wait to complete) 1. [slaveOf](#slaveof) - Make the server a slave of another instance, or promote it to master 1. [time](#time) - Return the current server time 1. [slowLog](#slowlog) - Access the Redis slowLog entries ### bgRewriteAOF ----- _**Description**_: Start the background rewrite of AOF (Append-Only File) ##### *Parameters* None. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->bgRewriteAOF(); ~~~ ### bgSave ----- _**Description**_: Asynchronously save the dataset to disk (in background) ##### *Parameters* None. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. If a save is already running, this command will fail and return `FALSE`. ##### *Example* ~~~ $redis->bgSave(); ~~~ ### config ----- _**Description**_: Get or Set the Redis server configuration parameters. ##### *Parameters* *operation* (string) either `GET` or `SET` *key* string for `SET`, glob-pattern for `GET`. See http://redis.io/commands/config-get for examples. *value* optional string (only for `SET`) ##### *Return value* *Associative array* for `GET`, key -> value *bool* for `SET` ##### *Examples* ~~~ $redis->config("GET", "*max-*-entries*"); $redis->config("SET", "dir", "/var/run/redis/dumps/"); ~~~ ### dbSize ----- _**Description**_: Return the number of keys in selected database. ##### *Parameters* None. ##### *Return value* *INTEGER*: DB size, in number of keys. ##### *Example* ~~~ $count = $redis->dbSize(); echo "Redis has $count keys\n"; ~~~ ### flushAll ----- _**Description**_: Remove all keys from all databases. ##### *Parameters* None. ##### *Return value* *BOOL*: Always `TRUE`. ##### *Example* ~~~ $redis->flushAll(); ~~~ ### flushDb ----- _**Description**_: Remove all keys from the current database. ##### *Parameters* None. ##### *Return value* *BOOL*: Always `TRUE`. ##### *Example* ~~~ $redis->flushDb(); ~~~ ### info ----- _**Description**_: Get information and statistics about the server Returns an associative array that provides information about the server. Passing no arguments to INFO will call the standard REDIS INFO command, which returns information such as the following: * redis_version * arch_bits * uptime_in_seconds * uptime_in_days * connected_clients * connected_slaves * used_memory * changes_since_last_save * bgsave_in_progress * last_save_time * total_connections_received * total_commands_processed * role You can pass a variety of options to INFO ([per the Redis documentation](http://redis.io/commands/info)), which will modify what is returned. ##### *Parameters* *option*: The option to provide redis (e.g. "COMMANDSTATS", "CPU") ##### *Example* ~~~ $redis->info(); /* standard redis INFO command */ $redis->info("COMMANDSTATS"); /* Information on the commands that have been run (>=2.6 only) $redis->info("CPU"); /* just CPU information from Redis INFO */ ~~~ ### lastSave ----- _**Description**_: Returns the timestamp of the last disk save. ##### *Parameters* None. ##### *Return value* *INT*: timestamp. ##### *Example* ~~~ $redis->lastSave(); ~~~ ### resetStat ----- _**Description**_: Reset the stats returned by [info](#info) method. These are the counters that are reset: * Keyspace hits * Keyspace misses * Number of commands processed * Number of connections received * Number of expired keys ##### *Parameters* None. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->resetStat(); ~~~ ### save ----- _**Description**_: Synchronously save the dataset to disk (wait to complete) ##### *Parameters* None. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. If a save is already running, this command will fail and return `FALSE`. ##### *Example* ~~~ $redis->save(); ~~~ ### slaveOf ----- _**Description**_: Changes the slave status ##### *Parameters* Either host (string) and port (int), or no parameter to stop being a slave. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->slaveOf('10.0.1.7', 6379); /* ... */ $redis->slaveOf(); ~~~ ### time ----- _**Description**_: Return the current server time. ##### *Parameters* (none) ##### *Return value* If successfull, the time will come back as an associative array with element zero being the unix timestamp, and element one being microseconds. ##### *Examples* ~~~ $redis->time(); ~~~ ### slowLog ----- _**Description**_: Access the Redis slowLog ##### *Parameters* *Operation* (string): This can be either `GET`, `LEN`, or `RESET` *Length* (integer), optional: If executing a `SLOWLOG GET` command, you can pass an optional length. ##### ##### *Return value* The return value of SLOWLOG will depend on which operation was performed. SLOWLOG GET: Array of slowLog entries, as provided by Redis SLOGLOG LEN: Integer, the length of the slowLog SLOWLOG RESET: Boolean, depending on success ##### ##### *Examples* ~~~ // Get ten slowLog entries $redis->slowLog('get', 10); // Get the default number of slowLog entries $redis->slowLog('get'); // Reset our slowLog $redis->slowLog('reset'); // Retrieve slowLog length $redis->slowLog('len'); ~~~ ## Keys and Strings ### Strings ----- * [append](#append) - Append a value to a key * [bitCount](#bitcount) - Count set bits in a string * [bitOp](#bitop) - Perform bitwise operations between strings * [decr, decrBy](#decr-decrby) - Decrement the value of a key * [get](#get) - Get the value of a key * [getBit](#getbit) - Returns the bit value at offset in the string value stored at key * [getRange](#getrange) - Get a substring of the string stored at a key * [getSet](#getset) - Set the string value of a key and return its old value * [incr, incrBy](#incr-incrby) - Increment the value of a key * [incrByFloat](#incrbyfloat) - Increment the float value of a key by the given amount * [mGet, getMultiple](#mget-getmultiple) - Get the values of all the given keys * [mSet, mSetNX](#mset-msetnx) - Set multiple keys to multiple values * [set](#set) - Set the string value of a key * [setBit](#setbit) - Sets or clears the bit at offset in the string value stored at key * [setEx, pSetEx](#setex-psetex) - Set the value and expiration of a key * [setNx](#setnx) - Set the value of a key, only if the key does not exist * [setRange](#setrange) - Overwrite part of a string at key starting at the specified offset * [strLen](#strlen) - Get the length of the value stored in a key ### Keys ----- * [del, delete](#del-delete) - Delete a key * [dump](#dump) - Return a serialized version of the value stored at the specified key. * [exists](#exists) - Determine if a key exists * [expire, setTimeout, pexpire](#expire-settimeout-pexpire) - Set a key's time to live in seconds * [expireAt, pexpireAt](#expireat-pexpireat) - Set the expiration for a key as a UNIX timestamp * [keys, getKeys](#keys-getkeys) - Find all keys matching the given pattern * [scan](#scan) - Scan for keys in the keyspace (Redis >= 2.8.0) * [migrate](#migrate) - Atomically transfer a key from a Redis instance to another one * [move](#move) - Move a key to another database * [object](#object) - Inspect the internals of Redis objects * [persist](#persist) - Remove the expiration from a key * [randomKey](#randomkey) - Return a random key from the keyspace * [rename, renameKey](#rename-renamekey) - Rename a key * [renameNx](#renamenx) - Rename a key, only if the new key does not exist * [type](#type) - Determine the type stored at key * [sort](#sort) - Sort the elements in a list, set or sorted set * [ttl, pttl](#ttl-pttl) - Get the time to live for a key * [restore](#restore) - Create a key using the provided serialized value, previously obtained with [dump](#dump). ----- ### get ----- _**Description**_: Get the value related to the specified key ##### *Parameters* *key* ##### *Return value* *String* or *Bool*: If key didn't exist, `FALSE` is returned. Otherwise, the value related to this key is returned. ##### *Examples* ~~~ $redis->get('key'); ~~~ ### set ----- _**Description**_: Set the string value in argument as value of the key. If you're using Redis >= 2.6.12, you can pass extended options as explained below ##### *Parameters* *Key* *Value* *Timeout or Options Array* (optional). If you pass an integer, phpredis will redirect to SETEX, and will try to use Redis >= 2.6.12 extended options if you pass an array with valid values ##### *Return value* *Bool* `TRUE` if the command is successful. ##### *Examples* ~~~ // Simple key -> value set $redis->set('key', 'value'); // Will redirect, and actually make an SETEX call $redis->set('key','value', 10); // Will set the key, if it doesn't exist, with a ttl of 10 seconds $redis->set('key', 'value', Array('nx', 'ex'=>10)); // Will set a key, if it does exist, with a ttl of 1000 miliseconds $redis->set('key', 'value', Array('xx', 'px'=>1000)); ~~~ ### setEx, pSetEx ----- _**Description**_: Set the string value in argument as value of the key, with a time to live. PSETEX uses a TTL in milliseconds. ##### *Parameters* *Key* *TTL* *Value* ##### *Return value* *Bool* `TRUE` if the command is successful. ##### *Examples* ~~~ $redis->setEx('key', 3600, 'value'); // sets key → value, with 1h TTL. $redis->pSetEx('key', 100, 'value'); // sets key → value, with 0.1 sec TTL. ~~~ ### setNx ----- _**Description**_: Set the string value in argument as value of the key if the key doesn't already exist in the database. ##### *Parameters* *key* *value* ##### *Return value* *Bool* `TRUE` in case of success, `FALSE` in case of failure. ##### *Examples* ~~~ $redis->setNx('key', 'value'); /* return TRUE */ $redis->setNx('key', 'value'); /* return FALSE */ ~~~ ### del, delete ----- _**Description**_: Remove specified keys. ##### *Parameters* An array of keys, or an undefined number of parameters, each a key: *key1* *key2* *key3* ... *keyN* ##### *Return value* *Long* Number of keys deleted. ##### *Examples* ~~~ $redis->set('key1', 'val1'); $redis->set('key2', 'val2'); $redis->set('key3', 'val3'); $redis->set('key4', 'val4'); $redis->delete('key1', 'key2'); /* return 2 */ $redis->delete(array('key3', 'key4')); /* return 2 */ ~~~ ### exists ----- _**Description**_: Verify if the specified key exists. ##### *Parameters* *key* ##### *Return value* *BOOL*: If the key exists, return `TRUE`, otherwise return `FALSE`. ##### *Examples* ~~~ $redis->set('key', 'value'); $redis->exists('key'); /* TRUE */ $redis->exists('NonExistingKey'); /* FALSE */ ~~~ ### incr, incrBy ----- _**Description**_: Increment the number stored at key by one. If the second argument is filled, it will be used as the integer value of the increment. ##### *Parameters* *key* *value*: value that will be added to key (only for incrBy) ##### *Return value* *INT* the new value ##### *Examples* ~~~ $redis->incr('key1'); /* key1 didn't exists, set to 0 before the increment */ /* and now has the value 1 */ $redis->incr('key1'); /* 2 */ $redis->incr('key1'); /* 3 */ $redis->incr('key1'); /* 4 */ $redis->incrBy('key1', 10); /* 14 */ ~~~ ### incrByFloat ----- _**Description**_: Increment the key with floating point precision. ##### *Parameters* *key* *value*: (float) value that will be added to the key ##### *Return value* *FLOAT* the new value ##### *Examples* ~~~ $redis->incrByFloat('key1', 1.5); /* key1 didn't exist, so it will now be 1.5 */ $redis->incrByFloat('key1', 1.5); /* 3 */ $redis->incrByFloat('key1', -1.5); /* 1.5 */ $redis->incrByFloat('key1', 2.5); /* 4 */ ~~~ ### decr, decrBy ----- _**Description**_: Decrement the number stored at key by one. If the second argument is filled, it will be used as the integer value of the decrement. ##### *Parameters* *key* *value*: value that will be substracted to key (only for decrBy) ##### *Return value* *INT* the new value ##### *Examples* ~~~ $redis->decr('key1'); /* key1 didn't exists, set to 0 before the increment */ /* and now has the value -1 */ $redis->decr('key1'); /* -2 */ $redis->decr('key1'); /* -3 */ $redis->decrBy('key1', 10); /* -13 */ ~~~ ### mGet, getMultiple ----- _**Description**_: Get the values of all the specified keys. If one or more keys dont exist, the array will contain `FALSE` at the position of the key. ##### *Parameters* *Array*: Array containing the list of the keys ##### *Return value* *Array*: Array containing the values related to keys in argument ##### *Examples* ~~~ $redis->set('key1', 'value1'); $redis->set('key2', 'value2'); $redis->set('key3', 'value3'); $redis->mGet(array('key1', 'key2', 'key3')); /* array('value1', 'value2', 'value3'); $redis->mGet(array('key0', 'key1', 'key5')); /* array(`FALSE`, 'value1', `FALSE`); ~~~ ### getSet ----- _**Description**_: Sets a value and returns the previous entry at that key. ##### *Parameters* *Key*: key *STRING*: value ##### *Return value* A string, the previous value located at this key. ##### *Example* ~~~ $redis->set('x', '42'); $exValue = $redis->getSet('x', 'lol'); // return '42', replaces x by 'lol' $newValue = $redis->get('x')' // return 'lol' ~~~ ### randomKey ----- _**Description**_: Returns a random key. ##### *Parameters* None. ##### *Return value* *STRING*: an existing key in redis. ##### *Example* ~~~ $key = $redis->randomKey(); $surprise = $redis->get($key); // who knows what's in there. ~~~ ### move ----- _**Description**_: Moves a key to a different database. ##### *Parameters* *Key*: key, the key to move. *INTEGER*: dbindex, the database number to move the key to. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->select(0); // switch to DB 0 $redis->set('x', '42'); // write 42 to x $redis->move('x', 1); // move to DB 1 $redis->select(1); // switch to DB 1 $redis->get('x'); // will return 42 ~~~ ### rename, renameKey ----- _**Description**_: Renames a key. ##### *Parameters* *STRING*: srckey, the key to rename. *STRING*: dstkey, the new name for the key. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->set('x', '42'); $redis->rename('x', 'y'); $redis->get('y'); // → 42 $redis->get('x'); // → `FALSE` ~~~ ### renameNx ----- _**Description**_: Same as rename, but will not replace a key if the destination already exists. This is the same behaviour as setNx. ### expire, setTimeout, pexpire ----- _**Description**_: Sets an expiration date (a timeout) on an item. pexpire requires a TTL in milliseconds. ##### *Parameters* *Key*: key. The key that will disappear. *Integer*: ttl. The key's remaining Time To Live, in seconds. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->set('x', '42'); $redis->setTimeout('x', 3); // x will disappear in 3 seconds. sleep(5); // wait 5 seconds $redis->get('x'); // will return `FALSE`, as 'x' has expired. ~~~ ### expireAt, pexpireAt ----- _**Description**_: Sets an expiration date (a timestamp) on an item. pexpireAt requires a timestamp in milliseconds. ##### *Parameters* *Key*: key. The key that will disappear. *Integer*: Unix timestamp. The key's date of death, in seconds from Epoch time. ##### *Return value* *BOOL*: `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->set('x', '42'); $now = time(NULL); // current timestamp $redis->expireAt('x', $now + 3); // x will disappear in 3 seconds. sleep(5); // wait 5 seconds $redis->get('x'); // will return `FALSE`, as 'x' has expired. ~~~ ### keys, getKeys ----- _**Description**_: Returns the keys that match a certain pattern. ##### *Parameters* *STRING*: pattern, using '*' as a wildcard. ##### *Return value* *Array of STRING*: The keys that match a certain pattern. ##### *Example* ~~~ $allKeys = $redis->keys('*'); // all keys will match this. $keyWithUserPrefix = $redis->keys('user*'); ~~~ ### scan ----- _**Description**_: Scan the keyspace for keys ##### *Parameters* *LONG (reference)*: Iterator, initialized to NULL *STRING, Optional*: Pattern to match *LONG, Optional*: Count of keys per iteration (only a suggestion to Redis) ##### *Return value* *Array, boolean*: This function will return an array of keys or FALSE if Redis returned zero keys ##### *Example* ~~~ /* Without enabling Redis::SCAN_RETRY (default condition) */ $it = NULL; do { // Scan for some keys $arr_keys = $redis->scan($it); // Redis may return empty results, so protect against that if ($arr_keys !== FALSE) { foreach($arr_keys as $str_key) { echo "Here is a key: $str_key\n"; } } } while ($it > 0); echo "No more keys to scan!\n"; /* With Redis::SCAN_RETRY enabled */ $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); $it = NULL; /* phpredis will retry the SCAN command if empty results are returned from the server, so no empty results check is required. */ while ($arr_keys = $redis->scan($it)) { foreach ($arr_keys as $str_key) { echo "Here is a key: $str_key\n"; } } echo "No more keys to scan!\n"; ~~~ ### object ----- _**Description**_: Describes the object pointed to by a key. ##### *Parameters* The information to retrieve (string) and the key (string). Info can be one of the following: * "encoding" * "refcount" * "idletime" ##### *Return value* *STRING* for "encoding", *LONG* for "refcount" and "idletime", `FALSE` if the key doesn't exist. ##### *Example* ~~~ $redis->object("encoding", "l"); // → ziplist $redis->object("refcount", "l"); // → 1 $redis->object("idletime", "l"); // → 400 (in seconds, with a precision of 10 seconds). ~~~ ### type ----- _**Description**_: Returns the type of data pointed by a given key. ##### *Parameters* *Key*: key ##### *Return value* Depending on the type of the data pointed by the key, this method will return the following value: string: Redis::REDIS_STRING set: Redis::REDIS_SET list: Redis::REDIS_LIST zset: Redis::REDIS_ZSET hash: Redis::REDIS_HASH other: Redis::REDIS_NOT_FOUND ##### *Example* ~~~ $redis->type('key'); ~~~ ### append ----- _**Description**_: Append specified string to the string stored in specified key. ##### *Parameters* *Key* *Value* ##### *Return value* *INTEGER*: Size of the value after the append ##### *Example* ~~~ $redis->set('key', 'value1'); $redis->append('key', 'value2'); /* 12 */ $redis->get('key'); /* 'value1value2' */ ~~~ ### getRange ----- _**Description**_: Return a substring of a larger string *Note*: substr also supported but deprecated in redis. ##### *Parameters* *key* *start* *end* ##### *Return value* *STRING*: the substring ##### *Example* ~~~ $redis->set('key', 'string value'); $redis->getRange('key', 0, 5); /* 'string' */ $redis->getRange('key', -5, -1); /* 'value' */ ~~~ ### setRange ----- _**Description**_: Changes a substring of a larger string. ##### *Parameters* *key* *offset* *value* ##### *Return value* *STRING*: the length of the string after it was modified. ##### *Example* ~~~ $redis->set('key', 'Hello world'); $redis->setRange('key', 6, "redis"); /* returns 11 */ $redis->get('key'); /* "Hello redis" */ ~~~ ### strLen ----- _**Description**_: Get the length of a string value. ##### *Parameters* *key* ##### *Return value* *INTEGER* ##### *Example* ~~~ $redis->set('key', 'value'); $redis->strlen('key'); /* 5 */ ~~~ ### getBit ----- _**Description**_: Return a single bit out of a larger string ##### *Parameters* *key* *offset* ##### *Return value* *LONG*: the bit value (0 or 1) ##### *Example* ~~~ $redis->set('key', "\x7f"); // this is 0111 1111 $redis->getBit('key', 0); /* 0 */ $redis->getBit('key', 1); /* 1 */ ~~~ ### setBit ----- _**Description**_: Changes a single bit of a string. ##### *Parameters* *key* *offset* *value*: bool or int (1 or 0) ##### *Return value* *LONG*: 0 or 1, the value of the bit before it was set. ##### *Example* ~~~ $redis->set('key', "*"); // ord("*") = 42 = 0x2f = "0010 1010" $redis->setBit('key', 5, 1); /* returns 0 */ $redis->setBit('key', 7, 1); /* returns 0 */ $redis->get('key'); /* chr(0x2f) = "/" = b("0010 1111") */ ~~~ ### bitOp ----- _**Description**_: Bitwise operation on multiple keys. ##### *Parameters* *operation*: either "AND", "OR", "NOT", "XOR" *ret_key*: return key *key1* *key2...* ##### *Return value* *LONG*: The size of the string stored in the destination key. ### bitCount ----- _**Description**_: Count bits in a string. ##### *Parameters* *key* ##### *Return value* *LONG*: The number of bits set to 1 in the value behind the input key. ### sort ----- _**Description**_: Sort the elements in a list, set or sorted set. ##### *Parameters* *Key*: key *Options*: array(key => value, ...) - optional, with the following keys and values: ~~~ 'by' => 'some_pattern_*', 'limit' => array(0, 1), 'get' => 'some_other_pattern_*' or an array of patterns, 'sort' => 'asc' or 'desc', 'alpha' => TRUE, 'store' => 'external-key' ~~~ ##### *Return value* An array of values, or a number corresponding to the number of elements stored if that was used. ##### *Example* ~~~ $redis->delete('s'); $redis->sAdd('s', 5); $redis->sAdd('s', 4); $redis->sAdd('s', 2); $redis->sAdd('s', 1); $redis->sAdd('s', 3); var_dump($redis->sort('s')); // 1,2,3,4,5 var_dump($redis->sort('s', array('sort' => 'desc'))); // 5,4,3,2,1 var_dump($redis->sort('s', array('sort' => 'desc', 'store' => 'out'))); // (int)5 ~~~ ### ttl, pttl ----- _**Description**_: Returns the time to live left for a given key in seconds (ttl), or milliseconds (pttl). ##### *Parameters* *Key*: key ##### *Return value* *LONG*: The time to live in seconds. If the key has no ttl, `-1` will be returned, and `-2` if the key doesn't exist. ##### *Example* ~~~ $redis->ttl('key'); ~~~ ### persist ----- _**Description**_: Remove the expiration timer from a key. ##### *Parameters* *Key*: key ##### *Return value* *BOOL*: `TRUE` if a timeout was removed, `FALSE` if the key didn’t exist or didn’t have an expiration timer. ##### *Example* ~~~ $redis->persist('key'); ~~~ ### mSet, mSetNx ----- _**Description**_: Sets multiple key-value pairs in one atomic command. MSETNX only returns TRUE if all the keys were set (see SETNX). ##### *Parameters* *Pairs*: array(key => value, ...) ##### *Return value* *Bool* `TRUE` in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->mSet(array('key0' => 'value0', 'key1' => 'value1')); var_dump($redis->get('key0')); var_dump($redis->get('key1')); ~~~ Output: ~~~ string(6) "value0" string(6) "value1" ~~~ ### dump ----- _**Description**_: Dump a key out of a redis database, the value of which can later be passed into redis using the RESTORE command. The data that comes out of DUMP is a binary representation of the key as Redis stores it. ##### *Parameters* *key* string ##### *Return value* The Redis encoded value of the key, or FALSE if the key doesn't exist ##### *Examples* ~~~ $redis->set('foo', 'bar'); $val = $redis->dump('foo'); // $val will be the Redis encoded key value ~~~ ### restore ----- _**Description**_: Restore a key from the result of a DUMP operation. ##### *Parameters* *key* string. The key name *ttl* integer. How long the key should live (if zero, no expire will be set on the key) *value* string (binary). The Redis encoded key value (from DUMP) ##### *Examples* ~~~ $redis->set('foo', 'bar'); $val = $redis->dump('foo'); $redis->restore('bar', 0, $val); // The key 'bar', will now be equal to the key 'foo' ~~~ ### migrate ----- _**Description**_: Migrates a key to a different Redis instance. **Note:**: Redis introduced migrating multiple keys in 3.0.6, so you must have at least that version in order to call `migrate` with an array of keys. ##### *Parameters* *host* string. The destination host *port* integer. The TCP port to connect to. *key(s)* string or array. *destination-db* integer. The target DB. *timeout* integer. The maximum amount of time given to this transfer. *copy* boolean, optional. Should we send the COPY flag to redis. *replace* boolean, optional. Should we send the REPLACE flag to redis ##### *Examples* ~~~ $redis->migrate('backup', 6379, 'foo', 0, 3600); $redis->migrate('backup', 6379, 'foo', 0, 3600, true, true); /* copy and replace */ $redis->migrate('backup', 6379, 'foo', 0, 3600, false, true); /* just REPLACE flag */ /* Migrate multiple keys (requires Redis >= 3.0.6) $redis->migrate('backup', 6379, ['key1', 'key2', 'key3'], 0, 3600); ~~~ ## Hashes * [hDel](#hdel) - Delete one or more hash fields * [hExists](#hexists) - Determine if a hash field exists * [hGet](#hget) - Get the value of a hash field * [hGetAll](#hgetall) - Get all the fields and values in a hash * [hIncrBy](#hincrby) - Increment the integer value of a hash field by the given number * [hIncrByFloat](#hincrbyfloat) - Increment the float value of a hash field by the given amount * [hKeys](#hkeys) - Get all the fields in a hash * [hLen](#hlen) - Get the number of fields in a hash * [hMGet](#hmget) - Get the values of all the given hash fields * [hMSet](#hmset) - Set multiple hash fields to multiple values * [hSet](#hset) - Set the string value of a hash field * [hSetNx](#hsetnx) - Set the value of a hash field, only if the field does not exist * [hVals](#hvals) - Get all the values in a hash * [hScan](#hscan) - Scan a hash key for members * [hStrLen](#hstrlen) - Get the string length of the value associated with field in the hash ### hSet ----- _**Description**_: Adds a value to the hash stored at key. ##### *Parameters* *key* *hashKey* *value* ##### *Return value* *LONG* `1` if value didn't exist and was added successfully, `0` if the value was already present and was replaced, `FALSE` if there was an error. ##### *Example* ~~~ $redis->delete('h') $redis->hSet('h', 'key1', 'hello'); /* 1, 'key1' => 'hello' in the hash at "h" */ $redis->hGet('h', 'key1'); /* returns "hello" */ $redis->hSet('h', 'key1', 'plop'); /* 0, value was replaced. */ $redis->hGet('h', 'key1'); /* returns "plop" */ ~~~ ### hSetNx ----- _**Description**_: Adds a value to the hash stored at key only if this field isn't already in the hash. ##### *Return value* *BOOL* `TRUE` if the field was set, `FALSE` if it was already present. ##### *Example* ~~~ $redis->delete('h') $redis->hSetNx('h', 'key1', 'hello'); /* TRUE, 'key1' => 'hello' in the hash at "h" */ $redis->hSetNx('h', 'key1', 'world'); /* FALSE, 'key1' => 'hello' in the hash at "h". No change since the field wasn't replaced. */ ~~~ ### hGet ----- _**Description**_: Gets a value from the hash stored at key. If the hash table doesn't exist, or the key doesn't exist, `FALSE` is returned. ##### *Parameters* *key* *hashKey* ##### *Return value* *STRING* The value, if the command executed successfully *BOOL* `FALSE` in case of failure ### hLen ----- _**Description**_: Returns the length of a hash, in number of items ##### *Parameters* *key* ##### *Return value* *LONG* the number of items in a hash, `FALSE` if the key doesn't exist or isn't a hash. ##### *Example* ~~~ $redis->delete('h') $redis->hSet('h', 'key1', 'hello'); $redis->hSet('h', 'key2', 'plop'); $redis->hLen('h'); /* returns 2 */ ~~~ ### hDel ----- _**Description**_: Removes a value from the hash stored at key. If the hash table doesn't exist, or the key doesn't exist, `FALSE` is returned. ##### *Parameters* *key* *hashKey1* *hashKey2* ... ##### *Return value* *LONG* the number of deleted keys, 0 if the key doesn't exist, `FALSE` if the key isn't a hash. ### hKeys ----- _**Description**_: Returns the keys in a hash, as an array of strings. ##### *Parameters* *Key*: key ##### *Return value* An array of elements, the keys of the hash. This works like PHP's array_keys(). ##### *Example* ~~~ $redis->delete('h'); $redis->hSet('h', 'a', 'x'); $redis->hSet('h', 'b', 'y'); $redis->hSet('h', 'c', 'z'); $redis->hSet('h', 'd', 't'); var_dump($redis->hKeys('h')); ~~~ Output: ~~~ array(4) { [0]=> string(1) "a" [1]=> string(1) "b" [2]=> string(1) "c" [3]=> string(1) "d" } ~~~ The order is random and corresponds to redis' own internal representation of the set structure. ### hVals ----- _**Description**_: Returns the values in a hash, as an array of strings. ##### *Parameters* *Key*: key ##### *Return value* An array of elements, the values of the hash. This works like PHP's array_values(). ##### *Example* ~~~ $redis->delete('h'); $redis->hSet('h', 'a', 'x'); $redis->hSet('h', 'b', 'y'); $redis->hSet('h', 'c', 'z'); $redis->hSet('h', 'd', 't'); var_dump($redis->hVals('h')); ~~~ Output: ~~~ array(4) { [0]=> string(1) "x" [1]=> string(1) "y" [2]=> string(1) "z" [3]=> string(1) "t" } ~~~ The order is random and corresponds to redis' own internal representation of the set structure. ### hGetAll ----- _**Description**_: Returns the whole hash, as an array of strings indexed by strings. ##### *Parameters* *Key*: key ##### *Return value* An array of elements, the contents of the hash. ##### *Example* ~~~ $redis->delete('h'); $redis->hSet('h', 'a', 'x'); $redis->hSet('h', 'b', 'y'); $redis->hSet('h', 'c', 'z'); $redis->hSet('h', 'd', 't'); var_dump($redis->hGetAll('h')); ~~~ Output: ~~~ array(4) { ["a"]=> string(1) "x" ["b"]=> string(1) "y" ["c"]=> string(1) "z" ["d"]=> string(1) "t" } ~~~ The order is random and corresponds to redis' own internal representation of the set structure. ### hExists ----- _**Description**_: Verify if the specified member exists in a key. ##### *Parameters* *key* *memberKey* ##### *Return value* *BOOL*: If the member exists in the hash table, return `TRUE`, otherwise return `FALSE`. ##### *Examples* ~~~ $redis->hSet('h', 'a', 'x'); $redis->hExists('h', 'a'); /* TRUE */ $redis->hExists('h', 'NonExistingKey'); /* FALSE */ ~~~ ### hIncrBy ----- _**Description**_: Increments the value of a member from a hash by a given amount. ##### *Parameters* *key* *member* *value*: (integer) value that will be added to the member's value ##### *Return value* *LONG* the new value ##### *Examples* ~~~ $redis->delete('h'); $redis->hIncrBy('h', 'x', 2); /* returns 2: h[x] = 2 now. */ $redis->hIncrBy('h', 'x', 1); /* h[x] ↠2 + 1. Returns 3 */ ~~~ ### hIncrByFloat ----- _**Description**_: Increments the value of a hash member by the provided float value ##### *Parameters* *key* *member* *value*: (float) value that will be added to the member's value ##### *Return value* *FLOAT* the new value ##### *Examples* ~~~ $redis->delete('h'); $redis->hIncrByFloat('h','x', 1.5); /* returns 1.5: h[x] = 1.5 now */ $redis->hIncrByFloat('h', 'x', 1.5); /* returns 3.0: h[x] = 3.0 now */ $redis->hIncrByFloat('h', 'x', -3.0); /* returns 0.0: h[x] = 0.0 now */ ~~~ ### hMSet ----- _**Description**_: Fills in a whole hash. Non-string values are converted to string, using the standard `(string)` cast. NULL values are stored as empty strings. ##### *Parameters* *key* *members*: key → value array ##### *Return value* *BOOL* ##### *Examples* ~~~ $redis->delete('user:1'); $redis->hMSet('user:1', array('name' => 'Joe', 'salary' => 2000)); $redis->hIncrBy('user:1', 'salary', 100); // Joe earns 100 more now. ~~~ ### hMGet ----- _**Description**_: Retrieve the values associated to the specified fields in the hash. ##### *Parameters* *key* *memberKeys* Array ##### *Return value* *Array* An array of elements, the values of the specified fields in the hash, with the hash keys as array keys. ##### *Examples* ~~~ $redis->delete('h'); $redis->hSet('h', 'field1', 'value1'); $redis->hSet('h', 'field2', 'value2'); $redis->hMGet('h', array('field1', 'field2')); /* returns array('field1' => 'value1', 'field2' => 'value2') */ ~~~ ### hScan ----- _**Description**_: Scan a HASH value for members, with an optional pattern and count ##### *Parameters* *key*: String *iterator*: Long (reference) *pattern*: Optional pattern to match against *count*: How many keys to return in a go (only a sugestion to Redis) ##### *Return value* *Array* An array of members that match our pattern ##### *Examples* ~~~ $it = NULL; /* Don't ever return an empty array until we're done iterating */ $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); while($arr_keys = $redis->hScan('hash', $it)) { foreach($arr_keys as $str_field => $str_value) { echo "$str_field => $str_value\n"; /* Print the hash member and value */ } } ~~~ ### hStrLen ----- _**Description**_: Get the string length of the value associated with field in the hash stored at key. ##### *Parameters* *key*: String *field*: String ##### *Return value* *LONG* the string length of the value associated with field, or zero when field is not present in the hash or key does not exist at all. ## Lists * [blPop, brPop](#blpop-brpop) - Remove and get the first/last element in a list * [bRPopLPush](#brpoplpush) - Pop a value from a list, push it to another list and return it * [lIndex, lGet](#lindex-lget) - Get an element from a list by its index * [lInsert](#linsert) - Insert an element before or after another element in a list * [lLen, lSize](#llen-lsize) - Get the length/size of a list * [lPop](#lpop) - Remove and get the first element in a list * [lPush](#lpush) - Prepend one or multiple values to a list * [lPushx](#lpushx) - Prepend a value to a list, only if the list exists * [lRange, lGetRange](#lrange-lgetrange) - Get a range of elements from a list * [lRem, lRemove](#lrem-lremove) - Remove elements from a list * [lSet](#lset) - Set the value of an element in a list by its index * [lTrim, listTrim](#ltrim-listtrim) - Trim a list to the specified range * [rPop](#rpop) - Remove and get the last element in a list * [rPopLPush](#rpoplpush) - Remove the last element in a list, append it to another list and return it (redis >= 1.1) * [rPush](#rpush) - Append one or multiple values to a list * [rPushX](#rpushx) - Append a value to a list, only if the list exists ### blPop, brPop ----- _**Description**_: Is a blocking lPop(rPop) primitive. If at least one of the lists contains at least one element, the element will be popped from the head of the list and returned to the caller. If all the list identified by the keys passed in arguments are empty, blPop will block during the specified timeout until an element is pushed to one of those lists. This element will be popped. ##### *Parameters* *ARRAY* Array containing the keys of the lists *INTEGER* Timeout Or *STRING* Key1 *STRING* Key2 *STRING* Key3 ... *STRING* Keyn *INTEGER* Timeout ##### *Return value* *ARRAY* array('listName', 'element') ##### *Example* ~~~ /* Non blocking feature */ $redis->lPush('key1', 'A'); $redis->delete('key2'); $redis->blPop('key1', 'key2', 10); /* array('key1', 'A') */ /* OR */ $redis->blPop(array('key1', 'key2'), 10); /* array('key1', 'A') */ $redis->brPop('key1', 'key2', 10); /* array('key1', 'A') */ /* OR */ $redis->brPop(array('key1', 'key2'), 10); /* array('key1', 'A') */ /* Blocking feature */ /* process 1 */ $redis->delete('key1'); $redis->blPop('key1', 10); /* blocking for 10 seconds */ /* process 2 */ $redis->lPush('key1', 'A'); /* process 1 */ /* array('key1', 'A') is returned*/ ~~~ ### bRPopLPush ----- _**Description**_: A blocking version of `rPopLPush`, with an integral timeout in the third parameter. ##### *Parameters* *Key*: srckey *Key*: dstkey *Long*: timeout ##### *Return value* *STRING* The element that was moved in case of success, `FALSE` in case of timeout. ### lIndex, lGet ----- _**Description**_: Return the specified element of the list stored at the specified key. 0 the first element, 1 the second ... -1 the last element, -2 the penultimate ... Return `FALSE` in case of a bad index or a key that doesn't point to a list. ##### *Parameters* *key* *index* ##### *Return value* *String* the element at this index *Bool* `FALSE` if the key identifies a non-string data type, or no value corresponds to this index in the list `Key`. ##### *Example* ~~~ $redis->rPush('key1', 'A'); $redis->rPush('key1', 'B'); $redis->rPush('key1', 'C'); /* key1 => [ 'A', 'B', 'C' ] */ $redis->lGet('key1', 0); /* 'A' */ $redis->lGet('key1', -1); /* 'C' */ $redis->lGet('key1', 10); /* `FALSE` */ ~~~ / ### lInsert ----- _**Description**_: Insert value in the list before or after the pivot value. The parameter options specify the position of the insert (before or after). If the list didn't exists, or the pivot didn't exists, the value is not inserted. ##### *Parameters* *key* *position* Redis::BEFORE | Redis::AFTER *pivot* *value* ##### *Return value* The number of the elements in the list, -1 if the pivot didn't exists. ##### *Example* ~~~ $redis->delete('key1'); $redis->lInsert('key1', Redis::AFTER, 'A', 'X'); /* 0 */ $redis->lPush('key1', 'A'); $redis->lPush('key1', 'B'); $redis->lPush('key1', 'C'); $redis->lInsert('key1', Redis::BEFORE, 'C', 'X'); /* 4 */ $redis->lRange('key1', 0, -1); /* array('A', 'B', 'X', 'C') */ $redis->lInsert('key1', Redis::AFTER, 'C', 'Y'); /* 5 */ $redis->lRange('key1', 0, -1); /* array('A', 'B', 'X', 'C', 'Y') */ $redis->lInsert('key1', Redis::AFTER, 'W', 'value'); /* -1 */ ~~~ ### lPop ----- _**Description**_: Return and remove the first element of the list. ##### *Parameters* *key* ##### *Return value* *STRING* if command executed successfully *BOOL* `FALSE` in case of failure (empty list) ##### *Example* ~~~ $redis->rPush('key1', 'A'); $redis->rPush('key1', 'B'); $redis->rPush('key1', 'C'); /* key1 => [ 'A', 'B', 'C' ] */ $redis->lPop('key1'); /* key1 => [ 'B', 'C' ] */ ~~~ ### lPush ----- _**Description**_: Adds the string value to the head (left) of the list. Creates the list if the key didn't exist. If the key exists and is not a list, `FALSE` is returned. ##### *Parameters* *key* *value* String, value to push in key ##### *Return value* *LONG* The new length of the list in case of success, `FALSE` in case of Failure. ##### *Examples* ~~~ $redis->delete('key1'); $redis->lPush('key1', 'C'); // returns 1 $redis->lPush('key1', 'B'); // returns 2 $redis->lPush('key1', 'A'); // returns 3 /* key1 now points to the following list: [ 'A', 'B', 'C' ] */ ~~~ ### lPushx ----- _**Description**_: Adds the string value to the head (left) of the list if the list exists. ##### *Parameters* *key* *value* String, value to push in key ##### *Return value* *LONG* The new length of the list in case of success, `FALSE` in case of Failure. ##### *Examples* ~~~ $redis->delete('key1'); $redis->lPushx('key1', 'A'); // returns 0 $redis->lPush('key1', 'A'); // returns 1 $redis->lPushx('key1', 'B'); // returns 2 $redis->lPushx('key1', 'C'); // returns 3 /* key1 now points to the following list: [ 'A', 'B', 'C' ] */ ~~~ ### lRange, lGetRange ----- _**Description**_: Returns the specified elements of the list stored at the specified key in the range [start, end]. start and stop are interpretated as indices: 0 the first element, 1 the second ... -1 the last element, -2 the penultimate ... ##### *Parameters* *key* *start* *end* ##### *Return value* *Array* containing the values in specified range. ##### *Example* ~~~ $redis->rPush('key1', 'A'); $redis->rPush('key1', 'B'); $redis->rPush('key1', 'C'); $redis->lRange('key1', 0, -1); /* array('A', 'B', 'C') */ ~~~ ### lRem, lRemove ----- _**Description**_: Removes the first `count` occurences of the value element from the list. If count is zero, all the matching elements are removed. If count is negative, elements are removed from tail to head. **Note**: The argument order is not the same as in the Redis documentation. This difference is kept for compatibility reasons. ##### *Parameters* *key* *value* *count* ##### *Return value* *LONG* the number of elements to remove *BOOL* `FALSE` if the value identified by key is not a list. ##### *Example* ~~~ $redis->lPush('key1', 'A'); $redis->lPush('key1', 'B'); $redis->lPush('key1', 'C'); $redis->lPush('key1', 'A'); $redis->lPush('key1', 'A'); $redis->lRange('key1', 0, -1); /* array('A', 'A', 'C', 'B', 'A') */ $redis->lRem('key1', 'A', 2); /* 2 */ $redis->lRange('key1', 0, -1); /* array('C', 'B', 'A') */ ~~~ ### lSet ----- _**Description**_: Set the list at index with the new value. ##### *Parameters* *key* *index* *value* ##### *Return value* *BOOL* `TRUE` if the new value is setted. `FALSE` if the index is out of range, or data type identified by key is not a list. ##### *Example* ~~~ $redis->rPush('key1', 'A'); $redis->rPush('key1', 'B'); $redis->rPush('key1', 'C'); /* key1 => [ 'A', 'B', 'C' ] */ $redis->lGet('key1', 0); /* 'A' */ $redis->lSet('key1', 0, 'X'); $redis->lGet('key1', 0); /* 'X' */ ~~~ ### lTrim, listTrim ----- _**Description**_: Trims an existing list so that it will contain only a specified range of elements. ##### *Parameters* *key* *start* *stop* ##### *Return value* *Array* *Bool* return `FALSE` if the key identify a non-list value. ##### *Example* ~~~ $redis->rPush('key1', 'A'); $redis->rPush('key1', 'B'); $redis->rPush('key1', 'C'); $redis->lRange('key1', 0, -1); /* array('A', 'B', 'C') */ $redis->lTrim('key1', 0, 1); $redis->lRange('key1', 0, -1); /* array('A', 'B') */ ~~~ ### rPop ----- _**Description**_: Returns and removes the last element of the list. ##### *Parameters* *key* ##### *Return value* *STRING* if command executed successfully *BOOL* `FALSE` in case of failure (empty list) ##### *Example* ~~~ $redis->rPush('key1', 'A'); $redis->rPush('key1', 'B'); $redis->rPush('key1', 'C'); /* key1 => [ 'A', 'B', 'C' ] */ $redis->rPop('key1'); /* key1 => [ 'A', 'B' ] */ ~~~ ### rPopLPush ----- _**Description**_: Pops a value from the tail of a list, and pushes it to the front of another list. Also return this value. (redis >= 1.1) ##### *Parameters* *Key*: srckey *Key*: dstkey ##### *Return value* *STRING* The element that was moved in case of success, `FALSE` in case of failure. ##### *Example* ~~~ $redis->delete('x', 'y'); $redis->lPush('x', 'abc'); $redis->lPush('x', 'def'); $redis->lPush('y', '123'); $redis->lPush('y', '456'); // move the last of x to the front of y. var_dump($redis->rPopLPush('x', 'y')); var_dump($redis->lRange('x', 0, -1)); var_dump($redis->lRange('y', 0, -1)); ~~~ Output: ~~~ string(3) "abc" array(1) { [0]=> string(3) "def" } array(3) { [0]=> string(3) "abc" [1]=> string(3) "456" [2]=> string(3) "123" } ~~~ ### rPush ----- _**Description**_: Adds the string value to the tail (right) of the list. Creates the list if the key didn't exist. If the key exists and is not a list, `FALSE` is returned. ##### *Parameters* *key* *value* String, value to push in key ##### *Return value* *LONG* The new length of the list in case of success, `FALSE` in case of Failure. ##### *Examples* ~~~ $redis->delete('key1'); $redis->rPush('key1', 'A'); // returns 1 $redis->rPush('key1', 'B'); // returns 2 $redis->rPush('key1', 'C'); // returns 3 /* key1 now points to the following list: [ 'A', 'B', 'C' ] */ ~~~ ### rPushX ----- _**Description**_: Adds the string value to the tail (right) of the list if the ist exists. `FALSE` in case of Failure. ##### *Parameters* *key* *value* String, value to push in key ##### *Return value* *LONG* The new length of the list in case of success, `FALSE` in case of Failure. ##### *Examples* ~~~ $redis->delete('key1'); $redis->rPushX('key1', 'A'); // returns 0 $redis->rPush('key1', 'A'); // returns 1 $redis->rPushX('key1', 'B'); // returns 2 $redis->rPushX('key1', 'C'); // returns 3 /* key1 now points to the following list: [ 'A', 'B', 'C' ] */ ~~~ ### lLen, lSize ----- _**Description**_: Returns the size of a list identified by Key. If the list didn't exist or is empty, the command returns 0. If the data type identified by Key is not a list, the command return `FALSE`. ##### *Parameters* *Key* ##### *Return value* *LONG* The size of the list identified by Key exists. *BOOL* `FALSE` if the data type identified by Key is not list ##### *Example* ~~~ $redis->rPush('key1', 'A'); $redis->rPush('key1', 'B'); $redis->rPush('key1', 'C'); /* key1 => [ 'A', 'B', 'C' ] */ $redis->lSize('key1');/* 3 */ $redis->rPop('key1'); $redis->lSize('key1');/* 2 */ ~~~ ## Sets * [sAdd](#sadd) - Add one or more members to a set * [sCard, sSize](#scard-ssize) - Get the number of members in a set * [sDiff](#sdiff) - Subtract multiple sets * [sDiffStore](#sdiffstore) - Subtract multiple sets and store the resulting set in a key * [sInter](#sinter) - Intersect multiple sets * [sInterStore](#sinterstore) - Intersect multiple sets and store the resulting set in a key * [sIsMember, sContains](#sismember-scontains) - Determine if a given value is a member of a set * [sMembers, sGetMembers](#smembers-sgetmembers) - Get all the members in a set * [sMove](#smove) - Move a member from one set to another * [sPop](#spop) - Remove and return one or more members of a set at random * [sRandMember](#srandmember) - Get one or multiple random members from a set * [sRem, sRemove](#srem-sremove) - Remove one or more members from a set * [sUnion](#sunion) - Add multiple sets * [sUnionStore](#sunionstore) - Add multiple sets and store the resulting set in a key * [sScan](#sscan) - Scan a set for members ### sAdd ----- _**Description**_: Adds a value to the set value stored at key. If this value is already in the set, `FALSE` is returned. ##### *Parameters* *key* *value* ##### *Return value* *LONG* the number of elements added to the set. ##### *Example* ~~~ $redis->sAdd('key1' , 'member1'); /* 1, 'key1' => {'member1'} */ $redis->sAdd('key1' , 'member2', 'member3'); /* 2, 'key1' => {'member1', 'member2', 'member3'}*/ $redis->sAdd('key1' , 'member2'); /* 0, 'key1' => {'member1', 'member2', 'member3'}*/ ~~~ ### sCard, sSize ----- _**Description**_: Returns the cardinality of the set identified by key. ##### *Parameters* *key* ##### *Return value* *LONG* the cardinality of the set identified by key, 0 if the set doesn't exist. ##### *Example* ~~~ $redis->sAdd('key1' , 'member1'); $redis->sAdd('key1' , 'member2'); $redis->sAdd('key1' , 'member3'); /* 'key1' => {'member1', 'member2', 'member3'}*/ $redis->sCard('key1'); /* 3 */ $redis->sCard('keyX'); /* 0 */ ~~~ ### sDiff ----- _**Description**_: Performs the difference between N sets and returns it. ##### *Parameters* *Keys*: key1, key2, ... , keyN: Any number of keys corresponding to sets in redis. ##### *Return value* *Array of strings*: The difference of the first set will all the others. ##### *Example* ~~~ $redis->delete('s0', 's1', 's2'); $redis->sAdd('s0', '1'); $redis->sAdd('s0', '2'); $redis->sAdd('s0', '3'); $redis->sAdd('s0', '4'); $redis->sAdd('s1', '1'); $redis->sAdd('s2', '3'); var_dump($redis->sDiff('s0', 's1', 's2')); ~~~ Return value: all elements of s0 that are neither in s1 nor in s2. ~~~ array(2) { [0]=> string(1) "4" [1]=> string(1) "2" } ~~~ ### sDiffStore ----- _**Description**_: Performs the same action as sDiff, but stores the result in the first key ##### *Parameters* *Key*: dstkey, the key to store the diff into. *Keys*: key1, key2, ... , keyN: Any number of keys corresponding to sets in redis ##### *Return value* *INTEGER*: The cardinality of the resulting set, or `FALSE` in case of a missing key. ##### *Example* ~~~ $redis->delete('s0', 's1', 's2'); $redis->sAdd('s0', '1'); $redis->sAdd('s0', '2'); $redis->sAdd('s0', '3'); $redis->sAdd('s0', '4'); $redis->sAdd('s1', '1'); $redis->sAdd('s2', '3'); var_dump($redis->sDiffStore('dst', 's0', 's1', 's2')); var_dump($redis->sMembers('dst')); ~~~ Return value: the number of elements of s0 that are neither in s1 nor in s2. ~~~ int(2) array(2) { [0]=> string(1) "4" [1]=> string(1) "2" } ~~~ ### sInter ----- _**Description**_: Returns the members of a set resulting from the intersection of all the sets held at the specified keys. If just a single key is specified, then this command produces the members of this set. If one of the keys is missing, `FALSE` is returned. ##### *Parameters* key1, key2, keyN: keys identifying the different sets on which we will apply the intersection. ##### *Return value* Array, contain the result of the intersection between those keys. If the intersection beteen the different sets is empty, the return value will be empty array. ##### *Examples* ~~~ $redis->sAdd('key1', 'val1'); $redis->sAdd('key1', 'val2'); $redis->sAdd('key1', 'val3'); $redis->sAdd('key1', 'val4'); $redis->sAdd('key2', 'val3'); $redis->sAdd('key2', 'val4'); $redis->sAdd('key3', 'val3'); $redis->sAdd('key3', 'val4'); var_dump($redis->sInter('key1', 'key2', 'key3')); ~~~ Output: ~~~ array(2) { [0]=> string(4) "val4" [1]=> string(4) "val3" } ~~~ ### sInterStore ----- _**Description**_: Performs a sInter command and stores the result in a new set. ##### *Parameters* *Key*: dstkey, the key to store the diff into. *Keys*: key1, key2... keyN. key1..keyN are intersected as in sInter. ##### *Return value* *INTEGER*: The cardinality of the resulting set, or `FALSE` in case of a missing key. ##### *Example* ~~~ $redis->sAdd('key1', 'val1'); $redis->sAdd('key1', 'val2'); $redis->sAdd('key1', 'val3'); $redis->sAdd('key1', 'val4'); $redis->sAdd('key2', 'val3'); $redis->sAdd('key2', 'val4'); $redis->sAdd('key3', 'val3'); $redis->sAdd('key3', 'val4'); var_dump($redis->sInterStore('output', 'key1', 'key2', 'key3')); var_dump($redis->sMembers('output')); ~~~ Output: ~~~ int(2) array(2) { [0]=> string(4) "val4" [1]=> string(4) "val3" } ~~~ ### sIsMember, sContains ----- _**Description**_: Checks if `value` is a member of the set stored at the key `key`. ##### *Parameters* *key* *value* ##### *Return value* *BOOL* `TRUE` if `value` is a member of the set at key `key`, `FALSE` otherwise. ##### *Example* ~~~ $redis->sAdd('key1' , 'member1'); $redis->sAdd('key1' , 'member2'); $redis->sAdd('key1' , 'member3'); /* 'key1' => {'member1', 'member2', 'member3'}*/ $redis->sIsMember('key1', 'member1'); /* TRUE */ $redis->sIsMember('key1', 'memberX'); /* FALSE */ ~~~ ### sMembers, sGetMembers ----- _**Description**_: Returns the contents of a set. ##### *Parameters* *Key*: key ##### *Return value* An array of elements, the contents of the set. ##### *Example* ~~~ $redis->delete('s'); $redis->sAdd('s', 'a'); $redis->sAdd('s', 'b'); $redis->sAdd('s', 'a'); $redis->sAdd('s', 'c'); var_dump($redis->sMembers('s')); ~~~ Output: ~~~ array(3) { [0]=> string(1) "c" [1]=> string(1) "a" [2]=> string(1) "b" } ~~~ The order is random and corresponds to redis' own internal representation of the set structure. ### sMove ----- _**Description**_: Moves the specified member from the set at srcKey to the set at dstKey. ##### *Parameters* *srcKey* *dstKey* *member* ##### *Return value* *BOOL* If the operation is successful, return `TRUE`. If the srcKey and/or dstKey didn't exist, and/or the member didn't exist in srcKey, `FALSE` is returned. ##### *Example* ~~~ $redis->sAdd('key1' , 'member11'); $redis->sAdd('key1' , 'member12'); $redis->sAdd('key1' , 'member13'); /* 'key1' => {'member11', 'member12', 'member13'}*/ $redis->sAdd('key2' , 'member21'); $redis->sAdd('key2' , 'member22'); /* 'key2' => {'member21', 'member22'}*/ $redis->sMove('key1', 'key2', 'member13'); /* 'key1' => {'member11', 'member12'} */ /* 'key2' => {'member21', 'member22', 'member13'} */ ~~~ ### sPop ----- _**Description**_: Removes and returns a random element from the set value at Key. ##### *Parameters* *key* *count*: Integer, optional ##### *Return value (without count argument)* *String* "popped" value *Bool* `FALSE` if set identified by key is empty or doesn't exist. ##### *Return value (with count argument)* *Array*: Member(s) returned or an empty array if the set doesn't exist *Bool*: `FALSE` on error if the key is not a set ##### *Example* ~~~ $redis->sAdd('key1' , 'member1'); $redis->sAdd('key1' , 'member2'); $redis->sAdd('key1' , 'member3'); /* 'key1' => {'member3', 'member1', 'member2'}*/ $redis->sPop('key1'); /* 'member1', 'key1' => {'member3', 'member2'} */ $redis->sPop('key1'); /* 'member3', 'key1' => {'member2'} */ /* With count */ $redis->sAdd('key2', 'member1', 'member2', 'member3'); $redis->sPop('key2', 3); /* Will return all members but in no particular order */ ~~~ ### sRandMember ----- _**Description**_: Returns a random element from the set value at Key, without removing it. ##### *Parameters* *key* *count* (Integer, optional) ##### *Return value* If no count is provided, a random *String* value from the set will be returned. If a count is provided, an array of values from the set will be returned. Read about the different ways to use the count here: [SRANDMEMBER](http://redis.io/commands/srandmember) *Bool* `FALSE` if set identified by key is empty or doesn't exist. ##### *Example* ~~~ $redis->sAdd('key1' , 'member1'); $redis->sAdd('key1' , 'member2'); $redis->sAdd('key1' , 'member3'); /* 'key1' => {'member3', 'member1', 'member2'}*/ // No count $redis->sRandMember('key1'); /* 'member1', 'key1' => {'member3', 'member1', 'member2'} */ $redis->sRandMember('key1'); /* 'member3', 'key1' => {'member3', 'member1', 'member2'} */ // With a count $redis->sRandMember('key1', 3); // Will return an array with all members from the set $redis->sRandMember('key1', 2); // Will an array with 2 members of the set $redis->sRandMember('key1', -100); // Will return an array of 100 elements, picked from our set (with dups) $redis->sRandMember('empty-set', 100); // Will return an empty array $redis->sRandMember('not-a-set', 100); // Will return FALSE ~~~ ### sRem, sRemove ----- _**Description**_: Removes the specified member from the set value stored at key. ##### *Parameters* *key* *member* ##### *Return value* *LONG* The number of elements removed from the set. ##### *Example* ~~~ $redis->sAdd('key1' , 'member1'); $redis->sAdd('key1' , 'member2'); $redis->sAdd('key1' , 'member3'); /* 'key1' => {'member1', 'member2', 'member3'}*/ $redis->sRem('key1', 'member2', 'member3'); /*return 2. 'key1' => {'member1'} */ ~~~ ### sUnion ----- _**Description**_: Performs the union between N sets and returns it. ##### *Parameters* *Keys*: key1, key2, ... , keyN: Any number of keys corresponding to sets in redis. ##### *Return value* *Array of strings*: The union of all these sets. ##### *Example* ~~~ $redis->delete('s0', 's1', 's2'); $redis->sAdd('s0', '1'); $redis->sAdd('s0', '2'); $redis->sAdd('s1', '3'); $redis->sAdd('s1', '1'); $redis->sAdd('s2', '3'); $redis->sAdd('s2', '4'); var_dump($redis->sUnion('s0', 's1', 's2')); ~~~ Return value: all elements that are either in s0 or in s1 or in s2. ~~~ array(4) { [0]=> string(1) "3" [1]=> string(1) "4" [2]=> string(1) "1" [3]=> string(1) "2" } ~~~ ### sUnionStore ----- _**Description**_: Performs the same action as sUnion, but stores the result in the first key ##### *Parameters* *Key*: dstkey, the key to store the diff into. *Keys*: key1, key2, ... , keyN: Any number of keys corresponding to sets in redis. ##### *Return value* *INTEGER*: The cardinality of the resulting set, or `FALSE` in case of a missing key. ##### *Example* ~~~ $redis->delete('s0', 's1', 's2'); $redis->sAdd('s0', '1'); $redis->sAdd('s0', '2'); $redis->sAdd('s1', '3'); $redis->sAdd('s1', '1'); $redis->sAdd('s2', '3'); $redis->sAdd('s2', '4'); var_dump($redis->sUnionStore('dst', 's0', 's1', 's2')); var_dump($redis->sMembers('dst')); ~~~ Return value: the number of elements that are either in s0 or in s1 or in s2. ~~~ int(4) array(4) { [0]=> string(1) "3" [1]=> string(1) "4" [2]=> string(1) "1" [3]=> string(1) "2" } ~~~ ### sScan ----- _**Description**_: Scan a set for members ##### *Parameters* *Key*: The set to search *iterator*: LONG (reference) to the iterator as we go *pattern*: String, optional pattern to match against *count*: How many members to return at a time (Redis might return a different amount) ##### *Return value* *Array, boolean*: PHPRedis will return an array of keys or FALSE when we're done iterating ##### *Example* ~~~ $it = NULL; $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); /* don't return empty results until we're done */ while($arr_mems = $redis->sScan('set', $it, "*pattern*")) { foreach($arr_mems as $str_mem) { echo "Member: $str_mem\n"; } } $it = NULL; $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_NORETRY); /* return after each iteration, even if empty */ while(($arr_mems = $redis->sScan('set', $it, "*pattern*"))!==FALSE) { if(count($arr_mems) > 0) { foreach($arr_mems as $str_mem) { echo "Member found: $str_mem\n"; } } else { echo "No members in this iteration, iterator value: $it\n"; } } ~~~ ## Sorted sets * [zAdd](#zadd) - Add one or more members to a sorted set or update its score if it already exists * [zCard, zSize](#zcard-zsize) - Get the number of members in a sorted set * [zCount](#zcount) - Count the members in a sorted set with scores within the given values * [zIncrBy](#zincrby) - Increment the score of a member in a sorted set * [zInter](#zinter) - Intersect multiple sorted sets and store the resulting sorted set in a new key * [zRange](#zrange) - Return a range of members in a sorted set, by index * [zRangeByScore, zRevRangeByScore](#zrangebyscore-zrevrangebyscore) - Return a range of members in a sorted set, by score * [zRangeByLex](#zrangebylex) - Return a lexigraphical range from members that share the same score * [zRank, zRevRank](#zrank-zrevrank) - Determine the index of a member in a sorted set * [zRem, zDelete](#zrem-zdelete) - Remove one or more members from a sorted set * [zRemRangeByRank, zDeleteRangeByRank](#zremrangebyrank-zdeleterangebyrank) - Remove all members in a sorted set within the given indexes * [zRemRangeByScore, zDeleteRangeByScore](#zremrangebyscore-zdeleterangebyscore) - Remove all members in a sorted set within the given scores * [zRevRange](#zrevrange) - Return a range of members in a sorted set, by index, with scores ordered from high to low * [zScore](#zscore) - Get the score associated with the given member in a sorted set * [zUnion](#zunion) - Add multiple sorted sets and store the resulting sorted set in a new key * [zScan](#zscan) - Scan a sorted set for members ### zAdd ----- _**Description**_: Add one or more members to a sorted set or update its score if it already exists ##### *Parameters* *key* *score* : double *value*: string ##### *Return value* *Long* 1 if the element is added. 0 otherwise. ##### *Example* ~~~ $redis->zAdd('key', 1, 'val1'); $redis->zAdd('key', 0, 'val0'); $redis->zAdd('key', 5, 'val5'); $redis->zRange('key', 0, -1); // array(val0, val1, val5) ~~~ ### zCard, zSize ----- _**Description**_: Returns the cardinality of an ordered set. ##### *Parameters* *key* ##### *Return value* *Long*, the set's cardinality ##### *Example* ~~~ $redis->zAdd('key', 0, 'val0'); $redis->zAdd('key', 2, 'val2'); $redis->zAdd('key', 10, 'val10'); $redis->zSize('key'); /* 3 */ ~~~ ### zCount ----- _**Description**_: Returns the *number* of elements of the sorted set stored at the specified key which have scores in the range [start,end]. Adding a parenthesis before `start` or `end` excludes it from the range. +inf and -inf are also valid limits. ##### *Parameters* *key* *start*: string *end*: string ##### *Return value* *LONG* the size of a corresponding zRangeByScore. ##### *Example* ~~~ $redis->zAdd('key', 0, 'val0'); $redis->zAdd('key', 2, 'val2'); $redis->zAdd('key', 10, 'val10'); $redis->zCount('key', 0, 3); /* 2, corresponding to array('val0', 'val2') */ ~~~ ### zIncrBy ----- _**Description**_: Increments the score of a member from a sorted set by a given amount. ##### *Parameters* *key* *value*: (double) value that will be added to the member's score *member* ##### *Return value* *DOUBLE* the new value ##### *Examples* ~~~ $redis->delete('key'); $redis->zIncrBy('key', 2.5, 'member1'); /* key or member1 didn't exist, so member1's score is to 0 before the increment */ /* and now has the value 2.5 */ $redis->zIncrBy('key', 1, 'member1'); /* 3.5 */ ~~~ ### zInter ----- _**Description**_: Creates an intersection of sorted sets given in second argument. The result of the union will be stored in the sorted set defined by the first argument. The third optionnel argument defines `weights` to apply to the sorted sets in input. In this case, the `weights` will be multiplied by the score of each element in the sorted set before applying the aggregation. The forth argument defines the `AGGREGATE` option which specify how the results of the union are aggregated. ##### *Parameters* *keyOutput* *arrayZSetKeys* *arrayWeights* *aggregateFunction* Either "SUM", "MIN", or "MAX": defines the behaviour to use on duplicate entries during the zInter. ##### *Return value* *LONG* The number of values in the new sorted set. ##### *Example* ~~~ $redis->delete('k1'); $redis->delete('k2'); $redis->delete('k3'); $redis->delete('ko1'); $redis->delete('ko2'); $redis->delete('ko3'); $redis->delete('ko4'); $redis->zAdd('k1', 0, 'val0'); $redis->zAdd('k1', 1, 'val1'); $redis->zAdd('k1', 3, 'val3'); $redis->zAdd('k2', 2, 'val1'); $redis->zAdd('k2', 3, 'val3'); $redis->zInter('ko1', array('k1', 'k2')); /* 2, 'ko1' => array('val1', 'val3') */ $redis->zInter('ko2', array('k1', 'k2'), array(1, 1)); /* 2, 'ko2' => array('val1', 'val3') */ /* Weighted zInter */ $redis->zInter('ko3', array('k1', 'k2'), array(1, 5), 'min'); /* 2, 'ko3' => array('val1', 'val3') */ $redis->zInter('ko4', array('k1', 'k2'), array(1, 5), 'max'); /* 2, 'ko4' => array('val3', 'val1') */ ~~~ ### zRange ----- _**Description**_: Returns a range of elements from the ordered set stored at the specified key, with values in the range [start, end]. Start and stop are interpreted as zero-based indices: 0 the first element, 1 the second ... -1 the last element, -2 the penultimate ... ##### *Parameters* *key* *start*: long *end*: long *withscores*: bool = false ##### *Return value* *Array* containing the values in specified range. ##### *Example* ~~~ $redis->zAdd('key1', 0, 'val0'); $redis->zAdd('key1', 2, 'val2'); $redis->zAdd('key1', 10, 'val10'); $redis->zRange('key1', 0, -1); /* array('val0', 'val2', 'val10') */ // with scores $redis->zRange('key1', 0, -1, true); /* array('val0' => 0, 'val2' => 2, 'val10' => 10) */ ~~~ ### zRangeByScore, zRevRangeByScore ----- _**Description**_: Returns the elements of the sorted set stored at the specified key which have scores in the range [start,end]. Adding a parenthesis before `start` or `end` excludes it from the range. +inf and -inf are also valid limits. zRevRangeByScore returns the same items in reverse order, when the `start` and `end` parameters are swapped. ##### *Parameters* *key* *start*: string *end*: string *options*: array Two options are available: `withscores => TRUE`, and `limit => array($offset, $count)` ##### *Return value* *Array* containing the values in specified range. ##### *Example* ~~~ $redis->zAdd('key', 0, 'val0'); $redis->zAdd('key', 2, 'val2'); $redis->zAdd('key', 10, 'val10'); $redis->zRangeByScore('key', 0, 3); /* array('val0', 'val2') */ $redis->zRangeByScore('key', 0, 3, array('withscores' => TRUE); /* array('val0' => 0, 'val2' => 2) */ $redis->zRangeByScore('key', 0, 3, array('limit' => array(1, 1)); /* array('val2') */ $redis->zRangeByScore('key', 0, 3, array('withscores' => TRUE, 'limit' => array(1, 1)); /* array('val2' => 2) */ ~~~ ### zRangeByLex ----- _**Description**_: Returns a lexigraphical range of members in a sorted set, assuming the members have the same score. The min and max values are required to start with '(' (exclusive), '[' (inclusive), or be exactly the values '-' (negative inf) or '+' (positive inf). The command must be called with either three *or* five arguments or will return FALSE. ##### *Parameters* *key*: The ZSET you wish to run against *min*: The minimum alphanumeric value you wish to get *max*: The maximum alphanumeric value you wish to get *offset*: Optional argument if you wish to start somewhere other than the first element. *limit*: Optional argument if you wish to limit the number of elements returned. ##### *Return value* *Array* containing the values in the specified range. ##### *Example* ~~~ foreach(Array('a','b','c','d','e','f','g') as $c) $redis->zAdd('key',0,$c); $redis->zRangeByLex('key','-','[c') /* Array('a','b','c'); */ $redis->zRangeByLex('key','-','(c') /* Array('a','b') */ $redis->zRangeByLex('key','-','[c',1,2) /* Array('b','c') */ ~~~ ### zRank, zRevRank ----- _**Description**_: Returns the rank of a given member in the specified sorted set, starting at 0 for the item with the smallest score. zRevRank starts at 0 for the item with the *largest* score. ##### *Parameters* *key* *member* ##### *Return value* *Long*, the item's score. ##### *Example* ~~~ $redis->delete('z'); $redis->zAdd('key', 1, 'one'); $redis->zAdd('key', 2, 'two'); $redis->zRank('key', 'one'); /* 0 */ $redis->zRank('key', 'two'); /* 1 */ $redis->zRevRank('key', 'one'); /* 1 */ $redis->zRevRank('key', 'two'); /* 0 */ ~~~ ### zRem, zDelete ----- _**Description**_: Deletes a specified member from the ordered set. ##### *Parameters* *key* *member* ##### *Return value* *LONG* 1 on success, 0 on failure. ##### *Example* ~~~ $redis->zAdd('key', 0, 'val0'); $redis->zAdd('key', 2, 'val2'); $redis->zAdd('key', 10, 'val10'); $redis->zDelete('key', 'val2'); $redis->zRange('key', 0, -1); /* array('val0', 'val10') */ ~~~ ### zRemRangeByRank, zDeleteRangeByRank ----- _**Description**_: Deletes the elements of the sorted set stored at the specified key which have rank in the range [start,end]. ##### *Parameters* *key* *start*: LONG *end*: LONG ##### *Return value* *LONG* The number of values deleted from the sorted set ##### *Example* ~~~ $redis->zAdd('key', 1, 'one'); $redis->zAdd('key', 2, 'two'); $redis->zAdd('key', 3, 'three'); $redis->zRemRangeByRank('key', 0, 1); /* 2 */ $redis->zRange('key', 0, -1, array('withscores' => TRUE)); /* array('three' => 3) */ ~~~ ### zRemRangeByScore, zDeleteRangeByScore ----- _**Description**_: Deletes the elements of the sorted set stored at the specified key which have scores in the range [start,end]. ##### *Parameters* *key* *start*: double or "+inf" or "-inf" string *end*: double or "+inf" or "-inf" string ##### *Return value* *LONG* The number of values deleted from the sorted set ##### *Example* ~~~ $redis->zAdd('key', 0, 'val0'); $redis->zAdd('key', 2, 'val2'); $redis->zAdd('key', 10, 'val10'); $redis->zRemRangeByScore('key', 0, 3); /* 2 */ ~~~ ### zRevRange ----- _**Description**_: Returns the elements of the sorted set stored at the specified key in the range [start, end] in reverse order. start and stop are interpretated as zero-based indices: 0 the first element, 1 the second ... -1 the last element, -2 the penultimate ... ##### *Parameters* *key* *start*: long *end*: long *withscores*: bool = false ##### *Return value* *Array* containing the values in specified range. ##### *Example* ~~~ $redis->zAdd('key', 0, 'val0'); $redis->zAdd('key', 2, 'val2'); $redis->zAdd('key', 10, 'val10'); $redis->zRevRange('key', 0, -1); /* array('val10', 'val2', 'val0') */ // with scores $redis->zRevRange('key', 0, -1, true); /* array('val10' => 10, 'val2' => 2, 'val0' => 0) */ ~~~ ### zScore ----- _**Description**_: Returns the score of a given member in the specified sorted set. ##### *Parameters* *key* *member* ##### *Return value* *Double* ##### *Example* ~~~ $redis->zAdd('key', 2.5, 'val2'); $redis->zScore('key', 'val2'); /* 2.5 */ ~~~ ### zUnion ----- _**Description**_: Creates an union of sorted sets given in second argument. The result of the union will be stored in the sorted set defined by the first argument. The third optionnel argument defines `weights` to apply to the sorted sets in input. In this case, the `weights` will be multiplied by the score of each element in the sorted set before applying the aggregation. The forth argument defines the `AGGREGATE` option which specify how the results of the union are aggregated. ##### *Parameters* *keyOutput* *arrayZSetKeys* *arrayWeights* *aggregateFunction* Either "SUM", "MIN", or "MAX": defines the behaviour to use on duplicate entries during the zUnion. ##### *Return value* *LONG* The number of values in the new sorted set. ##### *Example* ~~~ $redis->delete('k1'); $redis->delete('k2'); $redis->delete('k3'); $redis->delete('ko1'); $redis->delete('ko2'); $redis->delete('ko3'); $redis->zAdd('k1', 0, 'val0'); $redis->zAdd('k1', 1, 'val1'); $redis->zAdd('k2', 2, 'val2'); $redis->zAdd('k2', 3, 'val3'); $redis->zUnion('ko1', array('k1', 'k2')); /* 4, 'ko1' => array('val0', 'val1', 'val2', 'val3') */ /* Weighted zUnion */ $redis->zUnion('ko2', array('k1', 'k2'), array(1, 1)); /* 4, 'ko2' => array('val0', 'val1', 'val2', 'val3') */ $redis->zUnion('ko3', array('k1', 'k2'), array(5, 1)); /* 4, 'ko3' => array('val0', 'val2', 'val3', 'val1') */ ~~~ ### zScan ----- _**Description**_: Scan a sorted set for members, with optional pattern and count ##### *Parameters* *key*: String, the set to scan *iterator*: Long (reference), initialized to NULL *pattern*: String (optional), the pattern to match *count*: How many keys to return per iteration (Redis might return a different number) ##### *Return value* *Array, boolean* PHPRedis will return matching keys from Redis, or FALSE when iteration is complete ##### *Example* ~~~ $it = NULL; $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); while($arr_matches = $redis->zScan('zset', $it, '*pattern*')) { foreach($arr_matches as $str_mem => $f_score) { echo "Key: $str_mem, Score: $f_score\n"; } } ~~~ ## Pub/sub * [pSubscribe](#psubscribe) - Subscribe to channels by pattern * [publish](#publish) - Post a message to a channel * [subscribe](#subscribe) - Subscribe to channels * [pubSub](#pubsub) - Introspection into the pub/sub subsystem ### pSubscribe ----- _**Description**_: Subscribe to channels by pattern ##### *Parameters* *patterns*: An array of patterns to match *callback*: Either a string or an array with an object and method. The callback will get four arguments ($redis, $pattern, $channel, $message) *return value*: Mixed. Any non-null return value in the callback will be returned to the caller. ##### *Example* ~~~ function pSubscribe($redis, $pattern, $chan, $msg) { echo "Pattern: $pattern\n"; echo "Channel: $chan\n"; echo "Payload: $msg\n"; } ~~~ ### publish ----- _**Description**_: Publish messages to channels. Warning: this function will probably change in the future. ##### *Parameters* *channel*: a channel to publish to *messsage*: string ##### *Example* ~~~ $redis->publish('chan-1', 'hello, world!'); // send message. ~~~ ### subscribe ----- _**Description**_: Subscribe to channels. Warning: this function will probably change in the future. ##### *Parameters* *channels*: an array of channels to subscribe to *callback*: either a string or an array($instance, 'method_name'). The callback function receives 3 parameters: the redis instance, the channel name, and the message. *return value*: Mixed. Any non-null return value in the callback will be returned to the caller. ##### *Example* ~~~ function f($redis, $chan, $msg) { switch($chan) { case 'chan-1': ... break; case 'chan-2': ... break; case 'chan-2': ... break; } } $redis->subscribe(array('chan-1', 'chan-2', 'chan-3'), 'f'); // subscribe to 3 chans ~~~ ### pubSub ----- _**Description**_: A command allowing you to get information on the Redis pub/sub system. ##### *Parameters* *keyword*: String, which can be: "channels", "numsub", or "numpat" *argument*: Optional, variant. For the "channels" subcommand, you can pass a string pattern. For "numsub" an array of channel names. ##### *Return value* *CHANNELS*: Returns an array where the members are the matching channels. *NUMSUB*: Returns a key/value array where the keys are channel names and values are their counts. *NUMPAT*: Integer return containing the number active pattern subscriptions ##### *Example* ~~~ $redis->pubSub("channels"); /*All channels */ $redis->pubSub("channels", "*pattern*"); /* Just channels matching your pattern */ $redis->pubSub("numsub", Array("chan1", "chan2")); /*Get subscriber counts for 'chan1' and 'chan2'*/ $redis->pubSub("numpat"); /* Get the number of pattern subscribers */ ~~~ ## Generic 1. [rawCommand](#rawcommand) - Execute any generic command against the server. ### rawCommand ----- _**Description**_: A method to execute any arbitrary command against the a Redis server ##### *Parameters* This method is variadic and takes a dynamic number of arguments of various types (string, long, double), but must be passed at least one argument (the command keyword itself). ##### *Return value* The return value can be various types depending on what the server itself returns. No post processing is done to the returned value and must be handled by the client code. ##### *Example* ```php /* Returns: true */ $redis->rawCommand("set", "foo", "bar"); /* Returns: "bar" */ $redis->rawCommand("get", "foo"); /* Returns: 3 */ $redis->rawCommand("rpush", "mylist", "one", 2, 3.5)); /* Returns: ["one", "2", "3.5000000000000000"] */ $redis->rawCommand("lrange", "mylist", 0, -1); ``` ## Transactions 1. [multi, exec, discard](#multi-exec-discard) - Enter and exit transactional mode 2. [watch, unwatch](#watch-unwatch) - Watches a key for modifications by another client. ### multi, exec, discard. ----- _**Description**_: Enter and exit transactional mode. ##### *Parameters* (optional) `Redis::MULTI` or `Redis::PIPELINE`. Defaults to `Redis::MULTI`. A `Redis::MULTI` block of commands runs as a single transaction; a `Redis::PIPELINE` block is simply transmitted faster to the server, but without any guarantee of atomicity. `discard` cancels a transaction. ##### *Return value* `multi()` returns the Redis instance and enters multi-mode. Once in multi-mode, all subsequent method calls return the same object until `exec()` is called. ##### *Example* ~~~ $ret = $redis->multi() ->set('key1', 'val1') ->get('key1') ->set('key2', 'val2') ->get('key2') ->exec(); /* $ret == array( 0 => TRUE, 1 => 'val1', 2 => TRUE, 3 => 'val2'); */ ~~~ ### watch, unwatch ----- _**Description**_: Watches a key for modifications by another client. If the key is modified between `WATCH` and `EXEC`, the MULTI/EXEC transaction will fail (return `FALSE`). `unwatch` cancels all the watching of all keys by this client. ##### *Parameters* *keys*: string for one key or array for a list of keys ##### *Example* ~~~ $redis->watch('x'); // or for a list of keys: $redis->watch(array('x','another key')); /* long code here during the execution of which other clients could well modify `x` */ $ret = $redis->multi() ->incr('x') ->exec(); /* $ret = FALSE if x has been modified between the call to WATCH and the call to EXEC. */ ~~~ ## Scripting * [eval](#eval) - Evaluate a LUA script serverside * [evalSha](#evalsha) - Evaluate a LUA script serverside, from the SHA1 hash of the script instead of the script itself * [script](#script) - Execute the Redis SCRIPT command to perform various operations on the scripting subsystem * [getLastError](#getlasterror) - The last error message (if any) * [clearLastError](#clearlasterror) - Clear the last error message * [_prefix](#_prefix) - A utility method to prefix the value with the prefix setting for phpredis * [_unserialize](#_unserialize) - A utility method to unserialize data with whatever serializer is set up * [_serialize](#_serialize) - A utility method to serialize data with whatever serializer is set up ### eval ----- _**Description**_: Evaluate a LUA script serverside ##### *Parameters* *script* string. *args* array, optional. *num_keys* int, optional. ##### *Return value* Mixed. What is returned depends on what the LUA script itself returns, which could be a scalar value (int/string), or an array. Arrays that are returned can also contain other arrays, if that's how it was set up in your LUA script. If there is an error executing the LUA script, the getLastError() function can tell you the message that came back from Redis (e.g. compile error). ##### *Examples* ~~~ $redis->eval("return 1"); // Returns an integer: 1 $redis->eval("return {1,2,3}"); // Returns Array(1,2,3) $redis->del('mylist'); $redis->rpush('mylist','a'); $redis->rpush('mylist','b'); $redis->rpush('mylist','c'); // Nested response: Array(1,2,3,Array('a','b','c')); $redis->eval("return {1,2,3,redis.call('lrange','mylist',0,-1)}"); ~~~ ### evalSha ----- _**Description**_: Evaluate a LUA script serverside, from the SHA1 hash of the script instead of the script itself. In order to run this command Redis will have to have already loaded the script, either by running it or via the SCRIPT LOAD command. ##### *Parameters* *script_sha* string. The sha1 encoded hash of the script you want to run. *args* array, optional. Arguments to pass to the LUA script. *num_keys* int, optional. The number of arguments that should go into the KEYS array, vs. the ARGV array when Redis spins the script ##### *Return value* Mixed. See EVAL ##### *Examples* ~~~ $script = 'return 1'; $sha = $redis->script('load', $script); $redis->evalSha($sha); // Returns 1 ~~~ ### script ----- _**Description**_: Execute the Redis SCRIPT command to perform various operations on the scripting subsystem. ##### *Usage* ~~~ $redis->script('load', $script); $redis->script('flush'); $redis->script('kill'); $redis->script('exists', $script1, [$script2, $script3, ...]); ~~~ ##### *Return value* * SCRIPT LOAD will return the SHA1 hash of the passed script on success, and FALSE on failure. * SCRIPT FLUSH should always return TRUE * SCRIPT KILL will return true if a script was able to be killed and false if not * SCRIPT EXISTS will return an array with TRUE or FALSE for each passed script ### client ----- _**Description**_: Issue the CLIENT command with various arguments. The Redis CLIENT command can be used in four ways. * CLIENT LIST * CLIENT GETNAME * CLIENT SETNAME [name] * CLIENT KILL [ip:port] ##### *Usage* ~~~ $redis->client('list'); // Get a list of clients $redis->client('getname'); // Get the name of the current connection $redis->client('setname', 'somename'); // Set the name of the current connection $redis->client('kill', ); // Kill the process at ip:port ~~~ ##### *Return value* This will vary depending on which client command was executed. * CLIENT LIST will return an array of arrays with client information. * CLIENT GETNAME will return the client name or false if none has been set * CLIENT SETNAME will return true if it can be set and false if not * CLIENT KILL will return true if the client can be killed, and false if not Note: phpredis will attempt to reconnect so you can actually kill your own connection but may not notice losing it! ### getLastError ----- _**Description**_: The last error message (if any) ##### *Parameters* *none* ##### *Return value* A string with the last returned script based error message, or NULL if there is no error ##### *Examples* ~~~ $redis->eval('this-is-not-lua'); $err = $redis->getLastError(); // "ERR Error compiling script (new function): user_script:1: '=' expected near '-'" ~~~ ### clearLastError ----- _**Description**_: Clear the last error message ##### *Parameters* *none* ##### *Return value* *BOOL* TRUE ##### *Examples* ~~~ $redis->set('x', 'a'); $redis->incr('x'); $err = $redis->getLastError(); // "ERR value is not an integer or out of range" $redis->clearLastError(); $err = $redis->getLastError(); // NULL ~~~ ### _prefix ----- _**Description**_: A utility method to prefix the value with the prefix setting for phpredis. ##### *Parameters* *value* string. The value you wish to prefix ##### *Return value* If a prefix is set up, the value now prefixed. If there is no prefix, the value will be returned unchanged. ##### *Examples* ~~~ $redis->setOption(Redis::OPT_PREFIX, 'my-prefix:'); $redis->_prefix('my-value'); // Will return 'my-prefix:my-value' ~~~ ### _serialize ----- _**Description**_: A utility method to serialize values manually. This method allows you to serialize a value with whatever serializer is configured, manually. This can be useful for serialization/unserialization of data going in and out of EVAL commands as phpredis can't automatically do this itself. Note that if no serializer is set, phpredis will change Array values to 'Array', and Objects to 'Object'. ##### *Parameters* *value*: Mixed. The value to be serialized ##### *Examples* ~~~ $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); $redis->_serialize("foo"); // returns "foo" $redis->_serialize(Array()); // Returns "Array" $redis->_serialize(new stdClass()); // Returns "Object" $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); $redis->_serialize("foo"); // Returns 's:3:"foo";' ~~~ ### _unserialize ----- _**Description**_: A utility method to unserialize data with whatever serializer is set up. If there is no serializer set, the value will be returned unchanged. If there is a serializer set up, and the data passed in is malformed, an exception will be thrown. This can be useful if phpredis is serializing values, and you return something from redis in a LUA script that is serialized. ##### *Parameters* *value* string. The value to be unserialized ##### *Examples* ~~~ $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); $redis->_unserialize('a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}'); // Will return Array(1,2,3) ~~~ ## Introspection ### isConnected ----- _**Description**_: A method to determine if a phpredis object thinks it's connected to a server ##### *Parameters* None ##### *Return value* *Boolean* Returns TRUE if phpredis thinks it's connected and FALSE if not ### getHost ----- _**Description**_: Retreive our host or unix socket that we're connected to ##### *Parameters* None ##### *Return value* *Mixed* The host or unix socket we're connected to or FALSE if we're not connected ### getPort ----- _**Description**_: Get the port we're connected to ##### *Parameters* None ##### *Return value* *Mixed* Returns the port we're connected to or FALSE if we're not connected ### getDbNum ----- _**Description**_: Get the database number phpredis is pointed to ##### *Parameters* None ##### *Return value* *Mixed* Returns the database number (LONG) phpredis thinks it's pointing to or FALSE if we're not connected ### getTimeout ----- _**Description**_: Get the (write) timeout in use for phpredis ##### *Parameters* None ##### *Return value* *Mixed* The timeout (DOUBLE) specified in our connect call or FALSE if we're not connected ### getReadTimeout _**Description**_: Get the read timeout specified to phpredis or FALSE if we're not connected ##### *Parameters* None ##### *Return value* *Mixed* Returns the read timeout (which can be set using setOption and Redis::OPT_READ_TIMEOUT) or FALSE if we're not connected ### getPersistentID ----- _**Description**_: Gets the persistent ID that phpredis is using ##### *Parameters* None ##### *Return value* *Mixed* Returns the persistent id phpredis is using (which will only be set if connected with pconnect), NULL if we're not using a persistent ID, and FALSE if we're not connected ### getAuth ----- _**Description**_: Get the password used to authenticate the phpredis connection ### *Parameters* None ### *Return value* *Mixed* Returns the password used to authenticate a phpredis session or NULL if none was used, and FALSE if we're not connected redis-3.1.6/arrays.markdown0000644000175000000120000002106413223116600016450 0ustar pyatsukhnenkowheelRedis Arrays ============ A Redis array is an isolated namespace in which keys are related in some manner. Keys are distributed across a number of Redis instances, using consistent hashing. A hash function is used to spread the keys across the array in order to keep a uniform distribution. **This feature was added as the result of a generous sponsorship by [A+E Networks](http://www.aetn.com/).** An array is composed of the following: * A list of Redis hosts. * A key extraction function, used to hash part of the key in order to distribute related keys on the same node (optional). This is set by the "function" option. * A list of nodes previously in the ring, only present after a node has been added or removed. When a read command is sent to the array (e.g. GET, LRANGE...), the key is first queryied in the main ring, and then in the secondary ring if it was not found in the main one. Optionally, the keys can be migrated automatically when this happens. Write commands will always go to the main ring. This is set by the "previous" option. * An optional index in the form of a Redis set per node, used to migrate keys when nodes are added or removed; set by the "index" option. * An option to rehash the array automatically as nodes are added or removed, set by the "autorehash" option. ## Creating an array There are several ways of creating Redis arrays; they can be pre-defined in redis.ini using `new RedisArray(string $name);`, or created dynamically using `new RedisArray(array $hosts, array $options);` #### Declaring a new array with a list of nodes
$ra = new RedisArray(array("host1", "host2:63792", "host2:6380"));
#### Declaring a new array with a list of nodes and a function to extract a part of the key
function extract_key_part($k) {
    return substr($k, 0, 3);	// hash only on first 3 characters.
}
$ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("function" => "extract_key_part"));
#### Defining a "previous" array when nodes are added or removed. When a new node is added to an array, phpredis needs to know about it. The old list of nodes becomes the “previous” array, and the new list of nodes is used as a main ring. Right after a node has been added, some read commands will point to the wrong nodes and will need to look up the keys in the previous ring.
// adding host3 to a ring containing host1 and host2. Read commands will look in the previous ring if the data is not found in the main ring.
$ra = new RedisArray(array("host1", "host2", "host3"), array("previous" => array("host1", "host2")));
#### Specifying the "retry_interval" parameter The retry_interval is used to specify a delay in milliseconds between reconnection attempts in case the client loses connection with a server
$ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("retry_timeout" => 100));
#### Specifying the "lazy_connect" parameter This option is useful when a cluster has many shards but not of them are necessarily used at one time.
$ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("lazy_connect" => true));
#### Specifying the "connect_timeout" parameter The connect_timeout value is a double and is used to specify a timeout in number of seconds when creating redis socket connections used in the RedisArray.
$ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("connect_timeout" => 0.5));
#### Specifying the "read_timeout" parameter The read_timeout value is a double and is used to specify a timeout in number of seconds when waiting response from the server.
$ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("read_timeout" => 0.5));
#### Defining arrays in Redis.ini Because php.ini parameters must be pre-defined, Redis Arrays must all share the same .ini settings.
// list available Redis Arrays
ini_set('redis.array.names', 'users,friends');

// set host names for each array.
ini_set('redis.arrays.hosts', 'users[]=localhost:6379&users[]=localhost:6380&users[]=localhost:6381&users[]=localhost:6382&friends[]=localhost');

// set functions
ini_set('redis.arrays.functions', 'users=user_hash');

// use index only for users
ini_set('redis.arrays.index', 'users=1,friends=0');
## Usage Redis arrays can be used just as Redis objects:
$ra = new RedisArray("users");
$ra->set("user1:name", "Joe");
$ra->set("user2:name", "Mike");
## Key hashing By default and in order to be compatible with other libraries, phpredis will try to find a substring enclosed in curly braces within the key name, and use it to distribute the data. For instance, the keys “{user:1}:name” and “{user:1}:email” will be stored on the same server as only “user:1” will be hashed. You can provide a custom function name in your redis array with the "function" option; this function will be called every time a key needs to be hashed. It should take a string and return a string. ## Custom key distribution function In order to control the distribution of keys by hand, you can provide a custom function or closure that returns the server number, which is the index in the array of servers that you created the RedisArray object with. For instance, instanciate a RedisArray object with `new RedisArray(array("us-host", "uk-host", "de-host"), array("distributor" => "dist"));` and write a function called "dist" that will return `2` for all the keys that should end up on the "de-host" server. ### Example
$ra = new RedisArray(array("host1", "host2", "host3", "host4", "host5", "host6", "host7", "host8"), array("distributor" => array(2, 2)));
This declares that we started with 2 shards and moved to 4 then 8 shards. The number of initial shards is 2 and the resharding level (or number of iterations) is 2. ## Migrating keys When a node is added or removed from a ring, RedisArray instances must be instanciated with a “previous” list of nodes. A single call to `$ra->_rehash()` causes all the keys to be redistributed according to the new list of nodes. Passing a callback function to `_rehash()` makes it possible to track the progress of that operation: the function is called with a node name and a number of keys that will be examined, e.g. `_rehash(function ($host, $count){ ... });`. It is possible to automate this process, by setting `'autorehash' => TRUE` in the constructor options. This will cause keys to be migrated when they need to be read from the previous array. In order to migrate keys, they must all be examined and rehashed. If the "index" option was set, a single key per node lists all keys present there. Otherwise, the `KEYS` command is used to list them. If a “previous” list of servers is provided, it will be used as a backup ring when keys can not be found in the current ring. Writes will always go to the new ring, whilst reads will go to the new ring first, and to the second ring as a backup. Adding and/or removing several instances is supported. ### Example
$ra = new RedisArray("users"); // load up a new config from redis.ini, using the “.previous” listing.
$ra->_rehash();
Running this code will: * Create a new ring with the updated list of nodes. * Server by server, look up all the keys in the previous list of nodes. * Rehash each key and possibly move it to another server. * Update the array object with the new list of nodes. ## Multi/exec Multi/exec is still available, but must be run on a single node:
$host = $ra->_target("{users}:user1:name");	// find host first
$ra->multi($host)	// then run transaction on that host.
   ->del("{users}:user1:name")
   ->srem("{users}:index", "user1")
   ->exec();
## Limitations Key arrays offer no guarantee when using Redis commands that span multiple keys. Except for the use of MGET, MSET, and DEL, a single connection will be used and all the keys read or written there. Running KEYS() on a RedisArray object will execute the command on each node and return an associative array of keys, indexed by host name. ## Array info RedisArray objects provide several methods to help understand the state of the cluster. These methods start with an underscore. * `$ra->_hosts()` → returns a list of hosts for the selected array. * `$ra->_function()` → returns the name of the function used to extract key parts during consistent hashing. * `$ra->_target($key)` → returns the host to be used for a certain key. * `$ra->_instance($host)` → returns a redis instance connected to a specific node; use with `_target` to get a single Redis object. ## Running the unit tests
$ cd tests
$ ./mkring.sh start
$ php array-tests.php
redis-3.1.6/cluster.markdown0000644000175000000120000002360213223116600016630 0ustar pyatsukhnenkowheelRedis Cluster ============= Redis introduces cluster support as of version 3.0.0, and to communicate with a cluster using phpredis one needs to use the RedisCluster class. For the majority of operations the RedisCluster class can act as a drop-in replacement for the Redis class without needing to modify how it's called. **This feature was added as the result of a generous sponsorship by [Tradesy](https://www.tradesy.com/)** ## Creating and connecting to a cluster To maintain consistency with the RedisArray class, one can create and connect to a cluster either by passing it one or more 'seed' nodes, or by defining these in redis.ini as a 'named' cluster. #### Declaring a cluster with an array of seeds
// Create a cluster setting two nodes as seeds
$obj_cluster = new RedisCluster(NULL, Array('host:7000', 'host:7001', 'host:7003'));

// Connect and specify timeout and read_timeout
$obj_cluster = new RedisCluster(NULL, Array("host:7000", "host:7001"), 1.5, 1.5);

// Connect with read/write timeout as well as specify that phpredis should use
// persistent connections to each node.
$obj_cluster = new RedisCluster(NULL, Array("host:7000", "host:7001"), 1.5, 1.5, true);

#### Loading a cluster configuration by name In order to load a named array, one must first define the seed nodes in redis.ini. The following lines would define the cluster 'mycluster', and be loaded automatically by phpredis.
# In redis.ini
redis.clusters.seeds = "mycluster[]=localhost:7000&test[]=localhost:7001"
redis.clusters.timeout = "mycluster=5"
redis.clusters.read_timeout = "mycluster=10"
Then, this cluster can be loaded by doing the following
$obj_cluster = new RedisCluster('mycluster');
## Connection process On construction, the RedisCluster class will iterate over the provided seed nodes until it can attain a connection to the cluster and run CLUSTER SLOTS to map every node in the cluster locally. Once the keyspace is mapped, RedisCluster will only connect to nodes when it needs to (e.g. you're getting a key that we believe is on that node.) ## Timeouts Because Redis cluster is intended to provide high availability, timeouts do not work in the same way they do in normal socket communication. It's fully possible to have a timeout or even exception on a given socket (say in the case that a master node has failed), and continue to serve the request if and when a slave can be promoted as the new master. The way RedisCluster handles user specified timeout values is that every time a command is sent to the cluster, we record the the time at the start of the request and then again every time we have to re-issue the command to a different node (either because Redis cluster responded with MOVED/ASK or because we failed to communicate with a given node). Once we detect having been in the command loop for longer than our specified timeout, an error is raised. ## Keyspace map As previously described, RedisCluster makes an initial mapping of every master (and any slaves) on construction, which it uses to determine which nodes to direct a given command. However, one of the core functionalities of Redis cluster is that this keyspace can change while the cluster is running. Because of this, the RedisCluster class will update it's keyspace mapping whenever it receives a MOVED error when requesting data. In the case that we receive ASK redirection, it follows the Redis specification and requests the key from the ASK node, prefixed with an ASKING command. ## Automatic slave failover / distribution By default, RedisCluster will only ever send commands to master nodes, but can be configured differently for readonly commands if requested.
// The default option, only send commands to master nodes
$obj_cluster->setOption(RedisCluster::OPT_SLAVE_FAILOVER, RedisCluster::FAILOVER_NONE);

// In the event we can't reach a master, and it has slaves, failover for read commands
$obj_cluster->setOption(RedisCluster::OPT_SLAVE_FAILOVER, RedisCluster::FAILOVER_ERROR);

// Always distribute readonly commands between masters and slaves, at random
$obj_cluster->setOption(
    RedisCluster::OPT_SLAVE_FAILOVER, RedisCluster::FAILOVER_DISTRIBUTE
);
## Main command loop With the exception of commands that are directed to a specific node, each command executed via RedisCluster is processed through a command loop, where we make the request, handle any MOVED or ASK redirection, and repeat if necessary. This continues until one of the following conditions is met: 1. We fail to communicate with *any* node that we are aware of, in which case a ```RedisClusterExecption``` is raised. 2. We have been bounced around longer than the timeout which was set on construction. 3. Redis cluster returns us a ```CLUSTERDOWN``` error, in which case a ```RedisClusterException``` is raised. 4. We receive a valid response, in which case the data is returned to the caller. ## Transactions The RedisCluster class fully supports MULTI ... EXEC transactions, including commands such as MGET and MSET which operate on multiple keys. There are considerations that must be taken into account here however. When you call ```RedisCluster->multi()```, the cluster is put into a MULTI state, but the MULTI command is not delivered to any nodes until a key is requested on that node. In addition, calls to EXEC will always return an array (even in the event that a transaction to a given node failed), as the commands can be going to any number of nodes depending on what is called. Consider the following example:
// Cluster is put into MULTI state locally
$obj_cluster->multi();

// The cluster will issue MULTI on this node first (and only once)
$obj_cluster->get("mykey");
$obj_cluster->set("mykey", "new_value");

// If 'myotherkey' maps to a different node, MULTI will be issued there
// before requesting the key
$obj_cluster->get("myotherkey");

// This will always return an array, even in the event of a failed transaction
// on one of the nodes, in which case that element will be FALSE
print_r($obj_cluster->exec());
## Pipelining The RedisCluster class does not support pipelining as there is no way to detect whether the keys still live where our map indicates that they do and would therefore be inherently unsafe. It would be possible to implement this support as an option if there is demand for such a feature. ## Multiple key commands Redis cluster does allow commands that operate on multiple keys, but only if all of those keys hash to the same slot. Note that it is not enough that the keys are all on the same node, but must actually hash to the exact same hash slot. For all of these multiple key commands (with the exception of MGET and MSET), the RedisCluster class will verify each key maps to the same hash slot and raise a "CROSSSLOT" warning, returning false if they don't. ### MGET and MSET RedisCluster has specialized processing for MGET and MSET which allows you to send any number of keys (hashing to whichever slots) without having to consider where they live. The way this works, is that the RedisCluster class will split the command as it iterates through keys, delivering a subset of commands per each key's slot.
// This will be delivered in two commands.  First for all of the {hash1} keys, 
// and then to grab 'otherkey'
$obj_cluster->mget(Array("{hash1}key1","{hash1}key2","{hash1}key3","otherkey"));
This operation can also be done in MULTI mode transparently. ## Directed node commands There are a variety of commands which have to be directed at a specific node. In the case of these commands, the caller can either pass a key (which will be hashed and used to direct our command), or an array with host:port.
// This will be directed at the slot/node which would store "mykey"
$obj_cluster->echo("mykey","Hello World!");

// Here we're iterating all of our known masters, and delivering the command there
foreach ($obj_cluster->_masters() as $arr_master) {
    $obj_cluster->echo($arr_master, "Hello: " . implode(':', $arr_master));
}
In the case of all commands which need to be directed at a node, the calling convention is identical to the Redis call, except that they require an additional (first) argument in order to deliver the command. Following is a list of each of these commands: 1. SAVE 2. BGSAVE 3. FLUSHDB 4. FLUSHALL 5. DBSIZE 6. BGREWRITEAOF 7. LASTSAVE 8. INFO 9. CLIENT 10. CLUSTER 11. CONFIG 12. PUBSUB 13. SLOWLOG 14. RANDOMKEY 15. PING ## Session Handler You can use the cluster functionality of phpredis to store PHP session information in a Redis cluster as you can with a non cluster-enabled Redis instance. To do this, you must configure your `session.save_handler` and `session.save_path` INI variables to give phpredis enough information to communicate with the cluster. ~~~ session.save_handler = rediscluster session.save_path = "seed[]=host1:port1&seed[]=host2:port2&seed[]=hostN:portN&timeout=2&read_timeout=2&failover=error&persistent=1" ~~~ ### session.session_handler Set this variable to "rediscluster" to inform phpredis that this is a cluster instance. ### session.save_path The save path for cluster based session storage takes the form of a PHP GET request, and requires that you specify at least on `seed` node. Other options you can specify are as follows: * _timeout (double)_: The amount of time phpredis will wait when connecting or writing to the cluster. * _read_timeout (double)_: The amount of time phpredis will wait for a result from the cluster. * _persistent_: Tells phpredis whether persistent connections should be used. * _distribute_: phpredis will randomly distribute session reads between masters and any attached slaves (load balancing). * _failover (string)_: How phpredis should distribute session reads between master and slave nodes. * * _none_ : phpredis will only communicate with master nodes * * _error_: phpredis will communicate with master nodes unless one failes, in which case an attempt will be made to read session information from a slave. redis-3.1.6/CREDITS0000644000175000000120000000031413223116600014416 0ustar pyatsukhnenkowheelRedis client extension for PHP Alfonso Jimenez (yo@alfonsojimenez.com) Nasreddine Bouafif (n.bouafif@owlient.eu) Nicolas Favre-Felix (n.favre-felix@owlient.eu) Michael Grunder (michael.grunder@gmail.com) redis-3.1.6/COPYING0000644000175000000120000000622213223116600014435 0ustar pyatsukhnenkowheel-------------------------------------------------------------------- The PHP License, version 3.01 Copyright (c) 1999 - 2010 The PHP Group. All rights reserved. -------------------------------------------------------------------- Redistribution and use in source and binary forms, with or without modification, is permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name "PHP" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact group@php.net. 4. Products derived from this software may not be called "PHP", nor may "PHP" appear in their name, without prior written permission from group@php.net. You may indicate that your software works in conjunction with PHP by saying "Foo for PHP" instead of calling it "PHP Foo" or "phpfoo" 5. The PHP Group may publish revised and/or new versions of the license from time to time. Each version will be given a distinguishing version number. Once covered code has been published under a particular version of the license, you may always continue to use it under the terms of that version. You may also choose to use such covered code under the terms of any subsequent version of the license published by the PHP Group. No one other than the PHP Group has the right to modify the terms applicable to covered code created under this License. 6. Redistributions of any form whatsoever must retain the following acknowledgment: "This product includes PHP software, freely available from ". THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------- This software consists of voluntary contributions made by many individuals on behalf of the PHP Group. The PHP Group can be contacted via Email at group@php.net. For more information on the PHP Group and the PHP project, please see . PHP includes the Zend Engine, freely available at . redis-3.1.6/config.m40000755000175000000120000000650313223116600015116 0ustar pyatsukhnenkowheeldnl $Id$ dnl config.m4 for extension redis PHP_ARG_ENABLE(redis, whether to enable redis support, dnl Make sure that the comment is aligned: [ --enable-redis Enable redis support]) PHP_ARG_ENABLE(redis-session, whether to enable sessions, [ --disable-redis-session Disable session support], yes, no) PHP_ARG_ENABLE(redis-igbinary, whether to enable igbinary serializer support, [ --enable-redis-igbinary Enable igbinary serializer support], no, no) if test "$PHP_REDIS" != "no"; then if test "$PHP_REDIS_SESSION" != "no"; then AC_DEFINE(PHP_SESSION,1,[redis sessions]) fi dnl Check for igbinary if test "$PHP_REDIS_IGBINARY" != "no"; then AC_MSG_CHECKING([for igbinary includes]) igbinary_inc_path="" if test -f "$abs_srcdir/include/php/ext/igbinary/igbinary.h"; then igbinary_inc_path="$abs_srcdir/include/php" elif test -f "$abs_srcdir/ext/igbinary/igbinary.h"; then igbinary_inc_path="$abs_srcdir" elif test -f "$phpincludedir/ext/igbinary/igbinary.h"; then igbinary_inc_path="$phpincludedir" else for i in php php4 php5 php6; do if test -f "$prefix/include/$i/ext/igbinary/igbinary.h"; then igbinary_inc_path="$prefix/include/$i" fi done fi if test "$igbinary_inc_path" = ""; then AC_MSG_ERROR([Cannot find igbinary.h]) else AC_MSG_RESULT([$igbinary_inc_path]) fi fi AC_MSG_CHECKING([for redis igbinary support]) if test "$PHP_REDIS_IGBINARY" != "no"; then AC_MSG_RESULT([enabled]) AC_DEFINE(HAVE_REDIS_IGBINARY,1,[Whether redis igbinary serializer is enabled]) IGBINARY_INCLUDES="-I$igbinary_inc_path" IGBINARY_EXT_DIR="$igbinary_inc_path/ext" ifdef([PHP_ADD_EXTENSION_DEP], [ PHP_ADD_EXTENSION_DEP(redis, igbinary) ]) PHP_ADD_INCLUDE($IGBINARY_EXT_DIR) else IGBINARY_INCLUDES="" AC_MSG_RESULT([disabled]) fi dnl # --with-redis -> check with-path dnl SEARCH_PATH="/usr/local /usr" # you might want to change this dnl SEARCH_FOR="/include/redis.h" # you most likely want to change this dnl if test -r $PHP_REDIS/$SEARCH_FOR; then # path given as parameter dnl REDIS_DIR=$PHP_REDIS dnl else # search default path list dnl AC_MSG_CHECKING([for redis files in default path]) dnl for i in $SEARCH_PATH ; do dnl if test -r $i/$SEARCH_FOR; then dnl REDIS_DIR=$i dnl AC_MSG_RESULT(found in $i) dnl fi dnl done dnl fi dnl dnl if test -z "$REDIS_DIR"; then dnl AC_MSG_RESULT([not found]) dnl AC_MSG_ERROR([Please reinstall the redis distribution]) dnl fi dnl # --with-redis -> add include path dnl PHP_ADD_INCLUDE($REDIS_DIR/include) dnl # --with-redis -> check for lib and symbol presence dnl LIBNAME=redis # you may want to change this dnl LIBSYMBOL=redis # you most likely want to change this dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, dnl [ dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $REDIS_DIR/lib, REDIS_SHARED_LIBADD) dnl AC_DEFINE(HAVE_REDISLIB,1,[ ]) dnl ],[ dnl AC_MSG_ERROR([wrong redis lib version or lib not found]) dnl ],[ dnl -L$REDIS_DIR/lib -lm -ldl dnl ]) dnl dnl PHP_SUBST(REDIS_SHARED_LIBADD) PHP_NEW_EXTENSION(redis, redis.c redis_commands.c library.c redis_session.c redis_array.c redis_array_impl.c redis_cluster.c cluster_library.c, $ext_shared) fi redis-3.1.6/config.w320000644000175000000120000000163313223116600015205 0ustar pyatsukhnenkowheel// vim: ft=javascript: ARG_ENABLE("redis", "whether to enable redis support", "no"); ARG_ENABLE("redis-session", "whether to enable sessions", "yes"); ARG_ENABLE("redis-igbinary", "whether to enable igbinary serializer support", "no"); if (PHP_REDIS != "no") { var sources = "redis.c redis_commands.c library.c redis_session.c redis_array.c redis_array_impl.c redis_cluster.c cluster_library.c"; if (PHP_REDIS_SESSION != "no") { ADD_EXTENSION_DEP("redis", "session"); ADD_FLAG("CFLAGS_REDIS", ' /D PHP_SESSION=1 '); AC_DEFINE("HAVE_REDIS_SESSION", 1); } if (PHP_REDIS_IGBINARY != "no") { if (CHECK_HEADER_ADD_INCLUDE("igbinary.h", "CFLAGS_REDIS", configure_module_dirname + "\\..\\igbinary")) { ADD_EXTENSION_DEP("redis", "igbinary"); AC_DEFINE("HAVE_REDIS_IGBINARY", 1); } else { WARNING("redis igbinary support not enabled"); } } EXTENSION("redis", sources); } redis-3.1.6/common.h0000644000175000000120000007701313223116600015051 0ustar pyatsukhnenkowheel#include "php.h" #include "php_ini.h" #ifndef REDIS_COMMON_H #define REDIS_COMMON_H #define PHPREDIS_NOTUSED(v) ((void)v) #include #include #if (PHP_MAJOR_VERSION < 7) #include typedef smart_str smart_string; #define smart_string_0(x) smart_str_0(x) #define smart_string_appendc(dest, c) smart_str_appendc(dest, c) #define smart_string_append_long(dest, val) smart_str_append_long(dest, val) #define smart_string_appendl(dest, src, len) smart_str_appendl(dest, src, len) typedef struct { short gc; size_t len; char *val; } zend_string; #define ZSTR_VAL(s) (s)->val #define ZSTR_LEN(s) (s)->len static zend_always_inline zend_string * zend_string_init(const char *str, size_t len, int persistent) { zend_string *zstr = emalloc(sizeof(zend_string) + len + 1); ZSTR_VAL(zstr) = (char *)zstr + sizeof(zend_string); memcpy(ZSTR_VAL(zstr), str, len); ZSTR_VAL(zstr)[len] = '\0'; zstr->len = len; zstr->gc = 0x01; return zstr; } #define zend_string_release(s) do { \ if ((s) && (s)->gc) { \ if ((s)->gc & 0x10 && ZSTR_VAL(s)) efree(ZSTR_VAL(s)); \ if ((s)->gc & 0x01) efree((s)); \ } \ } while (0) #define ZEND_HASH_FOREACH_KEY_VAL(ht, _h, _key, _val) do { \ HashPosition _hpos; \ for (zend_hash_internal_pointer_reset_ex(ht, &_hpos); \ zend_hash_has_more_elements_ex(ht, &_hpos) == SUCCESS; \ zend_hash_move_forward_ex(ht, &_hpos) \ ) { \ zend_string _zstr = {0}; \ char *_str_index; uint _str_length; ulong _num_index; \ _h = 0; _key = NULL; _val = zend_hash_get_current_data_ex(ht, &_hpos); \ switch (zend_hash_get_current_key_ex(ht, &_str_index, &_str_length, &_num_index, 0, &_hpos)) { \ case HASH_KEY_IS_STRING: \ _zstr.len = _str_length - 1; \ _zstr.val = _str_index; \ _key = &_zstr; \ break; \ case HASH_KEY_IS_LONG: \ _h = _num_index; \ break; \ default: \ /* noop */ break; \ } #define ZEND_HASH_FOREACH_VAL(ht, _val) do { \ HashPosition _hpos; \ for (zend_hash_internal_pointer_reset_ex(ht, &_hpos); \ zend_hash_has_more_elements_ex(ht, &_hpos) == SUCCESS; \ zend_hash_move_forward_ex(ht, &_hpos) \ ) { \ _val = zend_hash_get_current_data_ex(ht, &_hpos); \ #define ZEND_HASH_FOREACH_PTR(ht, _ptr) do { \ HashPosition _hpos; \ for (zend_hash_internal_pointer_reset_ex(ht, &_hpos); \ zend_hash_has_more_elements_ex(ht, &_hpos) == SUCCESS; \ zend_hash_move_forward_ex(ht, &_hpos) \ ) { \ _ptr = zend_hash_get_current_data_ptr_ex(ht, &_hpos); \ #define ZEND_HASH_FOREACH_END() \ } \ } while(0) #undef zend_hash_get_current_key #define zend_hash_get_current_key(ht, str_index, num_index) \ zend_hash_get_current_key_ex(ht, str_index, NULL, num_index, 0, NULL) #define zend_hash_str_exists(ht, str, len) zend_hash_exists(ht, str, len + 1) static zend_always_inline zval * zend_hash_str_find(const HashTable *ht, const char *key, size_t len) { zval **zv; if (zend_hash_find(ht, key, len + 1, (void **)&zv) == SUCCESS) { return *zv; } return NULL; } static zend_always_inline void * zend_hash_str_find_ptr(const HashTable *ht, const char *str, size_t len) { void **ptr; if (zend_hash_find(ht, str, len + 1, (void **)&ptr) == SUCCESS) { return *ptr; } return NULL; } static zend_always_inline void * zend_hash_str_update_ptr(HashTable *ht, const char *str, size_t len, void *pData) { if (zend_hash_update(ht, str, len + 1, (void *)&pData, sizeof(void *), NULL) == SUCCESS) { return pData; } return NULL; } static zend_always_inline void * zend_hash_index_update_ptr(HashTable *ht, zend_ulong h, void *pData) { if (zend_hash_index_update(ht, h, (void **)&pData, sizeof(void *), NULL) == SUCCESS) { return pData; } return NULL; } #undef zend_hash_get_current_data static zend_always_inline zval * zend_hash_get_current_data(HashTable *ht) { zval **zv; if (zend_hash_get_current_data_ex(ht, (void **)&zv, NULL) == SUCCESS) { return *zv; } return NULL; } static zend_always_inline void * zend_hash_get_current_data_ptr_ex(HashTable *ht, HashPosition *pos) { void **ptr; if (zend_hash_get_current_data_ex(ht, (void **)&ptr, pos) == SUCCESS) { return *ptr; } return NULL; } #define zend_hash_get_current_data_ptr(ht) zend_hash_get_current_data_ptr_ex(ht, NULL) static int (*_zend_hash_index_find)(const HashTable *, ulong, void **) = &zend_hash_index_find; #define zend_hash_index_find(ht, h) inline_zend_hash_index_find(ht, h) static zend_always_inline zval * inline_zend_hash_index_find(const HashTable *ht, zend_ulong h) { zval **zv; if (_zend_hash_index_find(ht, h, (void **)&zv) == SUCCESS) { return *zv; } return NULL; } static zend_always_inline void * zend_hash_index_find_ptr(const HashTable *ht, zend_ulong h) { void **ptr; if (_zend_hash_index_find(ht, h, (void **)&ptr) == SUCCESS) { return *ptr; } return NULL; } static int (*_zend_hash_get_current_data_ex)(HashTable *, void **, HashPosition *) = &zend_hash_get_current_data_ex; #define zend_hash_get_current_data_ex(ht, pos) inline_zend_hash_get_current_data_ex(ht, pos) static zend_always_inline zval * inline_zend_hash_get_current_data_ex(HashTable *ht, HashPosition *pos) { zval **zv; if (_zend_hash_get_current_data_ex(ht, (void **)&zv, pos) == SUCCESS) { return *zv; } return NULL; } #undef zend_hash_next_index_insert #define zend_hash_next_index_insert(ht, pData) \ _zend_hash_next_index_insert(ht, pData ZEND_FILE_LINE_CC) static zend_always_inline zval * _zend_hash_next_index_insert(HashTable *ht, zval *pData ZEND_FILE_LINE_DC) { if (_zend_hash_index_update_or_next_insert(ht, 0, &pData, sizeof(pData), NULL, HASH_NEXT_INSERT ZEND_FILE_LINE_CC) == SUCCESS ) { return pData; } return NULL; } #undef zend_get_parameters_array #define zend_get_parameters_array(ht, param_count, argument_array) \ inline_zend_get_parameters_array(ht, param_count, argument_array TSRMLS_CC) static zend_always_inline int inline_zend_get_parameters_array(int ht, int param_count, zval *argument_array TSRMLS_DC) { int i, ret = FAILURE; zval **zv = ecalloc(param_count, sizeof(zval *)); if (_zend_get_parameters_array(ht, param_count, zv TSRMLS_CC) == SUCCESS) { for (i = 0; i < param_count; i++) { argument_array[i] = *zv[i]; } ret = SUCCESS; } efree(zv); return ret; } typedef zend_rsrc_list_entry zend_resource; extern int (*_add_next_index_string)(zval *, const char *, int); #define add_next_index_string(arg, str) _add_next_index_string(arg, str, 1); extern int (*_add_next_index_stringl)(zval *, const char *, uint, int); #define add_next_index_stringl(arg, str, length) _add_next_index_stringl(arg, str, length, 1); #undef ZVAL_STRING #define ZVAL_STRING(z, s) do { \ const char *_s = (s); \ ZVAL_STRINGL(z, _s, strlen(_s)); \ } while (0) #undef RETVAL_STRING #define RETVAL_STRING(s) ZVAL_STRING(return_value, s) #undef RETURN_STRING #define RETURN_STRING(s) { RETVAL_STRING(s); return; } #undef ZVAL_STRINGL #define ZVAL_STRINGL(z, s, l) do { \ const char *__s = (s); int __l = l; \ zval *__z = (z); \ Z_STRLEN_P(__z) = __l; \ Z_STRVAL_P(__z) = estrndup(__s, __l); \ Z_TYPE_P(__z) = IS_STRING; \ } while (0) #undef RETVAL_STRINGL #define RETVAL_STRINGL(s, l) ZVAL_STRINGL(return_value, s, l) #undef RETURN_STRINGL #define RETURN_STRINGL(s, l) { RETVAL_STRINGL(s, l); return; } static int (*_call_user_function)(HashTable *, zval **, zval *, zval *, zend_uint, zval *[] TSRMLS_DC) = &call_user_function; #define call_user_function(function_table, object, function_name, retval_ptr, param_count, params) \ inline_call_user_function(function_table, object, function_name, retval_ptr, param_count, params TSRMLS_CC) static zend_always_inline int inline_call_user_function(HashTable *function_table, zval *object, zval *function_name, zval *retval_ptr, zend_uint param_count, zval params[] TSRMLS_DC) { int i, ret; zval **_params = NULL; if (!params) param_count = 0; if (param_count > 0) { _params = ecalloc(param_count, sizeof(zval *)); for (i = 0; i < param_count; ++i) { zval *zv = ¶ms[i]; MAKE_STD_ZVAL(_params[i]); ZVAL_ZVAL(_params[i], zv, 1, 0); } } ret = _call_user_function(function_table, &object, function_name, retval_ptr, param_count, _params TSRMLS_CC); if (_params) { for (i = 0; i < param_count; ++i) { zval_ptr_dtor(&_params[i]); } efree(_params); } return ret; } #undef add_assoc_bool #define add_assoc_bool(__arg, __key, __b) add_assoc_bool_ex(__arg, __key, strlen(__key), __b) extern int (*_add_assoc_bool_ex)(zval *, const char *, uint, int); #define add_assoc_bool_ex(_arg, _key, _key_len, _b) _add_assoc_bool_ex(_arg, _key, _key_len + 1, _b) #undef add_assoc_long #define add_assoc_long(__arg, __key, __n) add_assoc_long_ex(__arg, __key, strlen(__key), __n) extern int (*_add_assoc_long_ex)(zval *, const char *, uint, long); #define add_assoc_long_ex(_arg, _key, _key_len, _n) _add_assoc_long_ex(_arg, _key, _key_len + 1, _n) #undef add_assoc_double #define add_assoc_double(__arg, __key, __d) add_assoc_double_ex(__arg, __key, strlen(__key), __d) extern int (*_add_assoc_double_ex)(zval *, const char *, uint, double); #define add_assoc_double_ex(_arg, _key, _key_len, _d) _add_assoc_double_ex(_arg, _key, _key_len + 1, _d) #undef add_assoc_string #define add_assoc_string(__arg, __key, __str) add_assoc_string_ex(__arg, __key, strlen(__key), __str) extern int (*_add_assoc_string_ex)(zval *, const char *, uint, char *, int); #define add_assoc_string_ex(_arg, _key, _key_len, _str) _add_assoc_string_ex(_arg, _key, _key_len + 1, _str, 1) extern int (*_add_assoc_stringl_ex)(zval *, const char *, uint, char *, uint, int); #define add_assoc_stringl_ex(_arg, _key, _key_len, _str, _length) _add_assoc_stringl_ex(_arg, _key, _key_len + 1, _str, _length, 1) #undef add_assoc_zval #define add_assoc_zval(__arg, __key, __value) add_assoc_zval_ex(__arg, __key, strlen(__key), __value) extern int (*_add_assoc_zval_ex)(zval *, const char *, uint, zval *); #define add_assoc_zval_ex(_arg, _key, _key_len, _value) _add_assoc_zval_ex(_arg, _key, _key_len + 1, _value); typedef long zend_long; static zend_always_inline zend_long zval_get_long(zval *op) { switch (Z_TYPE_P(op)) { case IS_BOOL: case IS_LONG: return Z_LVAL_P(op); case IS_DOUBLE: return zend_dval_to_lval(Z_DVAL_P(op)); case IS_STRING: { double dval; zend_long lval; zend_uchar type = is_numeric_string(Z_STRVAL_P(op), Z_STRLEN_P(op), &lval, &dval, 0); if (type == IS_LONG) { return lval; } else if (type == IS_DOUBLE) { return zend_dval_to_lval(dval); } } break; EMPTY_SWITCH_DEFAULT_CASE() } return 0; } static zend_always_inline double zval_get_double(zval *op) { switch (Z_TYPE_P(op)) { case IS_BOOL: case IS_LONG: return (double)Z_LVAL_P(op); case IS_DOUBLE: return Z_DVAL_P(op); case IS_STRING: return zend_strtod(Z_STRVAL_P(op), NULL); EMPTY_SWITCH_DEFAULT_CASE() } return 0.0; } static zend_always_inline zend_string * zval_get_string(zval *op) { zend_string *zstr = ecalloc(1, sizeof(zend_string)); zstr->gc = 0; ZSTR_VAL(zstr) = ""; ZSTR_LEN(zstr) = 0; switch (Z_TYPE_P(op)) { case IS_STRING: ZSTR_VAL(zstr) = Z_STRVAL_P(op); ZSTR_LEN(zstr) = Z_STRLEN_P(op); break; case IS_BOOL: if (Z_LVAL_P(op)) { ZSTR_VAL(zstr) = "1"; ZSTR_LEN(zstr) = 1; } break; case IS_LONG: { zstr->gc = 0x10; ZSTR_LEN(zstr) = spprintf(&ZSTR_VAL(zstr), 0, "%ld", Z_LVAL_P(op)); break; } case IS_DOUBLE: { zstr->gc = 0x10; ZSTR_LEN(zstr) = spprintf(&ZSTR_VAL(zstr), 0, "%.16g", Z_DVAL_P(op)); break; } EMPTY_SWITCH_DEFAULT_CASE() } zstr->gc |= 0x01; return zstr; } extern void (*_php_var_serialize)(smart_str *, zval **, php_serialize_data_t * TSRMLS_DC); #define php_var_serialize(buf, struc, data) _php_var_serialize(buf, &struc, data TSRMLS_CC) extern int (*_php_var_unserialize)(zval **, const unsigned char **, const unsigned char *, php_unserialize_data_t * TSRMLS_DC); #define php_var_unserialize(rval, p, max, var_hash) _php_var_unserialize(&rval, p, max, var_hash TSRMLS_CC) typedef int strlen_t; #define PHPREDIS_ZVAL_IS_STRICT_FALSE(z) (Z_TYPE_P(z) == IS_BOOL && !Z_BVAL_P(z)) /* If ZEND_MOD_END isn't defined, use legacy version */ #ifndef ZEND_MOD_END #define ZEND_MOD_END { NULL, NULL, NULL } #endif /* PHP_FE_END exists since 5.3.7 */ #ifndef PHP_FE_END #define PHP_FE_END { NULL, NULL, NULL } #endif /* References don't need any actions */ #define ZVAL_DEREF(v) PHPREDIS_NOTUSED(v) #else #include #include typedef size_t strlen_t; #define PHPREDIS_ZVAL_IS_STRICT_FALSE(z) (Z_TYPE_P(z) == IS_FALSE) #endif /* NULL check so Eclipse doesn't go crazy */ #ifndef NULL #define NULL ((void *) 0) #endif #define REDIS_SOCK_STATUS_FAILED 0 #define REDIS_SOCK_STATUS_DISCONNECTED 1 #define REDIS_SOCK_STATUS_CONNECTED 2 #define _NL "\r\n" /* properties */ #define REDIS_NOT_FOUND 0 #define REDIS_STRING 1 #define REDIS_SET 2 #define REDIS_LIST 3 #define REDIS_ZSET 4 #define REDIS_HASH 5 #ifdef PHP_WIN32 #define PHP_REDIS_API __declspec(dllexport) #else #define PHP_REDIS_API #endif /* reply types */ typedef enum _REDIS_REPLY_TYPE { TYPE_EOF = -1, TYPE_LINE = '+', TYPE_INT = ':', TYPE_ERR = '-', TYPE_BULK = '$', TYPE_MULTIBULK = '*' } REDIS_REPLY_TYPE; /* SCAN variants */ typedef enum _REDIS_SCAN_TYPE { TYPE_SCAN, TYPE_SSCAN, TYPE_HSCAN, TYPE_ZSCAN } REDIS_SCAN_TYPE; /* PUBSUB subcommands */ typedef enum _PUBSUB_TYPE { PUBSUB_CHANNELS, PUBSUB_NUMSUB, PUBSUB_NUMPAT } PUBSUB_TYPE; /* options */ #define REDIS_OPT_SERIALIZER 1 #define REDIS_OPT_PREFIX 2 #define REDIS_OPT_READ_TIMEOUT 3 #define REDIS_OPT_SCAN 4 #define REDIS_OPT_FAILOVER 5 /* cluster options */ #define REDIS_FAILOVER_NONE 0 #define REDIS_FAILOVER_ERROR 1 #define REDIS_FAILOVER_DISTRIBUTE 2 #define REDIS_FAILOVER_DISTRIBUTE_SLAVES 3 /* serializers */ #define REDIS_SERIALIZER_NONE 0 #define REDIS_SERIALIZER_PHP 1 #define REDIS_SERIALIZER_IGBINARY 2 /* SCAN options */ #define REDIS_SCAN_NORETRY 0 #define REDIS_SCAN_RETRY 1 /* GETBIT/SETBIT offset range limits */ #define BITOP_MIN_OFFSET 0 #define BITOP_MAX_OFFSET 4294967295U /* Transaction modes */ #define ATOMIC 0 #define MULTI 1 #define PIPELINE 2 #define IF_ATOMIC() if (redis_sock->mode == ATOMIC) #define IF_NOT_ATOMIC() if (redis_sock->mode != ATOMIC) #define IF_MULTI() if (redis_sock->mode & MULTI) #define IF_NOT_MULTI() if (!(redis_sock->mode & MULTI)) #define IF_PIPELINE() if (redis_sock->mode & PIPELINE) #define IF_NOT_PIPELINE() if (!(redis_sock->mode & PIPELINE)) #define PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len) do { \ if (redis_sock->pipeline_cmd == NULL) { \ redis_sock->pipeline_cmd = estrndup(cmd, cmd_len); \ } else { \ redis_sock->pipeline_cmd = erealloc(redis_sock->pipeline_cmd, \ redis_sock->pipeline_len + cmd_len); \ memcpy(&redis_sock->pipeline_cmd[redis_sock->pipeline_len], \ cmd, cmd_len); \ } \ redis_sock->pipeline_len += cmd_len; \ } while (0) #define SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len) \ if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { \ efree(cmd); \ RETURN_FALSE; \ } #define REDIS_SAVE_CALLBACK(callback, closure_context) do { \ fold_item *fi = malloc(sizeof(fold_item)); \ fi->fun = (void *)callback; \ fi->ctx = closure_context; \ fi->next = NULL; \ if (redis_sock->current) { \ redis_sock->current->next = fi; \ } \ redis_sock->current = fi; \ if (NULL == redis_sock->head) { \ redis_sock->head = redis_sock->current; \ } \ } while (0) #define REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len) \ IF_PIPELINE() { \ PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len); \ } else { \ SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len); \ } \ efree(cmd); #define REDIS_PROCESS_RESPONSE_CLOSURE(function, closure_context) \ IF_NOT_PIPELINE() { \ if (redis_response_enqueued(redis_sock TSRMLS_CC) != SUCCESS) { \ RETURN_FALSE; \ } \ } \ REDIS_SAVE_CALLBACK(function, closure_context); \ RETURN_ZVAL(getThis(), 1, 0); \ #define REDIS_PROCESS_RESPONSE(function) else { \ REDIS_PROCESS_RESPONSE_CLOSURE(function, NULL) \ } /* Clear redirection info */ #define REDIS_MOVED_CLEAR(redis_sock) \ redis_sock->redir_slot = 0; \ redis_sock->redir_port = 0; \ redis_sock->redir_type = MOVED_NONE; \ /* Process a command assuming our command where our command building * function is redis__cmd */ #define REDIS_PROCESS_CMD(cmdname, resp_func) \ RedisSock *redis_sock; char *cmd; int cmd_len; void *ctx=NULL; \ if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL || \ redis_##cmdname##_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU,redis_sock, \ &cmd, &cmd_len, NULL, &ctx)==FAILURE) { \ RETURN_FALSE; \ } \ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); \ IF_ATOMIC() { \ resp_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, ctx); \ } else { \ REDIS_PROCESS_RESPONSE_CLOSURE(resp_func, ctx) \ } /* Process a command but with a specific command building function * and keyword which is passed to us*/ #define REDIS_PROCESS_KW_CMD(kw, cmdfunc, resp_func) \ RedisSock *redis_sock; char *cmd; int cmd_len; void *ctx=NULL; \ if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL || \ cmdfunc(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, kw, &cmd, \ &cmd_len, NULL, &ctx)==FAILURE) { \ RETURN_FALSE; \ } \ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); \ IF_ATOMIC() { \ resp_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, ctx); \ } else { \ REDIS_PROCESS_RESPONSE_CLOSURE(resp_func, ctx) \ } #define REDIS_STREAM_CLOSE_MARK_FAILED(redis_sock) \ redis_stream_close(redis_sock TSRMLS_CC); \ redis_sock->stream = NULL; \ redis_sock->mode = ATOMIC; \ redis_sock->status = REDIS_SOCK_STATUS_FAILED; \ redis_sock->watching = 0 /* Extended SET argument detection */ #define IS_EX_ARG(a) \ ((a[0]=='e' || a[0]=='E') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') #define IS_PX_ARG(a) \ ((a[0]=='p' || a[0]=='P') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') #define IS_NX_ARG(a) \ ((a[0]=='n' || a[0]=='N') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') #define IS_XX_ARG(a) \ ((a[0]=='x' || a[0]=='X') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') #define IS_EX_PX_ARG(a) (IS_EX_ARG(a) || IS_PX_ARG(a)) #define IS_NX_XX_ARG(a) (IS_NX_ARG(a) || IS_XX_ARG(a)) /* Given a string and length, validate a zRangeByLex argument. The semantics * here are that the argument must start with '(' or '[' or be just the char * '+' or '-' */ #define IS_LEX_ARG(s,l) \ (l>0 && (*s=='(' || *s=='[' || (l==1 && (*s=='+' || *s=='-')))) #define REDIS_ENABLE_MODE(redis_sock, m) (redis_sock->mode |= m) #define REDIS_DISABLE_MODE(redis_sock, m) (redis_sock->mode &= ~m) typedef struct fold_item { zval * (*fun)(INTERNAL_FUNCTION_PARAMETERS, void *, ...); void *ctx; struct fold_item *next; } fold_item; /* {{{ struct RedisSock */ typedef struct { php_stream *stream; zend_string *host; short port; zend_string *auth; double timeout; double read_timeout; long retry_interval; int failed; int status; int persistent; int watching; zend_string *persistent_id; int serializer; long dbNumber; zend_string *prefix; short mode; fold_item *head; fold_item *current; char *pipeline_cmd; size_t pipeline_len; zend_string *err; zend_bool lazy_connect; int scan; int readonly; } RedisSock; /* }}} */ #if (PHP_MAJOR_VERSION < 7) typedef struct { zend_object std; RedisSock *sock; } redis_object; #else typedef struct { RedisSock *sock; zend_object std; } redis_object; #endif /** Argument info for any function expecting 0 args */ ZEND_BEGIN_ARG_INFO_EX(arginfo_void, 0, 0, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_key, 0, 0, 1) ZEND_ARG_INFO(0, key) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_value, 0, 0, 1) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_key_value, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_key_expire_value, 0, 0, 3) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, expire) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_key_newkey, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, newkey) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_pairs, 0, 0, 1) ZEND_ARG_ARRAY_INFO(0, pairs, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_nkeys, 0, 0, 1) ZEND_ARG_INFO(0, key) #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, other_keys) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_dst_nkeys, 0, 0, 2) ZEND_ARG_INFO(0, dst) ZEND_ARG_INFO(0, key) #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, other_keys) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_key_min_max, 0, 0, 3) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, min) ZEND_ARG_INFO(0, max) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_key_member, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, member) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_key_member_value, 0, 0, 3) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, member) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_key_members, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, member) #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, other_members) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_key_timestamp, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, timestamp) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_key_offset, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, offset) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_key_offset_value, 0, 0, 3) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, offset) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_key_start_end, 0, 0, 3) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, start) ZEND_ARG_INFO(0, end) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_echo, 0, 0, 1) ZEND_ARG_INFO(0, msg) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_expire, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, timeout) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_set, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, value) ZEND_ARG_INFO(0, timeout) ZEND_ARG_INFO(0, opt) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_lset, 0, 0, 3) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, index) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_blrpop, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, timeout_or_key) // Can't have variadic keys before timeout. #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, extra_args) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_linsert, 0, 0, 4) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, position) ZEND_ARG_INFO(0, pivot) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_lindex, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, index) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_brpoplpush, 0, 0, 3) ZEND_ARG_INFO(0, src) ZEND_ARG_INFO(0, dst) ZEND_ARG_INFO(0, timeout) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_rpoplpush, 0, 0, 2) ZEND_ARG_INFO(0, src) ZEND_ARG_INFO(0, dst) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_sadd_array, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_ARRAY_INFO(0, options, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_srand_member, 0, 0, 1) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, count) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_zadd, 0, 0, 3) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, score) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_zincrby, 0, 0, 3) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, value) ZEND_ARG_INFO(0, member) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_hmget, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_ARRAY_INFO(0, keys, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_hmset, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_ARRAY_INFO(0, pairs, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_bitop, 0, 0, 3) ZEND_ARG_INFO(0, operation) ZEND_ARG_INFO(0, ret_key) ZEND_ARG_INFO(0, key) #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, other_keys) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_bitpos, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, bit) ZEND_ARG_INFO(0, start) ZEND_ARG_INFO(0, end) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_ltrim, 0, 0, 3) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, start) ZEND_ARG_INFO(0, stop) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_publish, 0, 0, 2) ZEND_ARG_INFO(0, channel) ZEND_ARG_INFO(0, message) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_pfadd, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_ARRAY_INFO(0, elements, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_pfmerge, 0, 0, 2) ZEND_ARG_INFO(0, dstkey) ZEND_ARG_ARRAY_INFO(0, keys, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_restore, 0, 0, 3) ZEND_ARG_INFO(0, ttl) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_smove, 0, 0, 3) ZEND_ARG_INFO(0, src) ZEND_ARG_INFO(0, dst) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_zrange, 0, 0, 3) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, start) ZEND_ARG_INFO(0, end) ZEND_ARG_INFO(0, scores) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_zrangebyscore, 0, 0, 3) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, start) ZEND_ARG_INFO(0, end) ZEND_ARG_ARRAY_INFO(0, options, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_zrangebylex, 0, 0, 3) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, min) ZEND_ARG_INFO(0, max) ZEND_ARG_INFO(0, offset) ZEND_ARG_INFO(0, limit) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_zstore, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_ARRAY_INFO(0, keys, 0) ZEND_ARG_ARRAY_INFO(0, weights, 1) ZEND_ARG_INFO(0, aggregate) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_sort, 0, 0, 1) ZEND_ARG_INFO(0, key) ZEND_ARG_ARRAY_INFO(0, options, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_object, 0, 0, 2) ZEND_ARG_INFO(0, field) ZEND_ARG_INFO(0, key) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_subscribe, 0, 0, 1) ZEND_ARG_ARRAY_INFO(0, channels, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_psubscribe, 0, 0, 1) ZEND_ARG_ARRAY_INFO(0, patterns, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_unsubscribe, 0, 0, 1) ZEND_ARG_INFO(0, channel) #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, other_channels) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_punsubscribe, 0, 0, 1) ZEND_ARG_INFO(0, pattern) #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, other_patterns) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_eval, 0, 0, 1) ZEND_ARG_INFO(0, script) ZEND_ARG_INFO(0, args) ZEND_ARG_INFO(0, num_keys) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_evalsha, 0, 0, 1) ZEND_ARG_INFO(0, script_sha) ZEND_ARG_INFO(0, args) ZEND_ARG_INFO(0, num_keys) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_getoption, 0, 0, 1) ZEND_ARG_INFO(0, option) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_setoption, 0, 0, 2) ZEND_ARG_INFO(0, option) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_watch, 0, 0, 1) ZEND_ARG_INFO(0, key) #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, other_keys) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_info, 0, 0, 0) ZEND_ARG_INFO(0, option) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_command, 0, 0, 0) #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, args) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_rawcommand, 0, 0, 1) ZEND_ARG_INFO(0, cmd) #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, args) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_client, 0, 0, 1) ZEND_ARG_INFO(0, cmd) #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, args) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_config, 0, 0, 2) ZEND_ARG_INFO(0, cmd) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_pubsub, 0, 0, 1) ZEND_ARG_INFO(0, cmd) #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, args) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_script, 0, 0, 1) ZEND_ARG_INFO(0, cmd) #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, args) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_slowlog, 0, 0, 1) ZEND_ARG_INFO(0, arg) ZEND_ARG_INFO(0, option) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_geoadd, 0, 0, 4) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, lng) ZEND_ARG_INFO(0, lat) ZEND_ARG_INFO(0, member) #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, other_triples) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_geodist, 0, 0, 3) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, src) ZEND_ARG_INFO(0, dst) ZEND_ARG_INFO(0, unit) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_georadius, 0, 0, 5) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, lng) ZEND_ARG_INFO(0, lan) ZEND_ARG_INFO(0, radius) ZEND_ARG_INFO(0, unit) ZEND_ARG_ARRAY_INFO(0, opts, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_georadiusbymember, 0, 0, 4) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, member) ZEND_ARG_INFO(0, radius) ZEND_ARG_INFO(0, unit) ZEND_ARG_ARRAY_INFO(0, opts, 0) ZEND_END_ARG_INFO() #endif redis-3.1.6/library.c0000644000175000000120000017466713223116600015235 0ustar pyatsukhnenkowheel#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "common.h" #include "php_network.h" #include #ifndef _MSC_VER #include /* TCP_NODELAY */ #include #endif #ifdef HAVE_REDIS_IGBINARY #include "igbinary/igbinary.h" #endif #include #include "php_redis.h" #include "library.h" #include "redis_commands.h" #include #define UNSERIALIZE_NONE 0 #define UNSERIALIZE_KEYS 1 #define UNSERIALIZE_VALS 2 #define UNSERIALIZE_ALL 3 #define SCORE_DECODE_NONE 0 #define SCORE_DECODE_INT 1 #define SCORE_DECODE_DOUBLE 2 #ifdef PHP_WIN32 # if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION <= 4 /* This proto is available from 5.5 on only */ PHP_REDIS_API int usleep(unsigned int useconds); # endif #endif #if (PHP_MAJOR_VERSION < 7) int (*_add_next_index_string)(zval *, const char *, int) = &add_next_index_string; int (*_add_next_index_stringl)(zval *, const char *, uint, int) = &add_next_index_stringl; int (*_add_assoc_bool_ex)(zval *, const char *, uint, int) = &add_assoc_bool_ex; int (*_add_assoc_long_ex)(zval *, const char *, uint, long) = &add_assoc_long_ex; int (*_add_assoc_double_ex)(zval *, const char *, uint, double) = &add_assoc_double_ex; int (*_add_assoc_string_ex)(zval *, const char *, uint, char *, int) = &add_assoc_string_ex; int (*_add_assoc_stringl_ex)(zval *, const char *, uint, char *, uint, int) = &add_assoc_stringl_ex; int (*_add_assoc_zval_ex)(zval *, const char *, uint, zval *) = &add_assoc_zval_ex; void (*_php_var_serialize)(smart_str *, zval **, php_serialize_data_t * TSRMLS_DC) = &php_var_serialize; int (*_php_var_unserialize)(zval **, const unsigned char **, const unsigned char *, php_unserialize_data_t * TSRMLS_DC) = &php_var_unserialize; #endif extern zend_class_entry *redis_ce; extern zend_class_entry *redis_exception_ce; /* Helper to reselect the proper DB number when we reconnect */ static int reselect_db(RedisSock *redis_sock TSRMLS_DC) { char *cmd, *response; int cmd_len, response_len; cmd_len = redis_spprintf(redis_sock, NULL TSRMLS_CC, &cmd, "SELECT", "d", redis_sock->dbNumber); if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); return -1; } efree(cmd); if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { return -1; } if (strncmp(response, "+OK", 3)) { efree(response); return -1; } efree(response); return 0; } /* Helper to resend AUTH in the case of a reconnect */ static int resend_auth(RedisSock *redis_sock TSRMLS_DC) { char *cmd, *response; int cmd_len, response_len; cmd_len = redis_spprintf(redis_sock, NULL TSRMLS_CC, &cmd, "AUTH", "s", ZSTR_VAL(redis_sock->auth), ZSTR_LEN(redis_sock->auth)); if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); return -1; } efree(cmd); response = redis_sock_read(redis_sock, &response_len TSRMLS_CC); if (response == NULL) { return -1; } if (strncmp(response, "+OK", 3)) { efree(response); return -1; } efree(response); return 0; } /* Helper function that will throw an exception for a small number of ERR codes * returned by Redis. Typically we just return FALSE to the caller in the event * of an ERROR reply, but for the following error types: * 1) MASTERDOWN * 2) AUTH * 3) LOADING */ static void redis_error_throw(RedisSock *redis_sock TSRMLS_DC) { if (redis_sock != NULL && redis_sock->err != NULL && memcmp(ZSTR_VAL(redis_sock->err), "ERR", sizeof("ERR") - 1) != 0 && memcmp(ZSTR_VAL(redis_sock->err), "NOSCRIPT", sizeof("NOSCRIPT") - 1) != 0 && memcmp(ZSTR_VAL(redis_sock->err), "WRONGTYPE", sizeof("WRONGTYPE") - 1) != 0 ) { zend_throw_exception(redis_exception_ce, ZSTR_VAL(redis_sock->err), 0 TSRMLS_CC); } } PHP_REDIS_API void redis_stream_close(RedisSock *redis_sock TSRMLS_DC) { if (!redis_sock->persistent) { php_stream_close(redis_sock->stream); } else { php_stream_pclose(redis_sock->stream); } } PHP_REDIS_API int redis_check_eof(RedisSock *redis_sock, int no_throw TSRMLS_DC) { int count; if (!redis_sock->stream) { return -1; } /* NOITCE: set errno = 0 here * * There is a bug in php socket stream to check liveness of a connection: * if (0 >= recv(sock->socket, &buf, sizeof(buf), MSG_PEEK) && php_socket_errno() != EWOULDBLOCK) { * alive = 0; * } * If last errno is EWOULDBLOCK and recv returns 0 because of connection closed, alive would not be * set to 0. However, the connection is close indeed. The php_stream_eof is not reliable. This will * cause a "read error on connection" exception when use a closed persistent connection. * * We work around this by set errno = 0 first. * * Bug fix of php: https://github.com/php/php-src/pull/1456 * */ errno = 0; if (php_stream_eof(redis_sock->stream) == 0) { /* Success */ return 0; } else if (redis_sock->mode == MULTI || redis_sock->watching) { REDIS_STREAM_CLOSE_MARK_FAILED(redis_sock); if (!no_throw) { zend_throw_exception(redis_exception_ce, "Connection lost and socket is in MULTI/watching mode", 0 TSRMLS_CC); } return -1; } /* TODO: configurable max retry count */ for (count = 0; count < 10; ++count) { /* close existing stream before reconnecting */ if (redis_sock->stream) { redis_stream_close(redis_sock TSRMLS_CC); redis_sock->stream = NULL; } // Wait for a while before trying to reconnect if (redis_sock->retry_interval) { // Random factor to avoid having several (or many) concurrent connections trying to reconnect at the same time long retry_interval = (count ? redis_sock->retry_interval : (php_rand(TSRMLS_C) % redis_sock->retry_interval)); usleep(retry_interval); } /* reconnect */ if (redis_sock_connect(redis_sock TSRMLS_CC) == 0) { /* check for EOF again. */ errno = 0; if (php_stream_eof(redis_sock->stream) == 0) { /* If we're using a password, attempt a reauthorization */ if (redis_sock->auth && resend_auth(redis_sock TSRMLS_CC) != 0) { break; } /* If we're using a non-zero db, reselect it */ if (redis_sock->dbNumber && reselect_db(redis_sock TSRMLS_CC) != 0) { break; } /* Success */ return 0; } } } /* close stream if still here */ if (redis_sock->stream) { REDIS_STREAM_CLOSE_MARK_FAILED(redis_sock); } if (!no_throw) { zend_throw_exception(redis_exception_ce, "Connection lost", 0 TSRMLS_CC); } return -1; } PHP_REDIS_API int redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, REDIS_SCAN_TYPE type, zend_long *iter) { REDIS_REPLY_TYPE reply_type; long reply_info; char *p_iter; /* Our response should have two multibulk replies */ if(redis_read_reply_type(redis_sock, &reply_type, &reply_info TSRMLS_CC)<0 || reply_type != TYPE_MULTIBULK || reply_info != 2) { return -1; } /* The BULK response iterator */ if(redis_read_reply_type(redis_sock, &reply_type, &reply_info TSRMLS_CC)<0 || reply_type != TYPE_BULK) { return -1; } /* Attempt to read the iterator */ if(!(p_iter = redis_sock_read_bulk_reply(redis_sock, reply_info TSRMLS_CC))) { return -1; } /* Push the iterator out to the caller */ *iter = atol(p_iter); efree(p_iter); /* Read our actual keys/members/etc differently depending on what kind of scan command this is. They all come back in slightly different ways */ switch(type) { case TYPE_SCAN: return redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); case TYPE_SSCAN: return redis_sock_read_multibulk_reply( INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); case TYPE_ZSCAN: return redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); case TYPE_HSCAN: return redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); default: return -1; } } PHP_REDIS_API int redis_subscribe_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { subscribeContext *sctx = (subscribeContext*)ctx; zval *z_tmp, z_resp; // Consume response(s) from subscribe, which will vary on argc while(sctx->argc--) { if (!redis_sock_read_multibulk_reply_zval( INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp) ) { efree(sctx); return -1; } // We'll need to find the command response if ((z_tmp = zend_hash_index_find(Z_ARRVAL(z_resp), 0)) == NULL) { zval_dtor(&z_resp); efree(sctx); return -1; } // Make sure the command response matches the command we called if(strcasecmp(Z_STRVAL_P(z_tmp), sctx->kw) !=0) { zval_dtor(&z_resp); efree(sctx); return -1; } zval_dtor(&z_resp); } #if (PHP_MAJOR_VERSION < 7) zval *z_ret, **z_args[4]; sctx->cb.retval_ptr_ptr = &z_ret; #else zval z_ret, z_args[4]; sctx->cb.retval = &z_ret; #endif sctx->cb.params = z_args; sctx->cb.no_separation = 0; /* Multibulk response, {[pattern], type, channel, payload } */ while(1) { zval *z_type, *z_chan, *z_pat = NULL, *z_data; HashTable *ht_tab; int tab_idx=1, is_pmsg; if (!redis_sock_read_multibulk_reply_zval( INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp)) break; ht_tab = Z_ARRVAL(z_resp); if ((z_type = zend_hash_index_find(ht_tab, 0)) == NULL || Z_TYPE_P(z_type) != IS_STRING ) { break; } // Check for message or pmessage if(!strncmp(Z_STRVAL_P(z_type), "message", 7) || !strncmp(Z_STRVAL_P(z_type), "pmessage", 8)) { is_pmsg = *Z_STRVAL_P(z_type)=='p'; } else { break; } // Extract pattern if it's a pmessage if(is_pmsg) { if ((z_pat = zend_hash_index_find(ht_tab, tab_idx++)) == NULL) { break; } } // Extract channel and data if ((z_chan = zend_hash_index_find(ht_tab, tab_idx++)) == NULL || (z_data = zend_hash_index_find(ht_tab, tab_idx++)) == NULL ) { break; } // Different args for SUBSCRIBE and PSUBSCRIBE #if (PHP_MAJOR_VERSION < 7) z_args[0] = &getThis(); if(is_pmsg) { z_args[1] = &z_pat; z_args[2] = &z_chan; z_args[3] = &z_data; } else { z_args[1] = &z_chan; z_args[2] = &z_data; } #else z_args[0] = *getThis(); if(is_pmsg) { z_args[1] = *z_pat; z_args[2] = *z_chan; z_args[3] = *z_data; } else { z_args[1] = *z_chan; z_args[2] = *z_data; } #endif // Set arg count sctx->cb.param_count = tab_idx; // Execute callback if(zend_call_function(&(sctx->cb), &(sctx->cb_cache) TSRMLS_CC) ==FAILURE) { break; } // If we have a return value free it zval_ptr_dtor(&z_ret); zval_dtor(&z_resp); } // This is an error state, clean up zval_dtor(&z_resp); efree(sctx); return -1; } PHP_REDIS_API int redis_unsubscribe_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { subscribeContext *sctx = (subscribeContext*)ctx; zval *z_chan, zv, *z_ret = &zv, z_resp; int i; array_init(z_ret); for (i = 0; i < sctx->argc; i++) { if (!redis_sock_read_multibulk_reply_zval( INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp) || (z_chan = zend_hash_index_find(Z_ARRVAL(z_resp), 1)) == NULL ) { zval_dtor(z_ret); return -1; } add_assoc_bool(z_ret, Z_STRVAL_P(z_chan), 1); zval_dtor(&z_resp); } efree(sctx); RETVAL_ZVAL(z_ret, 0, 1); // Success return 0; } PHP_REDIS_API zval * redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab) { char inbuf[4096]; int numElems; size_t len; ZVAL_NULL(z_tab); if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) { return NULL; } if(inbuf[0] != '*') { return NULL; } numElems = atoi(inbuf+1); array_init(z_tab); redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, numElems, UNSERIALIZE_ALL); return z_tab; } /** * redis_sock_read_bulk_reply */ PHP_REDIS_API char * redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes TSRMLS_DC) { int offset = 0; char *reply, c[2]; size_t got; if (-1 == bytes || -1 == redis_check_eof(redis_sock, 0 TSRMLS_CC)) { return NULL; } /* Allocate memory for string */ reply = emalloc(bytes+1); /* Consume bulk string */ while(offset < bytes) { got = php_stream_read(redis_sock->stream, reply + offset, bytes-offset); if (got == 0) break; offset += got; } /* Protect against reading too few bytes */ if (offset < bytes) { /* Error or EOF */ zend_throw_exception(redis_exception_ce, "socket error on read socket", 0 TSRMLS_CC); efree(reply); return NULL; } /* Consume \r\n and null terminate reply string */ php_stream_read(redis_sock->stream, c, 2); reply[bytes] = '\0'; return reply; } /** * redis_sock_read */ PHP_REDIS_API char * redis_sock_read(RedisSock *redis_sock, int *buf_len TSRMLS_DC) { char inbuf[4096]; size_t len; *buf_len = 0; if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) { return NULL; } switch(inbuf[0]) { case '-': redis_sock_set_err(redis_sock, inbuf+1, len); /* Filter our ERROR through the few that should actually throw */ redis_error_throw(redis_sock TSRMLS_CC); return NULL; case '$': *buf_len = atoi(inbuf + 1); return redis_sock_read_bulk_reply(redis_sock, *buf_len TSRMLS_CC); case '*': /* For null multi-bulk replies (like timeouts from brpoplpush): */ if(memcmp(inbuf + 1, "-1", 2) == 0) { return NULL; } /* fall through */ case '+': case ':': /* Single Line Reply */ /* +OK or :123 */ if (len > 1) { *buf_len = len; return estrndup(inbuf, *buf_len); } default: zend_throw_exception_ex( redis_exception_ce, 0 TSRMLS_CC, "protocol error, got '%c' as reply type byte\n", inbuf[0] ); } return NULL; } /* A simple union to store the various arg types we might handle in our * redis_spprintf command formatting function */ union resparg { char *str; zend_string *zstr; zval *zv; int ival; long lval; double dval; }; /* A printf like method to construct a Redis RESP command. It has been extended * to take a few different format specifiers that are convienient to phpredis. * * s - C string followed by length as a strlen_t * S - Pointer to a zend_string * k - Same as 's' but the value will be prefixed if phpredis is set up do do * that and the working slot will be set if it has been passed. * v - A z_val which will be serialized if phpredis is configured to serialize. * f - A double value * F - Alias to 'f' * i - An integer * d - Alias to 'i' * l - A long * L - Alias to 'l' */ PHP_REDIS_API int redis_spprintf(RedisSock *redis_sock, short *slot TSRMLS_DC, char **ret, char *kw, char *fmt, ...) { smart_string cmd = {0}; va_list ap; union resparg arg; char *dup; int argfree; strlen_t arglen; va_start(ap, fmt); /* Header */ redis_cmd_init_sstr(&cmd, strlen(fmt), kw, strlen(kw)); while (*fmt) { switch (*fmt) { case 's': arg.str = va_arg(ap, char*); arglen = va_arg(ap, strlen_t); redis_cmd_append_sstr(&cmd, arg.str, arglen); break; case 'S': arg.zstr = va_arg(ap, zend_string*); redis_cmd_append_sstr(&cmd, ZSTR_VAL(arg.zstr), ZSTR_LEN(arg.zstr)); break; case 'k': arg.str = va_arg(ap, char*); arglen = va_arg(ap, strlen_t); argfree = redis_key_prefix(redis_sock, &arg.str, &arglen); redis_cmd_append_sstr(&cmd, arg.str, arglen); if (slot) *slot = cluster_hash_key(arg.str, arglen); if (argfree) efree(arg.str); break; case 'v': arg.zv = va_arg(ap, zval*); argfree = redis_serialize(redis_sock, arg.zv, &dup, &arglen TSRMLS_CC); redis_cmd_append_sstr(&cmd, dup, arglen); if (argfree) efree(dup); break; case 'f': case 'F': arg.dval = va_arg(ap, double); redis_cmd_append_sstr_dbl(&cmd, arg.dval); break; case 'i': case 'd': arg.ival = va_arg(ap, int); redis_cmd_append_sstr_int(&cmd, arg.ival); break; case 'l': case 'L': arg.lval = va_arg(ap, long); redis_cmd_append_sstr_long(&cmd, arg.lval); break; } fmt++; } /* varargs cleanup */ va_end(ap); /* Null terminate */ smart_string_0(&cmd); /* Push command string, return length */ *ret = cmd.c; return cmd.len; } /* * Given a smart string, number of arguments, a keyword, and the length of the keyword * initialize our smart string with the proper Redis header for the command to follow */ int redis_cmd_init_sstr(smart_string *str, int num_args, char *keyword, int keyword_len) { smart_string_appendc(str, '*'); smart_string_append_long(str, num_args + 1); smart_string_appendl(str, _NL, sizeof(_NL) -1); smart_string_appendc(str, '$'); smart_string_append_long(str, keyword_len); smart_string_appendl(str, _NL, sizeof(_NL) - 1); smart_string_appendl(str, keyword, keyword_len); smart_string_appendl(str, _NL, sizeof(_NL) - 1); return str->len; } /* * Append a command sequence to a smart_string */ int redis_cmd_append_sstr(smart_string *str, char *append, int append_len) { smart_string_appendc(str, '$'); smart_string_append_long(str, append_len); smart_string_appendl(str, _NL, sizeof(_NL) - 1); smart_string_appendl(str, append, append_len); smart_string_appendl(str, _NL, sizeof(_NL) - 1); /* Return our new length */ return str->len; } /* * Append an integer to a smart string command */ int redis_cmd_append_sstr_int(smart_string *str, int append) { char int_buf[32]; int int_len = snprintf(int_buf, sizeof(int_buf), "%d", append); return redis_cmd_append_sstr(str, int_buf, int_len); } /* * Append a long to a smart string command */ int redis_cmd_append_sstr_long(smart_string *str, long append) { char long_buf[32]; int long_len = snprintf(long_buf, sizeof(long_buf), "%ld", append); return redis_cmd_append_sstr(str, long_buf, long_len); } /* * Append a double to a smart string command */ int redis_cmd_append_sstr_dbl(smart_string *str, double value) { char tmp[64]; int len, retval; /* Convert to string */ len = snprintf(tmp, sizeof(tmp), "%.16g", value); // Append the string retval = redis_cmd_append_sstr(str, tmp, len); /* Return new length */ return retval; } /* Append a zval to a redis command. The value will be serialized if we are * configured to do that */ int redis_cmd_append_sstr_zval(smart_string *str, zval *z, RedisSock *redis_sock TSRMLS_DC) { char *val; strlen_t vallen; int valfree, retval; valfree = redis_serialize(redis_sock, z, &val, &vallen TSRMLS_CC); retval = redis_cmd_append_sstr(str, val, vallen); if (valfree) efree(val); return retval; } /* Append a string key to a redis command. This function takes care of prefixing the key * for the caller and setting the slot argument if it is passed non null */ int redis_cmd_append_sstr_key(smart_string *str, char *key, strlen_t len, RedisSock *redis_sock, short *slot) { int valfree, retval; valfree = redis_key_prefix(redis_sock, &key, &len); if (slot) *slot = cluster_hash_key(key, len); retval = redis_cmd_append_sstr(str, key, len); if (valfree) efree(key); return retval; } PHP_REDIS_API void redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; int response_len; double ret; if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { IF_NOT_ATOMIC() { add_next_index_bool(z_tab, 0); return; } else { RETURN_FALSE; } } ret = atof(response); efree(response); IF_NOT_ATOMIC() { add_next_index_double(z_tab, ret); } else { RETURN_DOUBLE(ret); } } PHP_REDIS_API void redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; int response_len; long l; if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { IF_NOT_ATOMIC() { add_next_index_bool(z_tab, 0); return; } else { RETURN_FALSE; } } if (strncmp(response, "+string", 7) == 0) { l = REDIS_STRING; } else if (strncmp(response, "+set", 4) == 0){ l = REDIS_SET; } else if (strncmp(response, "+list", 5) == 0){ l = REDIS_LIST; } else if (strncmp(response, "+zset", 5) == 0){ l = REDIS_ZSET; } else if (strncmp(response, "+hash", 5) == 0){ l = REDIS_HASH; } else { l = REDIS_NOT_FOUND; } efree(response); IF_NOT_ATOMIC() { add_next_index_long(z_tab, l); } else { RETURN_LONG(l); } } PHP_REDIS_API void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; int response_len; zval zv = {{0}}, *z_ret = &zv; /* Read bulk response */ if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { RETURN_FALSE; } /* Parse it into a zval array */ redis_parse_info_response(response, z_ret); /* Free source response */ efree(response); IF_NOT_ATOMIC() { add_next_index_zval(z_tab, z_ret); } else { RETVAL_ZVAL(z_ret, 0, 1); } } PHP_REDIS_API void redis_parse_info_response(char *response, zval *z_ret) { char *cur, *pos; array_init(z_ret); cur = response; while(1) { /* skip comments and empty lines */ if (*cur == '#' || *cur == '\r') { if ((cur = strstr(cur, _NL)) == NULL) { break; } cur += 2; continue; } /* key */ if ((pos = strchr(cur, ':')) == NULL) { break; } char *key = cur; int key_len = pos - cur; key[key_len] = '\0'; /* value */ cur = pos + 1; if ((pos = strstr(cur, _NL)) == NULL) { break; } char *value = cur; int value_len = pos - cur; value[value_len] = '\0'; double dval; zend_long lval; zend_uchar type = is_numeric_string(value, value_len, &lval, &dval, 0); if (type == IS_LONG) { add_assoc_long_ex(z_ret, key, key_len, lval); } else if (type == IS_DOUBLE) { add_assoc_double_ex(z_ret, key, key_len, dval); } else { add_assoc_stringl_ex(z_ret, key, key_len, value, value_len); } cur = pos + 2; /* \r, \n */ } } /* * Specialized handling of the CLIENT LIST output so it comes out in a simple way for PHP userland code * to handle. */ PHP_REDIS_API void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab) { char *resp; int resp_len; /* Make sure we can read the bulk response from Redis */ if ((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC)) == NULL) { RETURN_FALSE; } zval zv, *z_ret = &zv; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_ret); #endif /* Parse it out */ redis_parse_client_list_response(resp, z_ret); /* Free our response */ efree(resp); /* Return or append depending if we're atomic */ IF_NOT_ATOMIC() { add_next_index_zval(z_tab, z_ret); } else { RETVAL_ZVAL(z_ret, 0, 1); } } PHP_REDIS_API void redis_parse_client_list_response(char *response, zval *z_ret) { char *p, *lpos, *kpos = NULL, *vpos = NULL, *p2, *key, *value; int klen = 0, done = 0, is_numeric; // Allocate memory for our response array_init(z_ret); /* Allocate memory for one user (there should be at least one, namely us!) */ zval zv, *z_sub_result = &zv; #if (PHP_MAJOR_VERSION < 7) ALLOC_INIT_ZVAL(z_sub_result); #endif array_init(z_sub_result); // Pointers for parsing p = response; lpos = response; /* While we've got more to parse */ while(!done) { /* What character are we on */ switch(*p) { /* We're done */ case '\0': done = 1; break; /* \n, ' ' mean we can pull a k/v pair */ case '\n': case ' ': /* Grab our value */ vpos = lpos; /* There is some communication error or Redis bug if we don't have a key and value, but check anyway. */ if(kpos && vpos) { /* Allocate, copy in our key */ key = estrndup(kpos, klen); /* Allocate, copy in our value */ value = estrndup(lpos, p - lpos); /* Treat numbers as numbers, strings as strings */ is_numeric = 1; for(p2 = value; *p2; ++p2) { if(*p2 < '0' || *p2 > '9') { is_numeric = 0; break; } } /* Add as a long or string, depending */ if(is_numeric == 1) { add_assoc_long(z_sub_result, key, atol(value)); } else { add_assoc_string(z_sub_result, key, value); } efree(value); // If we hit a '\n', then we can add this user to our list if(*p == '\n') { /* Add our user */ add_next_index_zval(z_ret, z_sub_result); /* If we have another user, make another one */ if(*(p+1) != '\0') { #if (PHP_MAJOR_VERSION < 7) ALLOC_INIT_ZVAL(z_sub_result); #endif array_init(z_sub_result); } } // Free our key efree(key); } else { // Something is wrong zval_dtor(z_ret); ZVAL_BOOL(z_ret, 0); return; } /* Move forward */ lpos = p + 1; break; /* We can pull the key and null terminate at our sep */ case '=': /* Key, key length */ kpos = lpos; klen = p - lpos; /* Move forward */ lpos = p + 1; break; } /* Increment */ p++; } } PHP_REDIS_API void redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx, SuccessCallback success_callback) { char *response; int response_len; zend_bool ret = 0; if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) != NULL) { ret = (*response == '+'); efree(response); } if (ret && success_callback != NULL) { success_callback(redis_sock); } IF_NOT_ATOMIC() { add_next_index_bool(z_tab, ret); } else { RETURN_BOOL(ret); } } PHP_REDIS_API void redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, ctx, NULL); } PHP_REDIS_API void redis_long_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval * z_tab, void *ctx) { char *response; int response_len; if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { IF_NOT_ATOMIC() { add_next_index_bool(z_tab, 0); return; } else { RETURN_FALSE; } } if(response[0] == ':') { #ifdef PHP_WIN32 __int64 ret = _atoi64(response + 1); #else long long ret = atoll(response + 1); #endif IF_NOT_ATOMIC() { if(ret > LONG_MAX) { /* overflow */ add_next_index_stringl(z_tab, response + 1, response_len - 1); } else { add_next_index_long(z_tab, (long)ret); } } else { if(ret > LONG_MAX) { /* overflow */ RETVAL_STRINGL(response + 1, response_len - 1); } else { RETVAL_LONG((long)ret); } } } else { IF_NOT_ATOMIC() { add_next_index_null(z_tab); } else { RETVAL_FALSE; } } efree(response); } /* Helper method to convert [key, value, key, value] into [key => value, * key => value] when returning data to the caller. Depending on our decode * flag we'll convert the value data types */ static void array_zip_values_and_scores(RedisSock *redis_sock, zval *z_tab, int decode TSRMLS_DC) { zval zv, *z_ret = &zv; HashTable *keytable; array_init(z_ret); keytable = Z_ARRVAL_P(z_tab); for(zend_hash_internal_pointer_reset(keytable); zend_hash_has_more_elements(keytable) == SUCCESS; zend_hash_move_forward(keytable)) { zval *z_key_p, *z_value_p; if ((z_key_p = zend_hash_get_current_data(keytable)) == NULL) { continue; /* this should never happen, according to the PHP people. */ } /* get current value, a key */ zend_string *hkey = zval_get_string(z_key_p); /* move forward */ zend_hash_move_forward(keytable); /* fetch again */ if ((z_value_p = zend_hash_get_current_data(keytable)) == NULL) { zend_string_release(hkey); continue; /* this should never happen, according to the PHP people. */ } /* get current value, a hash value now. */ char *hval = Z_STRVAL_P(z_value_p); /* Decode the score depending on flag */ if (decode == SCORE_DECODE_INT && Z_STRLEN_P(z_value_p) > 0) { add_assoc_long_ex(z_ret, ZSTR_VAL(hkey), ZSTR_LEN(hkey), atoi(hval+1)); } else if (decode == SCORE_DECODE_DOUBLE) { add_assoc_double_ex(z_ret, ZSTR_VAL(hkey), ZSTR_LEN(hkey), atof(hval)); } else { zval zv0, *z = &zv0; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z); #endif ZVAL_ZVAL(z, z_value_p, 1, 0); add_assoc_zval_ex(z_ret, ZSTR_VAL(hkey), ZSTR_LEN(hkey), z); } zend_string_release(hkey); } /* replace */ zval_dtor(z_tab); ZVAL_ZVAL(z_tab, z_ret, 1, 0); zval_dtor(z_ret); } static int redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int unserialize, int decode) { char inbuf[4096]; int numElems; size_t len; if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) { return -1; } if(inbuf[0] != '*') { IF_NOT_ATOMIC() { add_next_index_bool(z_tab, 0); } else { RETVAL_FALSE; } return -1; } numElems = atoi(inbuf+1); zval zv, *z_multi_result = &zv; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_multi_result); #endif array_init(z_multi_result); /* pre-allocate array for multi's results. */ /* Grab our key, value, key, value array */ redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_multi_result, numElems, unserialize); /* Zip keys and values */ array_zip_values_and_scores(redis_sock, z_multi_result, decode TSRMLS_CC); IF_NOT_ATOMIC() { add_next_index_zval(z_tab, z_multi_result); } else { RETVAL_ZVAL(z_multi_result, 0, 1); } return 0; } /* Zipped key => value reply but we don't touch anything (e.g. CONFIG GET) */ PHP_REDIS_API int redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, UNSERIALIZE_NONE, SCORE_DECODE_NONE); } /* Zipped key => value reply unserializing keys and decoding the score as an integer (PUBSUB) */ PHP_REDIS_API int redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, UNSERIALIZE_KEYS, SCORE_DECODE_INT); } /* Zipped key => value reply unserializing keys and decoding the score as a double (ZSET commands) */ PHP_REDIS_API int redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, UNSERIALIZE_KEYS, SCORE_DECODE_DOUBLE); } /* Zipped key => value reply where only the values are unserialized (e.g. HMGET) */ PHP_REDIS_API int redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, UNSERIALIZE_VALS, SCORE_DECODE_NONE); } PHP_REDIS_API void redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; int response_len; zend_bool ret = 0; if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) != NULL) { ret = (response[1] == '1'); efree(response); } IF_NOT_ATOMIC() { add_next_index_bool(z_tab, ret); } else { RETURN_BOOL(ret); } } PHP_REDIS_API void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; int response_len; if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { IF_NOT_ATOMIC() { add_next_index_bool(z_tab, 0); return; } RETURN_FALSE; } IF_NOT_ATOMIC() { zval zv, *z = &zv; if (redis_unserialize(redis_sock, response, response_len, z TSRMLS_CC)) { #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z); *z = zv; #endif add_next_index_zval(z_tab, z); } else { add_next_index_stringl(z_tab, response, response_len); } } else { if (!redis_unserialize(redis_sock, response, response_len, return_value TSRMLS_CC)) { RETVAL_STRINGL(response, response_len); } } efree(response); } /* like string response, but never unserialized. */ PHP_REDIS_API void redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; int response_len; if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { IF_NOT_ATOMIC() { add_next_index_bool(z_tab, 0); return; } RETURN_FALSE; } IF_NOT_ATOMIC() { add_next_index_stringl(z_tab, response, response_len); } else { RETVAL_STRINGL(response, response_len); } efree(response); } /* Response for DEBUG object which is a formatted single line reply */ PHP_REDIS_API void redis_debug_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *resp, *p, *p2, *p3, *p4; int is_numeric, resp_len; /* Add or return false if we can't read from the socket */ if((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC))==NULL) { IF_NOT_ATOMIC() { add_next_index_bool(z_tab, 0); return; } RETURN_FALSE; } zval zv, *z_result = &zv; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_result); #endif array_init(z_result); /* Skip the '+' */ p = resp + 1; /* : ... */ while((p2 = strchr(p, ':'))!=NULL) { /* Null terminate at the ':' */ *p2++ = '\0'; /* Null terminate at the space if we have one */ if((p3 = strchr(p2, ' '))!=NULL) { *p3++ = '\0'; } else { p3 = resp + resp_len; } is_numeric = 1; for(p4=p2; *p4; ++p4) { if(*p4 < '0' || *p4 > '9') { is_numeric = 0; break; } } /* Add our value */ if(is_numeric) { add_assoc_long(z_result, p, atol(p2)); } else { add_assoc_string(z_result, p, p2); } p = p3; } efree(resp); IF_NOT_ATOMIC() { add_next_index_zval(z_tab, z_result); } else { RETVAL_ZVAL(z_result, 0, 1); } } /** * redis_sock_create */ PHP_REDIS_API RedisSock* redis_sock_create(char *host, int host_len, unsigned short port, double timeout, double read_timeout, int persistent, char *persistent_id, long retry_interval, zend_bool lazy_connect) { RedisSock *redis_sock; redis_sock = ecalloc(1, sizeof(RedisSock)); redis_sock->host = zend_string_init(host, host_len, 0); redis_sock->stream = NULL; redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED; redis_sock->watching = 0; redis_sock->dbNumber = 0; redis_sock->retry_interval = retry_interval * 1000; redis_sock->persistent = persistent; redis_sock->lazy_connect = lazy_connect; redis_sock->persistent_id = NULL; if (persistent && persistent_id != NULL) { redis_sock->persistent_id = zend_string_init(persistent_id, strlen(persistent_id), 0); } redis_sock->port = port; redis_sock->timeout = timeout; redis_sock->read_timeout = read_timeout; redis_sock->serializer = REDIS_SERIALIZER_NONE; redis_sock->mode = ATOMIC; redis_sock->head = NULL; redis_sock->current = NULL; redis_sock->pipeline_cmd = NULL; redis_sock->pipeline_len = 0; redis_sock->err = NULL; redis_sock->scan = REDIS_SCAN_NORETRY; redis_sock->readonly = 0; return redis_sock; } /** * redis_sock_connect */ PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock TSRMLS_DC) { struct timeval tv, read_tv, *tv_ptr = NULL; char host[1024], *persistent_id = NULL; const char *fmtstr = "%s:%d"; int host_len, usocket = 0, err = 0; php_netstream_data_t *sock; int tcp_flag = 1; if (redis_sock->stream != NULL) { redis_sock_disconnect(redis_sock TSRMLS_CC); } tv.tv_sec = (time_t)redis_sock->timeout; tv.tv_usec = (int)((redis_sock->timeout - tv.tv_sec) * 1000000); if(tv.tv_sec != 0 || tv.tv_usec != 0) { tv_ptr = &tv; } read_tv.tv_sec = (time_t)redis_sock->read_timeout; read_tv.tv_usec = (int)((redis_sock->read_timeout-read_tv.tv_sec)*1000000); if (ZSTR_VAL(redis_sock->host)[0] == '/' && redis_sock->port < 1) { host_len = snprintf(host, sizeof(host), "unix://%s", ZSTR_VAL(redis_sock->host)); usocket = 1; } else { if(redis_sock->port == 0) redis_sock->port = 6379; #ifdef HAVE_IPV6 /* If we've got IPv6 and find a colon in our address, convert to proper * IPv6 [host]:port format */ if (strchr(ZSTR_VAL(redis_sock->host), ':') != NULL) { fmtstr = "[%s]:%d"; } #endif host_len = snprintf(host, sizeof(host), fmtstr, ZSTR_VAL(redis_sock->host), redis_sock->port); } if (redis_sock->persistent) { if (redis_sock->persistent_id) { spprintf(&persistent_id, 0, "phpredis:%s:%s", host, ZSTR_VAL(redis_sock->persistent_id)); } else { spprintf(&persistent_id, 0, "phpredis:%s:%f", host, redis_sock->timeout); } } redis_sock->stream = php_stream_xport_create(host, host_len, 0, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, persistent_id, tv_ptr, NULL, NULL, &err); if (persistent_id) { efree(persistent_id); } if (!redis_sock->stream) { return -1; } /* Attempt to set TCP_NODELAY if we're not using a unix socket. */ sock = (php_netstream_data_t*)redis_sock->stream->abstract; if (!usocket) { err = setsockopt(sock->socket, IPPROTO_TCP, TCP_NODELAY, (char *) &tcp_flag, sizeof(int)); PHPREDIS_NOTUSED(err); } php_stream_auto_cleanup(redis_sock->stream); if (read_tv.tv_sec != 0 || read_tv.tv_usec != 0) { php_stream_set_option(redis_sock->stream,PHP_STREAM_OPTION_READ_TIMEOUT, 0, &read_tv); } php_stream_set_option(redis_sock->stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_NONE, NULL); redis_sock->status = REDIS_SOCK_STATUS_CONNECTED; return 0; } /** * redis_sock_server_open */ PHP_REDIS_API int redis_sock_server_open(RedisSock *redis_sock TSRMLS_DC) { int res = -1; switch (redis_sock->status) { case REDIS_SOCK_STATUS_DISCONNECTED: return redis_sock_connect(redis_sock TSRMLS_CC); case REDIS_SOCK_STATUS_CONNECTED: res = 0; break; } return res; } /** * redis_sock_disconnect */ PHP_REDIS_API int redis_sock_disconnect(RedisSock *redis_sock TSRMLS_DC) { if (redis_sock == NULL) { return 1; } redis_sock->dbNumber = 0; if (redis_sock->stream != NULL) { redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED; redis_sock->watching = 0; /* Stil valid? */ if (!redis_sock->persistent) { php_stream_close(redis_sock->stream); } redis_sock->stream = NULL; return 1; } return 0; } /** * redis_sock_set_err */ PHP_REDIS_API void redis_sock_set_err(RedisSock *redis_sock, const char *msg, int msg_len) { // Free our last error if (redis_sock->err != NULL) { zend_string_release(redis_sock->err); redis_sock->err = NULL; } if (msg != NULL && msg_len > 0) { // Copy in our new error message redis_sock->err = zend_string_init(msg, msg_len, 0); } } /** * redis_sock_read_multibulk_reply */ PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char inbuf[4096]; int numElems; size_t len; if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) { return -1; } if(inbuf[0] != '*') { IF_NOT_ATOMIC() { add_next_index_bool(z_tab, 0); } else { if (inbuf[0] == '-') { redis_sock_set_err(redis_sock, inbuf+1, len); } RETVAL_FALSE; } return -1; } numElems = atoi(inbuf+1); zval zv, *z_multi_result = &zv; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_multi_result); #endif array_init(z_multi_result); /* pre-allocate array for multi's results. */ redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_multi_result, numElems, UNSERIALIZE_ALL); IF_NOT_ATOMIC() { add_next_index_zval(z_tab, z_multi_result); } else { RETVAL_ZVAL(z_multi_result, 0, 1); } /*zval_copy_ctor(return_value); */ return 0; } /* Like multibulk reply, but don't touch the values, they won't be unserialized * (this is used by HKEYS). */ PHP_REDIS_API int redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char inbuf[4096]; int numElems; size_t len; if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) { return -1; } if(inbuf[0] != '*') { IF_NOT_ATOMIC() { add_next_index_bool(z_tab, 0); } else { if (inbuf[0] == '-') { redis_sock_set_err(redis_sock, inbuf+1, len); } RETVAL_FALSE; } return -1; } numElems = atoi(inbuf+1); zval zv, *z_multi_result = &zv; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_multi_result); #endif array_init(z_multi_result); /* pre-allocate array for multi's results. */ redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_multi_result, numElems, UNSERIALIZE_NONE); IF_NOT_ATOMIC() { add_next_index_zval(z_tab, z_multi_result); } else { RETVAL_ZVAL(z_multi_result, 0, 1); } /*zval_copy_ctor(return_value); */ return 0; } PHP_REDIS_API void redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int count, int unserialize) { char *line; int i, len; for (i = 0; i < count; ++i) { if ((line = redis_sock_read(redis_sock, &len TSRMLS_CC)) == NULL) { add_next_index_bool(z_tab, 0); continue; } /* We will attempt unserialization, if we're unserializing everything, * or if we're unserializing keys and we're on a key, or we're * unserializing values and we're on a value! */ int unwrap = ( (unserialize == UNSERIALIZE_ALL) || (unserialize == UNSERIALIZE_KEYS && i % 2 == 0) || (unserialize == UNSERIALIZE_VALS && i % 2 != 0) ); zval zv, *z = &zv; if (unwrap && redis_unserialize(redis_sock, line, len, z TSRMLS_CC)) { #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z); *z = zv; #endif add_next_index_zval(z_tab, z); } else { add_next_index_stringl(z_tab, line, len); } efree(line); } } /* Specialized multibulk processing for HMGET where we need to pair requested * keys with their returned values */ PHP_REDIS_API int redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char inbuf[4096], *response; int response_len; int i, numElems; size_t len; zval *z_keys = ctx; if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) { return -1; } if(inbuf[0] != '*') { IF_NOT_ATOMIC() { add_next_index_bool(z_tab, 0); } else { RETVAL_FALSE; } return -1; } numElems = atoi(inbuf+1); zval zv, *z_multi_result = &zv; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_multi_result); #endif array_init(z_multi_result); /* pre-allocate array for multi's results. */ for(i = 0; i < numElems; ++i) { response = redis_sock_read(redis_sock, &response_len TSRMLS_CC); if(response != NULL) { zval zv0, *z = &zv0; if (redis_unserialize(redis_sock, response, response_len, z TSRMLS_CC)) { #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z); *z = zv0; #endif add_assoc_zval_ex(z_multi_result, Z_STRVAL(z_keys[i]), Z_STRLEN(z_keys[i]), z); } else { add_assoc_stringl_ex(z_multi_result, Z_STRVAL(z_keys[i]), Z_STRLEN(z_keys[i]), response, response_len); } efree(response); } else { add_assoc_bool_ex(z_multi_result, Z_STRVAL(z_keys[i]), Z_STRLEN(z_keys[i]), 0); } zval_dtor(&z_keys[i]); } efree(z_keys); IF_NOT_ATOMIC() { add_next_index_zval(z_tab, z_multi_result); } else { RETVAL_ZVAL(z_multi_result, 0, 1); } return 0; } /** * redis_sock_write */ PHP_REDIS_API int redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz TSRMLS_DC) { if (!redis_sock || redis_sock->status == REDIS_SOCK_STATUS_DISCONNECTED) { zend_throw_exception(redis_exception_ce, "Connection closed", 0 TSRMLS_CC); } else if (redis_check_eof(redis_sock, 0 TSRMLS_CC) == 0 && php_stream_write(redis_sock->stream, cmd, sz) == sz ) { return sz; } return -1; } /** * redis_free_socket */ PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock) { if (redis_sock->prefix) { zend_string_release(redis_sock->prefix); } if (redis_sock->pipeline_cmd) { efree(redis_sock->pipeline_cmd); } if (redis_sock->err) { zend_string_release(redis_sock->err); } if (redis_sock->auth) { zend_string_release(redis_sock->auth); } if (redis_sock->persistent_id) { zend_string_release(redis_sock->persistent_id); } if (redis_sock->host) { zend_string_release(redis_sock->host); } efree(redis_sock); } PHP_REDIS_API int redis_serialize(RedisSock *redis_sock, zval *z, char **val, strlen_t *val_len TSRMLS_DC) { #if ZEND_MODULE_API_NO >= 20100000 php_serialize_data_t ht; #else HashTable ht; #endif smart_str sstr = {0}; #ifdef HAVE_REDIS_IGBINARY size_t sz; uint8_t *val8; #endif *val = NULL; *val_len = 0; switch(redis_sock->serializer) { case REDIS_SERIALIZER_NONE: switch(Z_TYPE_P(z)) { case IS_STRING: *val = Z_STRVAL_P(z); *val_len = Z_STRLEN_P(z); break; case IS_OBJECT: *val = "Object"; *val_len = 6; break; case IS_ARRAY: *val = "Array"; *val_len = 5; break; default: { /* copy */ zend_string *zstr = zval_get_string(z); *val = estrndup(ZSTR_VAL(zstr), ZSTR_LEN(zstr)); *val_len = ZSTR_LEN(zstr); zend_string_release(zstr); return 1; } } break; case REDIS_SERIALIZER_PHP: #if ZEND_MODULE_API_NO >= 20100000 PHP_VAR_SERIALIZE_INIT(ht); #else zend_hash_init(&ht, 10, NULL, NULL, 0); #endif php_var_serialize(&sstr, z, &ht); #if (PHP_MAJOR_VERSION < 7) *val = estrndup(sstr.c, sstr.len); *val_len = sstr.len; #else *val = estrndup(ZSTR_VAL(sstr.s), ZSTR_LEN(sstr.s)); *val_len = ZSTR_LEN(sstr.s); #endif smart_str_free(&sstr); #if ZEND_MODULE_API_NO >= 20100000 PHP_VAR_SERIALIZE_DESTROY(ht); #else zend_hash_destroy(&ht); #endif return 1; case REDIS_SERIALIZER_IGBINARY: #ifdef HAVE_REDIS_IGBINARY if(igbinary_serialize(&val8, (size_t *)&sz, z TSRMLS_CC) == 0) { *val = (char*)val8; *val_len = sz; return 1; } #endif break; } return 0; } PHP_REDIS_API int redis_unserialize(RedisSock* redis_sock, const char *val, int val_len, zval *z_ret TSRMLS_DC) { php_unserialize_data_t var_hash; int ret = 0; switch(redis_sock->serializer) { case REDIS_SERIALIZER_PHP: #if ZEND_MODULE_API_NO >= 20100000 PHP_VAR_UNSERIALIZE_INIT(var_hash); #else memset(&var_hash, 0, sizeof(var_hash)); #endif if (php_var_unserialize(z_ret, (const unsigned char**)&val, (const unsigned char*)val + val_len, &var_hash) ) { ret = 1; } #if ZEND_MODULE_API_NO >= 20100000 PHP_VAR_UNSERIALIZE_DESTROY(var_hash); #else var_destroy(&var_hash); #endif break; case REDIS_SERIALIZER_IGBINARY: #ifdef HAVE_REDIS_IGBINARY /* * Check if the given string starts with an igbinary header. * * A modern igbinary string consists of the following format: * * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- * | header (4) | type (1) | ... (n) | NUL (1) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- * * With header being either 0x00000001 or 0x00000002 * (encoded as big endian). * * Not all versions contain the trailing NULL byte though, so * do not check for that. */ if (val_len < 5 || (memcmp(val, "\x00\x00\x00\x01", 4) != 0 && memcmp(val, "\x00\x00\x00\x02", 4) != 0)) { /* This is most definitely not an igbinary string, so do not try to unserialize this as one. */ break; } #if (PHP_MAJOR_VERSION < 7) INIT_PZVAL(z_ret); ret = !igbinary_unserialize((const uint8_t *)val, (size_t)val_len, &z_ret TSRMLS_CC); #else ret = !igbinary_unserialize((const uint8_t *)val, (size_t)val_len, z_ret TSRMLS_CC); #endif #endif break; } return ret; } PHP_REDIS_API int redis_key_prefix(RedisSock *redis_sock, char **key, strlen_t *key_len) { int ret_len; char *ret; if (redis_sock->prefix == NULL) { return 0; } ret_len = ZSTR_LEN(redis_sock->prefix) + *key_len; ret = ecalloc(1 + ret_len, 1); memcpy(ret, ZSTR_VAL(redis_sock->prefix), ZSTR_LEN(redis_sock->prefix)); memcpy(ret + ZSTR_LEN(redis_sock->prefix), *key, *key_len); *key = ret; *key_len = ret_len; return 1; } /* * Processing for variant reply types (think EVAL) */ PHP_REDIS_API int redis_sock_gets(RedisSock *redis_sock, char *buf, int buf_size, size_t *line_size TSRMLS_DC) { // Handle EOF if(-1 == redis_check_eof(redis_sock, 0 TSRMLS_CC)) { return -1; } if(php_stream_get_line(redis_sock->stream, buf, buf_size, line_size) == NULL) { // Close, put our socket state into error REDIS_STREAM_CLOSE_MARK_FAILED(redis_sock); // Throw a read error exception zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); return -1; } /* We don't need \r\n */ *line_size-=2; buf[*line_size]='\0'; /* Success! */ return 0; } PHP_REDIS_API int redis_read_reply_type(RedisSock *redis_sock, REDIS_REPLY_TYPE *reply_type, long *reply_info TSRMLS_DC) { // Make sure we haven't lost the connection, even trying to reconnect if(-1 == redis_check_eof(redis_sock, 0 TSRMLS_CC)) { // Failure *reply_type = EOF; return -1; } // Attempt to read the reply-type byte if((*reply_type = php_stream_getc(redis_sock->stream)) == EOF) { zend_throw_exception(redis_exception_ce, "socket error on read socket", 0 TSRMLS_CC); return -1; } // If this is a BULK, MULTI BULK, or simply an INTEGER response, we can // extract the value or size info here if(*reply_type == TYPE_INT || *reply_type == TYPE_BULK || *reply_type == TYPE_MULTIBULK) { // Buffer to hold size information char inbuf[255]; /* Read up to our newline */ if(php_stream_gets(redis_sock->stream, inbuf, sizeof(inbuf)) == NULL) { return -1; } /* Set our size response */ *reply_info = atol(inbuf); } /* Success! */ return 0; } /* * Read a single line response, having already consumed the reply-type byte */ PHP_REDIS_API int redis_read_variant_line(RedisSock *redis_sock, REDIS_REPLY_TYPE reply_type, zval *z_ret TSRMLS_DC) { // Buffer to read our single line reply char inbuf[4096]; size_t line_size; /* Attempt to read our single line reply */ if(redis_sock_gets(redis_sock, inbuf, sizeof(inbuf), &line_size TSRMLS_CC) < 0) { return -1; } // If this is an error response, check if it is a SYNC error, and throw in // that case if(reply_type == TYPE_ERR) { /* Set our last error */ redis_sock_set_err(redis_sock, inbuf, line_size); /* Handle throwable errors */ redis_error_throw(redis_sock TSRMLS_CC); /* Set our response to FALSE */ ZVAL_FALSE(z_ret); } else { /* Set our response to TRUE */ ZVAL_TRUE(z_ret); } return 0; } PHP_REDIS_API int redis_read_variant_bulk(RedisSock *redis_sock, int size, zval *z_ret TSRMLS_DC) { // Attempt to read the bulk reply char *bulk_resp = redis_sock_read_bulk_reply(redis_sock, size TSRMLS_CC); /* Set our reply to FALSE on failure, and the string on success */ if(bulk_resp == NULL) { ZVAL_FALSE(z_ret); return -1; } ZVAL_STRINGL(z_ret, bulk_resp, size); efree(bulk_resp); return 0; } PHP_REDIS_API int redis_read_multibulk_recursive(RedisSock *redis_sock, int elements, zval *z_ret TSRMLS_DC) { long reply_info; REDIS_REPLY_TYPE reply_type; zval zv, *z_subelem = &zv; // Iterate while we have elements while(elements > 0) { // Attempt to read our reply type if(redis_read_reply_type(redis_sock, &reply_type, &reply_info TSRMLS_CC) < 0) { zend_throw_exception_ex(redis_exception_ce, 0 TSRMLS_CC, "protocol error, couldn't parse MULTI-BULK response\n", reply_type); return -1; } // Switch on our reply-type byte switch(reply_type) { case TYPE_ERR: case TYPE_LINE: #if (PHP_MAJOR_VERSION < 7) ALLOC_INIT_ZVAL(z_subelem); #endif redis_read_variant_line(redis_sock, reply_type, z_subelem TSRMLS_CC); add_next_index_zval(z_ret, z_subelem); break; case TYPE_INT: // Add our long value add_next_index_long(z_ret, reply_info); break; case TYPE_BULK: // Init a zval for our bulk response, read and add it #if (PHP_MAJOR_VERSION < 7) ALLOC_INIT_ZVAL(z_subelem); #endif redis_read_variant_bulk(redis_sock, reply_info, z_subelem TSRMLS_CC); add_next_index_zval(z_ret, z_subelem); break; case TYPE_MULTIBULK: // Construct an array for our sub element, and add it, // and recurse #if (PHP_MAJOR_VERSION < 7) ALLOC_INIT_ZVAL(z_subelem); #endif array_init(z_subelem); add_next_index_zval(z_ret, z_subelem); redis_read_multibulk_recursive(redis_sock, reply_info, z_subelem TSRMLS_CC); break; default: // Stop the compiler from whinging break; } /* Decrement our element counter */ elements--; } return 0; } PHP_REDIS_API int redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { // Reply type, and reply size vars REDIS_REPLY_TYPE reply_type; long reply_info; //char *bulk_resp; // Attempt to read our header if(redis_read_reply_type(redis_sock,&reply_type,&reply_info TSRMLS_CC) < 0) { return -1; } zval zv, *z_ret = &zv; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_ret); #endif /* Switch based on our top level reply type */ switch(reply_type) { case TYPE_ERR: case TYPE_LINE: redis_read_variant_line(redis_sock, reply_type, z_ret TSRMLS_CC); break; case TYPE_INT: ZVAL_LONG(z_ret, reply_info); break; case TYPE_BULK: redis_read_variant_bulk(redis_sock, reply_info, z_ret TSRMLS_CC); break; case TYPE_MULTIBULK: /* Initialize an array for our multi-bulk response */ array_init(z_ret); // If we've got more than zero elements, parse our multi bulk // response recursively if(reply_info > -1) { redis_read_multibulk_recursive(redis_sock, reply_info, z_ret TSRMLS_CC); } break; default: #if (PHP_MAJOR_VERSION < 7) efree(z_ret); #endif // Protocol error zend_throw_exception_ex(redis_exception_ce, 0 TSRMLS_CC, "protocol error, got '%c' as reply-type byte\n", reply_type); return FAILURE; } IF_NOT_ATOMIC() { add_next_index_zval(z_tab, z_ret); } else { /* Set our return value */ RETVAL_ZVAL(z_ret, 0, 1); } /* Success */ return 0; } /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */ redis-3.1.6/library.h0000644000175000000120000001426513223116600015225 0ustar pyatsukhnenkowheel#ifndef REDIS_LIBRARY_H #define REDIS_LIBRARY_H /* Non cluster command helper */ #define REDIS_SPPRINTF(ret, kw, fmt, ...) \ redis_spprintf(redis_sock, NULL TSRMLS_CC, ret, kw, fmt, ##__VA_ARGS__) #define REDIS_CMD_APPEND_SSTR_STATIC(sstr, str) \ redis_cmd_append_sstr(sstr, str, sizeof(str)-1); #define REDIS_CMD_APPEND_SSTR_OPT_STATIC(sstr, opt, str) \ if (opt) REDIS_CMD_APPEND_SSTR_STATIC(sstr, str); #define REDIS_CMD_INIT_SSTR_STATIC(sstr, argc, keyword) \ redis_cmd_init_sstr(sstr, argc, keyword, sizeof(keyword)-1); int redis_cmd_init_sstr(smart_string *str, int num_args, char *keyword, int keyword_len); int redis_cmd_append_sstr(smart_string *str, char *append, int append_len); int redis_cmd_append_sstr_int(smart_string *str, int append); int redis_cmd_append_sstr_long(smart_string *str, long append); int redis_cmd_append_sstr_dbl(smart_string *str, double value); int redis_cmd_append_sstr_zval(smart_string *str, zval *z, RedisSock *redis_sock TSRMLS_DC); int redis_cmd_append_sstr_key(smart_string *str, char *key, strlen_t len, RedisSock *redis_sock, short *slot); PHP_REDIS_API int redis_spprintf(RedisSock *redis_sock, short *slot TSRMLS_DC, char **ret, char *kw, char *fmt, ...); PHP_REDIS_API char * redis_sock_read(RedisSock *redis_sock, int *buf_len TSRMLS_DC); PHP_REDIS_API int redis_sock_gets(RedisSock *redis_sock, char *buf, int buf_size, size_t* line_len TSRMLS_DC); PHP_REDIS_API void redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void redis_long_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval* z_tab, void *ctx); typedef void (*SuccessCallback)(RedisSock *redis_sock); PHP_REDIS_API void redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx, SuccessCallback success_callback); PHP_REDIS_API void redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void redis_parse_info_response(char *response, zval *z_ret); PHP_REDIS_API void redis_parse_client_list_response(char *response, zval *z_ret); PHP_REDIS_API void redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API RedisSock* redis_sock_create(char *host, int host_len, unsigned short port, double timeout, double read_timeout, int persistent, char *persistent_id, long retry_interval, zend_bool lazy_connect); PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock TSRMLS_DC); PHP_REDIS_API int redis_sock_server_open(RedisSock *redis_sock TSRMLS_DC); PHP_REDIS_API int redis_sock_disconnect(RedisSock *redis_sock TSRMLS_DC); PHP_REDIS_API zval *redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab); PHP_REDIS_API char *redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes TSRMLS_DC); PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *_z_tab, void *ctx); PHP_REDIS_API void redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int count, int unserialize); PHP_REDIS_API int redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, REDIS_SCAN_TYPE type, zend_long *iter); PHP_REDIS_API int redis_subscribe_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_unsubscribe_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz TSRMLS_DC); PHP_REDIS_API void redis_stream_close(RedisSock *redis_sock TSRMLS_DC); PHP_REDIS_API int redis_check_eof(RedisSock *redis_sock, int no_throw TSRMLS_DC); PHP_REDIS_API RedisSock *redis_sock_get(zval *id TSRMLS_DC, int nothrow); PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock); PHP_REDIS_API void redis_sock_set_err(RedisSock *redis_sock, const char *msg, int msg_len); PHP_REDIS_API int redis_serialize(RedisSock *redis_sock, zval *z, char **val, strlen_t *val_len TSRMLS_DC); PHP_REDIS_API int redis_key_prefix(RedisSock *redis_sock, char **key, strlen_t *key_len); PHP_REDIS_API int redis_unserialize(RedisSock *redis_sock, const char *val, int val_len, zval *z_ret TSRMLS_DC); /* * Variant Read methods, mostly to implement eval */ PHP_REDIS_API int redis_read_reply_type(RedisSock *redis_sock, REDIS_REPLY_TYPE *reply_type, long *reply_info TSRMLS_DC); PHP_REDIS_API int redis_read_variant_line(RedisSock *redis_sock, REDIS_REPLY_TYPE reply_type, zval *z_ret TSRMLS_DC); PHP_REDIS_API int redis_read_variant_bulk(RedisSock *redis_sock, int size, zval *z_ret TSRMLS_DC); PHP_REDIS_API int redis_read_multibulk_recursive(RedisSock *redis_sock, int elements, zval *z_ret TSRMLS_DC); PHP_REDIS_API int redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab); #endif redis-3.1.6/php_redis.h0000644000175000000120000001756413223116600015543 0ustar pyatsukhnenkowheel/* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2009 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Original author: Alfonso Jimenez | | Maintainer: Nicolas Favre-Felix | | Maintainer: Michael Grunder | | Maintainer: Nasreddine Bouafif | +----------------------------------------------------------------------+ */ #include "common.h" #ifndef PHP_REDIS_H #define PHP_REDIS_H /* phpredis version */ #define PHP_REDIS_VERSION "3.1.6" PHP_METHOD(Redis, __construct); PHP_METHOD(Redis, __destruct); PHP_METHOD(Redis, connect); PHP_METHOD(Redis, pconnect); PHP_METHOD(Redis, close); PHP_METHOD(Redis, ping); PHP_METHOD(Redis, echo); PHP_METHOD(Redis, get); PHP_METHOD(Redis, set); PHP_METHOD(Redis, setex); PHP_METHOD(Redis, psetex); PHP_METHOD(Redis, setnx); PHP_METHOD(Redis, getSet); PHP_METHOD(Redis, randomKey); PHP_METHOD(Redis, renameKey); PHP_METHOD(Redis, renameNx); PHP_METHOD(Redis, getMultiple); PHP_METHOD(Redis, exists); PHP_METHOD(Redis, delete); PHP_METHOD(Redis, incr); PHP_METHOD(Redis, incrBy); PHP_METHOD(Redis, incrByFloat); PHP_METHOD(Redis, decr); PHP_METHOD(Redis, decrBy); PHP_METHOD(Redis, type); PHP_METHOD(Redis, append); PHP_METHOD(Redis, getRange); PHP_METHOD(Redis, setRange); PHP_METHOD(Redis, getBit); PHP_METHOD(Redis, setBit); PHP_METHOD(Redis, strlen); PHP_METHOD(Redis, getKeys); PHP_METHOD(Redis, sort); PHP_METHOD(Redis, sortAsc); PHP_METHOD(Redis, sortAscAlpha); PHP_METHOD(Redis, sortDesc); PHP_METHOD(Redis, sortDescAlpha); PHP_METHOD(Redis, lPush); PHP_METHOD(Redis, lPushx); PHP_METHOD(Redis, rPush); PHP_METHOD(Redis, rPushx); PHP_METHOD(Redis, lPop); PHP_METHOD(Redis, rPop); PHP_METHOD(Redis, blPop); PHP_METHOD(Redis, brPop); PHP_METHOD(Redis, lSize); PHP_METHOD(Redis, lRemove); PHP_METHOD(Redis, listTrim); PHP_METHOD(Redis, lGet); PHP_METHOD(Redis, lGetRange); PHP_METHOD(Redis, lSet); PHP_METHOD(Redis, lInsert); PHP_METHOD(Redis, sAdd); PHP_METHOD(Redis, sAddArray); PHP_METHOD(Redis, sSize); PHP_METHOD(Redis, sRemove); PHP_METHOD(Redis, sMove); PHP_METHOD(Redis, sPop); PHP_METHOD(Redis, sRandMember); PHP_METHOD(Redis, sContains); PHP_METHOD(Redis, sMembers); PHP_METHOD(Redis, sInter); PHP_METHOD(Redis, sInterStore); PHP_METHOD(Redis, sUnion); PHP_METHOD(Redis, sUnionStore); PHP_METHOD(Redis, sDiff); PHP_METHOD(Redis, sDiffStore); PHP_METHOD(Redis, setTimeout); PHP_METHOD(Redis, pexpire); PHP_METHOD(Redis, save); PHP_METHOD(Redis, bgSave); PHP_METHOD(Redis, lastSave); PHP_METHOD(Redis, flushDB); PHP_METHOD(Redis, flushAll); PHP_METHOD(Redis, dbSize); PHP_METHOD(Redis, auth); PHP_METHOD(Redis, ttl); PHP_METHOD(Redis, pttl); PHP_METHOD(Redis, persist); PHP_METHOD(Redis, info); PHP_METHOD(Redis, select); PHP_METHOD(Redis, move); PHP_METHOD(Redis, zAdd); PHP_METHOD(Redis, zDelete); PHP_METHOD(Redis, zRange); PHP_METHOD(Redis, zRevRange); PHP_METHOD(Redis, zRangeByScore); PHP_METHOD(Redis, zRevRangeByScore); PHP_METHOD(Redis, zRangeByLex); PHP_METHOD(Redis, zRevRangeByLex); PHP_METHOD(Redis, zRemRangeByLex); PHP_METHOD(Redis, zLexCount); PHP_METHOD(Redis, zCount); PHP_METHOD(Redis, zDeleteRangeByScore); PHP_METHOD(Redis, zDeleteRangeByRank); PHP_METHOD(Redis, zCard); PHP_METHOD(Redis, zScore); PHP_METHOD(Redis, zRank); PHP_METHOD(Redis, zRevRank); PHP_METHOD(Redis, zIncrBy); PHP_METHOD(Redis, zInter); PHP_METHOD(Redis, zUnion); PHP_METHOD(Redis, expireAt); PHP_METHOD(Redis, pexpireAt); PHP_METHOD(Redis, bgrewriteaof); PHP_METHOD(Redis, slaveof); PHP_METHOD(Redis, object); PHP_METHOD(Redis, bitop); PHP_METHOD(Redis, bitcount); PHP_METHOD(Redis, bitpos); PHP_METHOD(Redis, eval); PHP_METHOD(Redis, evalsha); PHP_METHOD(Redis, script); PHP_METHOD(Redis, debug); PHP_METHOD(Redis, dump); PHP_METHOD(Redis, restore); PHP_METHOD(Redis, migrate); PHP_METHOD(Redis, time); PHP_METHOD(Redis, role); PHP_METHOD(Redis, getLastError); PHP_METHOD(Redis, clearLastError); PHP_METHOD(Redis, _prefix); PHP_METHOD(Redis, _serialize); PHP_METHOD(Redis, _unserialize); PHP_METHOD(Redis, mset); PHP_METHOD(Redis, msetnx); PHP_METHOD(Redis, rpoplpush); PHP_METHOD(Redis, brpoplpush); PHP_METHOD(Redis, hGet); PHP_METHOD(Redis, hSet); PHP_METHOD(Redis, hSetNx); PHP_METHOD(Redis, hDel); PHP_METHOD(Redis, hLen); PHP_METHOD(Redis, hKeys); PHP_METHOD(Redis, hVals); PHP_METHOD(Redis, hGetAll); PHP_METHOD(Redis, hExists); PHP_METHOD(Redis, hIncrBy); PHP_METHOD(Redis, hIncrByFloat); PHP_METHOD(Redis, hMset); PHP_METHOD(Redis, hMget); PHP_METHOD(Redis, hStrLen); PHP_METHOD(Redis, multi); PHP_METHOD(Redis, discard); PHP_METHOD(Redis, exec); PHP_METHOD(Redis, watch); PHP_METHOD(Redis, unwatch); PHP_METHOD(Redis, pipeline); PHP_METHOD(Redis, publish); PHP_METHOD(Redis, subscribe); PHP_METHOD(Redis, psubscribe); PHP_METHOD(Redis, unsubscribe); PHP_METHOD(Redis, punsubscribe); PHP_METHOD(Redis, getOption); PHP_METHOD(Redis, setOption); PHP_METHOD(Redis, config); PHP_METHOD(Redis, slowlog); PHP_METHOD(Redis, wait); PHP_METHOD(Redis, pubsub); /* Geoadd and friends */ PHP_METHOD(Redis, geoadd); PHP_METHOD(Redis, geohash); PHP_METHOD(Redis, geopos); PHP_METHOD(Redis, geodist); PHP_METHOD(Redis, georadius); PHP_METHOD(Redis, georadiusbymember); PHP_METHOD(Redis, client); PHP_METHOD(Redis, command); PHP_METHOD(Redis, rawcommand); /* SCAN and friends */ PHP_METHOD(Redis, scan); PHP_METHOD(Redis, hscan); PHP_METHOD(Redis, sscan); PHP_METHOD(Redis, zscan); /* HyperLogLog commands */ PHP_METHOD(Redis, pfadd); PHP_METHOD(Redis, pfcount); PHP_METHOD(Redis, pfmerge); /* Reflection */ PHP_METHOD(Redis, getHost); PHP_METHOD(Redis, getPort); PHP_METHOD(Redis, getDBNum); PHP_METHOD(Redis, getTimeout); PHP_METHOD(Redis, getReadTimeout); PHP_METHOD(Redis, isConnected); PHP_METHOD(Redis, getPersistentID); PHP_METHOD(Redis, getAuth); PHP_METHOD(Redis, getMode); #ifdef ZTS #include "TSRM.h" #endif PHP_MINIT_FUNCTION(redis); PHP_MSHUTDOWN_FUNCTION(redis); PHP_MINFO_FUNCTION(redis); /* Redis response handler function callback prototype */ typedef void (*ResultCallback)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent); PHP_REDIS_API void generic_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sub_cmd); PHP_REDIS_API void generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *unsub_cmd); PHP_REDIS_API int redis_response_enqueued(RedisSock *redis_sock TSRMLS_DC); PHP_REDIS_API int get_flag(zval *object TSRMLS_DC); PHP_REDIS_API void set_flag(zval *object, int new_flag TSRMLS_DC); PHP_REDIS_API int redis_sock_read_multibulk_multi_reply_loop( INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int numElems); #ifndef _MSC_VER ZEND_BEGIN_MODULE_GLOBALS(redis) ZEND_END_MODULE_GLOBALS(redis) #endif extern zend_module_entry redis_module_entry; #define redis_module_ptr &redis_module_entry #define phpext_redis_ptr redis_module_ptr #endif /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: noet sw=4 ts=4 fdm=marker * vim<600: noet sw=4 ts=4 */ redis-3.1.6/redis_array.c0000644000175000000120000011444613223116600016062 0ustar pyatsukhnenkowheel/* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2009 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Nicolas Favre-Felix | | Maintainer: Michael Grunder | +----------------------------------------------------------------------+ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "common.h" #include "ext/standard/info.h" #include "php_ini.h" #include "php_redis.h" #include #include "library.h" #include "redis_array.h" #include "redis_array_impl.h" /* Simple macro to detect failure in a RedisArray call */ #define RA_CALL_FAILED(rv, cmd) ( \ PHPREDIS_ZVAL_IS_STRICT_FALSE(rv) || \ (Z_TYPE_P(rv) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(rv)) == 0) || \ (Z_TYPE_P(rv) == IS_LONG && Z_LVAL_P(rv) == 0 && !strcasecmp(cmd, "TYPE")) \ ) extern zend_class_entry *redis_ce; zend_class_entry *redis_array_ce; ZEND_BEGIN_ARG_INFO_EX(arginfo_ctor, 0, 0, 1) ZEND_ARG_INFO(0, name_or_hosts) ZEND_ARG_ARRAY_INFO(0, options, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_call, 0, 0, 2) ZEND_ARG_INFO(0, function_name) ZEND_ARG_INFO(0, arguments) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_target, 0, 0, 1) ZEND_ARG_INFO(0, key) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_instance, 0, 0, 1) ZEND_ARG_INFO(0, host) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_rehash, 0, 0, 0) ZEND_ARG_INFO(0, callable) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_select, 0, 0, 1) ZEND_ARG_INFO(0, index) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_mget, 0, 0, 1) ZEND_ARG_INFO(0, keys) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_mset, 0, 0, 1) ZEND_ARG_INFO(0, pairs) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_del, 0, 0, 1) ZEND_ARG_INFO(0, keys) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_getopt, 0, 0, 1) ZEND_ARG_INFO(0, opt) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_setopt, 0, 0, 2) ZEND_ARG_INFO(0, opt) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_keys, 0, 0, 1) ZEND_ARG_INFO(0, pattern) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_multi, 0, 0, 1) ZEND_ARG_INFO(0, host) ZEND_ARG_INFO(0, mode) ZEND_END_ARG_INFO() zend_function_entry redis_array_functions[] = { PHP_ME(RedisArray, __construct, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, __call, arginfo_call, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, _hosts, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, _target, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, _instance, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, _function, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, _distributor, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, _rehash, NULL, ZEND_ACC_PUBLIC) /* special implementation for a few functions */ PHP_ME(RedisArray, select, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, info, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, ping, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, flushdb, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, flushall, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, mget, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, mset, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, del, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, getOption, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, setOption,NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, keys, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, save, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, bgsave, NULL, ZEND_ACC_PUBLIC) /* Multi/Exec */ PHP_ME(RedisArray, multi, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, exec, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, discard, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, unwatch, NULL, ZEND_ACC_PUBLIC) /* Aliases */ PHP_MALIAS(RedisArray, delete, del, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(RedisArray, getMultiple, mget, NULL, ZEND_ACC_PUBLIC) PHP_FE_END }; static void redis_array_free(RedisArray *ra) { int i; /* Redis objects */ for(i=0;icount;i++) { zval_dtor(&ra->redis[i]); efree(ra->hosts[i]); } efree(ra->redis); efree(ra->hosts); /* delete hash function */ zval_dtor(&ra->z_fun); /* Distributor */ zval_dtor(&ra->z_dist); /* Delete pur commands */ zend_hash_destroy(ra->pure_cmds); FREE_HASHTABLE(ra->pure_cmds); /* Free structure itself */ efree(ra); } #if (PHP_MAJOR_VERSION < 7) typedef struct { zend_object std; RedisArray *ra; } redis_array_object; void free_redis_array_object(void *object TSRMLS_DC) { redis_array_object *obj = (redis_array_object *)object; zend_object_std_dtor(&obj->std TSRMLS_CC); if (obj->ra) { if (obj->ra->prev) redis_array_free(obj->ra->prev); redis_array_free(obj->ra); } efree(obj); } zend_object_value create_redis_array_object(zend_class_entry *ce TSRMLS_DC) { zend_object_value retval; redis_array_object *obj = ecalloc(1, sizeof(redis_array_object)); memset(obj, 0, sizeof(redis_array_object)); zend_object_std_init(&obj->std, ce TSRMLS_CC); #if PHP_VERSION_ID < 50399 zval *tmp; zend_hash_copy(obj->std.properties, &ce->default_properties, (copy_ctor_func_t)zval_add_ref, (void *)&tmp, sizeof(zval *)); #else object_properties_init(&obj->std, ce); #endif retval.handle = zend_objects_store_put(obj, (zend_objects_store_dtor_t)zend_objects_destroy_object, (zend_objects_free_object_storage_t)free_redis_array_object, NULL TSRMLS_CC); retval.handlers = zend_get_std_object_handlers(); return retval; } #else typedef struct { RedisArray *ra; zend_object std; } redis_array_object; zend_object_handlers redis_array_object_handlers; void free_redis_array_object(zend_object *object) { redis_array_object *obj = (redis_array_object *)((char *)(object) - XtOffsetOf(redis_array_object, std)); if (obj->ra) { if (obj->ra->prev) redis_array_free(obj->ra->prev); redis_array_free(obj->ra); } zend_object_std_dtor(&obj->std TSRMLS_CC); } zend_object * create_redis_array_object(zend_class_entry *ce TSRMLS_DC) { redis_array_object *obj = ecalloc(1, sizeof(redis_array_object) + zend_object_properties_size(ce)); obj->ra = NULL; zend_object_std_init(&obj->std, ce TSRMLS_CC); object_properties_init(&obj->std, ce); memcpy(&redis_array_object_handlers, zend_get_std_object_handlers(), sizeof(redis_array_object_handlers)); redis_array_object_handlers.offset = XtOffsetOf(redis_array_object, std); redis_array_object_handlers.free_obj = free_redis_array_object; obj->std.handlers = &redis_array_object_handlers; return &obj->std; } #endif /** * redis_array_get */ PHP_REDIS_API RedisArray * redis_array_get(zval *id TSRMLS_DC) { redis_array_object *obj; if (Z_TYPE_P(id) == IS_OBJECT) { #if (PHP_MAJOR_VERSION < 7) obj = (redis_array_object *)zend_objects_get_address(id TSRMLS_CC); #else obj = (redis_array_object *)((char *)Z_OBJ_P(id) - XtOffsetOf(redis_array_object, std)); #endif return obj->ra; } return NULL; } /* {{{ proto RedisArray RedisArray::__construct() Public constructor */ PHP_METHOD(RedisArray, __construct) { zval *z0, z_fun, z_dist, *zpData, *z_opts = NULL; RedisArray *ra = NULL; zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0; HashTable *hPrev = NULL, *hOpts = NULL; long l_retry_interval = 0; zend_bool b_lazy_connect = 0; double d_connect_timeout = 0, read_timeout = 0.0; redis_array_object *obj; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|a", &z0, &z_opts) == FAILURE) { RETURN_FALSE; } ZVAL_NULL(&z_fun); ZVAL_NULL(&z_dist); /* extract options */ if(z_opts) { hOpts = Z_ARRVAL_P(z_opts); /* extract previous ring. */ if ((zpData = zend_hash_str_find(hOpts, "previous", sizeof("previous") - 1)) != NULL && Z_TYPE_P(zpData) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(zpData)) != 0 ) { /* consider previous array as non-existent if empty. */ hPrev = Z_ARRVAL_P(zpData); } /* extract function name. */ if ((zpData = zend_hash_str_find(hOpts, "function", sizeof("function") - 1)) != NULL) { ZVAL_ZVAL(&z_fun, zpData, 1, 0); } /* extract function name. */ if ((zpData = zend_hash_str_find(hOpts, "distributor", sizeof("distributor") - 1)) != NULL) { ZVAL_ZVAL(&z_dist, zpData, 1, 0); } /* extract index option. */ if ((zpData = zend_hash_str_find(hOpts, "index", sizeof("index") - 1)) != NULL) { b_index = zval_is_true(zpData); } /* extract autorehash option. */ if ((zpData = zend_hash_str_find(hOpts, "autorehash", sizeof("autorehash") - 1)) != NULL) { b_autorehash = zval_is_true(zpData); } /* pconnect */ if ((zpData = zend_hash_str_find(hOpts, "pconnect", sizeof("pconnect") - 1)) != NULL) { b_pconnect = zval_is_true(zpData); } /* extract retry_interval option. */ if ((zpData = zend_hash_str_find(hOpts, "retry_interval", sizeof("retry_interval") - 1)) != NULL) { if (Z_TYPE_P(zpData) == IS_LONG) { l_retry_interval = Z_LVAL_P(zpData); } else if (Z_TYPE_P(zpData) == IS_STRING) { l_retry_interval = atol(Z_STRVAL_P(zpData)); } } /* extract lazy connect option. */ if ((zpData = zend_hash_str_find(hOpts, "lazy_connect", sizeof("lazy_connect") - 1)) != NULL) { b_lazy_connect = zval_is_true(zpData); } /* extract connect_timeout option */ if ((zpData = zend_hash_str_find(hOpts, "connect_timeout", sizeof("connect_timeout") - 1)) != NULL) { if (Z_TYPE_P(zpData) == IS_DOUBLE) { d_connect_timeout = Z_DVAL_P(zpData); } else if (Z_TYPE_P(zpData) == IS_LONG) { d_connect_timeout = Z_LVAL_P(zpData); } else if (Z_TYPE_P(zpData) == IS_STRING) { d_connect_timeout = atof(Z_STRVAL_P(zpData)); } } /* extract read_timeout option */ if ((zpData = zend_hash_str_find(hOpts, "read_timeout", sizeof("read_timeout") - 1)) != NULL) { if (Z_TYPE_P(zpData) == IS_DOUBLE) { read_timeout = Z_DVAL_P(zpData); } else if (Z_TYPE_P(zpData) == IS_LONG) { read_timeout = Z_LVAL_P(zpData); } else if (Z_TYPE_P(zpData) == IS_STRING) { read_timeout = atof(Z_STRVAL_P(zpData)); } } } /* extract either name of list of hosts from z0 */ switch(Z_TYPE_P(z0)) { case IS_STRING: ra = ra_load_array(Z_STRVAL_P(z0) TSRMLS_CC); break; case IS_ARRAY: ra = ra_make_array(Z_ARRVAL_P(z0), &z_fun, &z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout, read_timeout TSRMLS_CC); break; default: WRONG_PARAM_COUNT; } zval_dtor(&z_dist); zval_dtor(&z_fun); if(ra) { ra->auto_rehash = b_autorehash; ra->connect_timeout = d_connect_timeout; if(ra->prev) ra->prev->auto_rehash = b_autorehash; #if (PHP_MAJOR_VERSION < 7) obj = (redis_array_object *)zend_objects_get_address(getThis() TSRMLS_CC); #else obj = (redis_array_object *)((char *)Z_OBJ_P(getThis()) - XtOffsetOf(redis_array_object, std)); #endif obj->ra = ra; } } static void ra_forward_call(INTERNAL_FUNCTION_PARAMETERS, RedisArray *ra, const char *cmd, int cmd_len, zval *z_args, zval *z_new_target) { zval z_fun, *redis_inst, *z_callargs, *zp_tmp; char *key = NULL; /* set to avoid "unused-but-set-variable" */ int i, key_len = 0, argc; HashTable *h_args; zend_bool b_write_cmd = 0; h_args = Z_ARRVAL_P(z_args); if ((argc = zend_hash_num_elements(h_args)) == 0) { RETURN_FALSE; } if(ra->z_multi_exec) { redis_inst = ra->z_multi_exec; /* we already have the instance */ } else { /* extract key and hash it. */ if ((zp_tmp = zend_hash_index_find(h_args, 0)) == NULL || Z_TYPE_P(zp_tmp) != IS_STRING) { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not find key"); RETURN_FALSE; } key = Z_STRVAL_P(zp_tmp); key_len = Z_STRLEN_P(zp_tmp); /* find node */ redis_inst = ra_find_node(ra, key, key_len, NULL TSRMLS_CC); if(!redis_inst) { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not find any redis servers for this key."); RETURN_FALSE; } } /* pass call through */ ZVAL_STRINGL(&z_fun, cmd, cmd_len); /* method name */ z_callargs = ecalloc(argc, sizeof(zval)); /* copy args to array */ i = 0; ZEND_HASH_FOREACH_VAL(h_args, zp_tmp) { ZVAL_ZVAL(&z_callargs[i], zp_tmp, 1, 0); i++; } ZEND_HASH_FOREACH_END(); /* multi/exec */ if(ra->z_multi_exec) { call_user_function(&redis_ce->function_table, ra->z_multi_exec, &z_fun, return_value, argc, z_callargs); zval_dtor(return_value); zval_dtor(&z_fun); for (i = 0; i < argc; ++i) { zval_dtor(&z_callargs[i]); } efree(z_callargs); RETURN_ZVAL(getThis(), 1, 0); } /* check if write cmd */ b_write_cmd = ra_is_write_cmd(ra, cmd, cmd_len); /* CALL! */ if(ra->index && b_write_cmd) { /* add MULTI + SADD */ ra_index_multi(redis_inst, MULTI TSRMLS_CC); /* call using discarded temp value and extract exec results after. */ call_user_function(&redis_ce->function_table, redis_inst, &z_fun, return_value, argc, z_callargs); zval_dtor(return_value); /* add keys to index. */ ra_index_key(key, key_len, redis_inst TSRMLS_CC); /* call EXEC */ ra_index_exec(redis_inst, return_value, 0 TSRMLS_CC); } else { /* call directly through. */ call_user_function(&redis_ce->function_table, redis_inst, &z_fun, return_value, argc, z_callargs); if (!b_write_cmd) { /* check if we have an error. */ if (ra->prev && RA_CALL_FAILED(return_value, cmd)) { /* there was an error reading, try with prev ring. */ /* Free previous return value */ zval_dtor(return_value); /* ERROR, FALLBACK TO PREVIOUS RING and forward a reference to the first redis instance we were looking at. */ ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra->prev, cmd, cmd_len, z_args, z_new_target ? z_new_target : redis_inst); } /* Autorehash if the key was found on the previous node if this is a read command and auto rehashing is on */ if (ra->auto_rehash && z_new_target && !RA_CALL_FAILED(return_value, cmd)) { /* move key from old ring to new ring */ ra_move_key(key, key_len, redis_inst, z_new_target TSRMLS_CC); } } } /* cleanup */ zval_dtor(&z_fun); for (i = 0; i < argc; ++i) { zval_dtor(&z_callargs[i]); } efree(z_callargs); } PHP_METHOD(RedisArray, __call) { zval *object; RedisArray *ra; zval *z_args; char *cmd; strlen_t cmd_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa", &object, redis_array_ce, &cmd, &cmd_len, &z_args) == FAILURE) { RETURN_FALSE; } if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) { RETURN_FALSE; } ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra, cmd, cmd_len, z_args, NULL); } PHP_METHOD(RedisArray, _hosts) { zval *object; int i; RedisArray *ra; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_array_ce) == FAILURE) { RETURN_FALSE; } if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) { RETURN_FALSE; } array_init(return_value); for(i = 0; i < ra->count; ++i) { add_next_index_string(return_value, ra->hosts[i]); } } PHP_METHOD(RedisArray, _target) { zval *object; RedisArray *ra; char *key; strlen_t key_len; zval *redis_inst; int i; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_array_ce, &key, &key_len) == FAILURE) { RETURN_FALSE; } if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) { RETURN_FALSE; } redis_inst = ra_find_node(ra, key, key_len, &i TSRMLS_CC); if(redis_inst) { RETURN_STRING(ra->hosts[i]); } else { RETURN_NULL(); } } PHP_METHOD(RedisArray, _instance) { zval *object; RedisArray *ra; char *target; strlen_t target_len; zval *z_redis; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_array_ce, &target, &target_len) == FAILURE) { RETURN_FALSE; } if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) { RETURN_FALSE; } z_redis = ra_find_node_by_name(ra, target, target_len TSRMLS_CC); if(z_redis) { RETURN_ZVAL(z_redis, 1, 0); } else { RETURN_NULL(); } } PHP_METHOD(RedisArray, _function) { zval *object, *z_fun; RedisArray *ra; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_array_ce) == FAILURE) { RETURN_FALSE; } if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) { RETURN_FALSE; } z_fun = &ra->z_fun; RETURN_ZVAL(z_fun, 1, 0); } PHP_METHOD(RedisArray, _distributor) { zval *object, *z_dist; RedisArray *ra; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_array_ce) == FAILURE) { RETURN_FALSE; } if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) { RETURN_FALSE; } z_dist = &ra->z_dist; RETURN_ZVAL(z_dist, 1, 0); } PHP_METHOD(RedisArray, _rehash) { zval *object; RedisArray *ra; zend_fcall_info z_cb = {0}; zend_fcall_info_cache z_cb_cache = {0}; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|f", &object, redis_array_ce, &z_cb, &z_cb_cache) == FAILURE) { RETURN_FALSE; } if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) { RETURN_FALSE; } if (ZEND_NUM_ARGS() == 0) { ra_rehash(ra, NULL, NULL TSRMLS_CC); } else { ra_rehash(ra, &z_cb, &z_cb_cache TSRMLS_CC); } } static void multihost_distribute(INTERNAL_FUNCTION_PARAMETERS, const char *method_name) { zval *object, z_fun; int i; RedisArray *ra; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_array_ce) == FAILURE) { RETURN_FALSE; } if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) { RETURN_FALSE; } /* prepare call */ ZVAL_STRING(&z_fun, method_name); array_init(return_value); for(i = 0; i < ra->count; ++i) { zval zv, *z_tmp = &zv; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_tmp); #endif /* Call each node in turn */ call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 0, NULL); add_assoc_zval(return_value, ra->hosts[i], z_tmp); } zval_dtor(&z_fun); } PHP_METHOD(RedisArray, info) { multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "INFO"); } PHP_METHOD(RedisArray, ping) { multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PING"); } PHP_METHOD(RedisArray, flushdb) { multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "FLUSHDB"); } PHP_METHOD(RedisArray, flushall) { multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "FLUSHALL"); } PHP_METHOD(RedisArray, save) { multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SAVE"); } PHP_METHOD(RedisArray, bgsave) { multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "BGSAVE"); } PHP_METHOD(RedisArray, keys) { zval *object, z_args[1], z_fun; RedisArray *ra; char *pattern; strlen_t pattern_len; int i; /* Make sure the prototype is correct */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_array_ce, &pattern, &pattern_len) == FAILURE) { RETURN_FALSE; } /* Make sure we can grab our RedisArray object */ if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) { RETURN_FALSE; } /* Set up our function call (KEYS) */ ZVAL_STRINGL(&z_fun, "KEYS", 4); /* We will be passing with one string argument (the pattern) */ ZVAL_STRINGL(z_args, pattern, pattern_len); /* Init our array return */ array_init(return_value); /* Iterate our RedisArray nodes */ for(i=0; icount; ++i) { zval zv, *z_tmp = &zv; #if (PHP_MAJOR_VERSION < 7) /* Return for this node */ MAKE_STD_ZVAL(z_tmp); #endif /* Call KEYS on each node */ call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 1, z_args); /* Add the result for this host */ add_assoc_zval(return_value, ra->hosts[i], z_tmp); } zval_dtor(&z_args[0]); zval_dtor(&z_fun); } PHP_METHOD(RedisArray, getOption) { zval *object, z_fun, z_args[1]; int i; RedisArray *ra; zend_long opt; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", &object, redis_array_ce, &opt) == FAILURE) { RETURN_FALSE; } if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) { RETURN_FALSE; } /* prepare call */ ZVAL_STRINGL(&z_fun, "getOption", 9); /* copy arg */ ZVAL_LONG(&z_args[0], opt); array_init(return_value); for(i = 0; i < ra->count; ++i) { zval zv, *z_tmp = &zv; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_tmp); #endif /* Call each node in turn */ call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 1, z_args); add_assoc_zval(return_value, ra->hosts[i], z_tmp); } zval_dtor(&z_fun); } PHP_METHOD(RedisArray, setOption) { zval *object, z_fun, z_args[2]; int i; RedisArray *ra; zend_long opt; char *val_str; strlen_t val_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ols", &object, redis_array_ce, &opt, &val_str, &val_len) == FAILURE) { RETURN_FALSE; } if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) { RETURN_FALSE; } /* prepare call */ ZVAL_STRINGL(&z_fun, "setOption", 9); /* copy args */ ZVAL_LONG(&z_args[0], opt); ZVAL_STRINGL(&z_args[1], val_str, val_len); array_init(return_value); for(i = 0; i < ra->count; ++i) { zval zv, *z_tmp = &zv; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_tmp); #endif /* Call each node in turn */ call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 2, z_args); add_assoc_zval(return_value, ra->hosts[i], z_tmp); } zval_dtor(&z_args[1]); zval_dtor(&z_fun); } PHP_METHOD(RedisArray, select) { zval *object, z_fun, z_args[1]; int i; RedisArray *ra; zend_long opt; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", &object, redis_array_ce, &opt) == FAILURE) { RETURN_FALSE; } if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) { RETURN_FALSE; } /* prepare call */ ZVAL_STRINGL(&z_fun, "select", 6); /* copy args */ ZVAL_LONG(&z_args[0], opt); array_init(return_value); for(i = 0; i < ra->count; ++i) { zval zv, *z_tmp = &zv; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_tmp); #endif /* Call each node in turn */ call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 1, z_args); add_assoc_zval(return_value, ra->hosts[i], z_tmp); } zval_dtor(&z_fun); } #if (PHP_MAJOR_VERSION < 7) #define HANDLE_MULTI_EXEC(ra, cmd) do { \ if (ra && ra->z_multi_exec) { \ int i, num_varargs;\ zval ***varargs = NULL, *z_arg_array; \ if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O*",\ &object, redis_array_ce, &varargs, &num_varargs) == FAILURE) {\ RETURN_FALSE;\ }\ /* copy all args into a zval hash table */\ MAKE_STD_ZVAL(z_arg_array); \ array_init(z_arg_array);\ for(i = 0; i < num_varargs; ++i) {\ zval *z_tmp;\ MAKE_STD_ZVAL(z_tmp); \ ZVAL_ZVAL(z_tmp, *varargs[i], 1, 0); \ add_next_index_zval(z_arg_array, z_tmp); \ }\ /* call */\ ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra, cmd, sizeof(cmd) - 1, z_arg_array, NULL); \ zval_ptr_dtor(&z_arg_array); \ if(varargs) {\ efree(varargs);\ }\ return;\ }\ }while(0) #else #define HANDLE_MULTI_EXEC(ra, cmd) do { \ if (ra && ra->z_multi_exec) { \ int i, num_varargs; \ zval *varargs = NULL, z_arg_array; \ if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O*", \ &object, redis_array_ce, &varargs, &num_varargs) == FAILURE) { \ RETURN_FALSE;\ } \ /* copy all args into a zval hash table */\ array_init(&z_arg_array); \ for (i = 0; i < num_varargs; i++) { \ zval z_tmp; \ ZVAL_ZVAL(&z_tmp, &varargs[i], 1, 0); \ add_next_index_zval(&z_arg_array, &z_tmp); \ } \ /* call */\ ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra, cmd, sizeof(cmd) - 1, &z_arg_array, NULL); \ zval_dtor(&z_arg_array); \ return; \ } \ } while(0) #endif /* MGET will distribute the call to several nodes and regroup the values. */ PHP_METHOD(RedisArray, mget) { zval *object, *z_keys, z_argarray, *data, z_ret, *z_cur, z_tmp_array, *z_tmp; int i, j, n; RedisArray *ra; int *pos, argc, *argc_each; HashTable *h_keys; zval **argv; if ((ra = redis_array_get(getThis() TSRMLS_CC)) == NULL) { RETURN_FALSE; } /* Multi/exec support */ HANDLE_MULTI_EXEC(ra, "MGET"); if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", &object, redis_array_ce, &z_keys) == FAILURE) { RETURN_FALSE; } /* init data structures */ h_keys = Z_ARRVAL_P(z_keys); if ((argc = zend_hash_num_elements(h_keys)) == 0) { RETURN_FALSE; } argv = emalloc(argc * sizeof(zval*)); pos = emalloc(argc * sizeof(int)); argc_each = emalloc(ra->count * sizeof(int)); memset(argc_each, 0, ra->count * sizeof(int)); /* associate each key to a redis node */ i = 0; ZEND_HASH_FOREACH_VAL(h_keys, data) { /* If we need to represent a long key as a string */ unsigned int key_len; char kbuf[40], *key_lookup; /* phpredis proper can only use string or long keys, so restrict to that here */ if (Z_TYPE_P(data) != IS_STRING && Z_TYPE_P(data) != IS_LONG) { php_error_docref(NULL TSRMLS_CC, E_ERROR, "MGET: all keys must be strings or longs"); efree(argv); efree(pos); efree(argc_each); RETURN_FALSE; } /* Convert to a string for hash lookup if it isn't one */ if (Z_TYPE_P(data) == IS_STRING) { key_len = Z_STRLEN_P(data); key_lookup = Z_STRVAL_P(data); } else { key_len = snprintf(kbuf, sizeof(kbuf), "%ld", Z_LVAL_P(data)); key_lookup = (char*)kbuf; } /* Find our node */ if (ra_find_node(ra, key_lookup, key_len, &pos[i] TSRMLS_CC) == NULL) { /* TODO: handle */ } argc_each[pos[i]]++; /* count number of keys per node */ argv[i++] = data; } ZEND_HASH_FOREACH_END(); array_init(&z_tmp_array); /* calls */ for(n = 0; n < ra->count; ++n) { /* for each node */ /* We don't even need to make a call to this node if no keys go there */ if(!argc_each[n]) continue; /* copy args for MGET call on node. */ array_init(&z_argarray); for(i = 0; i < argc; ++i) { if(pos[i] != n) continue; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_tmp); #else zval zv; z_tmp = &zv; #endif ZVAL_ZVAL(z_tmp, argv[i], 1, 0); add_next_index_zval(&z_argarray, z_tmp); } zval z_fun; /* prepare call */ ZVAL_STRINGL(&z_fun, "MGET", 4); /* call MGET on the node */ call_user_function(&redis_ce->function_table, &ra->redis[n], &z_fun, &z_ret, 1, &z_argarray); zval_dtor(&z_fun); /* cleanup args array */ zval_dtor(&z_argarray); /* Error out if we didn't get a proper response */ if (Z_TYPE(z_ret) != IS_ARRAY) { /* cleanup */ zval_dtor(&z_ret); zval_dtor(&z_tmp_array); efree(argv); efree(pos); efree(argc_each); /* failure */ RETURN_FALSE; } for(i = 0, j = 0; i < argc; ++i) { if (pos[i] != n || (z_cur = zend_hash_index_find(Z_ARRVAL(z_ret), j++)) == NULL) continue; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_tmp); #else zval zv; z_tmp = &zv; #endif ZVAL_ZVAL(z_tmp, z_cur, 1, 0); add_index_zval(&z_tmp_array, i, z_tmp); } zval_dtor(&z_ret); } array_init(return_value); /* copy temp array in the right order to return_value */ for(i = 0; i < argc; ++i) { if ((z_cur = zend_hash_index_find(Z_ARRVAL(z_tmp_array), i)) == NULL) continue; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_tmp); #else zval zv; z_tmp = &zv; #endif ZVAL_ZVAL(z_tmp, z_cur, 1, 0); add_next_index_zval(return_value, z_tmp); } /* cleanup */ zval_dtor(&z_tmp_array); efree(argv); efree(pos); efree(argc_each); } /* MSET will distribute the call to several nodes and regroup the values. */ PHP_METHOD(RedisArray, mset) { zval *object, *z_keys, z_argarray, *data, z_ret, **argv; int i = 0, n; RedisArray *ra; int *pos, argc, *argc_each; HashTable *h_keys; char *key, **keys, kbuf[40]; int key_len, *key_lens; zend_string *zkey; ulong idx; if ((ra = redis_array_get(getThis() TSRMLS_CC)) == NULL) { RETURN_FALSE; } /* Multi/exec support */ HANDLE_MULTI_EXEC(ra, "MSET"); if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", &object, redis_array_ce, &z_keys) == FAILURE) { RETURN_FALSE; } /* init data structures */ h_keys = Z_ARRVAL_P(z_keys); if ((argc = zend_hash_num_elements(h_keys)) == 0) { RETURN_FALSE; } argv = emalloc(argc * sizeof(zval*)); pos = emalloc(argc * sizeof(int)); keys = emalloc(argc * sizeof(char*)); key_lens = emalloc(argc * sizeof(int)); argc_each = emalloc(ra->count * sizeof(int)); memset(argc_each, 0, ra->count * sizeof(int)); /* associate each key to a redis node */ ZEND_HASH_FOREACH_KEY_VAL(h_keys, idx, zkey, data) { /* If the key isn't a string, make a string representation of it */ if (zkey) { key_len = ZSTR_LEN(zkey); key = ZSTR_VAL(zkey); } else { key_len = snprintf(kbuf, sizeof(kbuf), "%lu", idx); key = kbuf; } if (ra_find_node(ra, key, (int)key_len, &pos[i] TSRMLS_CC) == NULL) { // TODO: handle } argc_each[pos[i]]++; /* count number of keys per node */ keys[i] = estrndup(key, key_len); key_lens[i] = (int)key_len; argv[i] = data; i++; } ZEND_HASH_FOREACH_END(); /* calls */ for (n = 0; n < ra->count; ++n) { /* for each node */ /* We don't even need to make a call to this node if no keys go there */ if(!argc_each[n]) continue; int found = 0; /* copy args */ array_init(&z_argarray); for(i = 0; i < argc; ++i) { if(pos[i] != n) continue; zval zv, *z_tmp = &zv; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_tmp); #endif if (argv[i] == NULL) { ZVAL_NULL(z_tmp); } else { ZVAL_ZVAL(z_tmp, argv[i], 1, 0); } add_assoc_zval_ex(&z_argarray, keys[i], key_lens[i], z_tmp); found++; } if(!found) { zval_dtor(&z_argarray); continue; /* don't run empty MSETs */ } if(ra->index) { /* add MULTI */ ra_index_multi(&ra->redis[n], MULTI TSRMLS_CC); } zval z_fun; /* prepare call */ ZVAL_STRINGL(&z_fun, "MSET", 4); /* call */ call_user_function(&redis_ce->function_table, &ra->redis[n], &z_fun, &z_ret, 1, &z_argarray); zval_dtor(&z_fun); zval_dtor(&z_ret); if(ra->index) { ra_index_keys(&z_argarray, &ra->redis[n] TSRMLS_CC); /* use SADD to add keys to node index */ ra_index_exec(&ra->redis[n], NULL, 0 TSRMLS_CC); /* run EXEC */ } zval_dtor(&z_argarray); } /* Free any keys that we needed to allocate memory for, because they weren't strings */ for(i = 0; i < argc; i++) { efree(keys[i]); } /* cleanup */ efree(keys); efree(key_lens); efree(argv); efree(pos); efree(argc_each); RETURN_TRUE; } /* DEL will distribute the call to several nodes and regroup the values. */ PHP_METHOD(RedisArray, del) { zval *object, z_keys, z_fun, *data, z_ret, *z_tmp, *z_args; int i, n; RedisArray *ra; int *pos, argc = ZEND_NUM_ARGS(), *argc_each; HashTable *h_keys; zval **argv; long total = 0; int free_zkeys = 0; if ((ra = redis_array_get(getThis() TSRMLS_CC)) == NULL) { RETURN_FALSE; } /* Multi/exec support */ HANDLE_MULTI_EXEC(ra, "DEL"); /* get all args in z_args */ z_args = emalloc(argc * sizeof(zval)); if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) { efree(z_args); RETURN_FALSE; } /* if single array arg, point z_keys to it. */ if (argc == 1 && Z_TYPE(z_args[0]) == IS_ARRAY) { z_keys = z_args[0]; } else { /* copy all elements to z_keys */ array_init(&z_keys); for (i = 0; i < argc; ++i) { zval *z_arg = &z_args[i]; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_tmp); #else zval zv; z_tmp = &zv; #endif ZVAL_ZVAL(z_tmp, z_arg, 1, 0); /* add copy to z_keys */ add_next_index_zval(&z_keys, z_tmp); } free_zkeys = 1; } /* init data structures */ h_keys = Z_ARRVAL(z_keys); if ((argc = zend_hash_num_elements(h_keys)) == 0) { if (free_zkeys) zval_dtor(&z_keys); efree(z_args); RETURN_FALSE; } argv = emalloc(argc * sizeof(zval*)); pos = emalloc(argc * sizeof(int)); argc_each = emalloc(ra->count * sizeof(int)); memset(argc_each, 0, ra->count * sizeof(int)); /* associate each key to a redis node */ i = 0; ZEND_HASH_FOREACH_VAL(h_keys, data) { if (Z_TYPE_P(data) != IS_STRING) { php_error_docref(NULL TSRMLS_CC, E_ERROR, "DEL: all keys must be string."); if (free_zkeys) zval_dtor(&z_keys); efree(z_args); efree(argv); efree(pos); efree(argc_each); RETURN_FALSE; } if (ra_find_node(ra, Z_STRVAL_P(data), Z_STRLEN_P(data), &pos[i] TSRMLS_CC) == NULL) { // TODO: handle } argc_each[pos[i]]++; /* count number of keys per node */ argv[i++] = data; } ZEND_HASH_FOREACH_END(); /* prepare call */ ZVAL_STRINGL(&z_fun, "DEL", 3); /* calls */ for(n = 0; n < ra->count; ++n) { /* for each node */ /* We don't even need to make a call to this node if no keys go there */ if(!argc_each[n]) continue; int found = 0; zval z_argarray; /* copy args */ array_init(&z_argarray); for(i = 0; i < argc; ++i) { if(pos[i] != n) continue; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_tmp); #else zval zv; z_tmp = &zv; #endif ZVAL_ZVAL(z_tmp, argv[i], 1, 0); add_next_index_zval(&z_argarray, z_tmp); found++; } if(!found) { /* don't run empty DELs */ zval_dtor(&z_argarray); continue; } if(ra->index) { /* add MULTI */ ra_index_multi(&ra->redis[n], MULTI TSRMLS_CC); } /* call */ call_user_function(&redis_ce->function_table, &ra->redis[n], &z_fun, &z_ret, 1, &z_argarray); if(ra->index) { zval_dtor(&z_ret); ra_index_del(&z_argarray, &ra->redis[n] TSRMLS_CC); /* use SREM to remove keys from node index */ ra_index_exec(&ra->redis[n], &z_ret, 0 TSRMLS_CC); /* run EXEC */ } total += Z_LVAL(z_ret); /* increment total */ zval_dtor(&z_argarray); zval_dtor(&z_ret); } /* cleanup */ zval_dtor(&z_fun); efree(argv); efree(pos); efree(argc_each); if(free_zkeys) { zval_dtor(&z_keys); } efree(z_args); RETURN_LONG(total); } PHP_METHOD(RedisArray, multi) { zval *object; RedisArray *ra; zval *z_redis; char *host; strlen_t host_len; zend_long multi_value = MULTI; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|l", &object, redis_array_ce, &host, &host_len, &multi_value) == FAILURE) { RETURN_FALSE; } if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) { RETURN_FALSE; } /* find node */ z_redis = ra_find_node_by_name(ra, host, host_len TSRMLS_CC); if(!z_redis) { RETURN_FALSE; } if(multi_value != MULTI && multi_value != PIPELINE) { RETURN_FALSE; } /* save multi object */ ra->z_multi_exec = z_redis; /* switch redis instance to multi/exec mode. */ ra_index_multi(z_redis, multi_value TSRMLS_CC); /* return this. */ RETURN_ZVAL(object, 1, 0); } PHP_METHOD(RedisArray, exec) { zval *object; RedisArray *ra; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_array_ce) == FAILURE) { RETURN_FALSE; } if ((ra = redis_array_get(object TSRMLS_CC)) == NULL || !ra->z_multi_exec) { RETURN_FALSE; } /* switch redis instance out of multi/exec mode. */ ra_index_exec(ra->z_multi_exec, return_value, 1 TSRMLS_CC); /* remove multi object */ ra->z_multi_exec = NULL; } PHP_METHOD(RedisArray, discard) { zval *object; RedisArray *ra; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_array_ce) == FAILURE) { RETURN_FALSE; } if ((ra = redis_array_get(object TSRMLS_CC)) == NULL || !ra->z_multi_exec) { RETURN_FALSE; } /* switch redis instance out of multi/exec mode. */ ra_index_discard(ra->z_multi_exec, return_value TSRMLS_CC); /* remove multi object */ ra->z_multi_exec = NULL; } PHP_METHOD(RedisArray, unwatch) { zval *object; RedisArray *ra; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_array_ce) == FAILURE) { RETURN_FALSE; } if ((ra = redis_array_get(object TSRMLS_CC)) == NULL || !ra->z_multi_exec) { RETURN_FALSE; } /* unwatch keys, stay in multi/exec mode. */ ra_index_unwatch(ra->z_multi_exec, return_value TSRMLS_CC); } redis-3.1.6/redis_array.h0000644000175000000120000000362013223116600016056 0ustar pyatsukhnenkowheel#ifndef REDIS_ARRAY_H #define REDIS_ARRAY_H #ifdef PHP_WIN32 #include "win32/php_stdint.h" #else #include #endif #include "common.h" PHP_METHOD(RedisArray, __construct); PHP_METHOD(RedisArray, __call); PHP_METHOD(RedisArray, _hosts); PHP_METHOD(RedisArray, _target); PHP_METHOD(RedisArray, _instance); PHP_METHOD(RedisArray, _function); PHP_METHOD(RedisArray, _distributor); PHP_METHOD(RedisArray, _rehash); PHP_METHOD(RedisArray, select); PHP_METHOD(RedisArray, info); PHP_METHOD(RedisArray, ping); PHP_METHOD(RedisArray, flushdb); PHP_METHOD(RedisArray, flushall); PHP_METHOD(RedisArray, mget); PHP_METHOD(RedisArray, mset); PHP_METHOD(RedisArray, del); PHP_METHOD(RedisArray, keys); PHP_METHOD(RedisArray, getOption); PHP_METHOD(RedisArray, setOption); PHP_METHOD(RedisArray, save); PHP_METHOD(RedisArray, bgsave); PHP_METHOD(RedisArray, multi); PHP_METHOD(RedisArray, exec); PHP_METHOD(RedisArray, discard); PHP_METHOD(RedisArray, unwatch); typedef struct RedisArray_ { int count; char **hosts; /* array of host:port strings */ zval *redis; /* array of Redis instances */ zval *z_multi_exec; /* Redis instance to be used in multi-exec */ zend_bool index; /* use per-node index */ zend_bool auto_rehash; /* migrate keys on read operations */ zend_bool pconnect; /* should we use pconnect */ zval z_fun; /* key extractor, callable */ zval z_dist; /* key distributor, callable */ HashTable *pure_cmds; /* hash table */ double connect_timeout; /* socket connect timeout */ double read_timeout; /* socket read timeout */ struct RedisArray_ *prev; } RedisArray; #if (PHP_MAJOR_VERSION < 7) zend_object_value create_redis_array_object(zend_class_entry *ce TSRMLS_DC); void free_redis_array_object(void *object TSRMLS_DC); #else zend_object *create_redis_array_object(zend_class_entry *ce TSRMLS_DC); void free_redis_array_object(zend_object *object); #endif #endif redis-3.1.6/redis_array_impl.c0000644000175000000120000010667613223116600017111 0ustar pyatsukhnenkowheel/* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2009 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Nicolas Favre-Felix | | Maintainer: Michael Grunder | +----------------------------------------------------------------------+ */ #include "redis_array_impl.h" #include "php_redis.h" #include "library.h" #include "php_variables.h" #include "SAPI.h" #include "ext/standard/url.h" #include "ext/standard/crc32.h" #define PHPREDIS_INDEX_NAME "__phpredis_array_index__" extern zend_class_entry *redis_ce; RedisArray* ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC) { int i = 0, host_len; char *host, *p; short port; zval *zpData, z_cons, z_ret; redis_object *redis; /* function calls on the Redis object */ ZVAL_STRINGL(&z_cons, "__construct", 11); /* init connections */ ZEND_HASH_FOREACH_VAL(hosts, zpData) { if (Z_TYPE_P(zpData) != IS_STRING) { zval_dtor(&z_cons); return NULL; } /* default values */ host = Z_STRVAL_P(zpData); host_len = Z_STRLEN_P(zpData); ra->hosts[i] = estrndup(host, host_len); port = 6379; if((p = strrchr(host, ':'))) { /* found port */ host_len = p - host; port = (short)atoi(p+1); } else if(strchr(host,'/') != NULL) { /* unix socket */ port = -1; } /* create Redis object */ #if (PHP_MAJOR_VERSION < 7) INIT_PZVAL(&ra->redis[i]); #endif object_init_ex(&ra->redis[i], redis_ce); call_user_function(&redis_ce->function_table, &ra->redis[i], &z_cons, &z_ret, 0, NULL); zval_dtor(&z_ret); #if (PHP_MAJOR_VERSION < 7) redis = (redis_object *)zend_objects_get_address(&ra->redis[i] TSRMLS_CC); #else redis = (redis_object *)((char *)Z_OBJ_P(&ra->redis[i]) - XtOffsetOf(redis_object, std)); #endif /* create socket */ redis->sock = redis_sock_create(host, host_len, port, ra->connect_timeout, ra->read_timeout, ra->pconnect, NULL, retry_interval, b_lazy_connect); if (!b_lazy_connect) { /* connect */ redis_sock_server_open(redis->sock TSRMLS_CC); } ra->count = ++i; } ZEND_HASH_FOREACH_END(); zval_dtor(&z_cons); return ra; } /* List pure functions */ void ra_init_function_table(RedisArray *ra) { ALLOC_HASHTABLE(ra->pure_cmds); zend_hash_init(ra->pure_cmds, 0, NULL, NULL, 0); zend_hash_str_update_ptr(ra->pure_cmds, "EXISTS", sizeof("EXISTS") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "GET", sizeof("GET") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "GETBIT", sizeof("GETBIT") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "GETRANGE", sizeof("GETRANGE") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "HEXISTS", sizeof("HEXISTS") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "HGET", sizeof("HGET") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "HGETALL", sizeof("HGETALL") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "HKEYS", sizeof("HKEYS") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "HLEN", sizeof("HLEN") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "HMGET", sizeof("HMGET") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "HVALS", sizeof("HVALS") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "LINDEX", sizeof("LINDEX") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "LLEN", sizeof("LLEN") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "LRANGE", sizeof("LRANGE") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "OBJECT", sizeof("OBJECT") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "SCARD", sizeof("SCARD") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "SDIFF", sizeof("SDIFF") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "SINTER", sizeof("SINTER") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "SISMEMBER", sizeof("SISMEMBER") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "SMEMBERS", sizeof("SMEMBERS") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "SRANDMEMBER", sizeof("SRANDMEMBER") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "STRLEN", sizeof("STRLEN") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "SUNION", sizeof("SUNION") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "TYPE", sizeof("TYPE") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "ZCARD", sizeof("ZCARD") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "ZCOUNT", sizeof("ZCOUNT") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "ZRANGE", sizeof("ZRANGE") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "ZRANK", sizeof("ZRANK") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "ZREVRANGE", sizeof("ZREVRANGE") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "ZREVRANGEBYSCORE", sizeof("ZREVRANGEBYSCORE") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "ZREVRANK", sizeof("ZREVRANK") - 1, NULL); zend_hash_str_update_ptr(ra->pure_cmds, "ZSCORE", sizeof("ZSCORE") - 1, NULL); } static int ra_find_name(const char *name) { const char *ini_names, *p, *next; /* php_printf("Loading redis array with name=[%s]\n", name); */ ini_names = INI_STR("redis.arrays.names"); for(p = ini_names; p;) { next = strchr(p, ','); if(next) { if(strncmp(p, name, next - p) == 0) { return 1; } } else { if(strcmp(p, name) == 0) { return 1; } break; } p = next + 1; } return 0; } /* laod array from INI settings */ RedisArray *ra_load_array(const char *name TSRMLS_DC) { zval *z_data, z_fun, z_dist; zval z_params_hosts; zval z_params_prev; zval z_params_funs; zval z_params_dist; zval z_params_index; zval z_params_autorehash; zval z_params_retry_interval; zval z_params_pconnect; zval z_params_connect_timeout; zval z_params_read_timeout; zval z_params_lazy_connect; RedisArray *ra = NULL; zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0; long l_retry_interval = 0; zend_bool b_lazy_connect = 0; double d_connect_timeout = 0, read_timeout = 0.0; HashTable *hHosts = NULL, *hPrev = NULL; size_t name_len = strlen(name); char *iptr; /* find entry */ if(!ra_find_name(name)) return ra; /* find hosts */ array_init(&z_params_hosts); if ((iptr = INI_STR("redis.arrays.hosts")) != NULL) { sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_hosts TSRMLS_CC); } if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_hosts), name, name_len)) != NULL) { hHosts = Z_ARRVAL_P(z_data); } /* find previous hosts */ array_init(&z_params_prev); if ((iptr = INI_STR("redis.arrays.previous")) != NULL) { sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_prev TSRMLS_CC); } if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_prev), name, name_len)) != NULL) { hPrev = Z_ARRVAL_P(z_data); } /* find function */ array_init(&z_params_funs); if ((iptr = INI_STR("redis.arrays.functions")) != NULL) { sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_funs TSRMLS_CC); } ZVAL_NULL(&z_fun); if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_funs), name, name_len)) != NULL) { ZVAL_ZVAL(&z_fun, z_data, 1, 0); } /* find distributor */ array_init(&z_params_dist); if ((iptr = INI_STR("redis.arrays.distributor")) != NULL) { sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_dist TSRMLS_CC); } ZVAL_NULL(&z_dist); if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_dist), name, name_len)) != NULL) { ZVAL_ZVAL(&z_dist, z_data, 1, 0); } /* find index option */ array_init(&z_params_index); if ((iptr = INI_STR("redis.arrays.index")) != NULL) { sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_index TSRMLS_CC); } if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_index), name, name_len)) != NULL) { if (Z_TYPE_P(z_data) == IS_STRING && strncmp(Z_STRVAL_P(z_data), "1", 1) == 0) { b_index = 1; } } /* find autorehash option */ array_init(&z_params_autorehash); if ((iptr = INI_STR("redis.arrays.autorehash")) != NULL) { sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_autorehash TSRMLS_CC); } if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_autorehash), name, name_len)) != NULL) { if(Z_TYPE_P(z_data) == IS_STRING && strncmp(Z_STRVAL_P(z_data), "1", 1) == 0) { b_autorehash = 1; } } /* find retry interval option */ array_init(&z_params_retry_interval); if ((iptr = INI_STR("redis.arrays.retryinterval")) != NULL) { sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_retry_interval TSRMLS_CC); } if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_retry_interval), name, name_len)) != NULL) { if (Z_TYPE_P(z_data) == IS_LONG) { l_retry_interval = Z_LVAL_P(z_data); } else if (Z_TYPE_P(z_data) == IS_STRING) { l_retry_interval = atol(Z_STRVAL_P(z_data)); } } /* find pconnect option */ array_init(&z_params_pconnect); if ((iptr = INI_STR("redis.arrays.pconnect")) != NULL) { sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_pconnect TSRMLS_CC); } if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_pconnect), name, name_len)) != NULL) { if(Z_TYPE_P(z_data) == IS_STRING && strncmp(Z_STRVAL_P(z_data), "1", 1) == 0) { b_pconnect = 1; } } /* find lazy connect option */ array_init(&z_params_lazy_connect); if ((iptr = INI_STR("redis.arrays.lazyconnect")) != NULL) { sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_lazy_connect TSRMLS_CC); } if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_lazy_connect), name, name_len)) != NULL) { if (Z_TYPE_P(z_data) == IS_STRING && strncmp(Z_STRVAL_P(z_data), "1", 1) == 0) { b_lazy_connect = 1; } } /* find connect timeout option */ array_init(&z_params_connect_timeout); if ((iptr = INI_STR("redis.arrays.connecttimeout")) != NULL) { sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_connect_timeout TSRMLS_CC); } if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_connect_timeout), name, name_len)) != NULL) { if (Z_TYPE_P(z_data) == IS_DOUBLE) { d_connect_timeout = Z_DVAL_P(z_data); } else if (Z_TYPE_P(z_data) == IS_STRING) { d_connect_timeout = atof(Z_STRVAL_P(z_data)); } else if (Z_TYPE_P(z_data) == IS_LONG) { d_connect_timeout = Z_LVAL_P(z_data); } } /* find read timeout option */ array_init(&z_params_read_timeout); if ((iptr = INI_STR("redis.arrays.readtimeout")) != NULL) { sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_read_timeout TSRMLS_CC); } if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_read_timeout), name, name_len)) != NULL) { if (Z_TYPE_P(z_data) == IS_DOUBLE) { read_timeout = Z_DVAL_P(z_data); } else if (Z_TYPE_P(z_data) == IS_STRING) { read_timeout = atof(Z_STRVAL_P(z_data)); } else if (Z_TYPE_P(z_data) == IS_LONG) { read_timeout = Z_LVAL_P(z_data); } } /* create RedisArray object */ ra = ra_make_array(hHosts, &z_fun, &z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout, read_timeout TSRMLS_CC); if (ra) { ra->auto_rehash = b_autorehash; if(ra->prev) ra->prev->auto_rehash = b_autorehash; } /* cleanup */ zval_dtor(&z_params_hosts); zval_dtor(&z_params_prev); zval_dtor(&z_params_funs); zval_dtor(&z_params_dist); zval_dtor(&z_params_index); zval_dtor(&z_params_autorehash); zval_dtor(&z_params_retry_interval); zval_dtor(&z_params_pconnect); zval_dtor(&z_params_connect_timeout); zval_dtor(&z_params_read_timeout); zval_dtor(&z_params_lazy_connect); zval_dtor(&z_dist); zval_dtor(&z_fun); return ra; } RedisArray * ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout, double read_timeout TSRMLS_DC) { int i, count; RedisArray *ra; if (!hosts || (count = zend_hash_num_elements(hosts)) == 0) return NULL; /* create object */ ra = emalloc(sizeof(RedisArray)); ra->hosts = ecalloc(count, sizeof(char *)); ra->redis = ecalloc(count, sizeof(zval)); ra->count = 0; ra->z_multi_exec = NULL; ra->index = b_index; ra->auto_rehash = 0; ra->pconnect = b_pconnect; ra->connect_timeout = connect_timeout; ra->read_timeout = read_timeout; if (ra_load_hosts(ra, hosts, retry_interval, b_lazy_connect TSRMLS_CC) == NULL || !ra->count) { for (i = 0; i < ra->count; ++i) { zval_dtor(&ra->redis[i]); efree(ra->hosts[i]); } efree(ra->redis); efree(ra->hosts); efree(ra); return NULL; } ra->prev = hosts_prev ? ra_make_array(hosts_prev, z_fun, z_dist, NULL, b_index, b_pconnect, retry_interval, b_lazy_connect, connect_timeout, read_timeout TSRMLS_CC) : NULL; /* init array data structures */ ra_init_function_table(ra); /* Set hash function and distribtor if provided */ ZVAL_ZVAL(&ra->z_fun, z_fun, 1, 0); ZVAL_ZVAL(&ra->z_dist, z_dist, 1, 0); return ra; } /* call userland key extraction function */ char * ra_call_extractor(RedisArray *ra, const char *key, int key_len, int *out_len TSRMLS_DC) { char *out = NULL; zval z_ret, z_argv; /* check that we can call the extractor function */ #if (PHP_MAJOR_VERSION < 7) if (!zend_is_callable_ex(&ra->z_fun, NULL, 0, NULL, NULL, NULL, NULL TSRMLS_CC)) { #else if (!zend_is_callable_ex(&ra->z_fun, NULL, 0, NULL, NULL, NULL)) { #endif php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not call extractor function"); return NULL; } ZVAL_NULL(&z_ret); /* call extraction function */ ZVAL_STRINGL(&z_argv, key, key_len); call_user_function(EG(function_table), NULL, &ra->z_fun, &z_ret, 1, &z_argv); if (Z_TYPE(z_ret) == IS_STRING) { *out_len = Z_STRLEN(z_ret); out = estrndup(Z_STRVAL(z_ret), *out_len); } zval_dtor(&z_argv); zval_dtor(&z_ret); return out; } static char * ra_extract_key(RedisArray *ra, const char *key, int key_len, int *out_len TSRMLS_DC) { char *start, *end; *out_len = key_len; if (Z_TYPE(ra->z_fun) != IS_NULL) { return ra_call_extractor(ra, key, key_len, out_len TSRMLS_CC); } else if ((start = strchr(key, '{')) == NULL || (end = strchr(start + 1, '}')) == NULL) { return estrndup(key, key_len); } /* found substring */ *out_len = end - start - 1; return estrndup(start + 1, *out_len); } /* call userland key distributor function */ int ra_call_distributor(RedisArray *ra, const char *key, int key_len TSRMLS_DC) { int ret; zval z_ret, z_argv; /* check that we can call the extractor function */ #if (PHP_MAJOR_VERSION < 7) if (!zend_is_callable_ex(&ra->z_dist, NULL, 0, NULL, NULL, NULL, NULL TSRMLS_CC)) { #else if (!zend_is_callable_ex(&ra->z_dist, NULL, 0, NULL, NULL, NULL)) { #endif php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not call distributor function"); return -1; } ZVAL_NULL(&z_ret); /* call extraction function */ ZVAL_STRINGL(&z_argv, key, key_len); call_user_function(EG(function_table), NULL, &ra->z_dist, &z_ret, 1, &z_argv); ret = (Z_TYPE(z_ret) == IS_LONG) ? Z_LVAL(z_ret) : -1; zval_dtor(&z_argv); zval_dtor(&z_ret); return ret; } zval * ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos TSRMLS_DC) { uint32_t hash; char *out; int pos, out_len; /* extract relevant part of the key */ out = ra_extract_key(ra, key, key_len, &out_len TSRMLS_CC); if(!out) return NULL; if (Z_TYPE(ra->z_dist) != IS_NULL) { pos = ra_call_distributor(ra, key, key_len TSRMLS_CC); if (pos < 0 || pos >= ra->count) { efree(out); return NULL; } } else { uint64_t h64; unsigned long ret = 0xffffffff; size_t i; /* hash */ for (i = 0; i < out_len; ++i) { CRC32(ret, (unsigned char)out[i]); } hash = (ret ^ 0xffffffff); /* get position on ring */ h64 = hash; h64 *= ra->count; h64 /= 0xffffffff; pos = (int)h64; } efree(out); if(out_pos) *out_pos = pos; return &ra->redis[pos]; } zval * ra_find_node_by_name(RedisArray *ra, const char *host, int host_len TSRMLS_DC) { int i; for(i = 0; i < ra->count; ++i) { if(strncmp(ra->hosts[i], host, host_len) == 0) { return &ra->redis[i]; } } return NULL; } void ra_index_multi(zval *z_redis, long multi_value TSRMLS_DC) { zval z_fun_multi, z_ret; zval z_args[1]; /* run MULTI */ ZVAL_STRINGL(&z_fun_multi, "MULTI", 5); ZVAL_LONG(&z_args[0], multi_value); call_user_function(&redis_ce->function_table, z_redis, &z_fun_multi, &z_ret, 1, z_args); zval_dtor(&z_fun_multi); zval_dtor(&z_ret); } static void ra_index_change_keys(const char *cmd, zval *z_keys, zval *z_redis TSRMLS_DC) { int i, argc; zval z_fun, z_ret, *z_args; /* alloc */ argc = 1 + zend_hash_num_elements(Z_ARRVAL_P(z_keys)); z_args = ecalloc(argc, sizeof(zval)); /* prepare first parameters */ ZVAL_STRING(&z_fun, cmd); ZVAL_STRINGL(&z_args[0], PHPREDIS_INDEX_NAME, sizeof(PHPREDIS_INDEX_NAME) - 1); /* prepare keys */ for(i = 0; i < argc - 1; ++i) { zval *zv = zend_hash_index_find(Z_ARRVAL_P(z_keys), i); if (zv == NULL) { ZVAL_NULL(&z_args[i+1]); } else { z_args[i+1] = *zv; } } /* run cmd */ call_user_function(&redis_ce->function_table, z_redis, &z_fun, &z_ret, argc, z_args); zval_dtor(&z_args[0]); zval_dtor(&z_fun); zval_dtor(&z_ret); efree(z_args); /* free container */ } void ra_index_del(zval *z_keys, zval *z_redis TSRMLS_DC) { ra_index_change_keys("SREM", z_keys, z_redis TSRMLS_CC); } void ra_index_keys(zval *z_pairs, zval *z_redis TSRMLS_DC) { zval z_keys, *z_val; zend_string *zkey; ulong idx; /* Initialize key array */ #if PHP_VERSION_ID > 50300 array_init_size(&z_keys, zend_hash_num_elements(Z_ARRVAL_P(z_pairs))); #else array_init(&z_keys); #endif /* Go through input array and add values to the key array */ ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(z_pairs), idx, zkey, z_val) { zval zv, *z_new = &zv; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_new); #endif PHPREDIS_NOTUSED(z_val); if (zkey) { ZVAL_STRINGL(z_new, ZSTR_VAL(zkey), ZSTR_LEN(zkey)); } else { ZVAL_LONG(z_new, idx); } zend_hash_next_index_insert(Z_ARRVAL(z_keys), z_new); } ZEND_HASH_FOREACH_END(); /* add keys to index */ ra_index_change_keys("SADD", &z_keys, z_redis TSRMLS_CC); /* cleanup */ zval_dtor(&z_keys); } void ra_index_key(const char *key, int key_len, zval *z_redis TSRMLS_DC) { zval z_fun_sadd, z_ret, z_args[2]; /* prepare args */ ZVAL_STRINGL(&z_fun_sadd, "SADD", 4); ZVAL_STRINGL(&z_args[0], PHPREDIS_INDEX_NAME, sizeof(PHPREDIS_INDEX_NAME) - 1); ZVAL_STRINGL(&z_args[1], key, key_len); /* run SADD */ call_user_function(&redis_ce->function_table, z_redis, &z_fun_sadd, &z_ret, 2, z_args); zval_dtor(&z_fun_sadd); zval_dtor(&z_args[1]); zval_dtor(&z_args[0]); zval_dtor(&z_ret); } void ra_index_exec(zval *z_redis, zval *return_value, int keep_all TSRMLS_DC) { zval z_fun_exec, z_ret, *zp_tmp; /* run EXEC */ ZVAL_STRINGL(&z_fun_exec, "EXEC", 4); call_user_function(&redis_ce->function_table, z_redis, &z_fun_exec, &z_ret, 0, NULL); zval_dtor(&z_fun_exec); /* extract first element of exec array and put into return_value. */ if(Z_TYPE(z_ret) == IS_ARRAY) { if(return_value) { if(keep_all) { zp_tmp = &z_ret; RETVAL_ZVAL(zp_tmp, 1, 0); } else if ((zp_tmp = zend_hash_index_find(Z_ARRVAL(z_ret), 0)) != NULL) { RETVAL_ZVAL(zp_tmp, 1, 0); } } } zval_dtor(&z_ret); /* zval *zptr = &z_ret; */ /* php_var_dump(&zptr, 0 TSRMLS_CC); */ } void ra_index_discard(zval *z_redis, zval *return_value TSRMLS_DC) { zval z_fun_discard, z_ret; /* run DISCARD */ ZVAL_STRINGL(&z_fun_discard, "DISCARD", 7); call_user_function(&redis_ce->function_table, z_redis, &z_fun_discard, &z_ret, 0, NULL); zval_dtor(&z_fun_discard); zval_dtor(&z_ret); } void ra_index_unwatch(zval *z_redis, zval *return_value TSRMLS_DC) { zval z_fun_unwatch, z_ret; /* run UNWATCH */ ZVAL_STRINGL(&z_fun_unwatch, "UNWATCH", 7); call_user_function(&redis_ce->function_table, z_redis, &z_fun_unwatch, &z_ret, 0, NULL); zval_dtor(&z_fun_unwatch); zval_dtor(&z_ret); } zend_bool ra_is_write_cmd(RedisArray *ra, const char *cmd, int cmd_len) { zend_bool ret; int i; char *cmd_up = emalloc(1 + cmd_len); /* convert to uppercase */ for(i = 0; i < cmd_len; ++i) cmd_up[i] = toupper(cmd[i]); cmd_up[cmd_len] = 0; ret = zend_hash_str_exists(ra->pure_cmds, cmd_up, cmd_len); efree(cmd_up); return !ret; } /* run TYPE to find the type */ static zend_bool ra_get_key_type(zval *z_redis, const char *key, int key_len, zval *z_from, long *res TSRMLS_DC) { int i = 0; zval z_fun, z_ret, z_arg, *z_data; long success = 1; /* Pipelined */ ra_index_multi(z_from, PIPELINE TSRMLS_CC); /* prepare args */ ZVAL_STRINGL(&z_arg, key, key_len); /* run TYPE */ ZVAL_NULL(&z_ret); ZVAL_STRINGL(&z_fun, "TYPE", 4); call_user_function(&redis_ce->function_table, z_redis, &z_fun, &z_ret, 1, &z_arg); zval_dtor(&z_fun); zval_dtor(&z_ret); /* run TYPE */ ZVAL_NULL(&z_ret); ZVAL_STRINGL(&z_fun, "TTL", 3); call_user_function(&redis_ce->function_table, z_redis, &z_fun, &z_ret, 1, &z_arg); zval_dtor(&z_fun); zval_dtor(&z_ret); /* Get the result from the pipeline. */ ra_index_exec(z_from, &z_ret, 1 TSRMLS_CC); if (Z_TYPE(z_ret) == IS_ARRAY) { ZEND_HASH_FOREACH_VAL(Z_ARRVAL(z_ret), z_data) { if (z_data == NULL || Z_TYPE_P(z_data) != IS_LONG) { success = 0; break; } /* Get the result - Might change in the future to handle doubles as well */ res[i++] = Z_LVAL_P(z_data); } ZEND_HASH_FOREACH_END(); } zval_dtor(&z_arg); zval_dtor(&z_ret); return success; } /* delete key from source server index during rehashing */ static void ra_remove_from_index(zval *z_redis, const char *key, int key_len TSRMLS_DC) { zval z_fun_srem, z_ret, z_args[2]; /* run SREM on source index */ ZVAL_STRINGL(&z_fun_srem, "SREM", 4); ZVAL_STRINGL(&z_args[0], PHPREDIS_INDEX_NAME, sizeof(PHPREDIS_INDEX_NAME) - 1); ZVAL_STRINGL(&z_args[1], key, key_len); call_user_function(&redis_ce->function_table, z_redis, &z_fun_srem, &z_ret, 2, z_args); /* cleanup */ zval_dtor(&z_fun_srem); zval_dtor(&z_args[1]); zval_dtor(&z_args[0]); zval_dtor(&z_ret); } /* delete key from source server during rehashing */ static zend_bool ra_del_key(const char *key, int key_len, zval *z_from TSRMLS_DC) { zval z_fun_del, z_ret, z_args[1]; /* in a transaction */ ra_index_multi(z_from, MULTI TSRMLS_CC); /* run DEL on source */ ZVAL_STRINGL(&z_fun_del, "DEL", 3); ZVAL_STRINGL(&z_args[0], key, key_len); call_user_function(&redis_ce->function_table, z_from, &z_fun_del, &z_ret, 1, z_args); zval_dtor(&z_fun_del); zval_dtor(&z_args[0]); zval_dtor(&z_ret); /* remove key from index */ ra_remove_from_index(z_from, key, key_len TSRMLS_CC); /* close transaction */ ra_index_exec(z_from, NULL, 0 TSRMLS_CC); return 1; } static zend_bool ra_expire_key(const char *key, int key_len, zval *z_to, long ttl TSRMLS_DC) { zval z_fun_expire, z_ret, z_args[2]; if (ttl > 0) { /* run EXPIRE on target */ ZVAL_STRINGL(&z_fun_expire, "EXPIRE", 6); ZVAL_STRINGL(&z_args[0], key, key_len); ZVAL_LONG(&z_args[1], ttl); call_user_function(&redis_ce->function_table, z_to, &z_fun_expire, &z_ret, 2, z_args); zval_dtor(&z_fun_expire); zval_dtor(&z_args[0]); zval_dtor(&z_ret); } return 1; } static zend_bool ra_move_zset(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) { zval z_fun_zrange, z_fun_zadd, z_ret, z_ret_dest, z_args[4], *z_zadd_args, *z_score_p; int i, count; HashTable *h_zset_vals; zend_string *zkey; ulong idx; /* run ZRANGE key 0 -1 WITHSCORES on source */ ZVAL_STRINGL(&z_fun_zrange, "ZRANGE", 6); ZVAL_STRINGL(&z_args[0], key, key_len); ZVAL_STRINGL(&z_args[1], "0", 1); ZVAL_STRINGL(&z_args[2], "-1", 2); ZVAL_BOOL(&z_args[3], 1); call_user_function(&redis_ce->function_table, z_from, &z_fun_zrange, &z_ret, 4, z_args); zval_dtor(&z_fun_zrange); zval_dtor(&z_args[2]); zval_dtor(&z_args[1]); zval_dtor(&z_args[0]); if(Z_TYPE(z_ret) != IS_ARRAY) { /* key not found or replaced */ /* TODO: report? */ zval_dtor(&z_ret); return 0; } /* we now have an array of value → score pairs in z_ret. */ h_zset_vals = Z_ARRVAL(z_ret); /* allocate argument array for ZADD */ count = zend_hash_num_elements(h_zset_vals); z_zadd_args = ecalloc((1 + 2*count), sizeof(zval)); ZVAL_STRINGL(&z_zadd_args[0], key, key_len); i = 1; ZEND_HASH_FOREACH_KEY_VAL(h_zset_vals, idx, zkey, z_score_p) { /* add score */ ZVAL_DOUBLE(&z_zadd_args[i], Z_DVAL_P(z_score_p)); /* add value */ if (zkey) { ZVAL_STRINGL(&z_zadd_args[i+1], ZSTR_VAL(zkey), ZSTR_LEN(zkey)); } else { ZVAL_LONG(&z_zadd_args[i+1], (long)idx); } i += 2; } ZEND_HASH_FOREACH_END(); /* run ZADD on target */ ZVAL_STRINGL(&z_fun_zadd, "ZADD", 4); call_user_function(&redis_ce->function_table, z_to, &z_fun_zadd, &z_ret_dest, 1 + 2 * count, z_zadd_args); /* Expire if needed */ ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC); /* cleanup */ zval_dtor(&z_fun_zadd); zval_dtor(&z_ret_dest); zval_dtor(&z_ret); /* Free the array itself */ for (i = 0; i < 1 + 2 * count; i++) { zval_dtor(&z_zadd_args[i]); } efree(z_zadd_args); return 1; } static zend_bool ra_move_string(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) { zval z_fun_get, z_fun_set, z_ret, z_args[3]; /* run GET on source */ ZVAL_STRINGL(&z_fun_get, "GET", 3); ZVAL_STRINGL(&z_args[0], key, key_len); call_user_function(&redis_ce->function_table, z_from, &z_fun_get, &z_ret, 1, z_args); zval_dtor(&z_fun_get); if(Z_TYPE(z_ret) != IS_STRING) { /* key not found or replaced */ /* TODO: report? */ zval_dtor(&z_args[0]); zval_dtor(&z_ret); return 0; } /* run SET on target */ if (ttl > 0) { ZVAL_STRINGL(&z_fun_set, "SETEX", 5); ZVAL_LONG(&z_args[1], ttl); ZVAL_STRINGL(&z_args[2], Z_STRVAL(z_ret), Z_STRLEN(z_ret)); /* copy z_ret to arg 1 */ zval_dtor(&z_ret); /* free memory from our previous call */ call_user_function(&redis_ce->function_table, z_to, &z_fun_set, &z_ret, 3, z_args); /* cleanup */ zval_dtor(&z_args[2]); } else { ZVAL_STRINGL(&z_fun_set, "SET", 3); ZVAL_STRINGL(&z_args[1], Z_STRVAL(z_ret), Z_STRLEN(z_ret)); /* copy z_ret to arg 1 */ zval_dtor(&z_ret); /* free memory from our previous return value */ call_user_function(&redis_ce->function_table, z_to, &z_fun_set, &z_ret, 2, z_args); /* cleanup */ zval_dtor(&z_args[1]); } zval_dtor(&z_fun_set); zval_dtor(&z_args[0]); zval_dtor(&z_ret); return 1; } static zend_bool ra_move_hash(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) { zval z_fun_hgetall, z_fun_hmset, z_ret_dest, z_args[2]; /* run HGETALL on source */ ZVAL_STRINGL(&z_args[0], key, key_len); ZVAL_STRINGL(&z_fun_hgetall, "HGETALL", 7); call_user_function(&redis_ce->function_table, z_from, &z_fun_hgetall, &z_args[1], 1, z_args); zval_dtor(&z_fun_hgetall); if (Z_TYPE(z_args[1]) != IS_ARRAY) { /* key not found or replaced */ /* TODO: report? */ zval_dtor(&z_args[1]); zval_dtor(&z_args[0]); return 0; } /* run HMSET on target */ ZVAL_STRINGL(&z_fun_hmset, "HMSET", 5); call_user_function(&redis_ce->function_table, z_to, &z_fun_hmset, &z_ret_dest, 2, z_args); zval_dtor(&z_fun_hmset); zval_dtor(&z_ret_dest); /* Expire if needed */ ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC); /* cleanup */ zval_dtor(&z_args[1]); zval_dtor(&z_args[0]); return 1; } static zend_bool ra_move_collection(const char *key, int key_len, zval *z_from, zval *z_to, int list_count, const char **cmd_list, int add_count, const char **cmd_add, long ttl TSRMLS_DC) { zval z_fun_retrieve, z_fun_sadd, z_ret, *z_retrieve_args, *z_sadd_args, *z_data_p; int count, i; HashTable *h_set_vals; /* run retrieval command on source */ ZVAL_STRING(&z_fun_retrieve, cmd_list[0]); /* set the command */ z_retrieve_args = ecalloc(list_count, sizeof(zval)); /* set the key */ ZVAL_STRINGL(&z_retrieve_args[0], key, key_len); /* possibly add some other args if they were provided. */ for(i = 1; i < list_count; ++i) { ZVAL_STRING(&z_retrieve_args[i], cmd_list[i]); } call_user_function(&redis_ce->function_table, z_from, &z_fun_retrieve, &z_ret, list_count, z_retrieve_args); /* cleanup */ zval_dtor(&z_fun_retrieve); for(i = 0; i < list_count; ++i) { zval_dtor(&z_retrieve_args[i]); } efree(z_retrieve_args); if(Z_TYPE(z_ret) != IS_ARRAY) { /* key not found or replaced */ /* TODO: report? */ zval_dtor(&z_ret); return 0; } /* run SADD/RPUSH on target */ h_set_vals = Z_ARRVAL(z_ret); count = 1 + zend_hash_num_elements(h_set_vals); ZVAL_STRING(&z_fun_sadd, cmd_add[0]); z_sadd_args = ecalloc(count, sizeof(zval)); ZVAL_STRINGL(&z_sadd_args[0], key, key_len); i = 1; ZEND_HASH_FOREACH_VAL(h_set_vals, z_data_p) { /* add set elements */ ZVAL_ZVAL(&z_sadd_args[i], z_data_p, 1, 0); i++; } ZEND_HASH_FOREACH_END(); /* Clean up our input return value */ zval_dtor(&z_ret); call_user_function(&redis_ce->function_table, z_to, &z_fun_sadd, &z_ret, count, z_sadd_args); /* cleanup */ zval_dtor(&z_fun_sadd); for (i = 0; i < count; i++) { zval_dtor(&z_sadd_args[i]); } efree(z_sadd_args); /* Clean up our output return value */ zval_dtor(&z_ret); /* Expire if needed */ ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC); return 1; } static zend_bool ra_move_set(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) { const char *cmd_list[] = {"SMEMBERS"}; const char *cmd_add[] = {"SADD"}; return ra_move_collection(key, key_len, z_from, z_to, 1, cmd_list, 1, cmd_add, ttl TSRMLS_CC); } static zend_bool ra_move_list(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) { const char *cmd_list[] = {"LRANGE", "0", "-1"}; const char *cmd_add[] = {"RPUSH"}; return ra_move_collection(key, key_len, z_from, z_to, 3, cmd_list, 1, cmd_add, ttl TSRMLS_CC); } void ra_move_key(const char *key, int key_len, zval *z_from, zval *z_to TSRMLS_DC) { long res[2] = {0}, type, ttl; zend_bool success = 0; if (ra_get_key_type(z_from, key, key_len, z_from, res TSRMLS_CC)) { type = res[0]; ttl = res[1]; /* open transaction on target server */ ra_index_multi(z_to, MULTI TSRMLS_CC); switch(type) { case REDIS_STRING: success = ra_move_string(key, key_len, z_from, z_to, ttl TSRMLS_CC); break; case REDIS_SET: success = ra_move_set(key, key_len, z_from, z_to, ttl TSRMLS_CC); break; case REDIS_LIST: success = ra_move_list(key, key_len, z_from, z_to, ttl TSRMLS_CC); break; case REDIS_ZSET: success = ra_move_zset(key, key_len, z_from, z_to, ttl TSRMLS_CC); break; case REDIS_HASH: success = ra_move_hash(key, key_len, z_from, z_to, ttl TSRMLS_CC); break; default: /* TODO: report? */ break; } } if(success) { ra_del_key(key, key_len, z_from TSRMLS_CC); ra_index_key(key, key_len, z_to TSRMLS_CC); } /* close transaction */ ra_index_exec(z_to, NULL, 0 TSRMLS_CC); } /* callback with the current progress, with hostname and count */ static void zval_rehash_callback(zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache, const char *hostname, long count TSRMLS_DC) { zval zv, *z_ret = &zv; ZVAL_NULL(z_ret); #if (PHP_MAJOR_VERSION < 7) zval *z_host, *z_count, **z_args_pp[2]; MAKE_STD_ZVAL(z_host); ZVAL_STRING(z_host, hostname); z_args_pp[0] = &z_host; MAKE_STD_ZVAL(z_count); ZVAL_LONG(z_count, count); z_args_pp[1] = &z_count; z_cb->params = z_args_pp; z_cb->retval_ptr_ptr = &z_ret; #else zval z_args[2]; ZVAL_STRING(&z_args[0], hostname); ZVAL_LONG(&z_args[1], count); z_cb->params = z_args; z_cb->retval = z_ret; #endif z_cb->no_separation = 0; z_cb->param_count = 2; /* run cb(hostname, count) */ zend_call_function(z_cb, z_cb_cache TSRMLS_CC); /* cleanup */ #if (PHP_MAJOR_VERSION < 7) zval_ptr_dtor(&z_host); zval_ptr_dtor(&z_count); #else zval_dtor(&z_args[0]); #endif zval_dtor(z_ret); } static void ra_rehash_server(RedisArray *ra, zval *z_redis, const char *hostname, zend_bool b_index, zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache TSRMLS_DC) { HashTable *h_keys; long count = 0; zval z_fun, z_ret, z_argv, *z_ele; /* list all keys */ if (b_index) { ZVAL_STRING(&z_fun, "SMEMBERS"); ZVAL_STRING(&z_argv, PHPREDIS_INDEX_NAME); } else { ZVAL_STRING(&z_fun, "KEYS"); ZVAL_STRING(&z_argv, "*"); } ZVAL_NULL(&z_ret); call_user_function(&redis_ce->function_table, z_redis, &z_fun, &z_ret, 1, &z_argv); zval_dtor(&z_argv); zval_dtor(&z_fun); if (Z_TYPE(z_ret) == IS_ARRAY) { h_keys = Z_ARRVAL(z_ret); count = zend_hash_num_elements(h_keys); } if (!count) { zval_dtor(&z_ret); return; } /* callback */ if(z_cb && z_cb_cache) { zval_rehash_callback(z_cb, z_cb_cache, hostname, count TSRMLS_CC); } /* for each key, redistribute */ ZEND_HASH_FOREACH_VAL(h_keys, z_ele) { int pos = 0; /* check that we're not moving to the same node. */ zval *z_target = ra_find_node(ra, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele), &pos TSRMLS_CC); if (z_target && strcmp(hostname, ra->hosts[pos])) { /* different host */ ra_move_key(Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele), z_redis, z_target TSRMLS_CC); } } ZEND_HASH_FOREACH_END(); /* cleanup */ zval_dtor(&z_ret); } void ra_rehash(RedisArray *ra, zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache TSRMLS_DC) { int i; /* redistribute the data, server by server. */ if(!ra->prev) return; /* TODO: compare the two rings for equality */ for(i = 0; i < ra->prev->count; ++i) { ra_rehash_server(ra, &ra->prev->redis[i], ra->prev->hosts[i], ra->index, z_cb, z_cb_cache TSRMLS_CC); } } redis-3.1.6/redis_array_impl.h0000644000175000000120000000277513223116600017111 0ustar pyatsukhnenkowheel#ifndef REDIS_ARRAY_IMPL_H #define REDIS_ARRAY_IMPL_H #ifdef PHP_WIN32 #include #else #include #endif #include "redis_array.h" RedisArray *ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC); RedisArray *ra_load_array(const char *name TSRMLS_DC); RedisArray *ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout, double read_timeout TSRMLS_DC); zval *ra_find_node_by_name(RedisArray *ra, const char *host, int host_len TSRMLS_DC); zval *ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos TSRMLS_DC); void ra_init_function_table(RedisArray *ra); void ra_move_key(const char *key, int key_len, zval *z_from, zval *z_to TSRMLS_DC); void ra_index_multi(zval *z_redis, long multi_value TSRMLS_DC); void ra_index_key(const char *key, int key_len, zval *z_redis TSRMLS_DC); void ra_index_keys(zval *z_pairs, zval *z_redis TSRMLS_DC); void ra_index_del(zval *z_keys, zval *z_redis TSRMLS_DC); void ra_index_exec(zval *z_redis, zval *return_value, int keep_all TSRMLS_DC); void ra_index_discard(zval *z_redis, zval *return_value TSRMLS_DC); void ra_index_unwatch(zval *z_redis, zval *return_value TSRMLS_DC); zend_bool ra_is_write_cmd(RedisArray *ra, const char *cmd, int cmd_len); void ra_rehash(RedisArray *ra, zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache TSRMLS_DC); #endif redis-3.1.6/redis_cluster.c0000644000175000000120000027173213223116600016427 0ustar pyatsukhnenkowheel/* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2009 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Michael Grunder | | Maintainer: Nicolas Favre-Felix | +----------------------------------------------------------------------+ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "common.h" #include "php_redis.h" #include "ext/standard/info.h" #include "crc16.h" #include "redis_cluster.h" #include "redis_commands.h" #include #include "library.h" #include #include zend_class_entry *redis_cluster_ce; /* Exception handler */ zend_class_entry *redis_cluster_exception_ce; /* Handlers for RedisCluster */ zend_object_handlers RedisCluster_handlers; ZEND_BEGIN_ARG_INFO_EX(arginfo_ctor, 0, 0, 1) ZEND_ARG_INFO(0, name) ZEND_ARG_ARRAY_INFO(0, seeds, 0) ZEND_ARG_INFO(0, timeout) ZEND_ARG_INFO(0, read_timeout) ZEND_ARG_INFO(0, persistent) ZEND_END_ARG_INFO(); ZEND_BEGIN_ARG_INFO_EX(arginfo_del, 0, 0, 1) ZEND_ARG_INFO(0, key) #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, other_keys) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_mget, 0, 0, 1) ZEND_ARG_ARRAY_INFO(0, keys, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_keys, 0, 0, 1) ZEND_ARG_INFO(0, pattern) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_cluster, 0, 0, 1) ZEND_ARG_INFO(0, arg) #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, other_args) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() /* Argument info for HSCAN, SSCAN, HSCAN */ ZEND_BEGIN_ARG_INFO_EX(arginfo_kscan_cl, 0, 0, 2) ZEND_ARG_INFO(0, str_key) ZEND_ARG_INFO(1, i_iterator) ZEND_ARG_INFO(0, str_pattern) ZEND_ARG_INFO(0, i_count) ZEND_END_ARG_INFO(); /* Argument infor for SCAN */ ZEND_BEGIN_ARG_INFO_EX(arginfo_scan_cl, 0, 0, 2) ZEND_ARG_INFO(1, i_iterator) ZEND_ARG_INFO(0, str_node) ZEND_ARG_INFO(0, str_pattern) ZEND_ARG_INFO(0, i_count) ZEND_END_ARG_INFO(); /* Function table */ zend_function_entry redis_cluster_functions[] = { PHP_ME(RedisCluster, __construct, NULL, ZEND_ACC_CTOR | ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, close, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, get, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, set, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, mget, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, mset, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, msetnx, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, del, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, setex, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, psetex, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, setnx, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, getset, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, exists, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, keys, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, type, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, lpop, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, rpop, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, lset, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, spop, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, lpush, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, rpush, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, blpop, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, brpop, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, rpushx, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, lpushx, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, linsert, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, lindex, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, lrem, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, brpoplpush, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, rpoplpush, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, llen, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, scard, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, smembers, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, sismember, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, sadd, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, saddarray, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, srem, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, sunion, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, sunionstore, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, sinter, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, sinterstore, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, sdiff, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, sdiffstore, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, srandmember, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, strlen, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, persist, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, ttl, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, pttl, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zcard, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zcount, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zremrangebyscore, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zscore, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zadd, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zincrby, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, hlen, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, hkeys, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, hvals, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, hget, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, hgetall, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, hexists, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, hincrby, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, hset, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, hsetnx, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, hmget, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, hmset, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, hdel, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, hincrbyfloat, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, hstrlen, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, dump, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zrank, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zrevrank, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, incr, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, decr, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, incrby, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, decrby, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, incrbyfloat, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, expire, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, pexpire, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, expireat, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, pexpireat, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, append, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, getbit, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, setbit, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, bitop, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, bitpos, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, bitcount, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, lget, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, getrange, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, ltrim, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, lrange, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zremrangebyrank, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, publish, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, rename, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, renamenx, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, pfcount, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, pfadd, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, pfmerge, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, setrange, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, restore, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, smove, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zrange, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zrevrange, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zrangebyscore, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zrevrangebyscore, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zrangebylex, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zrevrangebylex, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zlexcount, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zremrangebylex, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zunionstore, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zinterstore, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zrem, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, sort, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, object, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, subscribe, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, psubscribe, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, unsubscribe, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, punsubscribe, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, eval, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, evalsha, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, scan, arginfo_scan_cl, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, sscan, arginfo_kscan_cl, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, zscan, arginfo_kscan_cl, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, hscan, arginfo_kscan_cl, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, getmode, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, getlasterror, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, clearlasterror, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, getoption, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, setoption, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, _prefix, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, _serialize, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, _unserialize, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, _masters, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, _redir, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, multi, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, exec, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, discard, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, watch, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, unwatch, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, save, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, bgsave, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, flushdb, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, flushall, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, dbsize, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, bgrewriteaof, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, lastsave, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, info, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, role, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, time, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, randomkey, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, ping, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, echo, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, command, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, rawcommand, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, cluster, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, client, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, config, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, pubsub, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, script, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, slowlog, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, geoadd, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, geohash, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, geopos, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, geodist, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, georadius, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, georadiusbymember, NULL, ZEND_ACC_PUBLIC) PHP_FE_END }; /* Our context seeds will be a hash table with RedisSock* pointers */ #if (PHP_MAJOR_VERSION < 7) static void ht_free_seed(void *data) #else static void ht_free_seed(zval *data) #endif { RedisSock *redis_sock = *(RedisSock**)data; if(redis_sock) redis_free_socket(redis_sock); } /* Free redisClusterNode objects we've stored */ #if (PHP_MAJOR_VERSION < 7) static void ht_free_node(void *data) #else static void ht_free_node(zval *data) #endif { redisClusterNode *node = *(redisClusterNode**)data; cluster_free_node(node); } /* Create redisCluster context */ #if (PHP_MAJOR_VERSION < 7) zend_object_value create_cluster_context(zend_class_entry *class_type TSRMLS_DC) { redisCluster *cluster; // Allocate our actual struct cluster = ecalloc(1, sizeof(redisCluster)); #else zend_object * create_cluster_context(zend_class_entry *class_type TSRMLS_DC) { redisCluster *cluster; // Allocate our actual struct cluster = ecalloc(1, sizeof(redisCluster) + sizeof(zval) * (class_type->default_properties_count - 1)); #endif // We're not currently subscribed anywhere cluster->subscribed_slot = -1; // Allocate our RedisSock we'll use to store prefix/serialization flags cluster->flags = ecalloc(1, sizeof(RedisSock)); // Allocate our hash table for seeds ALLOC_HASHTABLE(cluster->seeds); zend_hash_init(cluster->seeds, 0, NULL, ht_free_seed, 0); // Allocate our hash table for connected Redis objects ALLOC_HASHTABLE(cluster->nodes); zend_hash_init(cluster->nodes, 0, NULL, ht_free_node, 0); // Initialize it zend_object_std_init(&cluster->std, class_type TSRMLS_CC); #if (PHP_MAJOR_VERSION < 7) zend_object_value retval; #if PHP_VERSION_ID < 50399 zval *tmp; zend_hash_copy(cluster->std.properties, &class_type->default_properties, (copy_ctor_func_t)zval_add_ref, (void*)&tmp, sizeof(zval*)); #else object_properties_init(&cluster->std, class_type); #endif retval.handle = zend_objects_store_put(cluster, (zend_objects_store_dtor_t)zend_objects_destroy_object, free_cluster_context, NULL TSRMLS_CC); retval.handlers = zend_get_std_object_handlers(); return retval; #else object_properties_init(&cluster->std, class_type); memcpy(&RedisCluster_handlers, zend_get_std_object_handlers(), sizeof(RedisCluster_handlers)); RedisCluster_handlers.offset = XtOffsetOf(redisCluster, std); RedisCluster_handlers.free_obj = free_cluster_context; cluster->std.handlers = &RedisCluster_handlers; return &cluster->std; #endif } /* Free redisCluster context */ void #if (PHP_MAJOR_VERSION < 7) free_cluster_context(void *object TSRMLS_DC) { redisCluster *cluster = (redisCluster*)object; #else free_cluster_context(zend_object *object) { redisCluster *cluster = (redisCluster*)((char*)(object) - XtOffsetOf(redisCluster, std)); #endif // Free any allocated prefix, as well as the struct if(cluster->flags->prefix) efree(cluster->flags->prefix); efree(cluster->flags); // Free seeds HashTable itself zend_hash_destroy(cluster->seeds); efree(cluster->seeds); // Destroy all Redis objects and free our nodes HashTable zend_hash_destroy(cluster->nodes); efree(cluster->nodes); if (cluster->err) zend_string_release(cluster->err); zend_object_std_dtor(&cluster->std TSRMLS_CC); #if (PHP_MAJOR_VERSION < 7) efree(cluster); #endif } /* Attempt to connect to a Redis cluster provided seeds and timeout options */ void redis_cluster_init(redisCluster *c, HashTable *ht_seeds, double timeout, double read_timeout, int persistent TSRMLS_DC) { // Validate timeout if(timeout < 0L || timeout > INT_MAX) { zend_throw_exception(redis_cluster_exception_ce, "Invalid timeout", 0 TSRMLS_CC); } // Validate our read timeout if(read_timeout < 0L || read_timeout > INT_MAX) { zend_throw_exception(redis_cluster_exception_ce, "Invalid read timeout", 0 TSRMLS_CC); } /* Make sure there are some seeds */ if(zend_hash_num_elements(ht_seeds)==0) { zend_throw_exception(redis_cluster_exception_ce, "Must pass seeds", 0 TSRMLS_CC); } /* Set our timeout and read_timeout which we'll pass through to the * socket type operations */ c->timeout = timeout; c->read_timeout = read_timeout; /* Set our option to use or not use persistent connections */ c->persistent = persistent; /* Calculate the number of miliseconds we will wait when bouncing around, * (e.g. a node goes down), which is not the same as a standard timeout. */ c->waitms = (long)(timeout * 1000); // Initialize our RedisSock "seed" objects cluster_init_seeds(c, ht_seeds); // Create and map our key space cluster_map_keyspace(c TSRMLS_CC); } /* Attempt to load a named cluster configured in php.ini */ void redis_cluster_load(redisCluster *c, char *name, int name_len TSRMLS_DC) { zval z_seeds, z_timeout, z_read_timeout, z_persistent, *z_value; char *iptr; double timeout=0, read_timeout=0; int persistent = 0; HashTable *ht_seeds = NULL; /* Seeds */ array_init(&z_seeds); if ((iptr = INI_STR("redis.clusters.seeds")) != NULL) { sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_seeds TSRMLS_CC); } if ((z_value = zend_hash_str_find(Z_ARRVAL(z_seeds), name, name_len)) != NULL) { ht_seeds = Z_ARRVAL_P(z_value); } else { zval_dtor(&z_seeds); zend_throw_exception(redis_cluster_exception_ce, "Couldn't find seeds for cluster", 0 TSRMLS_CC); return; } /* Connection timeout */ array_init(&z_timeout); if ((iptr = INI_STR("redis.clusters.timeout")) != NULL) { sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_timeout TSRMLS_CC); } if ((z_value = zend_hash_str_find(Z_ARRVAL(z_timeout), name, name_len)) != NULL) { if (Z_TYPE_P(z_value) == IS_STRING) { timeout = atof(Z_STRVAL_P(z_value)); } else if (Z_TYPE_P(z_value) == IS_DOUBLE) { timeout = Z_DVAL_P(z_value); } else if (Z_TYPE_P(z_value) == IS_LONG) { timeout = Z_LVAL_P(z_value); } } /* Read timeout */ array_init(&z_read_timeout); if ((iptr = INI_STR("redis.clusters.read_timeout")) != NULL) { sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_read_timeout TSRMLS_CC); } if ((z_value = zend_hash_str_find(Z_ARRVAL(z_read_timeout), name, name_len)) != NULL) { if (Z_TYPE_P(z_value) == IS_STRING) { read_timeout = atof(Z_STRVAL_P(z_value)); } else if (Z_TYPE_P(z_value) == IS_DOUBLE) { read_timeout = Z_DVAL_P(z_value); } else if (Z_TYPE_P(z_value) == IS_LONG) { read_timeout = Z_LVAL_P(z_value); } } /* Persistent connections */ array_init(&z_persistent); if ((iptr = INI_STR("redis.clusters.persistent")) != NULL) { sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_persistent TSRMLS_CC); } if ((z_value = zend_hash_str_find(Z_ARRVAL(z_persistent), name, name_len)) != NULL) { if (Z_TYPE_P(z_value) == IS_STRING) { persistent = atoi(Z_STRVAL_P(z_value)); } else if (Z_TYPE_P(z_value) == IS_LONG) { persistent = Z_LVAL_P(z_value); } } /* Attempt to create/connect to the cluster */ redis_cluster_init(c, ht_seeds, timeout, read_timeout, persistent TSRMLS_CC); /* Clean up our arrays */ zval_dtor(&z_seeds); zval_dtor(&z_timeout); zval_dtor(&z_read_timeout); zval_dtor(&z_persistent); } /* * PHP Methods */ /* Create a RedisCluster Object */ PHP_METHOD(RedisCluster, __construct) { zval *object, *z_seeds=NULL; char *name; strlen_t name_len; double timeout = 0.0, read_timeout = 0.0; zend_bool persistent = 0; redisCluster *context = GET_CONTEXT(); // Parse arguments if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os!|addb", &object, redis_cluster_ce, &name, &name_len, &z_seeds, &timeout, &read_timeout, &persistent)==FAILURE) { RETURN_FALSE; } // Require a name if(name_len == 0 && ZEND_NUM_ARGS() < 2) { zend_throw_exception(redis_cluster_exception_ce, "You must specify a name or pass seeds!", 0 TSRMLS_CC); } /* If we've been passed only one argument, the user is attempting to connect * to a named cluster, stored in php.ini, otherwise we'll need manual seeds */ if (ZEND_NUM_ARGS() > 1) { redis_cluster_init(context, Z_ARRVAL_P(z_seeds), timeout, read_timeout, persistent TSRMLS_CC); } else { redis_cluster_load(context, name, name_len TSRMLS_CC); } } /* * RedisCluster method implementation */ /* {{{ proto bool RedisCluster::close() */ PHP_METHOD(RedisCluster, close) { cluster_disconnect(GET_CONTEXT() TSRMLS_CC); RETURN_TRUE; } /* {{{ proto string RedisCluster::get(string key) */ PHP_METHOD(RedisCluster, get) { CLUSTER_PROCESS_KW_CMD("GET", redis_key_cmd, cluster_bulk_resp, 1); } /* }}} */ /* {{{ proto bool RedisCluster::set(string key, string value) */ PHP_METHOD(RedisCluster, set) { CLUSTER_PROCESS_CMD(set, cluster_bool_resp, 0); } /* }}} */ /* Generic handler for MGET/MSET/MSETNX */ static int distcmd_resp_handler(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, short slot, clusterMultiCmd *mc, zval *z_ret, int last, cluster_cb cb) { clusterMultiCtx *ctx; // Finalize multi command cluster_multi_fini(mc); // Spin up multi context ctx = emalloc(sizeof(clusterMultiCtx)); ctx->z_multi = z_ret; ctx->count = mc->argc; ctx->last = last; // Attempt to send the command if(cluster_send_command(c,slot,mc->cmd.c,mc->cmd.len TSRMLS_CC)<0 || c->err!=NULL) { cluster_multi_free(mc); zval_dtor(z_ret); efree(ctx); return -1; } if(CLUSTER_IS_ATOMIC(c)) { // Process response now cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, (void*)ctx); } else { CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx); } // Clear out our command but retain allocated memory CLUSTER_MULTI_CLEAR(mc); return 0; } /* Container struct for a key/value pair pulled from an array */ typedef struct clusterKeyValHT { char kbuf[22]; char *key; strlen_t key_len; int key_free; short slot; char *val; strlen_t val_len; int val_free; } clusterKeyValHT; /* Helper to pull a key/value pair from a HashTable */ static int get_key_val_ht(redisCluster *c, HashTable *ht, HashPosition *ptr, clusterKeyValHT *kv TSRMLS_DC) { zval *z_val; zend_ulong idx; // Grab the key, convert it to a string using provided kbuf buffer if it's // a LONG style key #if (PHP_MAJOR_VERSION < 7) uint key_len; switch(zend_hash_get_current_key_ex(ht, &(kv->key), &key_len, &idx, 0, ptr)) { case HASH_KEY_IS_STRING: kv->key_len = (int)(key_len-1); #else zend_string *zkey; switch (zend_hash_get_current_key_ex(ht, &zkey, &idx, ptr)) { case HASH_KEY_IS_STRING: kv->key_len = ZSTR_LEN(zkey); kv->key = ZSTR_VAL(zkey); #endif break; case HASH_KEY_IS_LONG: kv->key_len = snprintf(kv->kbuf,sizeof(kv->kbuf),"%ld",(long)idx); kv->key = kv->kbuf; break; default: zend_throw_exception(redis_cluster_exception_ce, "Internal Zend HashTable error", 0 TSRMLS_CC); return -1; } // Prefix our key if we need to, set the slot kv->key_free = redis_key_prefix(c->flags, &(kv->key), &(kv->key_len)); kv->slot = cluster_hash_key(kv->key, kv->key_len); // Now grab our value if ((z_val = zend_hash_get_current_data_ex(ht, ptr)) == NULL) { zend_throw_exception(redis_cluster_exception_ce, "Internal Zend HashTable error", 0 TSRMLS_CC); return -1; } // Serialize our value if required kv->val_free = redis_serialize(c->flags,z_val,&(kv->val),&(kv->val_len) TSRMLS_CC); // Success return 0; } /* Helper to pull, prefix, and hash a key from a HashTable value */ static int get_key_ht(redisCluster *c, HashTable *ht, HashPosition *ptr, clusterKeyValHT *kv TSRMLS_DC) { zval *z_key; if ((z_key = zend_hash_get_current_data_ex(ht, ptr)) == NULL) { // Shouldn't happen, but check anyway zend_throw_exception(redis_cluster_exception_ce, "Internal Zend HashTable error", 0 TSRMLS_CC); return -1; } // Always want to work with strings convert_to_string(z_key); kv->key = Z_STRVAL_P(z_key); kv->key_len = Z_STRLEN_P(z_key); kv->key_free = redis_key_prefix(c->flags, &(kv->key), &(kv->key_len)); // Hash our key kv->slot = cluster_hash_key(kv->key, kv->key_len); // Success return 0; } /* Turn variable arguments into a HashTable for processing */ static HashTable *method_args_to_ht(zval *z_args, int argc) { HashTable *ht_ret; int i; /* Allocate our hash table */ ALLOC_HASHTABLE(ht_ret); zend_hash_init(ht_ret, argc, NULL, NULL, 0); /* Populate our return hash table with our arguments */ for (i = 0; i < argc; i++) { zend_hash_next_index_insert(ht_ret, &z_args[i]); } /* Return our hash table */ return ht_ret; } /* Handler for both MGET and DEL */ static int cluster_mkey_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len, zval *z_ret, cluster_cb cb) { redisCluster *c = GET_CONTEXT(); clusterMultiCmd mc = {0}; clusterKeyValHT kv; zval *z_args; HashTable *ht_arr; HashPosition ptr; int i=1, argc = ZEND_NUM_ARGS(), ht_free=0; short slot; /* If we don't have any arguments we're invalid */ if (!argc) return -1; /* Extract our arguments into an array */ z_args = ecalloc(argc, sizeof(zval)); if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) { efree(z_args); return -1; } /* Determine if we're working with a single array or variadic args */ if (argc == 1 && Z_TYPE(z_args[0]) == IS_ARRAY) { ht_arr = Z_ARRVAL(z_args[0]); argc = zend_hash_num_elements(ht_arr); if (!argc) { efree(z_args); return -1; } } else { ht_arr = method_args_to_ht(z_args, argc); ht_free = 1; } /* MGET is readonly, DEL is not */ c->readonly = kw_len == 4 && CLUSTER_IS_ATOMIC(c); // Initialize our "multi" command handler with command/len CLUSTER_MULTI_INIT(mc, kw, kw_len); // Process the first key outside of our loop, so we don't have to check if // it's the first iteration every time, needlessly zend_hash_internal_pointer_reset_ex(ht_arr, &ptr); if(get_key_ht(c, ht_arr, &ptr, &kv TSRMLS_CC)<0) { efree(z_args); return -1; } // Process our key and add it to the command cluster_multi_add(&mc, kv.key, kv.key_len); // Free key if we prefixed if(kv.key_free) efree(kv.key); // Move to the next key zend_hash_move_forward_ex(ht_arr, &ptr); // Iterate over keys 2...N slot = kv.slot; while(zend_hash_has_more_elements_ex(ht_arr, &ptr)==SUCCESS) { if(get_key_ht(c, ht_arr, &ptr, &kv TSRMLS_CC)<0) { cluster_multi_free(&mc); if (ht_free) { zend_hash_destroy(ht_arr); efree(ht_arr); } efree(z_args); return -1; } // If the slots have changed, kick off the keys we've aggregated if(slot != kv.slot) { // Process this batch of MGET keys if(distcmd_resp_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, slot, &mc, z_ret, i==argc, cb)<0) { cluster_multi_free(&mc); if (ht_free) { zend_hash_destroy(ht_arr); efree(ht_arr); } efree(z_args); return -1; } } // Add this key to the command cluster_multi_add(&mc, kv.key, kv.key_len); // Free key if we prefixed if(kv.key_free) efree(kv.key); // Update the last slot we encountered, and the key we're on slot = kv.slot; i++; zend_hash_move_forward_ex(ht_arr, &ptr); } efree(z_args); // If we've got straggler(s) process them if(mc.argc > 0) { if(distcmd_resp_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, slot, &mc, z_ret, 1, cb)<0) { cluster_multi_free(&mc); if (ht_free) { zend_hash_destroy(ht_arr); efree(ht_arr); } return -1; } } // Free our command cluster_multi_free(&mc); /* Clean up our hash table if we constructed it from variadic args */ if (ht_free) { zend_hash_destroy(ht_arr); efree(ht_arr); } /* Return our object if we're in MULTI mode */ if (!CLUSTER_IS_ATOMIC(c)) RETVAL_ZVAL(getThis(), 1, 0); // Success return 0; } /* Handler for both MSET and MSETNX */ static int cluster_mset_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len, zval *z_ret, cluster_cb cb) { redisCluster *c = GET_CONTEXT(); clusterKeyValHT kv; clusterMultiCmd mc = {0}; zval *z_arr; HashTable *ht_arr; HashPosition ptr; int i=1, argc; short slot; // Parse our arguments if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &z_arr)==FAILURE) { return -1; } // No reason to send zero args ht_arr = Z_ARRVAL_P(z_arr); if((argc = zend_hash_num_elements(ht_arr))==0) { return -1; } /* This is a write command */ c->readonly = 0; // Set up our multi command handler CLUSTER_MULTI_INIT(mc, kw, kw_len); // Process the first key/value pair outside of our loop zend_hash_internal_pointer_reset_ex(ht_arr, &ptr); if(get_key_val_ht(c, ht_arr, &ptr, &kv TSRMLS_CC)==-1) return -1; zend_hash_move_forward_ex(ht_arr, &ptr); // Add this to our multi cmd, set slot, free key if we prefixed cluster_multi_add(&mc, kv.key, kv.key_len); cluster_multi_add(&mc, kv.val, kv.val_len); if(kv.key_free) efree(kv.key); if(kv.val_free) efree(kv.val); // While we've got more keys to set slot = kv.slot; while(zend_hash_has_more_elements_ex(ht_arr, &ptr)==SUCCESS) { // Pull the next key/value pair if(get_key_val_ht(c, ht_arr, &ptr, &kv TSRMLS_CC)==-1) { return -1; } // If the slots have changed, process responses if(slot != kv.slot) { if(distcmd_resp_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, slot, &mc, z_ret, i==argc, cb)<0) { return -1; } } // Add this key and value to our command cluster_multi_add(&mc, kv.key, kv.key_len); cluster_multi_add(&mc, kv.val, kv.val_len); // Free our key and value if we need to if(kv.key_free) efree(kv.key); if(kv.val_free) efree(kv.val); // Update our slot, increment position slot = kv.slot; i++; // Move on zend_hash_move_forward_ex(ht_arr, &ptr); } // If we've got stragglers, process them too if(mc.argc > 0) { if(distcmd_resp_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, slot, &mc, z_ret, 1, cb)<0) { return -1; } } // Free our command cluster_multi_free(&mc); /* Return our object if we're in MULTI mode */ if (!CLUSTER_IS_ATOMIC(c)) RETVAL_ZVAL(getThis(), 1, 0); // Success return 0; } /* {{{ proto array RedisCluster::del(string key1, string key2, ... keyN) */ PHP_METHOD(RedisCluster, del) { zval *z_ret; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_ret); #else z_ret = emalloc(sizeof(zval)); #endif // Initialize a LONG value to zero for our return ZVAL_LONG(z_ret, 0); // Parse args, process if(cluster_mkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DEL", sizeof("DEL")-1, z_ret, cluster_del_resp)<0) { efree(z_ret); RETURN_FALSE; } } /* {{{ proto array RedisCluster::mget(array keys) */ PHP_METHOD(RedisCluster, mget) { zval *z_ret; // Array response #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_ret); #else z_ret = emalloc(sizeof(zval)); #endif array_init(z_ret); // Parse args, process if(cluster_mkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "MGET", sizeof("MGET")-1, z_ret, cluster_mbulk_mget_resp)<0) { zval_dtor(z_ret); efree(z_ret); RETURN_FALSE; } } /* {{{ proto bool RedisCluster::mset(array keyvalues) */ PHP_METHOD(RedisCluster, mset) { zval *z_ret; // Response, defaults to TRUE #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_ret); #else z_ret = emalloc(sizeof(zval)); #endif ZVAL_TRUE(z_ret); // Parse args and process. If we get a failure, free zval and return FALSE. if(cluster_mset_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "MSET", sizeof("MSET")-1, z_ret, cluster_mset_resp)==-1) { efree(z_ret); RETURN_FALSE; } } /* {{{ proto array RedisCluster::msetnx(array keyvalues) */ PHP_METHOD(RedisCluster, msetnx) { zval *z_ret; // Array response #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_ret); #else z_ret = emalloc(sizeof(zval)); #endif array_init(z_ret); // Parse args and process. If we get a failure, free mem and return FALSE if(cluster_mset_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "MSETNX", sizeof("MSETNX")-1, z_ret, cluster_msetnx_resp)==-1) { zval_dtor(z_ret); efree(z_ret); RETURN_FALSE; } } /* }}} */ /* {{{ proto bool RedisCluster::setex(string key, string value, int expiry) */ PHP_METHOD(RedisCluster, setex) { CLUSTER_PROCESS_KW_CMD("SETEX", redis_key_long_val_cmd, cluster_bool_resp, 0); } /* }}} */ /* {{{ proto bool RedisCluster::psetex(string key, string value, int expiry) */ PHP_METHOD(RedisCluster, psetex) { CLUSTER_PROCESS_KW_CMD("PSETEX", redis_key_long_val_cmd, cluster_bool_resp, 0); } /* }}} */ /* {{{ proto bool RedisCluster::setnx(string key, string value) */ PHP_METHOD(RedisCluster, setnx) { CLUSTER_PROCESS_KW_CMD("SETNX", redis_kv_cmd, cluster_1_resp, 0); } /* }}} */ /* {{{ proto string RedisCluster::getSet(string key, string value) */ PHP_METHOD(RedisCluster, getset) { CLUSTER_PROCESS_KW_CMD("GETSET", redis_kv_cmd, cluster_bulk_resp, 0); } /* }}} */ /* {{{ proto int RedisCluster::exists(string key) */ PHP_METHOD(RedisCluster, exists) { CLUSTER_PROCESS_KW_CMD("EXISTS", redis_key_cmd, cluster_1_resp, 1); } /* }}} */ /* {{{ proto array Redis::keys(string pattern) */ PHP_METHOD(RedisCluster, keys) { redisCluster *c = GET_CONTEXT(); redisClusterNode *node; strlen_t pat_len; char *pat, *cmd; clusterReply *resp; zval zv, *z_ret = &zv; int i, cmd_len; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &pat, &pat_len) ==FAILURE) { RETURN_FALSE; } /* Prefix and then build our command */ cmd_len = redis_spprintf(c->flags, NULL TSRMLS_CC, &cmd, "KEYS", "k", pat, pat_len); array_init(z_ret); /* Treat as readonly */ c->readonly = CLUSTER_IS_ATOMIC(c); /* Iterate over our known nodes */ ZEND_HASH_FOREACH_PTR(c->nodes, node) { if (node == NULL) break; if (cluster_send_slot(c, node->slot, cmd, cmd_len, TYPE_MULTIBULK TSRMLS_CC)<0) { php_error_docref(0 TSRMLS_CC, E_ERROR, "Can't send KEYS to %s:%d", ZSTR_VAL(node->sock->host), node->sock->port); efree(cmd); RETURN_FALSE; } /* Ensure we can get a response */ resp = cluster_read_resp(c TSRMLS_CC); if(!resp) { php_error_docref(0 TSRMLS_CC, E_WARNING, "Can't read response from %s:%d", ZSTR_VAL(node->sock->host), node->sock->port); continue; } /* Iterate keys, adding to our big array */ for(i=0;ielements;i++) { /* Skip non bulk responses, they should all be bulk */ if(resp->element[i]->type != TYPE_BULK) { continue; } add_next_index_stringl(z_ret, resp->element[i]->str, resp->element[i]->len); } /* Free response, don't free data */ cluster_free_reply(resp, 0); } ZEND_HASH_FOREACH_END(); efree(cmd); /* Return our keys */ RETURN_ZVAL(z_ret, 1, 0); } /* }}} */ /* {{{ proto int RedisCluster::type(string key) */ PHP_METHOD(RedisCluster, type) { CLUSTER_PROCESS_KW_CMD("TYPE", redis_key_cmd, cluster_type_resp, 1); } /* }}} */ /* {{{ proto string RedisCluster::pop(string key) */ PHP_METHOD(RedisCluster, lpop) { CLUSTER_PROCESS_KW_CMD("LPOP", redis_key_cmd, cluster_bulk_resp, 0); } /* }}} */ /* {{{ proto string RedisCluster::rpop(string key) */ PHP_METHOD(RedisCluster, rpop) { CLUSTER_PROCESS_KW_CMD("RPOP", redis_key_cmd, cluster_bulk_resp, 0); } /* }}} */ /* {{{ proto bool RedisCluster::lset(string key, long index, string val) */ PHP_METHOD(RedisCluster, lset) { CLUSTER_PROCESS_KW_CMD("LSET", redis_key_long_val_cmd, cluster_bool_resp, 0); } /* }}} */ /* {{{ proto string RedisCluster::spop(string key) */ PHP_METHOD(RedisCluster, spop) { if (ZEND_NUM_ARGS() == 1) { CLUSTER_PROCESS_KW_CMD("SPOP", redis_key_cmd, cluster_bulk_resp, 0); } else if (ZEND_NUM_ARGS() == 2) { CLUSTER_PROCESS_KW_CMD("SPOP", redis_key_long_cmd, cluster_mbulk_resp, 0); } else { ZEND_WRONG_PARAM_COUNT(); } } /* }}} */ /* {{{ proto string|array RedisCluster::srandmember(string key, [long count]) */ PHP_METHOD(RedisCluster, srandmember) { redisCluster *c = GET_CONTEXT(); cluster_cb cb; char *cmd; int cmd_len; short slot; short have_count; /* Treat as readonly */ c->readonly = CLUSTER_IS_ATOMIC(c); if(redis_srandmember_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, &cmd, &cmd_len, &slot, NULL, &have_count) ==FAILURE) { RETURN_FALSE; } if(cluster_send_command(c,slot,cmd,cmd_len TSRMLS_CC)<0 || c->err!=NULL) { efree(cmd); RETURN_FALSE; } // Clean up command efree(cmd); cb = have_count ? cluster_mbulk_resp : cluster_bulk_resp; if (CLUSTER_IS_ATOMIC(c)) { cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL); } else { void *ctx = NULL; CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx); RETURN_ZVAL(getThis(), 1, 0); } } /* {{{ proto string RedisCluster::strlen(string key) */ PHP_METHOD(RedisCluster, strlen) { CLUSTER_PROCESS_KW_CMD("STRLEN", redis_key_cmd, cluster_long_resp, 1); } /* {{{ proto long RedisCluster::lpush(string key, string val1, ... valN) */ PHP_METHOD(RedisCluster, lpush) { CLUSTER_PROCESS_KW_CMD("LPUSH", redis_key_varval_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::rpush(string key, string val1, ... valN) */ PHP_METHOD(RedisCluster, rpush) { CLUSTER_PROCESS_KW_CMD("RPUSH", redis_key_varval_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto array RedisCluster::blpop(string key1, ... keyN, long timeout) */ PHP_METHOD(RedisCluster, blpop) { CLUSTER_PROCESS_CMD(blpop, cluster_mbulk_resp, 0); } /* }}} */ /* {{{ proto array RedisCluster::brpop(string key1, ... keyN, long timeout */ PHP_METHOD(RedisCluster, brpop) { CLUSTER_PROCESS_CMD(brpop, cluster_mbulk_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::rpushx(string key, mixed value) */ PHP_METHOD(RedisCluster, rpushx) { CLUSTER_PROCESS_KW_CMD("RPUSHX", redis_kv_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::lpushx(string key, mixed value) */ PHP_METHOD(RedisCluster, lpushx) { CLUSTER_PROCESS_KW_CMD("LPUSHX", redis_kv_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::linsert(string k,string pos,mix pvt,mix val) */ PHP_METHOD(RedisCluster, linsert) { CLUSTER_PROCESS_CMD(linsert, cluster_long_resp, 0); } /* }}} */ /* {{{ proto string RedisCluster::lindex(string key, long index) */ PHP_METHOD(RedisCluster, lindex) { CLUSTER_PROCESS_KW_CMD("LINDEX", redis_key_long_cmd, cluster_bulk_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::lrem(string key, long count, string val) */ PHP_METHOD(RedisCluster, lrem) { CLUSTER_PROCESS_CMD(lrem, cluster_long_resp, 0); } /* }}} */ /* {{{ proto string RedisCluster::rpoplpush(string key, string key) */ PHP_METHOD(RedisCluster, rpoplpush) { CLUSTER_PROCESS_KW_CMD("RPOPLPUSH", redis_key_key_cmd, cluster_bulk_resp, 0); } /* }}} */ /* {{{ proto string RedisCluster::brpoplpush(string key, string key, long tm) */ PHP_METHOD(RedisCluster, brpoplpush) { CLUSTER_PROCESS_CMD(brpoplpush, cluster_bulk_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::llen(string key) */ PHP_METHOD(RedisCluster, llen) { CLUSTER_PROCESS_KW_CMD("LLEN", redis_key_cmd, cluster_long_resp, 1); } /* }}} */ /* {{{ proto long RedisCluster::scard(string key) */ PHP_METHOD(RedisCluster, scard) { CLUSTER_PROCESS_KW_CMD("SCARD", redis_key_cmd, cluster_long_resp, 1); } /* }}} */ /* {{{ proto array RedisCluster::smembers(string key) */ PHP_METHOD(RedisCluster, smembers) { CLUSTER_PROCESS_KW_CMD("SMEMBERS", redis_key_cmd, cluster_mbulk_resp, 1); } /* }}} */ /* {{{ proto long RedisCluster::sismember(string key) */ PHP_METHOD(RedisCluster, sismember) { CLUSTER_PROCESS_KW_CMD("SISMEMBER", redis_kv_cmd, cluster_1_resp, 1); } /* }}} */ /* {{{ proto long RedisCluster::sadd(string key, string val1 [, ...]) */ PHP_METHOD(RedisCluster, sadd) { CLUSTER_PROCESS_KW_CMD("SADD", redis_key_varval_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::saddarray(string key, array values) */ PHP_METHOD(RedisCluster, saddarray) { CLUSTER_PROCESS_KW_CMD("SADD", redis_key_arr_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::srem(string key, string val1 [, ...]) */ PHP_METHOD(RedisCluster, srem) { CLUSTER_PROCESS_KW_CMD("SREM", redis_key_varval_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto array RedisCluster::sunion(string key1, ... keyN) */ PHP_METHOD(RedisCluster, sunion) { CLUSTER_PROCESS_CMD(sunion, cluster_mbulk_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::sunionstore(string dst, string k1, ... kN) */ PHP_METHOD(RedisCluster, sunionstore) { CLUSTER_PROCESS_CMD(sunionstore, cluster_long_resp, 0); } /* }}} */ /* {{{ ptoto array RedisCluster::sinter(string k1, ... kN) */ PHP_METHOD(RedisCluster, sinter) { CLUSTER_PROCESS_CMD(sinter, cluster_mbulk_resp, 0); } /* }}} */ /* {{{ ptoto long RedisCluster::sinterstore(string dst, string k1, ... kN) */ PHP_METHOD(RedisCluster, sinterstore) { CLUSTER_PROCESS_CMD(sinterstore, cluster_long_resp, 0); } /* }}} */ /* {{{ proto array RedisCluster::sdiff(string k1, ... kN) */ PHP_METHOD(RedisCluster, sdiff) { CLUSTER_PROCESS_CMD(sdiff, cluster_mbulk_resp, 1); } /* }}} */ /* {{{ proto long RedisCluster::sdiffstore(string dst, string k1, ... kN) */ PHP_METHOD(RedisCluster, sdiffstore) { CLUSTER_PROCESS_CMD(sdiffstore, cluster_long_resp, 0); } /* }}} */ /* {{{ proto bool RedisCluster::smove(sting src, string dst, string mem) */ PHP_METHOD(RedisCluster, smove) { CLUSTER_PROCESS_CMD(smove, cluster_1_resp, 0); } /* }}} */ /* {{{ proto bool RedisCluster::persist(string key) */ PHP_METHOD(RedisCluster, persist) { CLUSTER_PROCESS_KW_CMD("PERSIST", redis_key_cmd, cluster_1_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::ttl(string key) */ PHP_METHOD(RedisCluster, ttl) { CLUSTER_PROCESS_KW_CMD("TTL", redis_key_cmd, cluster_long_resp, 1); } /* }}} */ /* {{{ proto long RedisCluster::pttl(string key) */ PHP_METHOD(RedisCluster, pttl) { CLUSTER_PROCESS_KW_CMD("PTTL", redis_key_cmd, cluster_long_resp, 1); } /* }}} */ /* {{{ proto long RedisCluster::zcard(string key) */ PHP_METHOD(RedisCluster, zcard) { CLUSTER_PROCESS_KW_CMD("ZCARD", redis_key_cmd, cluster_long_resp, 1); } /* }}} */ /* {{{ proto double RedisCluster::zscore(string key) */ PHP_METHOD(RedisCluster, zscore) { CLUSTER_PROCESS_KW_CMD("ZSCORE", redis_kv_cmd, cluster_dbl_resp, 1); } /* }}} */ /* {{{ proto long RedisCluster::zadd(string key,double score,string mem, ...) */ PHP_METHOD(RedisCluster, zadd) { CLUSTER_PROCESS_CMD(zadd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto double RedisCluster::zincrby(string key, double by, string mem) */ PHP_METHOD(RedisCluster, zincrby) { CLUSTER_PROCESS_CMD(zincrby, cluster_dbl_resp, 0); } /* }}} */ /* {{{ proto RedisCluster::zremrangebyscore(string k, string s, string e) */ PHP_METHOD(RedisCluster, zremrangebyscore) { CLUSTER_PROCESS_KW_CMD("ZREMRANGEBYSCORE", redis_key_str_str_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto RedisCluster::zcount(string key, string s, string e) */ PHP_METHOD(RedisCluster, zcount) { CLUSTER_PROCESS_KW_CMD("ZCOUNT", redis_key_str_str_cmd, cluster_long_resp, 1); } /* }}} */ /* {{{ proto long RedisCluster::zrank(string key, mixed member) */ PHP_METHOD(RedisCluster, zrank) { CLUSTER_PROCESS_KW_CMD("ZRANK", redis_kv_cmd, cluster_long_resp, 1); } /* }}} */ /* {{{ proto long RedisCluster::zrevrank(string key, mixed member) */ PHP_METHOD(RedisCluster, zrevrank) { CLUSTER_PROCESS_KW_CMD("ZREVRANK", redis_kv_cmd, cluster_long_resp, 1); } /* }}} */ /* {{{ proto long RedisCluster::hlen(string key) */ PHP_METHOD(RedisCluster, hlen) { CLUSTER_PROCESS_KW_CMD("HLEN", redis_key_cmd, cluster_long_resp, 1); } /* }}} */ /* {{{ proto array RedisCluster::hkeys(string key) */ PHP_METHOD(RedisCluster, hkeys) { CLUSTER_PROCESS_KW_CMD("HKEYS", redis_key_cmd, cluster_mbulk_raw_resp, 1); } /* }}} */ /* {{{ proto array RedisCluster::hvals(string key) */ PHP_METHOD(RedisCluster, hvals) { CLUSTER_PROCESS_KW_CMD("HVALS", redis_key_cmd, cluster_mbulk_resp, 1); } /* }}} */ /* {{{ proto string RedisCluster::hget(string key, string mem) */ PHP_METHOD(RedisCluster, hget) { CLUSTER_PROCESS_KW_CMD("HGET", redis_key_str_cmd, cluster_bulk_resp, 1); } /* }}} */ /* {{{ proto bool RedisCluster::hset(string key, string mem, string val) */ PHP_METHOD(RedisCluster, hset) { CLUSTER_PROCESS_CMD(hset, cluster_long_resp, 0); } /* }}} */ /* {{{ proto bool RedisCluster::hsetnx(string key, string mem, string val) */ PHP_METHOD(RedisCluster, hsetnx) { CLUSTER_PROCESS_CMD(hsetnx, cluster_1_resp, 0); } /* }}} */ /* {{{ proto array RedisCluster::hgetall(string key) */ PHP_METHOD(RedisCluster, hgetall) { CLUSTER_PROCESS_KW_CMD("HGETALL", redis_key_cmd, cluster_mbulk_zipstr_resp, 1); } /* }}} */ /* {{{ proto bool RedisCluster::hexists(string key, string member) */ PHP_METHOD(RedisCluster, hexists) { CLUSTER_PROCESS_KW_CMD("HEXISTS", redis_key_str_cmd, cluster_1_resp, 1); } /* }}} */ /* {{{ proto long RedisCluster::hincr(string key, string mem, long val) */ PHP_METHOD(RedisCluster, hincrby) { CLUSTER_PROCESS_CMD(hincrby, cluster_long_resp, 0); } /* }}} */ /* {{{ proto double RedisCluster::hincrbyfloat(string k, string m, double v) */ PHP_METHOD(RedisCluster, hincrbyfloat) { CLUSTER_PROCESS_CMD(hincrbyfloat, cluster_dbl_resp, 0); } /* }}} */ /* {{{ proto bool RedisCluster::hmset(string key, array key_vals) */ PHP_METHOD(RedisCluster, hmset) { CLUSTER_PROCESS_CMD(hmset, cluster_bool_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::hdel(string key, string mem1, ... memN) */ PHP_METHOD(RedisCluster, hdel) { CLUSTER_PROCESS_CMD(hdel, cluster_long_resp, 0); } /* }}} */ /* {{{ proto array RedisCluster::hmget(string key, array members) */ PHP_METHOD(RedisCluster, hmget) { CLUSTER_PROCESS_CMD(hmget, cluster_mbulk_assoc_resp, 1); } /* }}} */ /* {{{ proto array RedisCluster::hstrlen(string key, string field) */ PHP_METHOD(RedisCluster, hstrlen) { CLUSTER_PROCESS_CMD(hstrlen, cluster_long_resp, 1); } /* }}} */ /* {{{ proto string RedisCluster::dump(string key) */ PHP_METHOD(RedisCluster, dump) { CLUSTER_PROCESS_KW_CMD("DUMP", redis_key_cmd, cluster_bulk_raw_resp, 1); } /* {{{ proto long RedisCluster::incr(string key) */ PHP_METHOD(RedisCluster, incr) { CLUSTER_PROCESS_CMD(incr, cluster_long_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::incrby(string key, long byval) */ PHP_METHOD(RedisCluster, incrby) { CLUSTER_PROCESS_KW_CMD("INCRBY", redis_key_long_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::decr(string key) */ PHP_METHOD(RedisCluster, decr) { CLUSTER_PROCESS_CMD(decr, cluster_long_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::decrby(string key, long byval) */ PHP_METHOD(RedisCluster, decrby) { CLUSTER_PROCESS_KW_CMD("DECRBY", redis_key_long_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto double RedisCluster::incrbyfloat(string key, double val) */ PHP_METHOD(RedisCluster, incrbyfloat) { CLUSTER_PROCESS_KW_CMD("INCRBYFLOAT", redis_key_dbl_cmd, cluster_dbl_resp, 0); } /* }}} */ /* {{{ proto double RedisCluster::decrbyfloat(string key, double val) */ PHP_METHOD(RedisCluster, decrbyfloat) { CLUSTER_PROCESS_KW_CMD("DECRBYFLOAT", redis_key_dbl_cmd, cluster_dbl_resp, 0); } /* }}} */ /* {{{ proto bool RedisCluster::expire(string key, long sec) */ PHP_METHOD(RedisCluster, expire) { CLUSTER_PROCESS_KW_CMD("EXPIRE", redis_key_long_cmd, cluster_1_resp, 0); } /* }}} */ /* {{{ proto bool RedisCluster::expireat(string key, long ts) */ PHP_METHOD(RedisCluster, expireat) { CLUSTER_PROCESS_KW_CMD("EXPIREAT", redis_key_long_cmd, cluster_1_resp, 0); } /* {{{ proto bool RedisCluster::pexpire(string key, long ms) */ PHP_METHOD(RedisCluster, pexpire) { CLUSTER_PROCESS_KW_CMD("PEXPIRE", redis_key_long_cmd, cluster_1_resp, 0); } /* }}} */ /* {{{ proto bool RedisCluster::pexpireat(string key, long ts) */ PHP_METHOD(RedisCluster, pexpireat) { CLUSTER_PROCESS_KW_CMD("PEXPIREAT", redis_key_long_cmd, cluster_1_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::append(string key, string val) */ PHP_METHOD(RedisCluster, append) { CLUSTER_PROCESS_KW_CMD("APPEND", redis_kv_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::getbit(string key, long val) */ PHP_METHOD(RedisCluster, getbit) { CLUSTER_PROCESS_KW_CMD("GETBIT", redis_key_long_cmd, cluster_long_resp, 1); } /* }}} */ /* {{{ proto long RedisCluster::setbit(string key, long offset, bool onoff) */ PHP_METHOD(RedisCluster, setbit) { CLUSTER_PROCESS_CMD(setbit, cluster_long_resp, 0); } /* {{{ proto long RedisCluster::bitop(string op,string key,[string key2,...]) */ PHP_METHOD(RedisCluster, bitop) { CLUSTER_PROCESS_CMD(bitop, cluster_long_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::bitcount(string key, [int start, int end]) */ PHP_METHOD(RedisCluster, bitcount) { CLUSTER_PROCESS_CMD(bitcount, cluster_long_resp, 1); } /* }}} */ /* {{{ proto long RedisCluster::bitpos(string key, int bit, [int s, int end]) */ PHP_METHOD(RedisCluster, bitpos) { CLUSTER_PROCESS_CMD(bitpos, cluster_long_resp, 1); } /* }}} */ /* {{{ proto string Redis::lget(string key, long index) */ PHP_METHOD(RedisCluster, lget) { CLUSTER_PROCESS_KW_CMD("LINDEX", redis_key_long_cmd, cluster_bulk_resp, 1); } /* }}} */ /* {{{ proto string RedisCluster::getrange(string key, long start, long end) */ PHP_METHOD(RedisCluster, getrange) { CLUSTER_PROCESS_KW_CMD("GETRANGE", redis_key_long_long_cmd, cluster_bulk_resp, 1); } /* }}} */ /* {{{ proto string RedisCluster::ltrim(string key, long start, long end) */ PHP_METHOD(RedisCluster, ltrim) { CLUSTER_PROCESS_KW_CMD("LTRIM", redis_key_long_long_cmd, cluster_bool_resp, 0); } /* }}} */ /* {{{ proto array RedisCluster::lrange(string key, long start, long end) */ PHP_METHOD(RedisCluster, lrange) { CLUSTER_PROCESS_KW_CMD("LRANGE", redis_key_long_long_cmd, cluster_mbulk_resp, 1); } /* }}} */ /* {{{ proto long RedisCluster::zremrangebyrank(string k, long s, long e) */ PHP_METHOD(RedisCluster, zremrangebyrank) { CLUSTER_PROCESS_KW_CMD("ZREMRANGEBYRANK", redis_key_long_long_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::publish(string key, string msg) */ PHP_METHOD(RedisCluster, publish) { CLUSTER_PROCESS_KW_CMD("PUBLISH", redis_key_str_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto bool RedisCluster::rename(string key1, string key2) */ PHP_METHOD(RedisCluster, rename) { CLUSTER_PROCESS_KW_CMD("RENAME", redis_key_key_cmd, cluster_bool_resp, 0); } /* }}} */ /* {{{ proto bool RedisCluster::renamenx(string key1, string key2) */ PHP_METHOD(RedisCluster, renamenx) { CLUSTER_PROCESS_KW_CMD("RENAMENX", redis_key_key_cmd, cluster_1_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::pfcount(string key) */ PHP_METHOD(RedisCluster, pfcount) { CLUSTER_PROCESS_CMD(pfcount, cluster_long_resp, 1); } /* }}} */ /* {{{ proto bool RedisCluster::pfadd(string key, array vals) */ PHP_METHOD(RedisCluster, pfadd) { CLUSTER_PROCESS_CMD(pfadd, cluster_1_resp, 0); } /* }}} */ /* {{{ proto bool RedisCluster::pfmerge(string key, array keys) */ PHP_METHOD(RedisCluster, pfmerge) { CLUSTER_PROCESS_CMD(pfmerge, cluster_bool_resp, 0); } /* }}} */ /* {{{ proto boolean RedisCluster::restore(string key, long ttl, string val) */ PHP_METHOD(RedisCluster, restore) { CLUSTER_PROCESS_KW_CMD("RESTORE", redis_key_long_str_cmd, cluster_bool_resp, 0); } /* }}} */ /* {{{ proto long RedisCluster::setrange(string key, long offset, string val) */ PHP_METHOD(RedisCluster, setrange) { CLUSTER_PROCESS_KW_CMD("SETRANGE", redis_key_long_str_cmd, cluster_long_resp, 0); } /* }}} */ /* Generic implementation for ZRANGE, ZREVRANGE, ZRANGEBYSCORE, ZREVRANGEBYSCORE */ static void generic_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw, zrange_cb fun) { redisCluster *c = GET_CONTEXT(); c->readonly = CLUSTER_IS_ATOMIC(c); cluster_cb cb; char *cmd; int cmd_len; short slot; int withscores=0; if(fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, kw, &cmd, &cmd_len, &withscores, &slot, NULL)==FAILURE) { efree(cmd); RETURN_FALSE; } if(cluster_send_command(c,slot,cmd,cmd_len TSRMLS_CC)<0 || c->err!=NULL) { efree(cmd); RETURN_FALSE; } efree(cmd); cb = withscores ? cluster_mbulk_zipdbl_resp : cluster_mbulk_resp; if (CLUSTER_IS_ATOMIC(c)) { cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL); } else { void *ctx = NULL; CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx); RETURN_ZVAL(getThis(), 1, 0); } } /* {{{ proto * array RedisCluster::zrange(string k, long s, long e, bool score=0) */ PHP_METHOD(RedisCluster, zrange) { generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZRANGE", redis_zrange_cmd); } /* }}} */ /* {{{ proto * array RedisCluster::zrevrange(string k,long s,long e,bool scores=0) */ PHP_METHOD(RedisCluster, zrevrange) { generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZREVRANGE", redis_zrange_cmd); } /* }}} */ /* {{{ proto array * RedisCluster::zrangebyscore(string k, long s, long e, array opts) */ PHP_METHOD(RedisCluster, zrangebyscore) { generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZRANGEBYSCORE", redis_zrangebyscore_cmd); } /* }}} */ /* {{{ proto RedisCluster::zunionstore(string dst, array keys, [array weights, * string agg]) */ PHP_METHOD(RedisCluster, zunionstore) { CLUSTER_PROCESS_KW_CMD("ZUNIONSTORE", redis_zinter_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto RedisCluster::zinterstore(string dst, array keys, [array weights, * string agg]) */ PHP_METHOD(RedisCluster, zinterstore) { CLUSTER_PROCESS_KW_CMD("ZINTERSTORE", redis_zinter_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto RedisCluster::zrem(string key, string val1, ... valN) */ PHP_METHOD(RedisCluster, zrem) { CLUSTER_PROCESS_KW_CMD("ZREM", redis_key_varval_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto array * RedisCluster::zrevrangebyscore(string k, long s, long e, array opts) */ PHP_METHOD(RedisCluster, zrevrangebyscore) { generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZREVRANGEBYSCORE", redis_zrangebyscore_cmd); } /* }}} */ /* {{{ proto array RedisCluster::zrangebylex(string key, string min, string max, * [offset, count]) */ PHP_METHOD(RedisCluster, zrangebylex) { CLUSTER_PROCESS_KW_CMD("ZRANGEBYLEX", redis_zrangebylex_cmd, cluster_mbulk_resp, 1); } /* }}} */ /* {{{ proto array RedisCluster::zrevrangebylex(string key, string min, * string min, [long off, long limit) */ PHP_METHOD(RedisCluster, zrevrangebylex) { CLUSTER_PROCESS_KW_CMD("ZREVRANGEBYLEX", redis_zrangebylex_cmd, cluster_mbulk_resp, 1); } /* }}} */ /* {{{ proto long RedisCluster::zlexcount(string key, string min, string max) */ PHP_METHOD(RedisCluster, zlexcount) { CLUSTER_PROCESS_KW_CMD("ZLEXCOUNT", redis_gen_zlex_cmd, cluster_long_resp, 1); } /* }}} */ /* {{{ proto long RedisCluster::zremrangebylex(string key, string min, string max) */ PHP_METHOD(RedisCluster, zremrangebylex) { CLUSTER_PROCESS_KW_CMD("ZREMRANGEBYLEX", redis_gen_zlex_cmd, cluster_long_resp, 0); } /* }}} */ /* {{{ proto RedisCluster::sort(string key, array options) */ PHP_METHOD(RedisCluster, sort) { redisCluster *c = GET_CONTEXT(); char *cmd; int cmd_len, have_store; short slot; if(redis_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, &have_store, &cmd, &cmd_len, &slot, NULL)==FAILURE) { RETURN_FALSE; } if(cluster_send_command(c,slot,cmd,cmd_len TSRMLS_CC)<0 || c->err!=NULL) { efree(cmd); RETURN_FALSE; } efree(cmd); // Response type differs based on presence of STORE argument if(!have_store) { cluster_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL); } else { cluster_long_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL); } } /* {{{ proto RedisCluster::object(string subcmd, string key) */ PHP_METHOD(RedisCluster, object) { redisCluster *c = GET_CONTEXT(); char *cmd; int cmd_len; short slot; REDIS_REPLY_TYPE rtype; if(redis_object_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, &rtype, &cmd, &cmd_len, &slot, NULL)==FAILURE) { RETURN_FALSE; } if(cluster_send_command(c,slot,cmd,cmd_len TSRMLS_CC)<0 || c->err!=NULL) { efree(cmd); RETURN_FALSE; } efree(cmd); // Use the correct response type if(rtype == TYPE_INT) { cluster_long_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL); } else { cluster_bulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL); } } /* {{{ proto null RedisCluster::subscribe(array chans, callable cb) */ PHP_METHOD(RedisCluster, subscribe) { CLUSTER_PROCESS_KW_CMD("SUBSCRIBE", redis_subscribe_cmd, cluster_sub_resp, 0); } /* }}} */ /* {{{ proto null RedisCluster::psubscribe(array pats, callable cb) */ PHP_METHOD(RedisCluster, psubscribe) { CLUSTER_PROCESS_KW_CMD("PSUBSCRIBE", redis_subscribe_cmd, cluster_sub_resp, 0); } /* }}} */ static void generic_unsub_cmd(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, char *kw) { char *cmd; int cmd_len; void *ctx; short slot; // There is not reason to unsubscribe outside of a subscribe loop if(c->subscribed_slot == -1) { php_error_docref(0 TSRMLS_CC, E_WARNING, "You can't unsubscribe outside of a subscribe loop"); RETURN_FALSE; } // Call directly because we're going to set the slot manually if(redis_unsubscribe_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, kw, &cmd, &cmd_len, &slot, &ctx) ==FAILURE) { RETURN_FALSE; } // This has to operate on our subscribe slot if(cluster_send_slot(c, c->subscribed_slot, cmd, cmd_len, TYPE_MULTIBULK TSRMLS_CC) ==FAILURE) { zend_throw_exception(redis_cluster_exception_ce, "Failed to UNSUBSCRIBE within our subscribe loop!", 0 TSRMLS_CC); RETURN_FALSE; } // Now process response from the slot we're subscribed on cluster_unsub_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx); // Cleanup our command efree(cmd); } /* {{{ proto array RedisCluster::unsubscribe(array chans) */ PHP_METHOD(RedisCluster, unsubscribe) { generic_unsub_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, GET_CONTEXT(), "UNSUBSCRIBE"); } /* }}} */ /* {{{ proto array RedisCluster::punsubscribe(array pats) */ PHP_METHOD(RedisCluster, punsubscribe) { generic_unsub_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, GET_CONTEXT(), "PUNSUBSCRIBE"); } /* }}} */ /* {{{ proto mixed RedisCluster::eval(string script, [array args, int numkeys) */ PHP_METHOD(RedisCluster, eval) { CLUSTER_PROCESS_KW_CMD("EVAL", redis_eval_cmd, cluster_variant_resp, 0); } /* }}} */ /* {{{ proto mixed RedisCluster::evalsha(string sha, [array args, int numkeys]) */ PHP_METHOD(RedisCluster, evalsha) { CLUSTER_PROCESS_KW_CMD("EVALSHA", redis_eval_cmd, cluster_variant_resp, 0); } /* }}} */ /* Commands that do not interact with Redis, but just report stuff about * various options, etc */ /* {{{ proto string RedisCluster::getmode() */ PHP_METHOD(RedisCluster, getmode) { redisCluster *c = GET_CONTEXT(); RETURN_LONG(c->flags->mode); } /* }}} */ /* {{{ proto string RedisCluster::getlasterror() */ PHP_METHOD(RedisCluster, getlasterror) { redisCluster *c = GET_CONTEXT(); if (c->err) { RETURN_STRINGL(ZSTR_VAL(c->err), ZSTR_LEN(c->err)); } RETURN_NULL(); } /* }}} */ /* {{{ proto bool RedisCluster::clearlasterror() */ PHP_METHOD(RedisCluster, clearlasterror) { redisCluster *c = GET_CONTEXT(); if (c->err) { zend_string_release(c->err); c->err = NULL; } RETURN_TRUE; } /* }}} */ /* {{{ proto long RedisCluster::getOption(long option */ PHP_METHOD(RedisCluster, getoption) { redis_getoption_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, GET_CONTEXT()->flags, GET_CONTEXT()); } /* }}} */ /* {{{ proto bool RedisCluster::setOption(long option, mixed value) */ PHP_METHOD(RedisCluster, setoption) { redis_setoption_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, GET_CONTEXT()->flags, GET_CONTEXT()); } /* }}} */ /* {{{ proto string RedisCluster::_prefix(string key) */ PHP_METHOD(RedisCluster, _prefix) { redis_prefix_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, GET_CONTEXT()->flags); } /* }}} */ /* {{{ proto string RedisCluster::_serialize(mixed val) */ PHP_METHOD(RedisCluster, _serialize) { redis_serialize_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, GET_CONTEXT()->flags); } /* }}} */ /* {{{ proto mixed RedisCluster::_unserialize(string val) */ PHP_METHOD(RedisCluster, _unserialize) { redis_unserialize_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, GET_CONTEXT()->flags, redis_cluster_exception_ce); } /* }}} */ /* {{{ proto array RedisCluster::_masters() */ PHP_METHOD(RedisCluster, _masters) { redisCluster *c = GET_CONTEXT(); redisClusterNode *node; zval zv, *z_ret = &zv; array_init(z_ret); ZEND_HASH_FOREACH_PTR(c->nodes, node) { if (node == NULL) break; zval z, *z_sub = &z; #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_sub); #endif array_init(z_sub); add_next_index_stringl(z_sub, ZSTR_VAL(node->sock->host), ZSTR_LEN(node->sock->host)); add_next_index_long(z_sub, node->sock->port); add_next_index_zval(z_ret, z_sub); } ZEND_HASH_FOREACH_END(); RETVAL_ZVAL(z_ret, 1, 0); } PHP_METHOD(RedisCluster, _redir) { redisCluster *c = GET_CONTEXT(); char buf[255]; size_t len; len = snprintf(buf, sizeof(buf), "%s:%d", c->redir_host, c->redir_port); if(*c->redir_host && c->redir_host_len) { RETURN_STRINGL(buf, len); } else { RETURN_NULL(); } } /* * Transaction handling */ /* {{{ proto bool RedisCluster::multi() */ PHP_METHOD(RedisCluster, multi) { redisCluster *c = GET_CONTEXT(); if(c->flags->mode == MULTI) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "RedisCluster is already in MULTI mode, ignoring"); RETURN_FALSE; } /* Flag that we're in MULTI mode */ c->flags->mode = MULTI; /* Return our object so we can chain MULTI calls */ RETVAL_ZVAL(getThis(), 1, 0); } /* {{{ proto bool RedisCluster::watch() */ PHP_METHOD(RedisCluster, watch) { redisCluster *c = GET_CONTEXT(); HashTable *ht_dist; clusterDistList *dl; smart_string cmd = {0}; zval *z_args; int argc = ZEND_NUM_ARGS(), i; zend_ulong slot; zend_string *zstr; // Disallow in MULTI mode if(c->flags->mode == MULTI) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "WATCH command not allowed in MULTI mode"); RETURN_FALSE; } // Don't need to process zero arguments if(!argc) RETURN_FALSE; // Create our distribution HashTable ht_dist = cluster_dist_create(); // Allocate args, and grab them z_args = emalloc(sizeof(zval) * argc); if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) { efree(z_args); cluster_dist_free(ht_dist); RETURN_FALSE; } // Loop through arguments, prefixing if needed for(i=0;ilen, "WATCH", sizeof("WATCH")-1); for (i = 0; i < dl->len; i++) { redis_cmd_append_sstr(&cmd, dl->entry[i].key, dl->entry[i].key_len); } // If we get a failure from this, we have to abort if (cluster_send_command(c,(short)slot,cmd.c,cmd.len TSRMLS_CC)==-1) { RETURN_FALSE; } // This node is watching SLOT_SOCK(c, (short)slot)->watching = 1; // Zero out our command buffer cmd.len = 0; } ZEND_HASH_FOREACH_END(); // Cleanup cluster_dist_free(ht_dist); efree(z_args); efree(cmd.c); RETURN_TRUE; } /* {{{ proto bool RedisCluster::unwatch() */ PHP_METHOD(RedisCluster, unwatch) { redisCluster *c = GET_CONTEXT(); short slot; // Send UNWATCH to nodes that need it for(slot=0;slotmaster[slot] && SLOT_SOCK(c,slot)->watching) { if(cluster_send_slot(c, slot, RESP_UNWATCH_CMD, sizeof(RESP_UNWATCH_CMD)-1, TYPE_LINE TSRMLS_CC)==-1) { CLUSTER_RETURN_BOOL(c, 0); } // No longer watching SLOT_SOCK(c,slot)->watching = 0; } } CLUSTER_RETURN_BOOL(c, 1); } /* {{{ proto array RedisCluster::exec() */ PHP_METHOD(RedisCluster, exec) { redisCluster *c = GET_CONTEXT(); clusterFoldItem *fi; // Verify we are in fact in multi mode if(CLUSTER_IS_ATOMIC(c)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "RedisCluster is not in MULTI mode"); RETURN_FALSE; } // First pass, send EXEC and abort on failure fi = c->multi_head; while(fi) { if(SLOT_SOCK(c, fi->slot)->mode == MULTI) { if(cluster_send_exec(c, fi->slot TSRMLS_CC)<0) { cluster_abort_exec(c TSRMLS_CC); zend_throw_exception(redis_cluster_exception_ce, "Error processing EXEC across the cluster", 0 TSRMLS_CC); // Free our queue, reset MULTI state CLUSTER_FREE_QUEUE(c); CLUSTER_RESET_MULTI(c); RETURN_FALSE; } SLOT_SOCK(c, fi->slot)->mode = ATOMIC; SLOT_SOCK(c, fi->slot)->watching = 0; } fi = fi->next; } // MULTI multi-bulk response handler cluster_multi_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL); // Free our callback queue, any enqueued distributed command context items // and reset our MULTI state. CLUSTER_FREE_QUEUE(c); CLUSTER_RESET_MULTI(c); } /* {{{ proto bool RedisCluster::discard() */ PHP_METHOD(RedisCluster, discard) { redisCluster *c = GET_CONTEXT(); if(CLUSTER_IS_ATOMIC(c)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cluster is not in MULTI mode"); RETURN_FALSE; } if(cluster_abort_exec(c TSRMLS_CC)<0) { CLUSTER_RESET_MULTI(c); } CLUSTER_FREE_QUEUE(c); RETURN_TRUE; } /* Get a slot either by key (string) or host/port array */ static short cluster_cmd_get_slot(redisCluster *c, zval *z_arg TSRMLS_DC) { strlen_t key_len; int key_free; zval *z_host, *z_port; short slot; char *key; zend_string *zstr; /* If it's a string, treat it as a key. Otherwise, look for a two * element array */ if(Z_TYPE_P(z_arg)==IS_STRING || Z_TYPE_P(z_arg)==IS_LONG || Z_TYPE_P(z_arg)==IS_DOUBLE) { /* Allow for any scalar here */ zstr = zval_get_string(z_arg); key = ZSTR_VAL(zstr); key_len = ZSTR_LEN(zstr); /* Hash it */ key_free = redis_key_prefix(c->flags, &key, &key_len); slot = cluster_hash_key(key, key_len); zend_string_release(zstr); if(key_free) efree(key); } else if (Z_TYPE_P(z_arg) == IS_ARRAY && (z_host = zend_hash_index_find(Z_ARRVAL_P(z_arg), 0)) != NULL && (z_port = zend_hash_index_find(Z_ARRVAL_P(z_arg), 1)) != NULL && Z_TYPE_P(z_host) == IS_STRING && Z_TYPE_P(z_port) == IS_LONG ) { /* Attempt to find this specific node by host:port */ slot = cluster_find_slot(c,(const char *)Z_STRVAL_P(z_host), (unsigned short)Z_LVAL_P(z_port)); /* Inform the caller if they've passed bad data */ if(slot < 0) { php_error_docref(0 TSRMLS_CC, E_WARNING, "Unknown node %s:%ld", Z_STRVAL_P(z_host), Z_LVAL_P(z_port)); } } else { php_error_docref(0 TSRMLS_CC, E_WARNING, "Direted commands musty be passed a key or [host,port] array"); return -1; } return slot; } /* Generic handler for things we want directed at a given node, like SAVE, * BGSAVE, FLUSHDB, FLUSHALL, etc */ static void cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw, REDIS_REPLY_TYPE reply_type, cluster_cb cb) { redisCluster *c = GET_CONTEXT(); char *cmd; int cmd_len; zval *z_arg; short slot; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &z_arg)==FAILURE) { RETURN_FALSE; } // One argument means find the node (treated like a key), and two means // send the command to a specific host and port slot = cluster_cmd_get_slot(c, z_arg TSRMLS_CC); if(slot<0) { RETURN_FALSE; } // Construct our command cmd_len = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, kw, ""); // Kick off our command if(cluster_send_slot(c, slot, cmd, cmd_len, reply_type TSRMLS_CC)<0) { zend_throw_exception(redis_cluster_exception_ce, "Unable to send command at a specific node", 0 TSRMLS_CC); efree(cmd); RETURN_FALSE; } // Our response callback cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL); // Free our command efree(cmd); } /* Generic routine for handling various commands which need to be directed at * a node, but have complex syntax. We simply parse out the arguments and send * the command as constructed by the caller */ static void cluster_raw_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len) { redisCluster *c = GET_CONTEXT(); smart_string cmd = {0}; zval *z_args; short slot; int i, argc = ZEND_NUM_ARGS(); /* Commands using this pass-thru don't need to be enabled in MULTI mode */ if(!CLUSTER_IS_ATOMIC(c)) { php_error_docref(0 TSRMLS_CC, E_WARNING, "Command can't be issued in MULTI mode"); RETURN_FALSE; } /* We at least need the key or [host,port] argument */ if(argc<1) { php_error_docref(0 TSRMLS_CC, E_WARNING, "Command requires at least an argument to direct to a node"); RETURN_FALSE; } /* Allocate an array to process arguments */ z_args = emalloc(argc * sizeof(zval)); /* Grab args */ if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) { efree(z_args); RETURN_FALSE; } /* First argument needs to be the "where" */ if((slot = cluster_cmd_get_slot(c, &z_args[0] TSRMLS_CC))<0) { RETURN_FALSE; } /* Initialize our command */ redis_cmd_init_sstr(&cmd, argc-1, kw, kw_len); /* Iterate, appending args */ for(i=1;ireadonly = 1; // Convert iterator to long if it isn't, update our long iterator if it's // set and >0, and finish if it's back to zero if(Z_TYPE_P(z_it) != IS_LONG || Z_LVAL_P(z_it)<0) { convert_to_long(z_it); it = 0; } else if(Z_LVAL_P(z_it)!=0) { it = Z_LVAL_P(z_it); } else { RETURN_FALSE; } // Apply any key prefix we have, get the slot key_free = redis_key_prefix(c->flags, &key, &key_len); slot = cluster_hash_key(key, key_len); // If SCAN_RETRY is set, loop until we get a zero iterator or until // we get non-zero elements. Otherwise we just send the command once. do { /* Free our return value if we're back in the loop */ if (Z_TYPE_P(return_value) == IS_ARRAY) { zval_dtor(return_value); ZVAL_NULL(return_value); } // Create command cmd_len = redis_fmt_scan_cmd(&cmd, type, key, key_len, it, pat, pat_len, count); // Send it off if(cluster_send_command(c, slot, cmd, cmd_len TSRMLS_CC)==FAILURE) { zend_throw_exception(redis_cluster_exception_ce, "Couldn't send SCAN command", 0 TSRMLS_CC); if(key_free) efree(key); efree(cmd); RETURN_FALSE; } // Read response if(cluster_scan_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, type, &it)==FAILURE) { zend_throw_exception(redis_cluster_exception_ce, "Couldn't read SCAN response", 0 TSRMLS_CC); if(key_free) efree(key); efree(cmd); RETURN_FALSE; } // Count the elements we got back hash = Z_ARRVAL_P(return_value); num_ele = zend_hash_num_elements(hash); // Free our command efree(cmd); } while(c->flags->scan == REDIS_SCAN_RETRY && it != 0 && num_ele == 0); // Free our key if(key_free) efree(key); // Update iterator reference Z_LVAL_P(z_it) = it; } /* {{{ proto RedisCluster::scan(string master, long it [, string pat, long cnt]) */ PHP_METHOD(RedisCluster, scan) { redisCluster *c = GET_CONTEXT(); char *cmd, *pat=NULL; strlen_t pat_len = 0; int cmd_len; short slot; zval *z_it, *z_node; long it, num_ele; zend_long count = 0; /* Treat as read-only */ c->readonly = CLUSTER_IS_ATOMIC(c); /* Can't be in MULTI mode */ if(!CLUSTER_IS_ATOMIC(c)) { zend_throw_exception(redis_cluster_exception_ce, "SCAN type commands can't be called in MULTI mode", 0 TSRMLS_CC); RETURN_FALSE; } /* Parse arguments */ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z/z|s!l", &z_it, &z_node, &pat, &pat_len, &count)==FAILURE) { RETURN_FALSE; } /* Convert or update iterator */ if(Z_TYPE_P(z_it) != IS_LONG || Z_LVAL_P(z_it)<0) { convert_to_long(z_it); it = 0; } else if(Z_LVAL_P(z_it)!=0) { it = Z_LVAL_P(z_it); } else { RETURN_FALSE; } /* With SCAN_RETRY on, loop until we get some keys, otherwise just return * what Redis does, as it does */ do { /* Free our return value if we're back in the loop */ if (Z_TYPE_P(return_value) == IS_ARRAY) { zval_dtor(return_value); ZVAL_NULL(return_value); } /* Construct our command */ cmd_len = redis_fmt_scan_cmd(&cmd, TYPE_SCAN, NULL, 0, it, pat, pat_len, count); if((slot = cluster_cmd_get_slot(c, z_node TSRMLS_CC))<0) { RETURN_FALSE; } // Send it to the node in question if(cluster_send_command(c, slot, cmd, cmd_len TSRMLS_CC)<0) { zend_throw_exception(redis_cluster_exception_ce, "Couldn't send SCAN to node", 0 TSRMLS_CC); efree(cmd); RETURN_FALSE; } if(cluster_scan_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, TYPE_SCAN, &it)==FAILURE || Z_TYPE_P(return_value)!=IS_ARRAY) { zend_throw_exception(redis_cluster_exception_ce, "Couldn't process SCAN response from node", 0 TSRMLS_CC); efree(cmd); RETURN_FALSE; } efree(cmd); num_ele = zend_hash_num_elements(Z_ARRVAL_P(return_value)); } while(c->flags->scan == REDIS_SCAN_RETRY && it != 0 && num_ele == 0); Z_LVAL_P(z_it) = it; } /* }}} */ /* {{{ proto RedisCluster::sscan(string key, long it [string pat, long cnt]) */ PHP_METHOD(RedisCluster, sscan) { cluster_kscan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_SSCAN); } /* }}} */ /* {{{ proto RedisCluster::zscan(string key, long it [string pat, long cnt]) */ PHP_METHOD(RedisCluster, zscan) { cluster_kscan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_ZSCAN); } /* }}} */ /* {{{ proto RedisCluster::hscan(string key, long it [string pat, long cnt]) */ PHP_METHOD(RedisCluster, hscan) { cluster_kscan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_HSCAN); } /* }}} */ /* {{{ proto RedisCluster::save(string key) * proto RedisCluster::save(string host, long port) */ PHP_METHOD(RedisCluster, save) { cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SAVE", TYPE_LINE, cluster_bool_resp); } /* }}} */ /* {{{ proto RedisCluster::bgsave(string key) * proto RedisCluster::bgsave(string host, long port) */ PHP_METHOD(RedisCluster, bgsave) { cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "BGSAVE", TYPE_LINE, cluster_bool_resp); } /* }}} */ /* {{{ proto RedisCluster::flushdb(string key) * proto RedisCluster::flushdb(string host, long port) */ PHP_METHOD(RedisCluster, flushdb) { cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "FLUSHDB", TYPE_LINE, cluster_bool_resp); } /* }}} */ /* {{{ proto RedisCluster::flushall(string key) * proto RedisCluster::flushall(string host, long port) */ PHP_METHOD(RedisCluster, flushall) { cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "FLUSHALL", TYPE_LINE, cluster_bool_resp); } /* }}} */ /* {{{ proto RedisCluster::dbsize(string key) * proto RedisCluster::dbsize(string host, long port) */ PHP_METHOD(RedisCluster, dbsize) { cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DBSIZE", TYPE_INT, cluster_long_resp); } /* }}} */ /* {{{ proto RedisCluster::bgrewriteaof(string key) * proto RedisCluster::bgrewriteaof(string host, long port) */ PHP_METHOD(RedisCluster, bgrewriteaof) { cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "BGREWRITEAOF", TYPE_LINE, cluster_bool_resp); } /* }}} */ /* {{{ proto RedisCluster::lastsave(string key) * proto RedisCluster::lastsave(array $host_port) */ PHP_METHOD(RedisCluster, lastsave) { cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "LASTSAVE", TYPE_INT, cluster_long_resp); } /* }}} */ /* {{{ proto array RedisCluster::info(string key, [string $arg]) * proto array RedisCluster::info(array host_port, [string $arg]) */ PHP_METHOD(RedisCluster, info) { redisCluster *c = GET_CONTEXT(); REDIS_REPLY_TYPE rtype; char *cmd, *opt=NULL; int cmd_len; strlen_t opt_len = 0; void *ctx = NULL; zval *z_arg; short slot; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|s", &z_arg, &opt, &opt_len)==FAILURE) { RETURN_FALSE; } /* Treat INFO as non read-only, as we probably want the master */ c->readonly = 0; slot = cluster_cmd_get_slot(c, z_arg TSRMLS_CC); if (slot < 0) { RETURN_FALSE; } if (opt != NULL) { cmd_len = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "INFO", "s", opt, opt_len); } else { cmd_len = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "INFO", ""); } rtype = CLUSTER_IS_ATOMIC(c) ? TYPE_BULK : TYPE_LINE; if (cluster_send_slot(c, slot, cmd, cmd_len, rtype TSRMLS_CC)<0) { zend_throw_exception(redis_cluster_exception_ce, "Unable to send INFO command to specific node", 0 TSRMLS_CC); efree(cmd); RETURN_FALSE; } if (CLUSTER_IS_ATOMIC(c)) { cluster_info_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL); } else { CLUSTER_ENQUEUE_RESPONSE(c, slot, cluster_info_resp, ctx); } efree(cmd); } /* }}} */ /* {{{ proto array RedisCluster::client('list') * proto bool RedisCluster::client('kill', $ipport) * proto bool RedisCluster::client('setname', $name) * proto string RedisCluster::client('getname') */ PHP_METHOD(RedisCluster, client) { redisCluster *c = GET_CONTEXT(); char *cmd, *opt=NULL, *arg=NULL; int cmd_len; strlen_t opt_len, arg_len = 0; REDIS_REPLY_TYPE rtype; zval *z_node; short slot; cluster_cb cb; /* Parse args */ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zs|s", &z_node, &opt, &opt_len, &arg, &arg_len)==FAILURE) { RETURN_FALSE; } /* Make sure we can properly resolve the slot */ slot = cluster_cmd_get_slot(c, z_node TSRMLS_CC); if(slot<0) RETURN_FALSE; /* Our return type and reply callback is different for all subcommands */ if (opt_len == 4 && !strncasecmp(opt, "list", 4)) { rtype = CLUSTER_IS_ATOMIC(c) ? TYPE_BULK : TYPE_LINE; cb = cluster_client_list_resp; } else if ((opt_len == 4 && !strncasecmp(opt, "kill", 4)) || (opt_len == 7 && !strncasecmp(opt, "setname", 7))) { rtype = TYPE_LINE; cb = cluster_bool_resp; } else if (opt_len == 7 && !strncasecmp(opt, "getname", 7)) { rtype = CLUSTER_IS_ATOMIC(c) ? TYPE_BULK : TYPE_LINE; cb = cluster_bulk_resp; } else { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid CLIENT subcommand (LIST, KILL, GETNAME, and SETNAME are valid"); RETURN_FALSE; } /* Construct the command */ if (ZEND_NUM_ARGS() == 3) { cmd_len = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "CLIENT", "ss", opt, opt_len, arg, arg_len); } else if(ZEND_NUM_ARGS() == 2) { cmd_len = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "CLIENT", "s", opt, opt_len); } else { zend_wrong_param_count(TSRMLS_C); RETURN_FALSE; } /* Attempt to write our command */ if (cluster_send_slot(c, slot, cmd, cmd_len, rtype TSRMLS_CC)<0) { zend_throw_exception(redis_cluster_exception_ce, "Unable to send CLIENT command to specific node", 0 TSRMLS_CC); efree(cmd); RETURN_FALSE; } /* Now enqueue or process response */ if (CLUSTER_IS_ATOMIC(c)) { cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL); } else { void *ctx = NULL; CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx); } efree(cmd); } /* {{{ proto mixed RedisCluster::cluster(variant) */ PHP_METHOD(RedisCluster, cluster) { cluster_raw_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "CLUSTER", sizeof("CLUSTER")-1); } /* }}} */ /* }}} */ /* {{{ proto mixed RedisCluster::config(string key, ...) * proto mixed RedisCluster::config(array host_port, ...) */ PHP_METHOD(RedisCluster, config) { cluster_raw_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "CONFIG", sizeof("CONFIG")-1); } /* }}} */ /* {{{ proto mixed RedisCluster::pubsub(string key, ...) * proto mixed RedisCluster::pubsub(array host_port, ...) */ PHP_METHOD(RedisCluster, pubsub) { cluster_raw_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PUBSUB", sizeof("PUBSUB")-1); } /* }}} */ /* {{{ proto mixed RedisCluster::script(string key, ...) * proto mixed RedisCluster::script(array host_port, ...) */ PHP_METHOD(RedisCluster, script) { cluster_raw_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SCRIPT", sizeof("SCRIPT")-1); } /* }}} */ /* {{{ proto mixed RedisCluster::slowlog(string key, ...) * proto mixed RedisCluster::slowlog(array host_port, ...) */ PHP_METHOD(RedisCluster, slowlog) { cluster_raw_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SLOWLOG", sizeof("SLOWLOG")-1); } /* }}} */ /* {{{ proto int RedisCluster::geoadd(string key, float long float lat string mem, ...) */ PHP_METHOD(RedisCluster, geoadd) { CLUSTER_PROCESS_KW_CMD("GEOADD", redis_key_varval_cmd, cluster_long_resp, 0); } /* {{{ proto array RedisCluster::geohash(string key, string mem1, [string mem2...]) */ PHP_METHOD(RedisCluster, geohash) { CLUSTER_PROCESS_KW_CMD("GEOHASH", redis_key_varval_cmd, cluster_mbulk_raw_resp, 1); } /* {{{ proto array RedisCluster::geopos(string key, string mem1, [string mem2...]) */ PHP_METHOD(RedisCluster, geopos) { CLUSTER_PROCESS_KW_CMD("GEOPOS", redis_key_varval_cmd, cluster_variant_resp, 1); } /* {{{ proto array RedisCluster::geodist(string key, string mem1, string mem2 [string unit]) */ PHP_METHOD(RedisCluster, geodist) { CLUSTER_PROCESS_CMD(geodist, cluster_dbl_resp, 1); } /* {{{ proto array RedisCluster::georadius() }}} */ PHP_METHOD(RedisCluster, georadius) { CLUSTER_PROCESS_CMD(georadius, cluster_variant_resp, 1); } /* {{{ proto array RedisCluster::georadiusbymember() }}} */ PHP_METHOD(RedisCluster, georadiusbymember) { CLUSTER_PROCESS_CMD(georadiusbymember, cluster_variant_resp, 1) } /* {{{ proto array RedisCluster::role(string key) * proto array RedisCluster::role(array host_port) */ PHP_METHOD(RedisCluster, role) { cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ROLE", TYPE_MULTIBULK, cluster_variant_resp); } /* {{{ proto array RedisCluster::time(string key) * proto array RedisCluster::time(array host_port) */ PHP_METHOD(RedisCluster, time) { cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "TIME", TYPE_MULTIBULK, cluster_variant_resp); } /* }}} */ /* {{{ proto string RedisCluster::randomkey(string key) * proto string RedisCluster::randomkey(array host_port) */ PHP_METHOD(RedisCluster, randomkey) { cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "RANDOMKEY", TYPE_BULK, cluster_bulk_resp); } /* }}} */ /* {{{ proto bool RedisCluster::ping(string key) * proto bool RedisCluster::ping(array host_port) */ PHP_METHOD(RedisCluster, ping) { cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PING", TYPE_LINE, cluster_ping_resp); } /* }}} */ /* {{{ proto string RedisCluster::echo(string key, string msg) * proto string RedisCluster::echo(array host_port, string msg) */ PHP_METHOD(RedisCluster, echo) { redisCluster *c = GET_CONTEXT(); REDIS_REPLY_TYPE rtype; zval *z_arg; char *cmd, *msg; int cmd_len; strlen_t msg_len; short slot; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zs", &z_arg, &msg, &msg_len)==FAILURE) { RETURN_FALSE; } /* Treat this as a readonly command */ c->readonly = CLUSTER_IS_ATOMIC(c); /* Grab slot either by key or host/port */ slot = cluster_cmd_get_slot(c, z_arg TSRMLS_CC); if(slot<0) { RETURN_FALSE; } /* Construct our command */ cmd_len = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "ECHO", "s", msg, msg_len); /* Send it off */ rtype = CLUSTER_IS_ATOMIC(c) ? TYPE_BULK : TYPE_LINE; if(cluster_send_slot(c,slot,cmd,cmd_len,rtype TSRMLS_CC)<0) { zend_throw_exception(redis_cluster_exception_ce, "Unable to send commnad at the specificed node", 0 TSRMLS_CC); efree(cmd); RETURN_FALSE; } /* Process bulk response */ if (CLUSTER_IS_ATOMIC(c)) { cluster_bulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL); } else { void *ctx = NULL; CLUSTER_ENQUEUE_RESPONSE(c, slot, cluster_bulk_resp, ctx); } efree(cmd); } /* }}} */ /* {{{ proto mixed RedisCluster::rawcommand(string $key, string $cmd, [ $argv1 .. $argvN]) * proto mixed RedisCluster::rawcommand(array $host_port, string $cmd, [ $argv1 .. $argvN]) */ PHP_METHOD(RedisCluster, rawcommand) { REDIS_REPLY_TYPE rtype; int argc = ZEND_NUM_ARGS(), cmd_len; redisCluster *c = GET_CONTEXT(); char *cmd = NULL; zval *z_args; short slot; /* Sanity check on our arguments */ if (argc < 2) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "You must pass at least node information as well as at least a command."); RETURN_FALSE; } z_args = emalloc(argc * sizeof(zval)); if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Internal PHP error parsing method parameters."); efree(z_args); RETURN_FALSE; } else if (redis_build_raw_cmd(&z_args[1], argc-1, &cmd, &cmd_len TSRMLS_CC) || (slot = cluster_cmd_get_slot(c, &z_args[0] TSRMLS_CC))<0) { if (cmd) efree(cmd); efree(z_args); RETURN_FALSE; } /* Free argument array */ efree(z_args); /* Direct the command */ rtype = CLUSTER_IS_ATOMIC(c) ? TYPE_EOF : TYPE_LINE; if (cluster_send_slot(c,slot,cmd,cmd_len,rtype TSRMLS_CC)<0) { zend_throw_exception(redis_cluster_exception_ce, "Unable to send command to the specified node", 0 TSRMLS_CC); efree(cmd); RETURN_FALSE; } /* Process variant response */ if (CLUSTER_IS_ATOMIC(c)) { cluster_variant_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL); } else { void *ctx = NULL; CLUSTER_ENQUEUE_RESPONSE(c, slot, cluster_variant_resp, ctx); } efree(cmd); } /* }}} */ /* {{{ proto array RedisCluster::command() * proto array RedisCluster::command('INFO', string cmd) * proto array RedisCluster::command('GETKEYS', array cmd_args) */ PHP_METHOD(RedisCluster, command) { CLUSTER_PROCESS_CMD(command, cluster_variant_resp, 0); } /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */ redis-3.1.6/redis_cluster.h0000644000175000000120000002362713223116600016432 0ustar pyatsukhnenkowheel#ifndef REDIS_CLUSTER_H #define REDIS_CLUSTER_H #include "cluster_library.h" #include #include /* Redis cluster hash slots and N-1 which we'll use to find it */ #define REDIS_CLUSTER_SLOTS 16384 #define REDIS_CLUSTER_MOD (REDIS_CLUSTER_SLOTS-1) /* Get attached object context */ #if (PHP_MAJOR_VERSION < 7) #define GET_CONTEXT() \ ((redisCluster*)zend_object_store_get_object(getThis() TSRMLS_CC)) #else #define GET_CONTEXT() \ ((redisCluster *)((char *)Z_OBJ_P(getThis()) - XtOffsetOf(redisCluster, std))) #endif /* Command building/processing is identical for every command */ #define CLUSTER_BUILD_CMD(name, c, cmd, cmd_len, slot) \ redis_##name##_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, &cmd, \ &cmd_len, &slot) /* Append information required to handle MULTI commands to the tail of our MULTI * linked list. */ #define CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx) \ clusterFoldItem *_item; \ _item = emalloc(sizeof(clusterFoldItem)); \ _item->callback = cb; \ _item->slot = slot; \ _item->ctx = ctx; \ _item->next = NULL; \ if(c->multi_head == NULL) { \ c->multi_head = _item; \ c->multi_curr = _item; \ } else { \ c->multi_curr->next = _item; \ c->multi_curr = _item; \ } \ /* Simple macro to free our enqueued callbacks after we EXEC */ #define CLUSTER_FREE_QUEUE(c) \ clusterFoldItem *_item = c->multi_head, *_tmp; \ while(_item) { \ _tmp = _item->next; \ efree(_item); \ _item = _tmp; \ } \ c->multi_head = c->multi_curr = NULL; \ /* Reset anything flagged as MULTI */ #define CLUSTER_RESET_MULTI(c) \ redisClusterNode *_node; \ ZEND_HASH_FOREACH_PTR(c->nodes, _node) { \ if (_node == NULL) break; \ _node->sock->watching = 0; \ _node->sock->mode = ATOMIC; \ } ZEND_HASH_FOREACH_END(); \ c->flags->watching = 0; \ c->flags->mode = ATOMIC; \ /* Simple 1-1 command -> response macro */ #define CLUSTER_PROCESS_CMD(cmdname, resp_func, readcmd) \ redisCluster *c = GET_CONTEXT(); \ c->readonly = CLUSTER_IS_ATOMIC(c) && readcmd; \ char *cmd; int cmd_len; short slot; void *ctx=NULL; \ if(redis_##cmdname##_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU,c->flags, &cmd, \ &cmd_len, &slot, &ctx)==FAILURE) { \ RETURN_FALSE; \ } \ if(cluster_send_command(c,slot,cmd,cmd_len TSRMLS_CC)<0 || c->err!=NULL) {\ efree(cmd); \ RETURN_FALSE; \ } \ efree(cmd); \ if(c->flags->mode == MULTI) { \ CLUSTER_ENQUEUE_RESPONSE(c, slot, resp_func, ctx); \ RETURN_ZVAL(getThis(), 1, 0); \ } \ resp_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx); /* More generic processing, where only the keyword differs */ #define CLUSTER_PROCESS_KW_CMD(kw, cmdfunc, resp_func, readcmd) \ redisCluster *c = GET_CONTEXT(); \ c->readonly = CLUSTER_IS_ATOMIC(c) && readcmd; \ char *cmd; int cmd_len; short slot; void *ctx=NULL; \ if(cmdfunc(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, kw, &cmd, &cmd_len,\ &slot,&ctx)==FAILURE) { \ RETURN_FALSE; \ } \ if(cluster_send_command(c,slot,cmd,cmd_len TSRMLS_CC)<0 || c->err!=NULL) { \ efree(cmd); \ RETURN_FALSE; \ } \ efree(cmd); \ if(c->flags->mode == MULTI) { \ CLUSTER_ENQUEUE_RESPONSE(c, slot, resp_func, ctx); \ RETURN_ZVAL(getThis(), 1, 0); \ } \ resp_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx); /* For the creation of RedisCluster specific exceptions */ PHP_REDIS_API zend_class_entry *rediscluster_get_exception_base(int root TSRMLS_DC); #if (PHP_MAJOR_VERSION < 7) /* Create cluster context */ zend_object_value create_cluster_context(zend_class_entry *class_type TSRMLS_DC); /* Free cluster context struct */ void free_cluster_context(void *object TSRMLS_DC); #else /* Create cluster context */ zend_object *create_cluster_context(zend_class_entry *class_type TSRMLS_DC); /* Free cluster context struct */ void free_cluster_context(zend_object *object); #endif /* Inittialize our class with PHP */ void init_rediscluster(TSRMLS_D); /* RedisCluster method implementation */ PHP_METHOD(RedisCluster, __construct); PHP_METHOD(RedisCluster, close); PHP_METHOD(RedisCluster, get); PHP_METHOD(RedisCluster, set); PHP_METHOD(RedisCluster, mget); PHP_METHOD(RedisCluster, mset); PHP_METHOD(RedisCluster, msetnx); PHP_METHOD(RedisCluster, mset); PHP_METHOD(RedisCluster, del); PHP_METHOD(RedisCluster, dump); PHP_METHOD(RedisCluster, setex); PHP_METHOD(RedisCluster, psetex); PHP_METHOD(RedisCluster, setnx); PHP_METHOD(RedisCluster, getset); PHP_METHOD(RedisCluster, exists); PHP_METHOD(RedisCluster, keys); PHP_METHOD(RedisCluster, type); PHP_METHOD(RedisCluster, persist); PHP_METHOD(RedisCluster, lpop); PHP_METHOD(RedisCluster, rpop); PHP_METHOD(RedisCluster, spop); PHP_METHOD(RedisCluster, rpush); PHP_METHOD(RedisCluster, lpush); PHP_METHOD(RedisCluster, blpop); PHP_METHOD(RedisCluster, brpop); PHP_METHOD(RedisCluster, rpushx); PHP_METHOD(RedisCluster, lpushx); PHP_METHOD(RedisCluster, linsert); PHP_METHOD(RedisCluster, lindex); PHP_METHOD(RedisCluster, lrem); PHP_METHOD(RedisCluster, brpoplpush); PHP_METHOD(RedisCluster, rpoplpush); PHP_METHOD(RedisCluster, llen); PHP_METHOD(RedisCluster, scard); PHP_METHOD(RedisCluster, smembers); PHP_METHOD(RedisCluster, sismember); PHP_METHOD(RedisCluster, sadd); PHP_METHOD(RedisCluster, saddarray); PHP_METHOD(RedisCluster, srem); PHP_METHOD(RedisCluster, sunion); PHP_METHOD(RedisCluster, sunionstore); PHP_METHOD(RedisCluster, sinter); PHP_METHOD(RedisCluster, sinterstore); PHP_METHOD(RedisCluster, sdiff); PHP_METHOD(RedisCluster, sdiffstore); PHP_METHOD(RedisCluster, strlen); PHP_METHOD(RedisCluster, ttl); PHP_METHOD(RedisCluster, pttl); PHP_METHOD(RedisCluster, zcard); PHP_METHOD(RedisCluster, zscore); PHP_METHOD(RedisCluster, zcount); PHP_METHOD(RedisCluster, zrem); PHP_METHOD(RedisCluster, zremrangebyscore); PHP_METHOD(RedisCluster, zrank); PHP_METHOD(RedisCluster, zrevrank); PHP_METHOD(RedisCluster, zadd); PHP_METHOD(RedisCluster, zincrby); PHP_METHOD(RedisCluster, hlen); PHP_METHOD(RedisCluster, hget); PHP_METHOD(RedisCluster, hkeys); PHP_METHOD(RedisCluster, hvals); PHP_METHOD(RedisCluster, hmget); PHP_METHOD(RedisCluster, hmset); PHP_METHOD(RedisCluster, hdel); PHP_METHOD(RedisCluster, hgetall); PHP_METHOD(RedisCluster, hexists); PHP_METHOD(RedisCluster, hincrby); PHP_METHOD(RedisCluster, hincrbyfloat); PHP_METHOD(RedisCluster, hset); PHP_METHOD(RedisCluster, hsetnx); PHP_METHOD(RedisCluster, hstrlen); PHP_METHOD(RedisCluster, incr); PHP_METHOD(RedisCluster, decr); PHP_METHOD(RedisCluster, incrby); PHP_METHOD(RedisCluster, decrby); PHP_METHOD(RedisCluster, incrbyfloat); PHP_METHOD(RedisCluster, expire); PHP_METHOD(RedisCluster, expireat); PHP_METHOD(RedisCluster, pexpire); PHP_METHOD(RedisCluster, pexpireat); PHP_METHOD(RedisCluster, append); PHP_METHOD(RedisCluster, getbit); PHP_METHOD(RedisCluster, setbit); PHP_METHOD(RedisCluster, bitop); PHP_METHOD(RedisCluster, bitpos); PHP_METHOD(RedisCluster, bitcount); PHP_METHOD(RedisCluster, lget); PHP_METHOD(RedisCluster, getrange); PHP_METHOD(RedisCluster, ltrim); PHP_METHOD(RedisCluster, lrange); PHP_METHOD(RedisCluster, zremrangebyrank); PHP_METHOD(RedisCluster, publish); PHP_METHOD(RedisCluster, lset); PHP_METHOD(RedisCluster, rename); PHP_METHOD(RedisCluster, renamenx); PHP_METHOD(RedisCluster, pfcount); PHP_METHOD(RedisCluster, pfadd); PHP_METHOD(RedisCluster, pfmerge); PHP_METHOD(RedisCluster, restore); PHP_METHOD(RedisCluster, setrange); PHP_METHOD(RedisCluster, smove); PHP_METHOD(RedisCluster, srandmember); PHP_METHOD(RedisCluster, zrange); PHP_METHOD(RedisCluster, zrevrange); PHP_METHOD(RedisCluster, zrangebyscore); PHP_METHOD(RedisCluster, zrevrangebyscore); PHP_METHOD(RedisCluster, zrangebylex); PHP_METHOD(RedisCluster, zrevrangebylex); PHP_METHOD(RedisCluster, zlexcount); PHP_METHOD(RedisCluster, zremrangebylex); PHP_METHOD(RedisCluster, zunionstore); PHP_METHOD(RedisCluster, zinterstore); PHP_METHOD(RedisCluster, sort); PHP_METHOD(RedisCluster, object); PHP_METHOD(RedisCluster, subscribe); PHP_METHOD(RedisCluster, psubscribe); PHP_METHOD(RedisCluster, unsubscribe); PHP_METHOD(RedisCluster, punsubscribe); PHP_METHOD(RedisCluster, eval); PHP_METHOD(RedisCluster, evalsha); PHP_METHOD(RedisCluster, info); PHP_METHOD(RedisCluster, cluster); PHP_METHOD(RedisCluster, client); PHP_METHOD(RedisCluster, config); PHP_METHOD(RedisCluster, pubsub); PHP_METHOD(RedisCluster, script); PHP_METHOD(RedisCluster, slowlog); PHP_METHOD(RedisCluster, command); PHP_METHOD(RedisCluster, geoadd); PHP_METHOD(RedisCluster, geohash); PHP_METHOD(RedisCluster, geopos); PHP_METHOD(RedisCluster, geodist); PHP_METHOD(RedisCluster, georadius); PHP_METHOD(RedisCluster, georadiusbymember); /* SCAN and friends */ PHP_METHOD(RedisCluster, scan); PHP_METHOD(RedisCluster, zscan); PHP_METHOD(RedisCluster, hscan); PHP_METHOD(RedisCluster, sscan); /* Transactions */ PHP_METHOD(RedisCluster, multi); PHP_METHOD(RedisCluster, exec); PHP_METHOD(RedisCluster, discard); PHP_METHOD(RedisCluster, watch); PHP_METHOD(RedisCluster, unwatch); /* Commands we direct to a node */ PHP_METHOD(RedisCluster, save); PHP_METHOD(RedisCluster, bgsave); PHP_METHOD(RedisCluster, flushdb); PHP_METHOD(RedisCluster, flushall); PHP_METHOD(RedisCluster, dbsize); PHP_METHOD(RedisCluster, bgrewriteaof); PHP_METHOD(RedisCluster, lastsave); PHP_METHOD(RedisCluster, role); PHP_METHOD(RedisCluster, time); PHP_METHOD(RedisCluster, randomkey); PHP_METHOD(RedisCluster, ping); PHP_METHOD(RedisCluster, echo); PHP_METHOD(RedisCluster, rawcommand); /* Introspection */ PHP_METHOD(RedisCluster, getmode); PHP_METHOD(RedisCluster, getlasterror); PHP_METHOD(RedisCluster, clearlasterror); PHP_METHOD(RedisCluster, getoption); PHP_METHOD(RedisCluster, setoption); PHP_METHOD(RedisCluster, _prefix); PHP_METHOD(RedisCluster, _serialize); PHP_METHOD(RedisCluster, _unserialize); PHP_METHOD(RedisCluster, _masters); PHP_METHOD(RedisCluster, _redir); #endif redis-3.1.6/cluster_library.c0000644000175000000120000022314713223116600016762 0ustar pyatsukhnenkowheel#include "php_redis.h" #include "common.h" #include "library.h" #include "redis_commands.h" #include "cluster_library.h" #include "crc16.h" #include extern zend_class_entry *redis_cluster_exception_ce; /* Debugging methods/ static void cluster_dump_nodes(redisCluster *c) { redisClusterNode *p; ZEND_HASH_FOREACH_PTR(c->nodes, p) { if (p == NULL) { continue; } const char *slave = (p->slave) ? "slave" : "master"; php_printf("%d %s %d %d", p->sock->port, slave,p->sock->prefix_len, p->slot); php_printf("\n"); } ZEND_HASH_FOREACH_END(); } static void cluster_log(char *fmt, ...) { va_list args; char buffer[1024]; va_start(args, fmt); vsnprintf(buffer,sizeof(buffer),fmt,args); va_end(args); fprintf(stderr, "%s\n", buffer); } // Debug function to dump a clusterReply structure recursively static void dump_reply(clusterReply *reply, int indent) { smart_string buf = {0}; int i; switch(reply->type) { case TYPE_ERR: smart_string_appendl(&buf, "(error) ", sizeof("(error) ")-1); smart_string_appendl(&buf, reply->str, reply->len); break; case TYPE_LINE: smart_string_appendl(&buf, reply->str, reply->len); break; case TYPE_INT: smart_string_appendl(&buf, "(integer) ", sizeof("(integer) ")-1); smart_string_append_long(&buf, reply->integer); break; case TYPE_BULK: smart_string_appendl(&buf,"\"", 1); smart_string_appendl(&buf, reply->str, reply->len); smart_string_appendl(&buf, "\"", 1); break; case TYPE_MULTIBULK: if(reply->elements == (size_t)-1) { smart_string_appendl(&buf, "(nil)", sizeof("(nil)")-1); } else { for(i=0;ielements;i++) { dump_reply(reply->element[i], indent+2); } } break; default: break; } if(buf.len > 0) { for(i=0;itype) { case TYPE_ERR: case TYPE_LINE: case TYPE_BULK: if(free_data && reply->str) efree(reply->str); break; case TYPE_MULTIBULK: for(i=0;ielements && reply->element[i]; i++) { cluster_free_reply(reply->element[i], free_data); } efree(reply->element); break; default: break; } efree(reply); } static void cluster_multibulk_resp_recursive(RedisSock *sock, size_t elements, clusterReply **element, int *err TSRMLS_DC) { int i; size_t sz; clusterReply *r; long len; char buf[1024]; for (i = 0; i < elements; i++) { r = element[i] = ecalloc(1, sizeof(clusterReply)); // Bomb out, flag error condition on a communication failure if(redis_read_reply_type(sock, &r->type, &len TSRMLS_CC)<0) { *err = 1; return; } /* Set our reply len */ r->len = len; switch(r->type) { case TYPE_ERR: case TYPE_LINE: if(redis_sock_gets(sock,buf,sizeof(buf),&sz TSRMLS_CC)<0) { *err = 1; return; } r->len = (long long)sz; break; case TYPE_INT: r->integer = len; break; case TYPE_BULK: if (r->len > 0) { r->str = redis_sock_read_bulk_reply(sock,r->len TSRMLS_CC); if(!r->str) { *err = 1; return; } } break; case TYPE_MULTIBULK: r->element = ecalloc(r->len,sizeof(clusterReply*)); r->elements = r->len; cluster_multibulk_resp_recursive(sock, r->elements, r->element, err TSRMLS_CC); if(*err) return; break; default: *err = 1; return; } } } /* Return the socket for a slot and slave index */ static RedisSock *cluster_slot_sock(redisCluster *c, unsigned short slot, ulong slaveidx) { redisClusterNode *node; /* Return the master if we're not looking for a slave */ if (slaveidx == 0) { return SLOT_SOCK(c, slot); } /* Abort if we can't find this slave */ if (!SLOT_SLAVES(c, slot) || (node = zend_hash_index_find_ptr(SLOT_SLAVES(c,slot), slaveidx)) == NULL ) { return NULL; } /* Success, return the slave */ return node->sock; } /* Read the response from a cluster */ clusterReply *cluster_read_resp(redisCluster *c TSRMLS_DC) { return cluster_read_sock_resp(c->cmd_sock,c->reply_type,c->reply_len TSRMLS_CC); } /* Read any sort of response from the socket, having already issued the * command and consumed the reply type and meta info (length) */ clusterReply* cluster_read_sock_resp(RedisSock *redis_sock, REDIS_REPLY_TYPE type, size_t len TSRMLS_DC) { clusterReply *r; r = ecalloc(1, sizeof(clusterReply)); r->type = type; // Error flag in case we go recursive int err = 0; switch(r->type) { case TYPE_INT: r->integer = len; break; case TYPE_LINE: case TYPE_ERR: return r; case TYPE_BULK: r->len = len; r->str = redis_sock_read_bulk_reply(redis_sock, len TSRMLS_CC); if(r->len != -1 && !r->str) { cluster_free_reply(r, 1); return NULL; } break; case TYPE_MULTIBULK: r->elements = len; if(len != (size_t)-1) { r->element = ecalloc(len, sizeof(clusterReply*)*len); cluster_multibulk_resp_recursive(redis_sock, len, r->element, &err TSRMLS_CC); } break; default: cluster_free_reply(r,1); return NULL; } // Free/return null on communication error if(err) { cluster_free_reply(r,1); return NULL; } // Success, return the reply return r; } /* * Helpers to send various 'control type commands to a specific node, e.g. * MULTI, ASKING, READONLY, READWRITE, etc */ /* Send a command to the specific socket and validate reply type */ static int cluster_send_direct(RedisSock *redis_sock, char *cmd, int cmd_len, REDIS_REPLY_TYPE type TSRMLS_DC) { char buf[1024]; /* Connect to the socket if we aren't yet */ CLUSTER_LAZY_CONNECT(redis_sock); /* Send our command, validate the reply type, and consume the first line */ if (!CLUSTER_SEND_PAYLOAD(redis_sock,cmd,cmd_len) || !CLUSTER_VALIDATE_REPLY_TYPE(redis_sock, type) || !php_stream_gets(redis_sock->stream, buf, sizeof(buf))) return -1; /* Success! */ return 0; } static int cluster_send_asking(RedisSock *redis_sock TSRMLS_DC) { return cluster_send_direct(redis_sock, RESP_ASKING_CMD, sizeof(RESP_ASKING_CMD)-1, TYPE_LINE TSRMLS_CC); } /* Send READONLY to a specific RedisSock unless it's already flagged as being * in READONLY mode. If we can send the command, we flag the socket as being * in that mode. */ static int cluster_send_readonly(RedisSock *redis_sock TSRMLS_DC) { int ret; /* We don't have to do anything if we're already in readonly mode */ if (redis_sock->readonly) return 0; /* Return success if we can send it */ ret = cluster_send_direct(redis_sock, RESP_READONLY_CMD, sizeof(RESP_READONLY_CMD)-1, TYPE_LINE TSRMLS_CC); /* Flag this socket as READONLY if our command worked */ redis_sock->readonly = !ret; /* Return the result of our send */ return ret; } /* Send MULTI to a specific ReidsSock */ static int cluster_send_multi(redisCluster *c, short slot TSRMLS_DC) { if (cluster_send_direct(SLOT_SOCK(c,slot), RESP_MULTI_CMD, sizeof(RESP_MULTI_CMD)-1, TYPE_LINE TSRMLS_CC)==0) { c->cmd_sock->mode = MULTI; return 0; } return -1; } /* Send EXEC to a given slot. We can use the normal command processing mechanism * here because we know we'll only have sent MULTI to the master nodes. We can't * failover inside a transaction, as we don't know if the transaction will only * be readonly commands, or contain write commands as well */ PHP_REDIS_API int cluster_send_exec(redisCluster *c, short slot TSRMLS_DC) { int retval; /* Send exec */ retval = cluster_send_slot(c, slot, RESP_EXEC_CMD, sizeof(RESP_EXEC_CMD)-1, TYPE_MULTIBULK TSRMLS_CC); /* We'll either get a length corresponding to the number of commands sent to * this node, or -1 in the case of EXECABORT or WATCH failure. */ c->multi_len[slot] = c->reply_len > 0 ? 1 : -1; /* Return our retval */ return retval; } PHP_REDIS_API int cluster_send_discard(redisCluster *c, short slot TSRMLS_DC) { if (cluster_send_direct(SLOT_SOCK(c,slot), RESP_DISCARD_CMD, sizeof(RESP_DISCARD_CMD)-1, TYPE_LINE TSRMLS_CC)) { return 0; } return -1; } /* * Cluster key distribution helpers. For a small handlful of commands, we want * to distribute them across 1-N nodes. These methods provide simple containers * for the purposes of splitting keys/values in this way * */ /* Free cluster distribution list inside a HashTable */ #if (PHP_MAJOR_VERSION < 7) static void cluster_dist_free_ht(void *p) #else static void cluster_dist_free_ht(zval *p) #endif { clusterDistList *dl = *(clusterDistList**)p; int i; for(i=0; i < dl->len; i++) { if(dl->entry[i].key_free) efree(dl->entry[i].key); if(dl->entry[i].val_free) efree(dl->entry[i].val); } efree(dl->entry); efree(dl); } /* Spin up a HashTable that will contain distribution lists */ HashTable *cluster_dist_create() { HashTable *ret; ALLOC_HASHTABLE(ret); zend_hash_init(ret, 0, NULL, cluster_dist_free_ht, 0); return ret; } /* Free distribution list */ void cluster_dist_free(HashTable *ht) { zend_hash_destroy(ht); efree(ht); } /* Create a clusterDistList object */ static clusterDistList *cluster_dl_create() { clusterDistList *dl; dl = emalloc(sizeof(clusterDistList)); dl->entry = emalloc(CLUSTER_KEYDIST_ALLOC * sizeof(clusterKeyVal)); dl->size = CLUSTER_KEYDIST_ALLOC; dl->len = 0; return dl; } /* Add a key to a dist list, returning the keval entry */ static clusterKeyVal *cluster_dl_add_key(clusterDistList *dl, char *key, int key_len, int key_free) { // Reallocate if required if(dl->len==dl->size) { dl->entry = erealloc(dl->entry, sizeof(clusterKeyVal) * dl->size * 2); dl->size *= 2; } // Set key info dl->entry[dl->len].key = key; dl->entry[dl->len].key_len = key_len; dl->entry[dl->len].key_free = key_free; // NULL out any values dl->entry[dl->len].val = NULL; dl->entry[dl->len].val_len = 0; dl->entry[dl->len].val_free = 0; return &(dl->entry[dl->len++]); } /* Add a key, returning a pointer to the entry where passed for easy adding * of values to match this key */ int cluster_dist_add_key(redisCluster *c, HashTable *ht, char *key, strlen_t key_len, clusterKeyVal **kv) { int key_free; short slot; clusterDistList *dl; clusterKeyVal *retptr; // Prefix our key and hash it key_free = redis_key_prefix(c->flags, &key, &key_len); slot = cluster_hash_key(key, key_len); // We can't do this if we don't fully understand the keyspace if(c->master[slot] == NULL) { if(key_free) efree(key); return FAILURE; } // Look for this slot if ((dl = zend_hash_index_find_ptr(ht, (zend_ulong)slot)) == NULL) { dl = cluster_dl_create(); zend_hash_index_update_ptr(ht, (zend_ulong)slot, dl); } // Now actually add this key retptr = cluster_dl_add_key(dl, key, key_len, key_free); // Push our return pointer if requested if(kv) *kv = retptr; return SUCCESS; } /* Provided a clusterKeyVal, add a value */ void cluster_dist_add_val(redisCluster *c, clusterKeyVal *kv, zval *z_val TSRMLS_DC) { char *val; strlen_t val_len; int val_free; // Serialize our value val_free = redis_serialize(c->flags, z_val, &val, &val_len TSRMLS_CC); // Attach it to the provied keyval entry kv->val = val; kv->val_len = val_len; kv->val_free = val_free; } /* Free allocated memory for a clusterMultiCmd */ void cluster_multi_free(clusterMultiCmd *mc) { efree(mc->cmd.c); efree(mc->args.c); } /* Add an argument to a clusterMultiCmd */ void cluster_multi_add(clusterMultiCmd *mc, char *data, int data_len) { mc->argc++; redis_cmd_append_sstr(&(mc->args), data, data_len); } /* Finalize a clusterMutliCmd by constructing the whole thing */ void cluster_multi_fini(clusterMultiCmd *mc) { mc->cmd.len = 0; redis_cmd_init_sstr(&(mc->cmd), mc->argc, mc->kw, mc->kw_len); smart_string_appendl(&(mc->cmd), mc->args.c, mc->args.len); } /* Set our last error string encountered */ static void cluster_set_err(redisCluster *c, char *err, int err_len) { // Free our last error if (c->err != NULL) { zend_string_release(c->err); c->err = NULL; } if (err != NULL && err_len > 0) { c->err = zend_string_init(err, err_len, 0); if (err_len >= sizeof("CLUSTERDOWN") - 1 && !memcmp(err, "CLUSTERDOWN", sizeof("CLUSTERDOWN") - 1) ) { c->clusterdown = 1; } } } /* Destructor for slaves */ #if (PHP_MAJOR_VERSION < 7) static void ht_free_slave(void *data) #else static void ht_free_slave(zval *data) #endif { if(*(redisClusterNode**)data) { cluster_free_node(*(redisClusterNode**)data); } } /* Get the hash slot for a given key */ unsigned short cluster_hash_key(const char *key, int len) { int s, e; // Find first occurrence of {, if any for(s=0;s 1) { r = ((int)((double)n-- * (rand() / (RAND_MAX+1.0)))); temp = array[n]; array[n] = array[r]; array[r] = temp; }; } /* Execute a CLUSTER SLOTS command against the seed socket, and return the * reply or NULL on failure. */ clusterReply* cluster_get_slots(RedisSock *redis_sock TSRMLS_DC) { clusterReply *r; REDIS_REPLY_TYPE type; long len; // Send the command to the socket and consume reply type if(redis_sock_write(redis_sock, RESP_CLUSTER_SLOTS_CMD, sizeof(RESP_CLUSTER_SLOTS_CMD)-1 TSRMLS_CC)<0 || redis_read_reply_type(redis_sock, &type, &len TSRMLS_CC)<0) { return NULL; } // Consume the rest of our response if((r = cluster_read_sock_resp(redis_sock, type, len TSRMLS_CC))==NULL || r->type != TYPE_MULTIBULK || r->elements < 1) { if(r) cluster_free_reply(r, 1); return NULL; } // Return our reply return r; } /* Create a cluster node */ static redisClusterNode* cluster_node_create(redisCluster *c, char *host, size_t host_len, unsigned short port, unsigned short slot, short slave) { redisClusterNode *node = emalloc(sizeof(redisClusterNode)); // It lives in at least this slot, flag slave status node->slot = slot; node->slave = slave; node->slaves = NULL; // Attach socket node->sock = redis_sock_create(host, host_len, port, c->timeout, c->read_timeout, c->persistent, NULL, 0, 1); return node; } /* Attach a slave to a master */ PHP_REDIS_API int cluster_node_add_slave(redisClusterNode *master, redisClusterNode *slave) { ulong index; // Allocate our slaves hash table if we haven't yet if(!master->slaves) { ALLOC_HASHTABLE(master->slaves); zend_hash_init(master->slaves, 0, NULL, ht_free_slave, 0); index = 1; } else { index = master->slaves->nNextFreeElement; } return zend_hash_index_update_ptr(master->slaves, index, slave) != NULL; } /* Sanity check/validation for CLUSTER SLOTS command */ #define VALIDATE_SLOTS_OUTER(r) \ (r->elements>=3 && r2->element[0]->type == TYPE_INT && \ r->element[1]->type==TYPE_INT) #define VALIDATE_SLOTS_INNER(r) \ (r->type == TYPE_MULTIBULK && r->elements>=2 && \ r->element[0]->type == TYPE_BULK && r->element[1]->type==TYPE_INT) /* Use the output of CLUSTER SLOTS to map our nodes */ static int cluster_map_slots(redisCluster *c, clusterReply *r) { int i,j, hlen, klen; short low, high; clusterReply *r2, *r3; redisClusterNode *pnode, *master, *slave; unsigned short port; char *host, key[1024]; for(i=0;ielements;i++) { // Inner response r2 = r->element[i]; // Validate outer and master slot if(!VALIDATE_SLOTS_OUTER(r2) || !VALIDATE_SLOTS_INNER(r2->element[2])) { return -1; } // Master r3 = r2->element[2]; // Grab our slot range, as well as master host/port low = (unsigned short)r2->element[0]->integer; high = (unsigned short)r2->element[1]->integer; host = r3->element[0]->str; hlen = r3->element[0]->len; port = (unsigned short)r3->element[1]->integer; // If the node is new, create and add to nodes. Otherwise use it. klen = snprintf(key,sizeof(key),"%s:%ld",host,port); if ((pnode = zend_hash_str_find_ptr(c->nodes, key, klen)) == NULL) { master = cluster_node_create(c, host, hlen, port, low, 0); zend_hash_str_update_ptr(c->nodes, key, klen, master); } else { master = pnode; } // Attach slaves for(j=3;jelements;j++) { r3 = r2->element[j]; if(!VALIDATE_SLOTS_INNER(r3)) { return -1; } // Skip slaves where the host is "" if(r3->element[0]->len == 0) continue; // Attach this node to our slave slave = cluster_node_create(c, r3->element[0]->str, (int)r3->element[0]->len, (unsigned short)r3->element[1]->integer, low, 1); cluster_node_add_slave(master, slave); } // Attach this node to each slot in the range for(j=low;j<=high;j++) { c->master[j]=master; } } // Success return 0; } /* Free a redisClusterNode structure */ PHP_REDIS_API void cluster_free_node(redisClusterNode *node) { if(node->slaves) { zend_hash_destroy(node->slaves); efree(node->slaves); } redis_free_socket(node->sock); efree(node); } /* Get or create a redisClusterNode that corresponds to the asking redirection */ static redisClusterNode *cluster_get_asking_node(redisCluster *c TSRMLS_DC) { redisClusterNode *pNode; char key[1024]; int key_len; /* Hashed by host:port */ key_len = snprintf(key, sizeof(key), "%s:%u", c->redir_host, c->redir_port); /* See if we've already attached to it */ if ((pNode = zend_hash_str_find_ptr(c->nodes, key, key_len)) != NULL) { return pNode; } /* This host:port is unknown to us, so add it */ pNode = cluster_node_create(c, c->redir_host, c->redir_host_len, c->redir_port, c->redir_slot, 0); /* Return the node */ return pNode; } /* Get or create a node at the host:port we were asked to check, and return the * redis_sock for it. */ static RedisSock *cluster_get_asking_sock(redisCluster *c TSRMLS_DC) { return cluster_get_asking_node(c TSRMLS_CC)->sock; } /* Our context seeds will be a hash table with RedisSock* pointers */ #if (PHP_MAJOR_VERSION < 7) static void ht_free_seed(void *data) #else static void ht_free_seed(zval *data) #endif { RedisSock *redis_sock = *(RedisSock**)data; if(redis_sock) redis_free_socket(redis_sock); } /* Free redisClusterNode objects we've stored */ #if (PHP_MAJOR_VERSION < 7) static void ht_free_node(void *data) #else static void ht_free_node(zval *data) #endif { redisClusterNode *node = *(redisClusterNode**)data; cluster_free_node(node); } /* Construct a redisCluster object */ PHP_REDIS_API redisCluster *cluster_create(double timeout, double read_timeout, int failover, int persistent) { redisCluster *c; /* Actual our actual cluster structure */ c = ecalloc(1, sizeof(redisCluster)); /* Initialize flags and settings */ c->flags = ecalloc(1, sizeof(RedisSock)); c->subscribed_slot = -1; c->clusterdown = 0; c->timeout = timeout; c->read_timeout = read_timeout; c->failover = failover; c->persistent = persistent; c->err = NULL; /* Set up our waitms based on timeout */ c->waitms = (long)(1000 * timeout); /* Allocate our seeds hash table */ ALLOC_HASHTABLE(c->seeds); zend_hash_init(c->seeds, 0, NULL, ht_free_seed, 0); /* Allocate our nodes HashTable */ ALLOC_HASHTABLE(c->nodes); zend_hash_init(c->nodes, 0, NULL, ht_free_node, 0); return c; } PHP_REDIS_API void cluster_free(redisCluster *c) { /* Free any allocated prefix */ if (c->flags->prefix) efree(c->flags->prefix); efree(c->flags); /* Call hash table destructors */ zend_hash_destroy(c->seeds); zend_hash_destroy(c->nodes); /* Free hash tables themselves */ efree(c->seeds); efree(c->nodes); /* Free any error we've got */ if (c->err) zend_string_release(c->err); /* Free structure itself */ efree(c); } /* Takes our input hash table and returns a straigt C array with elements, * which have been randomized. The return value needs to be freed. */ static zval **cluster_shuffle_seeds(HashTable *seeds, int *len) { zval **z_seeds, *z_ele; int *map, i, count, index=0; /* How many */ count = zend_hash_num_elements(seeds); /* Allocate our return value and map */ z_seeds = ecalloc(count, sizeof(zval*)); map = emalloc(sizeof(int)*count); /* Fill in and shuffle our map */ for (i = 0; i < count; i++) map[i] = i; fyshuffle(map, count); /* Iterate over our source array and use our map to create a random list */ ZEND_HASH_FOREACH_VAL(seeds, z_ele) { z_seeds[map[index++]] = z_ele; } ZEND_HASH_FOREACH_END(); efree(map); *len = count; return z_seeds; } /* Initialize seeds */ PHP_REDIS_API int cluster_init_seeds(redisCluster *cluster, HashTable *ht_seeds) { RedisSock *redis_sock; char *str, *psep, key[1024]; int key_len, count, i; zval **z_seeds, *z_seed; /* Get our seeds in a randomized array */ z_seeds = cluster_shuffle_seeds(ht_seeds, &count); // Iterate our seeds array for (i = 0; i < count; i++) { if ((z_seed = z_seeds[i]) == NULL) continue; ZVAL_DEREF(z_seed); /* Has to be a string */ if (Z_TYPE_P(z_seed) != IS_STRING) continue; // Grab a copy of the string str = Z_STRVAL_P(z_seed); /* Make sure we have a colon for host:port. Search right to left in the * case of IPv6 */ if ((psep = strrchr(str, ':')) == NULL) continue; // Allocate a structure for this seed redis_sock = redis_sock_create(str, psep-str, (unsigned short)atoi(psep+1), cluster->timeout, cluster->read_timeout, cluster->persistent, NULL, 0, 0); // Index this seed by host/port key_len = snprintf(key, sizeof(key), "%s:%u", ZSTR_VAL(redis_sock->host), redis_sock->port); // Add to our seed HashTable zend_hash_str_update_ptr(cluster->seeds, key, key_len, redis_sock); } efree(z_seeds); // Success if at least one seed seems valid return zend_hash_num_elements(cluster->seeds) > 0 ? 0 : -1; } /* Initial mapping of our cluster keyspace */ PHP_REDIS_API int cluster_map_keyspace(redisCluster *c TSRMLS_DC) { RedisSock *seed; clusterReply *slots=NULL; int mapped=0; // Iterate over seeds until we can get slots ZEND_HASH_FOREACH_PTR(c->seeds, seed) { // Attempt to connect to this seed node if (seed == NULL || redis_sock_connect(seed TSRMLS_CC) != 0) { continue; } // Parse out cluster nodes. Flag mapped if we are valid slots = cluster_get_slots(seed TSRMLS_CC); if (slots) { mapped = !cluster_map_slots(c, slots); // Bin anything mapped, if we failed somewhere if (!mapped) { memset(c->master, 0, sizeof(redisClusterNode*)*REDIS_CLUSTER_SLOTS); } } redis_sock_disconnect(seed TSRMLS_CC); if (mapped) break; } ZEND_HASH_FOREACH_END(); // Clean up slots reply if we got one if(slots) cluster_free_reply(slots, 1); // Throw an exception if we couldn't map if(!mapped) { zend_throw_exception(redis_cluster_exception_ce, "Couldn't map cluster keyspace using any provided seed", 0 TSRMLS_CC); return -1; } return 0; } /* Parse the MOVED OR ASK redirection payload when we get such a response * and apply this information to our cluster. If we encounter a parse error * nothing in the cluster will be modified, and -1 is returned. */ static int cluster_set_redirection(redisCluster* c, char *msg, int moved) { char *host, *port; /* Move past "MOVED" or "ASK */ msg += moved ? MOVED_LEN : ASK_LEN; /* Make sure we can find host */ if ((host = strchr(msg, ' ')) == NULL) return -1; *host++ = '\0'; /* Find port, searching right to left in case of IPv6 */ if ((port = strrchr(host, ':')) == NULL) return -1; *port++ = '\0'; // Success, apply it c->redir_type = moved ? REDIR_MOVED : REDIR_ASK; strncpy(c->redir_host, host, sizeof(c->redir_host) - 1); c->redir_host_len = port - host - 1; c->redir_slot = (unsigned short)atoi(msg); c->redir_port = (unsigned short)atoi(port); return 0; } /* Once we write a command to a node in our cluster, this function will check * the reply type and extract information from those that will specify a length * bit. If we encounter an error condition, we'll check for MOVED or ASK * redirection, parsing out slot host and port so the caller can take * appropriate action. * * In the case of a non MOVED/ASK error, we wlll set our cluster error * condition so GetLastError can be queried by the client. * * This function will return -1 on a critical error (e.g. parse/communication * error, 0 if no redirection was encountered, and 1 if the data was moved. */ static int cluster_check_response(redisCluster *c, REDIS_REPLY_TYPE *reply_type TSRMLS_DC) { size_t sz; // Clear out any prior error state and our last line response CLUSTER_CLEAR_ERROR(c); CLUSTER_CLEAR_REPLY(c); if(-1 == redis_check_eof(c->cmd_sock, 1 TSRMLS_CC) || EOF == (*reply_type = php_stream_getc(c->cmd_sock->stream))) { return -1; } // In the event of an ERROR, check if it's a MOVED/ASK error if(*reply_type == TYPE_ERR) { char inbuf[4096]; int moved; // Attempt to read the error if(!php_stream_gets(c->cmd_sock->stream, inbuf, sizeof(inbuf))) { return -1; } // Check for MOVED or ASK redirection if((moved = IS_MOVED(inbuf)) || IS_ASK(inbuf)) { // Set our redirection information if(cluster_set_redirection(c,inbuf,moved)<0) { return -1; } // Data moved return 1; } else { // Capture the error string Redis returned cluster_set_err(c, inbuf, strlen(inbuf)-2); return 0; } } // Fetch the first line of our response from Redis. if(redis_sock_gets(c->cmd_sock,c->line_reply,sizeof(c->line_reply), &sz TSRMLS_CC)<0) { return -1; } // For replies that will give us a numberic length, convert it if(*reply_type != TYPE_LINE) { c->reply_len = strtol(c->line_reply, NULL, 10); } else { c->reply_len = (long long)sz; } // Clear out any previous error, and return that the data is here CLUSTER_CLEAR_ERROR(c); return 0; } /* Disconnect from each node we're connected to */ PHP_REDIS_API void cluster_disconnect(redisCluster *c TSRMLS_DC) { redisClusterNode *node; ZEND_HASH_FOREACH_PTR(c->nodes, node) { if (node == NULL) break; redis_sock_disconnect(node->sock TSRMLS_CC); node->sock->lazy_connect = 1; } ZEND_HASH_FOREACH_END(); } /* This method attempts to write our command at random to the master and any * attached slaves, until we either successufly do so, or fail. */ static int cluster_dist_write(redisCluster *c, const char *cmd, size_t sz, int nomaster TSRMLS_DC) { int i, count=1, *nodes; RedisSock *redis_sock; /* Determine our overall node count */ if (c->master[c->cmd_slot]->slaves) { count += zend_hash_num_elements(c->master[c->cmd_slot]->slaves); } /* Allocate memory for master + slaves or just slaves */ nodes = emalloc(sizeof(int)*count); /* Populate our array with the master and each of it's slaves, then * randomize them, so we will pick from the master or some slave. */ for (i = 0; i < count; i++) nodes[i] = i; fyshuffle(nodes, count); /* Iterate through our nodes until we find one we can write to or fail */ for (i = 0; i < count; i++) { /* Skip if this is the master node and we don't want to query that */ if (nomaster && nodes[i] == 0) continue; /* Get the slave for this index */ redis_sock = cluster_slot_sock(c, c->cmd_slot, nodes[i]); if (!redis_sock) continue; /* Connect to this node if we haven't already */ CLUSTER_LAZY_CONNECT(redis_sock); /* If we're not on the master, attempt to send the READONLY command to * this slave, and skip it if that fails */ if (nodes[i] == 0 || redis_sock->readonly || cluster_send_readonly(redis_sock TSRMLS_CC) == 0) { /* Attempt to send the command */ if (CLUSTER_SEND_PAYLOAD(redis_sock, cmd, sz)) { c->cmd_sock = redis_sock; efree(nodes); return 0; } } } /* Clean up our shuffled array */ efree(nodes); /* Couldn't send to the master or any slave */ return -1; } /* Attempt to write our command to the current c->cmd_sock socket. For write * commands, we attempt to query the master for this slot, and in the event of * a failure, try to query every remaining node for a redirection. * * If we're issuing a readonly command, we use one of three strategies, depending * on our redisCluster->failover setting. * * REDIS_FAILOVER_NONE: * The command is treated just like a write command, and will only be executed * against the known master for this slot. * REDIS_FAILOVER_ERROR: * If we're unable to communicate with this slot's master, we attempt the query * against any slaves (at random) that this master has. * REDIS_FAILOVER_DISTRIBUTE: * We pick at random from the master and any slaves it has. This option will * load balance between masters and slaves * REDIS_FAILOVER_DISTRIBUTE_SLAVES: * We pick at random from slave nodes of a given master. This option is * used to load balance read queries against N slaves. * * Once we are able to find a node we can write to, we check for MOVED or * ASKING redirection, such that the keyspace can be updated. */ static int cluster_sock_write(redisCluster *c, const char *cmd, size_t sz, int direct TSRMLS_DC) { redisClusterNode *seed_node; RedisSock *redis_sock; int failover, nomaster; /* First try the socket requested */ redis_sock = c->cmd_sock; /* Readonly is irrelevant if we're not configured to failover */ failover = c->readonly && c->failover != REDIS_FAILOVER_NONE ? c->failover : REDIS_FAILOVER_NONE; /* If in ASK redirection, get/create the node for that host:port, otherwise * just use the command socket. */ if(c->redir_type == REDIR_ASK) { redis_sock = cluster_get_asking_sock(c TSRMLS_CC); if(cluster_send_asking(redis_sock TSRMLS_CC)<0) { return -1; } } /* Attempt to send our command payload to the cluster. If we're not set up * to failover, just try the master. If we're configured to failover on * error, try the master and then fall back to any slaves. When we're set * up to distribute the commands, try to write to any node on this slot * at random. */ if (failover == REDIS_FAILOVER_NONE) { /* Success if we can send our payload to the master */ CLUSTER_LAZY_CONNECT(redis_sock); if (CLUSTER_SEND_PAYLOAD(redis_sock, cmd, sz)) return 0; } else if (failover == REDIS_FAILOVER_ERROR) { /* Try the master, then fall back to any slaves we may have */ CLUSTER_LAZY_CONNECT(redis_sock); if (CLUSTER_SEND_PAYLOAD(redis_sock, cmd, sz) || !cluster_dist_write(c, cmd, sz, 1 TSRMLS_CC)) return 0; } else { /* Include or exclude master node depending on failover option and * attempt to make our write */ nomaster = failover == REDIS_FAILOVER_DISTRIBUTE_SLAVES; if (!cluster_dist_write(c, cmd, sz, nomaster TSRMLS_CC)) { /* We were able to write to a master or slave at random */ return 0; } } /* Don't fall back if direct communication with this slot is required. */ if(direct) return -1; /* Fall back by attempting the request against every known node */ ZEND_HASH_FOREACH_PTR(c->nodes, seed_node) { /* Skip this node if it's the one that failed, or if it's a slave */ if (seed_node == NULL || seed_node->sock == redis_sock || seed_node->slave) continue; /* Connect to this node if we haven't already */ CLUSTER_LAZY_CONNECT(seed_node->sock); /* Attempt to write our request to this node */ if (CLUSTER_SEND_PAYLOAD(seed_node->sock, cmd, sz)) { c->cmd_slot = seed_node->slot; c->cmd_sock = seed_node->sock; return 0; } } ZEND_HASH_FOREACH_END(); /* We were unable to write to any node in our cluster */ return -1; } /* Helper to find if we've got a host:port mapped in our cluster nodes. */ static redisClusterNode *cluster_find_node(redisCluster *c, const char *host, unsigned short port) { int key_len; char key[1024]; key_len = snprintf(key,sizeof(key),"%s:%d", host, port); return zend_hash_str_find_ptr(c->nodes, key, key_len); } /* Provided a redisCluster object, the slot where we thought data was and * the slot where data was moved, update our node mapping */ static void cluster_update_slot(redisCluster *c TSRMLS_DC) { redisClusterNode *node; char key[1024]; size_t klen; /* Do we already have the new slot mapped */ if(c->master[c->redir_slot]) { /* No need to do anything if it's the same node */ if(!CLUSTER_REDIR_CMP(c)) { return; } /* Check to see if we have this new node mapped */ node = cluster_find_node(c, c->redir_host, c->redir_port); if(node) { /* Just point to this slot */ c->master[c->redir_slot] = node; } else { /* Create our node */ node = cluster_node_create(c, c->redir_host, c->redir_host_len, c->redir_port, c->redir_slot, 0); /* Our node is new, so keep track of it for cleanup */ klen = snprintf(key,sizeof(key),"%s:%ld",c->redir_host,c->redir_port); zend_hash_str_update_ptr(c->nodes, key, klen, node); /* Now point our slot at the node */ c->master[c->redir_slot] = node; } } else { /* Check to see if the ip and port are mapped */ node = cluster_find_node(c, c->redir_host, c->redir_port); if(!node) { node = cluster_node_create(c, c->redir_host, c->redir_host_len, c->redir_port, c->redir_slot, 0); } /* Map the slot to this node */ c->master[c->redir_slot] = node; } /* Update slot inside of node, so it can be found for command sending */ node->slot = c->redir_slot; /* Make sure we unflag this node as a slave, as Redis Cluster will only ever * direct us to master nodes. */ node->slave = 0; } /* Abort any transaction in process, by sending DISCARD to any nodes that * have active transactions in progress. If we can't send DISCARD, we need * to disconnect as it would leave us in an undefined state. */ PHP_REDIS_API int cluster_abort_exec(redisCluster *c TSRMLS_DC) { clusterFoldItem *fi = c->multi_head; /* Loop through our fold items */ while(fi) { if(SLOT_SOCK(c,fi->slot)->mode == MULTI) { if(cluster_send_discard(c, fi->slot TSRMLS_CC)<0) { cluster_disconnect(c TSRMLS_CC); return -1; } SLOT_SOCK(c,fi->slot)->mode = ATOMIC; SLOT_SOCK(c,fi->slot)->watching = 0; } fi = fi->next; } /* Update our overall cluster state */ c->flags->mode = ATOMIC; /* Success */ return 0; } /* Iterate through our slots, looking for the host/port in question. This * should perform well enough as in almost all situations, a few or a few * dozen servers will map all the slots */ PHP_REDIS_API short cluster_find_slot(redisCluster *c, const char *host, unsigned short port) { int i; for(i=0;imaster[i] && c->master[i]->sock && c->master[i]->sock->port == port && !strcasecmp(ZSTR_VAL(c->master[i]->sock->host), host)) { return i; } } // We didn't find it return -1; } /* Send a command to a specific slot */ PHP_REDIS_API int cluster_send_slot(redisCluster *c, short slot, char *cmd, int cmd_len, REDIS_REPLY_TYPE rtype TSRMLS_DC) { /* Point our cluster to this slot and it's socket */ c->cmd_slot = slot; c->cmd_sock = SLOT_SOCK(c, slot); /* Enable multi mode on this slot if we've been directed to but haven't * send it to this node yet */ if (c->flags->mode == MULTI && c->cmd_sock->mode != MULTI) { if (cluster_send_multi(c, slot TSRMLS_CC) == -1) { zend_throw_exception(redis_cluster_exception_ce, "Unable to enter MULTI mode on requested slot", 0 TSRMLS_CC); return -1; } } /* Try the slot */ if(cluster_sock_write(c, cmd, cmd_len, 1 TSRMLS_CC)==-1) { return -1; } /* Check our response */ if(cluster_check_response(c, &c->reply_type TSRMLS_CC)!=0 || (rtype != TYPE_EOF && rtype != c->reply_type)) return -1; /* Success */ return 0; } /* Send a command to given slot in our cluster. If we get a MOVED or ASK error * we attempt to send the command to the node as directed. */ PHP_REDIS_API short cluster_send_command(redisCluster *c, short slot, const char *cmd, int cmd_len TSRMLS_DC) { int resp, timedout=0; long msstart; /* Set the slot we're operating against as well as it's socket. These can * change during our request loop if we have a master failure and are * configured to fall back to slave nodes, or if we have to fall back to * a different slot due to no nodes serving this slot being reachable. */ c->cmd_slot = slot; c->cmd_sock = SLOT_SOCK(c, slot); /* Get the current time in milliseconds to handle any timeout */ msstart = mstime(); /* Our main cluster request/reply loop. This loop runs until we're able to * get a valid reply from a node, hit our "request" timeout, or enounter a * CLUSTERDOWN state from Redis Cluster. */ do { /* Send MULTI to the socket if we're in MULTI mode but haven't yet */ if (c->flags->mode == MULTI && CMD_SOCK(c)->mode != MULTI) { /* We have to fail if we can't send MULTI to the node */ if (cluster_send_multi(c, slot TSRMLS_CC) == -1) { zend_throw_exception(redis_cluster_exception_ce, "Unable to enter MULTI mode on requested slot", 0 TSRMLS_CC); return -1; } } /* Attempt to deliver our command to the node, and that failing, to any * node until we find one that is available. */ if (cluster_sock_write(c, cmd, cmd_len, 0 TSRMLS_CC) == -1) { /* We have to abort, as no nodes are reachable */ zend_throw_exception(redis_cluster_exception_ce, "Can't communicate with any node in the cluster", 0 TSRMLS_CC); return -1; } /* Now check the response from the node we queried. */ resp = cluster_check_response(c, &c->reply_type TSRMLS_CC); /* Handle MOVED or ASKING redirection */ if (resp == 1) { /* Abort if we're in a transaction as it will be invalid */ if (c->flags->mode == MULTI) { zend_throw_exception(redis_cluster_exception_ce, "Can't process MULTI sequence when cluster is resharding", 0 TSRMLS_CC); return -1; } /* Update mapping if the data has MOVED */ if (c->redir_type == REDIR_MOVED) { cluster_update_slot(c TSRMLS_CC); c->cmd_sock = SLOT_SOCK(c, slot); } } /* Figure out if we've timed out trying to read or write the data */ timedout = resp && c->waitms ? mstime() - msstart >= c->waitms : 0; } while(resp != 0 && !c->clusterdown && !timedout); // If we've detected the cluster is down, throw an exception if(c->clusterdown) { zend_throw_exception(redis_cluster_exception_ce, "The Redis Cluster is down (CLUSTERDOWN)", 0 TSRMLS_CC); return -1; } else if (timedout) { zend_throw_exception(redis_cluster_exception_ce, "Timed out attempting to find data in the correct node!", 0 TSRMLS_CC); } /* Clear redirection flag */ c->redir_type = REDIR_NONE; // Success, return the slot where data exists. return 0; } /* RedisCluster response handlers. These methods all have the same prototype * and set the proper return value for the calling cluster method. These * methods will never be called in the case of a communication error when * we try to send the request to the Cluster *or* if a non MOVED or ASK * error is encountered, in which case our response processing macro will * short circuit and RETURN_FALSE, as the error will have already been * consumed. */ /* RAW bulk response handler */ PHP_REDIS_API void cluster_bulk_raw_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { char *resp; // Make sure we can read the response if(c->reply_type != TYPE_BULK || (resp = redis_sock_read_bulk_reply(c->cmd_sock, c->reply_len TSRMLS_CC))==NULL) { if(c->flags->mode != MULTI) { RETURN_FALSE; } else { add_next_index_bool(&c->multi_resp, 0); return; } } // Return our response raw CLUSTER_RETURN_STRING(c, resp, c->reply_len); efree(resp); } /* BULK response handler */ PHP_REDIS_API void cluster_bulk_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { char *resp; // Make sure we can read the response if(c->reply_type != TYPE_BULK || (resp = redis_sock_read_bulk_reply(c->cmd_sock, c->reply_len TSRMLS_CC))==NULL) { CLUSTER_RETURN_FALSE(c); } if (CLUSTER_IS_ATOMIC(c)) { if (!redis_unserialize(c->flags, resp, c->reply_len, return_value TSRMLS_CC)) { CLUSTER_RETURN_STRING(c, resp, c->reply_len); } } else { zval zv, *z = &zv; if (redis_unserialize(c->flags, resp, c->reply_len, z TSRMLS_CC)) { #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z); *z = zv; #endif add_next_index_zval(&c->multi_resp, z); } else { add_next_index_stringl(&c->multi_resp, resp, c->reply_len); } } efree(resp); } /* Bulk response where we expect a double */ PHP_REDIS_API void cluster_dbl_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { char *resp; double dbl; // Make sure we can read the response if(c->reply_type != TYPE_BULK || (resp = redis_sock_read_bulk_reply(c->cmd_sock, c->reply_len TSRMLS_CC))==NULL) { CLUSTER_RETURN_FALSE(c); } // Convert to double, free response dbl = atof(resp); efree(resp); CLUSTER_RETURN_DOUBLE(c, dbl); } /* A boolean response. If we get here, we've consumed the '+' reply * type and will now just verify we can read the OK */ PHP_REDIS_API void cluster_bool_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { // Check that we have +OK if(c->reply_type != TYPE_LINE || c->reply_len != 2 || c->line_reply[0] != 'O' || c->line_reply[1] != 'K') { CLUSTER_RETURN_FALSE(c); } CLUSTER_RETURN_BOOL(c, 1); } /* Boolean response, specialized for PING */ PHP_REDIS_API void cluster_ping_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { if(c->reply_type != TYPE_LINE || c->reply_len != 4 || memcmp(c->line_reply,"PONG",sizeof("PONG")-1)) { CLUSTER_RETURN_FALSE(c); } CLUSTER_RETURN_BOOL(c, 1); } /* 1 or 0 response, for things like SETNX */ PHP_REDIS_API void cluster_1_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { // Validate our reply type, and check for a zero if(c->reply_type != TYPE_INT || c->reply_len == 0) { CLUSTER_RETURN_FALSE(c); } CLUSTER_RETURN_BOOL(c, 1); } /* Generic integer response */ PHP_REDIS_API void cluster_long_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { if(c->reply_type != TYPE_INT) { CLUSTER_RETURN_FALSE(c); } CLUSTER_RETURN_LONG(c, c->reply_len); } /* TYPE response handler */ PHP_REDIS_API void cluster_type_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { // Make sure we got the right kind of response if(c->reply_type != TYPE_LINE) { CLUSTER_RETURN_FALSE(c); } // Switch on the type if(strncmp (c->line_reply, "string", 6)==0) { CLUSTER_RETURN_LONG(c, REDIS_STRING); } else if (strncmp(c->line_reply, "set", 3)==0) { CLUSTER_RETURN_LONG(c, REDIS_SET); } else if (strncmp(c->line_reply, "list", 4)==0) { CLUSTER_RETURN_LONG(c, REDIS_LIST); } else if (strncmp(c->line_reply, "hash", 4)==0) { CLUSTER_RETURN_LONG(c, REDIS_HASH); } else if (strncmp(c->line_reply, "zset", 4)==0) { CLUSTER_RETURN_LONG(c, REDIS_ZSET); } else { CLUSTER_RETURN_LONG(c, REDIS_NOT_FOUND); } } /* SUBSCRIBE/PSCUBSCRIBE handler */ PHP_REDIS_API void cluster_sub_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { subscribeContext *sctx = (subscribeContext*)ctx; zval z_tab, *z_tmp; int pull=0; // Consume each MULTI BULK response (one per channel/pattern) while(sctx->argc--) { if (!cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, pull, mbulk_resp_loop_raw, &z_tab) ) { efree(sctx); RETURN_FALSE; } if ((z_tmp = zend_hash_index_find(Z_ARRVAL(z_tab), 0)) == NULL || strcasecmp(Z_STRVAL_P(z_tmp), sctx->kw) != 0 ) { zval_dtor(&z_tab); efree(sctx); RETURN_FALSE; } zval_dtor(&z_tab); pull = 1; } // Set up our callback pointers #if (PHP_MAJOR_VERSION < 7) zval *z_ret, **z_args[4]; sctx->cb.retval_ptr_ptr = &z_ret; #else zval z_ret, z_args[4]; sctx->cb.retval = &z_ret; #endif sctx->cb.params = z_args; sctx->cb.no_separation = 0; /* We're in a subscribe loop */ c->subscribed_slot = c->cmd_slot; /* Multibulk response, {[pattern], type, channel, payload} */ while(1) { /* Arguments */ zval *z_type, *z_chan, *z_pat = NULL, *z_data; int tab_idx=1, is_pmsg; // Get the next subscribe response if (!cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, 1, mbulk_resp_loop, &z_tab) || (z_type = zend_hash_index_find(Z_ARRVAL(z_tab), 0)) == NULL ) { break; } // Make sure we have a message or pmessage if (!strncmp(Z_STRVAL_P(z_type), "message", 7) || !strncmp(Z_STRVAL_P(z_type), "pmessage", 8) ) { is_pmsg = *Z_STRVAL_P(z_type) == 'p'; } else { zval_dtor(&z_tab); continue; } if (is_pmsg && (z_pat = zend_hash_index_find(Z_ARRVAL(z_tab), tab_idx++)) == NULL) { break; } // Extract channel and data if ((z_chan = zend_hash_index_find(Z_ARRVAL(z_tab), tab_idx++)) == NULL || (z_data = zend_hash_index_find(Z_ARRVAL(z_tab), tab_idx++)) == NULL ) { break; } // Always pass our object through #if (PHP_MAJOR_VERSION < 7) z_args[0] = &getThis(); // Set up calbacks depending on type if(is_pmsg) { z_args[1] = &z_pat; z_args[2] = &z_chan; z_args[3] = &z_data; } else { z_args[1] = &z_chan; z_args[2] = &z_data; } #else z_args[0] = *getThis(); // Set up calbacks depending on type if(is_pmsg) { z_args[1] = *z_pat; z_args[2] = *z_chan; z_args[3] = *z_data; } else { z_args[1] = *z_chan; z_args[2] = *z_data; } #endif // Set arg count sctx->cb.param_count = tab_idx; // Execute our callback if(zend_call_function(&(sctx->cb), &(sctx->cb_cache) TSRMLS_CC)!= SUCCESS) { break; } // If we have a return value, free it zval_ptr_dtor(&z_ret); zval_dtor(&z_tab); } // We're no longer subscribing, due to an error c->subscribed_slot = -1; // Cleanup zval_dtor(&z_tab); efree(sctx); // Failure RETURN_FALSE; } /* UNSUBSCRIBE/PUNSUBSCRIBE */ PHP_REDIS_API void cluster_unsub_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { subscribeContext *sctx = (subscribeContext*)ctx; zval z_tab, *z_chan, *z_flag; int pull = 0, argc = sctx->argc; efree(sctx); array_init(return_value); // Consume each response while(argc--) { // Fail if we didn't get an array or can't find index 1 if (!cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, pull, mbulk_resp_loop_raw, &z_tab) || (z_chan = zend_hash_index_find(Z_ARRVAL(z_tab), 1)) == NULL ) { zval_dtor(&z_tab); zval_dtor(return_value); RETURN_FALSE; } // Find the flag for this channel/pattern if ((z_flag = zend_hash_index_find(Z_ARRVAL(z_tab), 2)) == NULL || Z_STRLEN_P(z_flag) != 2 ) { zval_dtor(&z_tab); zval_dtor(return_value); RETURN_FALSE; } // Redis will give us either :1 or :0 here char *flag = Z_STRVAL_P(z_flag); // Add result add_assoc_bool(return_value, Z_STRVAL_P(z_chan), flag[1]=='1'); zval_dtor(&z_tab); pull = 1; } } /* Recursive MULTI BULK -> PHP style response handling */ static void cluster_mbulk_variant_resp(clusterReply *r, zval *z_ret) { zval zv, *z_sub_ele = &zv; int i; switch(r->type) { case TYPE_INT: add_next_index_long(z_ret, r->integer); break; case TYPE_LINE: add_next_index_bool(z_ret, 1); break; case TYPE_BULK: if (r->len > -1) { add_next_index_stringl(z_ret, r->str, r->len); efree(r->str); } else { add_next_index_null(z_ret); } break; case TYPE_MULTIBULK: #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_sub_ele); #endif array_init(z_sub_ele); for(i=0;ielements;i++) { cluster_mbulk_variant_resp(r->element[i], z_sub_ele); } add_next_index_zval(z_ret, z_sub_ele); break; default: add_next_index_bool(z_ret, 0); break; } } /* Variant response handling, for things like EVAL and various other responses * where we just map the replies from Redis type values to PHP ones directly. */ PHP_REDIS_API void cluster_variant_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { clusterReply *r; zval zv, *z_arr = &zv; int i; // Make sure we can read it if((r = cluster_read_resp(c TSRMLS_CC))==NULL) { CLUSTER_RETURN_FALSE(c); } // Handle ATOMIC vs. MULTI mode in a seperate switch if(CLUSTER_IS_ATOMIC(c)) { switch(r->type) { case TYPE_INT: RETVAL_LONG(r->integer); break; case TYPE_ERR: RETVAL_FALSE; break; case TYPE_LINE: RETVAL_TRUE; break; case TYPE_BULK: if (r->len < 0) { RETVAL_NULL(); } else { RETVAL_STRINGL(r->str, r->len); } break; case TYPE_MULTIBULK: array_init(z_arr); for(i=0;ielements;i++) { cluster_mbulk_variant_resp(r->element[i], z_arr); } RETVAL_ZVAL(z_arr, 1, 0); break; default: RETVAL_FALSE; break; } } else { switch(r->type) { case TYPE_INT: add_next_index_long(&c->multi_resp, r->integer); break; case TYPE_ERR: add_next_index_bool(&c->multi_resp, 0); break; case TYPE_LINE: add_next_index_bool(&c->multi_resp, 1); break; case TYPE_BULK: if (r->len < 0) { add_next_index_null(&c->multi_resp); } else { add_next_index_stringl(&c->multi_resp, r->str, r->len); efree(r->str); } break; case TYPE_MULTIBULK: cluster_mbulk_variant_resp(r, &c->multi_resp); break; default: add_next_index_bool(&c->multi_resp, 0); break; } } // Free our response structs, but not allocated data itself cluster_free_reply(r, 0); } /* Generic MULTI BULK response processor */ PHP_REDIS_API void cluster_gen_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, mbulk_cb cb, void *ctx) { zval zv, *z_result = &zv; /* Return FALSE if we didn't get a multi-bulk response */ if (c->reply_type != TYPE_MULTIBULK) { CLUSTER_RETURN_FALSE(c); } /* Allocate our array */ #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_result); #endif array_init(z_result); /* Consume replies as long as there are more than zero */ if (c->reply_len > 0) { /* Push serialization settings from the cluster into our socket */ c->cmd_sock->serializer = c->flags->serializer; /* Call our specified callback */ if (cb(c->cmd_sock, z_result, c->reply_len, ctx TSRMLS_CC)==FAILURE) { zval_dtor(z_result); #if (PHP_MAJOR_VERSION < 7) efree(z_result); #endif CLUSTER_RETURN_FALSE(c); } } // Success, make this array our return value if(CLUSTER_IS_ATOMIC(c)) { RETVAL_ZVAL(z_result, 0, 1); } else { add_next_index_zval(&c->multi_resp, z_result); } } /* HSCAN, SSCAN, ZSCAN */ PHP_REDIS_API int cluster_scan_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, REDIS_SCAN_TYPE type, long *it) { char *pit; // We always want to see a MULTIBULK response with two elements if(c->reply_type != TYPE_MULTIBULK || c->reply_len != 2) { return FAILURE; } // Read the BULK size if(cluster_check_response(c, &c->reply_type TSRMLS_CC),0 || c->reply_type != TYPE_BULK) { return FAILURE; } // Read the iterator if((pit = redis_sock_read_bulk_reply(c->cmd_sock,c->reply_len TSRMLS_CC))==NULL) { return FAILURE; } // Push the new iterator value to our caller *it = atol(pit); efree(pit); // We'll need another MULTIBULK response for the payload if(cluster_check_response(c, &c->reply_type TSRMLS_CC)<0) { return FAILURE; } // Use the proper response callback depending on scan type switch(type) { case TYPE_SCAN: cluster_mbulk_raw_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU,c,NULL); break; case TYPE_SSCAN: cluster_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU,c,NULL); break; case TYPE_HSCAN: cluster_mbulk_zipstr_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU,c,NULL); break; case TYPE_ZSCAN: cluster_mbulk_zipdbl_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU,c,NULL); break; default: return FAILURE; } // Success return SUCCESS; } /* INFO response */ PHP_REDIS_API void cluster_info_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { zval zv, *z_result = &zv; char *info; // Read our bulk response if((info = redis_sock_read_bulk_reply(c->cmd_sock, c->reply_len TSRMLS_CC))==NULL) { CLUSTER_RETURN_FALSE(c); } /* Parse response, free memory */ redis_parse_info_response(info, z_result); efree(info); // Return our array if(CLUSTER_IS_ATOMIC(c)) { RETVAL_ZVAL(z_result, 1, 0); } else { add_next_index_zval(&c->multi_resp, z_result); } } /* CLIENT LIST response */ PHP_REDIS_API void cluster_client_list_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { char *info; zval zv, *z_result = &zv; /* Read the bulk response */ info = redis_sock_read_bulk_reply(c->cmd_sock, c->reply_len TSRMLS_CC); if (info == NULL) { CLUSTER_RETURN_FALSE(c); } #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z_result); #endif /* Parse it and free the bulk string */ redis_parse_client_list_response(info, z_result); efree(info); if (CLUSTER_IS_ATOMIC(c)) { RETVAL_ZVAL(z_result, 0, 1); } else { add_next_index_zval(&c->multi_resp, z_result); } } /* MULTI BULK response loop where we might pull the next one */ PHP_REDIS_API zval *cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, int pull, mbulk_cb cb, zval *z_ret) { ZVAL_NULL(z_ret); // Pull our next response if directed if(pull) { if(cluster_check_response(c, &c->reply_type TSRMLS_CC)<0) { return NULL; } } // Validate reply type and length if(c->reply_type != TYPE_MULTIBULK || c->reply_len == -1) { return NULL; } array_init(z_ret); // Call our callback if(cb(c->cmd_sock, z_ret, c->reply_len, NULL TSRMLS_CC)==FAILURE) { zval_dtor(z_ret); return NULL; } return z_ret; } /* MULTI MULTI BULK reply (for EXEC) */ PHP_REDIS_API void cluster_multi_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { zval *multi_resp = &c->multi_resp; array_init(multi_resp); clusterFoldItem *fi = c->multi_head; while(fi) { /* Make sure our transaction didn't fail here */ if (c->multi_len[fi->slot] > -1) { /* Set the slot where we should look for responses. We don't allow * failover inside a transaction, so it will be the master we have * mapped. */ c->cmd_slot = fi->slot; c->cmd_sock = SLOT_SOCK(c, fi->slot); if(cluster_check_response(c, &c->reply_type TSRMLS_CC)<0) { zval_dtor(multi_resp); RETURN_FALSE; } fi->callback(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, fi->ctx); } else { /* Just add false */ add_next_index_bool(multi_resp, 0); } fi = fi->next; } // Set our return array zval_dtor(return_value); RETVAL_ZVAL(multi_resp, 0, 1); } /* Generic handler for MGET */ PHP_REDIS_API void cluster_mbulk_mget_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { clusterMultiCtx *mctx = (clusterMultiCtx*)ctx; /* Protect against an invalid response type, -1 response length, and failure * to consume the responses. */ c->cmd_sock->serializer = c->flags->serializer; short fail = c->reply_type != TYPE_MULTIBULK || c->reply_len == -1 || mbulk_resp_loop(c->cmd_sock, mctx->z_multi, c->reply_len, NULL TSRMLS_CC)==FAILURE; // If we had a failure, pad results with FALSE to indicate failure. Non // existant keys (e.g. for MGET will come back as NULL) if(fail) { while(mctx->count--) { add_next_index_bool(mctx->z_multi, 0); } } // If this is the tail of our multi command, we can set our returns if(mctx->last) { if(CLUSTER_IS_ATOMIC(c)) { RETVAL_ZVAL(mctx->z_multi, 0, 1); } else { add_next_index_zval(&c->multi_resp, mctx->z_multi); } } // Clean up this context item efree(mctx); } /* Handler for MSETNX */ PHP_REDIS_API void cluster_msetnx_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { clusterMultiCtx *mctx = (clusterMultiCtx*)ctx; int real_argc = mctx->count/2; // Protect against an invalid response type if(c->reply_type != TYPE_INT) { php_error_docref(0 TSRMLS_CC, E_WARNING, "Invalid response type for MSETNX"); while(real_argc--) { add_next_index_bool(mctx->z_multi, 0); } return; } // Response will be 1/0 per key, so the client can match them up while(real_argc--) { add_next_index_long(mctx->z_multi, c->reply_len); } // Set return value if it's our last response if(mctx->last) { if(CLUSTER_IS_ATOMIC(c)) { RETVAL_ZVAL(mctx->z_multi, 0, 1); } else { add_next_index_zval(&c->multi_resp, mctx->z_multi); } } // Free multi context efree(mctx); } /* Handler for DEL */ PHP_REDIS_API void cluster_del_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { clusterMultiCtx *mctx = (clusterMultiCtx*)ctx; // If we get an invalid reply, inform the client if(c->reply_type != TYPE_INT) { php_error_docref(0 TSRMLS_CC, E_WARNING, "Invalid reply type returned for DEL command"); efree(mctx); return; } // Increment by the number of keys deleted Z_LVAL_P(mctx->z_multi) += c->reply_len; if(mctx->last) { if(CLUSTER_IS_ATOMIC(c)) { ZVAL_LONG(return_value, Z_LVAL_P(mctx->z_multi)); } else { add_next_index_long(&c->multi_resp, Z_LVAL_P(mctx->z_multi)); } efree(mctx->z_multi); } efree(ctx); } /* Handler for MSET */ PHP_REDIS_API void cluster_mset_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { clusterMultiCtx *mctx = (clusterMultiCtx*)ctx; // If we get an invalid reply type something very wrong has happened, // and we have to abort. if(c->reply_type != TYPE_LINE) { php_error_docref(0 TSRMLS_CC, E_ERROR, "Invalid reply type returned for MSET command"); zval_dtor(mctx->z_multi); efree(mctx->z_multi); efree(mctx); RETURN_FALSE; } // Set our return if it's the last call if(mctx->last) { if(CLUSTER_IS_ATOMIC(c)) { ZVAL_BOOL(return_value, zval_is_true(mctx->z_multi)); } else { add_next_index_bool(&c->multi_resp, zval_is_true(mctx->z_multi)); } efree(mctx->z_multi); } efree(mctx); } /* Raw MULTI BULK reply */ PHP_REDIS_API void cluster_mbulk_raw_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { cluster_gen_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, mbulk_resp_loop_raw, NULL); } /* Unserialize all the things */ PHP_REDIS_API void cluster_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { cluster_gen_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, mbulk_resp_loop, NULL); } /* For handling responses where we get key, value, key, value that * we will turn into key => value, key => value. */ PHP_REDIS_API void cluster_mbulk_zipstr_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { cluster_gen_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, mbulk_resp_loop_zipstr, NULL); } /* Handling key,value to key=>value where the values are doubles */ PHP_REDIS_API void cluster_mbulk_zipdbl_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { cluster_gen_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, mbulk_resp_loop_zipdbl, NULL); } /* Associate multi bulk response (for HMGET really) */ PHP_REDIS_API void cluster_mbulk_assoc_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { cluster_gen_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, mbulk_resp_loop_assoc, ctx); } /* * Various MULTI BULK reply callback functions */ /* MULTI BULK response where we don't touch the values (e.g. KEYS) */ int mbulk_resp_loop_raw(RedisSock *redis_sock, zval *z_result, long long count, void *ctx TSRMLS_DC) { char *line; int line_len; // Iterate over the number we have while(count--) { // Read the line, which should never come back null line = redis_sock_read(redis_sock, &line_len TSRMLS_CC); if(line == NULL) return FAILURE; // Add to our result array add_next_index_stringl(z_result, line, line_len); efree(line); } // Success! return SUCCESS; } /* MULTI BULK response where we unserialize everything */ int mbulk_resp_loop(RedisSock *redis_sock, zval *z_result, long long count, void *ctx TSRMLS_DC) { char *line; int line_len; /* Iterate over the lines we have to process */ while(count--) { /* Read our line */ line = redis_sock_read(redis_sock, &line_len TSRMLS_CC); if (line != NULL) { zval zv, *z = &zv; if (redis_unserialize(redis_sock, line, line_len, z TSRMLS_CC)) { #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z); *z = zv; #endif add_next_index_zval(z_result, z); } else { add_next_index_stringl(z_result, line, line_len); } efree(line); } else { if (line) efree(line); add_next_index_bool(z_result, 0); } } return SUCCESS; } /* MULTI BULK response where we turn key1,value1 into key1=>value1 */ int mbulk_resp_loop_zipstr(RedisSock *redis_sock, zval *z_result, long long count, void *ctx TSRMLS_DC) { char *line, *key = NULL; int line_len, key_len = 0; long long idx = 0; // Our count wil need to be divisible by 2 if(count % 2 != 0) { return -1; } // Iterate through our elements while(count--) { // Grab our line, bomb out on failure line = redis_sock_read(redis_sock, &line_len TSRMLS_CC); if(!line) return -1; if(idx++ % 2 == 0) { // Save our key and length key = line; key_len = line_len; } else { /* Attempt serialization */ zval zv, *z = &zv; if (redis_unserialize(redis_sock, line, line_len, z TSRMLS_CC)) { #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z); *z = zv; #endif add_assoc_zval(z_result, key, z); } else { add_assoc_stringl_ex(z_result, key, key_len, line, line_len); } efree(line); efree(key); } } return SUCCESS; } /* MULTI BULK loop processor where we expect key,score key, score */ int mbulk_resp_loop_zipdbl(RedisSock *redis_sock, zval *z_result, long long count, void *ctx TSRMLS_DC) { char *line, *key = NULL; int line_len, key_len = 0; long long idx = 0; // Our context will need to be divisible by 2 if(count %2 != 0) { return -1; } // While we have elements while(count--) { line = redis_sock_read(redis_sock, &line_len TSRMLS_CC); if (line != NULL) { if(idx++ % 2 == 0) { key = line; key_len = line_len; } else { zval zv, *z = &zv; if (redis_unserialize(redis_sock,key,key_len, z TSRMLS_CC)) { zend_string *zstr = zval_get_string(z); add_assoc_double_ex(z_result, ZSTR_VAL(zstr), ZSTR_LEN(zstr), atof(line)); zend_string_release(zstr); zval_dtor(z); } else { add_assoc_double_ex(z_result, key, key_len, atof(line)); } /* Free our key and line */ efree(key); efree(line); } } } return SUCCESS; } /* MULTI BULK where we're passed the keys, and we attach vals */ int mbulk_resp_loop_assoc(RedisSock *redis_sock, zval *z_result, long long count, void *ctx TSRMLS_DC) { char *line; int line_len,i=0; zval *z_keys = ctx; // Loop while we've got replies while(count--) { line = redis_sock_read(redis_sock, &line_len TSRMLS_CC); if(line != NULL) { zval zv, *z = &zv; if (redis_unserialize(redis_sock, line, line_len, z TSRMLS_CC)) { #if (PHP_MAJOR_VERSION < 7) MAKE_STD_ZVAL(z); *z = zv; #endif add_assoc_zval_ex(z_result,Z_STRVAL(z_keys[i]), Z_STRLEN(z_keys[i]), z); } else { add_assoc_stringl_ex(z_result, Z_STRVAL(z_keys[i]), Z_STRLEN(z_keys[i]), line, line_len); } efree(line); } else { add_assoc_bool_ex(z_result, Z_STRVAL(z_keys[i]), Z_STRLEN(z_keys[i]), 0); } // Clean up key context zval_dtor(&z_keys[i]); // Move to the next key i++; } // Clean up our keys overall efree(z_keys); // Success! return SUCCESS; } /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */ redis-3.1.6/cluster_library.h0000644000175000000120000004013013223116600016754 0ustar pyatsukhnenkowheel#ifndef _PHPREDIS_CLUSTER_LIBRARY_H #define _PHPREDIS_CLUSTER_LIBRARY_H #include "common.h" #ifdef ZTS #include "TSRM.h" #endif /* Redis cluster hash slots and N-1 which we'll use to find it */ #define REDIS_CLUSTER_SLOTS 16384 #define REDIS_CLUSTER_MOD (REDIS_CLUSTER_SLOTS-1) /* Complete representation for various commands in RESP */ #define RESP_MULTI_CMD "*1\r\n$5\r\nMULTI\r\n" #define RESP_EXEC_CMD "*1\r\n$4\r\nEXEC\r\n" #define RESP_DISCARD_CMD "*1\r\n$7\r\nDISCARD\r\n" #define RESP_UNWATCH_CMD "*1\r\n$7\r\nUNWATCH\r\n" #define RESP_CLUSTER_SLOTS_CMD "*2\r\n$7\r\nCLUSTER\r\n$5\r\nSLOTS\r\n" #define RESP_ASKING_CMD "*1\r\n$6\r\nASKING\r\n" #define RESP_READONLY_CMD "*1\r\n$8\r\nREADONLY\r\n" #define RESP_READWRITE_CMD "*1\r\n$9\r\nREADWRITE\r\n" #define RESP_READONLY_CMD_LEN (sizeof(RESP_READONLY_CMD)-1) /* MOVED/ASK comparison macros */ #define IS_MOVED(p) (p[0]=='M' && p[1]=='O' && p[2]=='V' && p[3]=='E' && \ p[4]=='D' && p[5]==' ') #define IS_ASK(p) (p[0]=='A' && p[1]=='S' && p[3]=='K' && p[4]==' ') /* MOVED/ASK lengths */ #define MOVED_LEN (sizeof("MOVED ")-1) #define ASK_LEN (sizeof("ASK ")-1) /* Initial allocation size for key distribution container */ #define CLUSTER_KEYDIST_ALLOC 8 /* Macros to access nodes, sockets, and streams for a given slot */ #define SLOT(c,s) (c->master[s]) #define SLOT_SOCK(c,s) (SLOT(c,s)->sock) #define SLOT_STREAM(c,s) (SLOT_SOCK(c,s)->stream) #define SLOT_SLAVES(c,s) (c->master[s]->slaves) /* Macros to access socket and stream for the node we're communicating with */ #define CMD_SOCK(c) (c->cmd_sock) #define CMD_STREAM(c) (c->cmd_sock->stream) /* Compare redirection slot information with what we have */ #define CLUSTER_REDIR_CMP(c) \ (SLOT_SOCK(c,c->redir_slot)->port != c->redir_port || \ ZSTR_LEN(SLOT_SOCK(c,c->redir_slot)->host) != c->redir_host_len || \ memcmp(ZSTR_VAL(SLOT_SOCK(c,c->redir_slot)->host),c->redir_host,c->redir_host_len)) /* Lazy connect logic */ #define CLUSTER_LAZY_CONNECT(s) \ if(s->lazy_connect) { \ s->lazy_connect = 0; \ redis_sock_server_open(s TSRMLS_CC); \ } /* Clear out our "last error" */ #define CLUSTER_CLEAR_ERROR(c) do { \ if (c->err) { \ zend_string_release(c->err); \ c->err = NULL; \ } \ c->clusterdown = 0; \ } while (0) /* Protected sending of data down the wire to a RedisSock->stream */ #define CLUSTER_SEND_PAYLOAD(sock, buf, len) \ (sock && sock->stream && !redis_check_eof(sock, 1 TSRMLS_CC) && \ php_stream_write(sock->stream, buf, len)==len) /* Macro to read our reply type character */ #define CLUSTER_VALIDATE_REPLY_TYPE(sock, type) \ (redis_check_eof(sock, 1 TSRMLS_CC) == 0 && \ (php_stream_getc(sock->stream) == type)) /* Reset our last single line reply buffer and length */ #define CLUSTER_CLEAR_REPLY(c) \ *c->line_reply = '\0'; c->reply_len = 0; /* Helper to determine if we're in MULTI mode */ #define CLUSTER_IS_ATOMIC(c) (c->flags->mode != MULTI) /* Helper that either returns false or adds false in multi mode */ #define CLUSTER_RETURN_FALSE(c) \ if(CLUSTER_IS_ATOMIC(c)) { \ RETURN_FALSE; \ } else { \ add_next_index_bool(&c->multi_resp, 0); \ return; \ } /* Helper to either return a bool value or add it to MULTI response */ #define CLUSTER_RETURN_BOOL(c, b) \ if(CLUSTER_IS_ATOMIC(c)) { \ if(b==1) {\ RETURN_TRUE; \ } else {\ RETURN_FALSE; \ } \ } else { \ add_next_index_bool(&c->multi_resp, b); \ } /* Helper to respond with a double or add it to our MULTI response */ #define CLUSTER_RETURN_DOUBLE(c, d) \ if(CLUSTER_IS_ATOMIC(c)) { \ RETURN_DOUBLE(d); \ } else { \ add_next_index_double(&c->multi_resp, d); \ } /* Helper to return a string value */ #define CLUSTER_RETURN_STRING(c, str, len) \ if(CLUSTER_IS_ATOMIC(c)) { \ RETVAL_STRINGL(str, len); \ } else { \ add_next_index_stringl(&c->multi_resp, str, len); \ } \ /* Return a LONG value */ #define CLUSTER_RETURN_LONG(c, val) \ if(CLUSTER_IS_ATOMIC(c)) { \ RETURN_LONG(val); \ } else { \ add_next_index_long(&c->multi_resp, val); \ } /* Macro to clear out a clusterMultiCmd structure */ #define CLUSTER_MULTI_CLEAR(mc) \ mc->cmd.len = 0; \ mc->args.len = 0; \ mc->argc = 0; \ /* Initialzie a clusterMultiCmd with a keyword and length */ #define CLUSTER_MULTI_INIT(mc, keyword, keyword_len) \ mc.kw = keyword; \ mc.kw_len = keyword_len; \ /* Cluster redirection enum */ typedef enum CLUSTER_REDIR_TYPE { REDIR_NONE, REDIR_MOVED, REDIR_ASK } CLUSTER_REDIR_TYPE; /* MULTI BULK response callback typedef */ typedef int (*mbulk_cb)(RedisSock*,zval*,long long, void* TSRMLS_DC); /* Specific destructor to free a cluster object */ // void redis_destructor_redis_cluster(zend_resource *rsrc TSRMLS_DC); /* A Redis Cluster master node */ typedef struct redisClusterNode { /* Our Redis socket in question */ RedisSock *sock; /* A slot where one of these lives */ short slot; /* Is this a slave node */ unsigned short slave; /* A HashTable containing any slaves */ HashTable *slaves; } redisClusterNode; /* Forward declarations */ typedef struct clusterFoldItem clusterFoldItem; /* RedisCluster implementation structure */ typedef struct redisCluster { #if (PHP_MAJOR_VERSION < 7) zend_object std; #endif /* Timeout and read timeout (for normal operations) */ double timeout; double read_timeout; /* Are we using persistent connections */ int persistent; /* How long in milliseconds should we wait when being bounced around */ long waitms; /* Are we flagged as being in readonly mode, meaning we could fall back to * a given master's slave */ short readonly; /* RedisCluster failover options (never, on error, to load balance) */ short failover; /* Hash table of seed host/ports */ HashTable *seeds; /* RedisCluster masters, by direct slot */ redisClusterNode *master[REDIS_CLUSTER_SLOTS]; /* All RedisCluster objects we've created/are connected to */ HashTable *nodes; /* Transaction handling linked list, and where we are as we EXEC */ clusterFoldItem *multi_head; clusterFoldItem *multi_curr; /* When we issue EXEC to nodes, we need to keep track of how many replies * we have, as this can fail for various reasons (EXECABORT, watch, etc.) */ char multi_len[REDIS_CLUSTER_SLOTS]; /* Variable to store MULTI response */ zval multi_resp; /* Flag for when we get a CLUSTERDOWN error */ short clusterdown; /* The last ERROR we encountered */ zend_string *err; /* The slot our command is operating on, as well as it's socket */ unsigned short cmd_slot; RedisSock *cmd_sock; /* The slot where we're subscribed */ short subscribed_slot; /* One RedisSock struct for serialization and prefix information */ RedisSock *flags; /* The first line of our last reply, not including our reply type byte * or the trailing \r\n */ char line_reply[1024]; /* The last reply type and length or integer response we got */ REDIS_REPLY_TYPE reply_type; long long reply_len; /* Last MOVED or ASK redirection response information */ CLUSTER_REDIR_TYPE redir_type; char redir_host[255]; int redir_host_len; unsigned short redir_slot; unsigned short redir_port; #if (PHP_MAJOR_VERSION >= 7) /* Zend object handler */ zend_object std; #endif } redisCluster; /* RedisCluster response processing callback */ typedef void (*cluster_cb)(INTERNAL_FUNCTION_PARAMETERS, redisCluster*, void*); /* Context for processing transactions */ struct clusterFoldItem { /* Response processing callback */ cluster_cb callback; /* The actual socket where we send this request */ unsigned short slot; /* Any context we need to send to our callback */ void *ctx; /* Next item in our list */ struct clusterFoldItem *next; }; /* Key and value container, with info if they need freeing */ typedef struct clusterKeyVal { char *key, *val; int key_len, val_len; int key_free, val_free; } clusterKeyVal; /* Container to hold keys (and possibly values) for when we need to distribute * commands across more than 1 node (e.g. WATCH, MGET, MSET, etc) */ typedef struct clusterDistList { clusterKeyVal *entry; size_t len, size; } clusterDistList; /* Context for things like MGET/MSET/MSETNX. When executing in MULTI mode, * we'll want to re-integrate into one running array, except for the last * command execution, in which we'll want to return the value (or add it) */ typedef struct clusterMultiCtx { /* Our running array */ zval *z_multi; /* How many keys did we request for this bit */ int count; /* Is this the last entry */ short last; } clusterMultiCtx; /* Container for things like MGET, MSET, and MSETNX, which split the command * into a header and payload while aggregating to a specific slot. */ typedef struct clusterMultiCmd { /* Keyword and keyword length */ char *kw; int kw_len; /* Arguments in our payload */ int argc; /* The full command, built into cmd, and args as we aggregate */ smart_string cmd; smart_string args; } clusterMultiCmd; /* Hiredis like structure for processing any sort of reply Redis Cluster might * give us, including N level deep nested multi-bulk replies. Unlike hiredis * we don't encode errors, here as that's handled in the cluster structure. */ typedef struct clusterReply { REDIS_REPLY_TYPE type; /* Our reply type */ size_t integer; /* Integer reply */ long long len; /* Length of our string */ char *str; /* String reply */ size_t elements; /* Count of array elements */ struct clusterReply **element; /* Array elements */ } clusterReply; /* Direct variant response handler */ clusterReply *cluster_read_resp(redisCluster *c TSRMLS_DC); clusterReply *cluster_read_sock_resp(RedisSock *redis_sock, REDIS_REPLY_TYPE type, size_t reply_len TSRMLS_DC); void cluster_free_reply(clusterReply *reply, int free_data); /* Cluster distribution helpers for WATCH */ HashTable *cluster_dist_create(); void cluster_dist_free(HashTable *ht); int cluster_dist_add_key(redisCluster *c, HashTable *ht, char *key, strlen_t key_len, clusterKeyVal **kv); void cluster_dist_add_val(redisCluster *c, clusterKeyVal *kv, zval *val TSRMLS_DC); /* Aggregation for multi commands like MGET, MSET, and MSETNX */ void cluster_multi_init(clusterMultiCmd *mc, char *kw, int kw_len); void cluster_multi_free(clusterMultiCmd *mc); void cluster_multi_add(clusterMultiCmd *mc, char *data, int data_len); void cluster_multi_fini(clusterMultiCmd *mc); /* Hash a key to it's slot, using the Redis Cluster hash algorithm */ unsigned short cluster_hash_key_zval(zval *key); unsigned short cluster_hash_key(const char *key, int len); /* Get the current time in miliseconds */ long long mstime(void); PHP_REDIS_API short cluster_send_command(redisCluster *c, short slot, const char *cmd, int cmd_len TSRMLS_DC); PHP_REDIS_API void cluster_disconnect(redisCluster *c TSRMLS_DC); PHP_REDIS_API int cluster_send_exec(redisCluster *c, short slot TSRMLS_DC); PHP_REDIS_API int cluster_send_discard(redisCluster *c, short slot TSRMLS_DC); PHP_REDIS_API int cluster_abort_exec(redisCluster *c TSRMLS_DC); PHP_REDIS_API int cluster_reset_multi(redisCluster *c); PHP_REDIS_API short cluster_find_slot(redisCluster *c, const char *host, unsigned short port); PHP_REDIS_API int cluster_send_slot(redisCluster *c, short slot, char *cmd, int cmd_len, REDIS_REPLY_TYPE rtype TSRMLS_DC); PHP_REDIS_API redisCluster *cluster_create(double timeout, double read_timeout, int failover, int persistent); PHP_REDIS_API void cluster_free(redisCluster *c); PHP_REDIS_API int cluster_init_seeds(redisCluster *c, HashTable *ht_seeds); PHP_REDIS_API int cluster_map_keyspace(redisCluster *c TSRMLS_DC); PHP_REDIS_API void cluster_free_node(redisClusterNode *node); PHP_REDIS_API char **cluster_sock_read_multibulk_reply(RedisSock *redis_sock, int *len TSRMLS_DC); /* * Redis Cluster response handlers. Our response handlers generally take the * following form: * PHP_REDIS_API void handler(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, * void *ctx) * * Reply handlers are responsible for setting the PHP return value (either to * something valid, or FALSE in the case of some failures). */ PHP_REDIS_API void cluster_bool_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_ping_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_bulk_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_bulk_raw_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_dbl_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_1_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_long_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_type_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_sub_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_unsub_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); /* Generic/Variant handler for stuff like EVAL */ PHP_REDIS_API void cluster_variant_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); /* MULTI BULK response functions */ PHP_REDIS_API void cluster_gen_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, mbulk_cb func, void *ctx); PHP_REDIS_API void cluster_mbulk_raw_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_mbulk_zipstr_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_mbulk_zipdbl_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_mbulk_assoc_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_multi_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API zval *cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, int pull, mbulk_cb cb, zval *z_ret); /* Handlers for things like DEL/MGET/MSET/MSETNX */ PHP_REDIS_API void cluster_del_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_mbulk_mget_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_mset_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_msetnx_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); /* Response handler for ZSCAN, SSCAN, and HSCAN */ PHP_REDIS_API int cluster_scan_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, REDIS_SCAN_TYPE type, long *it); /* INFO response handler */ PHP_REDIS_API void cluster_info_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); /* CLIENT LIST response handler */ PHP_REDIS_API void cluster_client_list_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); /* MULTI BULK processing callbacks */ int mbulk_resp_loop(RedisSock *redis_sock, zval *z_result, long long count, void *ctx TSRMLS_DC); int mbulk_resp_loop_raw(RedisSock *redis_sock, zval *z_result, long long count, void *ctx TSRMLS_DC); int mbulk_resp_loop_zipstr(RedisSock *redis_sock, zval *z_result, long long count, void *ctx TSRMLS_DC); int mbulk_resp_loop_zipdbl(RedisSock *redis_sock, zval *z_result, long long count, void *ctx TSRMLS_DC); int mbulk_resp_loop_assoc(RedisSock *redis_sock, zval *z_result, long long count, void *ctx TSRMLS_DC); #endif /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */ redis-3.1.6/redis_commands.c0000644000175000000120000030132413223116600016536 0ustar pyatsukhnenkowheel/* -*- Mode: C; tab-width: 4 -*- */ /* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2009 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Original Author: Michael Grunder | +----------------------------------------------------------------------+ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "redis_commands.h" #include /* Local passthrough macro for command construction. Given that these methods * are generic (so they work whether the caller is Redis or RedisCluster) we * will always have redis_sock, slot*, and TSRMLS_CC */ #define REDIS_CMD_SPPRINTF(ret, kw, fmt, ...) \ redis_spprintf(redis_sock, slot TSRMLS_CC, ret, kw, fmt, ##__VA_ARGS__) /* Generic commands based on method signature and what kind of things we're * processing. Lots of Redis commands take something like key, value, or * key, value long. Each unique signature like this is written only once */ /* A command that takes no arguments */ int redis_empty_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, ""); return SUCCESS; } /* Helper to construct a raw command. Given that the cluster and non cluster * versions are different (RedisCluster needs an additional argument to direct * the command) we take the start of our array and count */ int redis_build_raw_cmd(zval *z_args, int argc, char **cmd, int *cmd_len TSRMLS_DC) { smart_string cmdstr = {0}; int i; /* Make sure our first argument is a string */ if (Z_TYPE(z_args[0]) != IS_STRING) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "When sending a 'raw' command, the first argument must be a string!"); return FAILURE; } /* Initialize our command string */ redis_cmd_init_sstr(&cmdstr, argc-1, Z_STRVAL(z_args[0]), Z_STRLEN(z_args[0])); for (i = 1; i < argc; i++) { switch (Z_TYPE(z_args[i])) { case IS_STRING: redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[i]), Z_STRLEN(z_args[i])); break; case IS_LONG: redis_cmd_append_sstr_long(&cmdstr,Z_LVAL(z_args[i])); break; case IS_DOUBLE: redis_cmd_append_sstr_dbl(&cmdstr,Z_DVAL(z_args[i])); break; default: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Raw command arguments must be scalar values!"); efree(cmdstr.c); return FAILURE; } } /* Push command and length to caller */ *cmd = cmdstr.c; *cmd_len = cmdstr.len; return SUCCESS; } /* Generic command where we just take a string and do nothing to it*/ int redis_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { char *arg; strlen_t arg_len; // Parse args if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) ==FAILURE) { return FAILURE; } // Build the command without molesting the string *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "s", arg, arg_len); return SUCCESS; } /* Key, long, zval (serialized) */ int redis_key_long_val_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key = NULL; strlen_t key_len; zend_long expire; zval *z_val; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "slz", &key, &key_len, &expire, &z_val)==FAILURE) { return FAILURE; } *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "klv", key, key_len, expire, z_val); return SUCCESS; } /* Generic key, long, string (unserialized) */ int redis_key_long_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key, *val; strlen_t key_len, val_len; zend_long lval; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sls", &key, &key_len, &lval, &val, &val_len)==FAILURE) { return FAILURE; } *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kds", key, key_len, (int)lval, val, val_len); return SUCCESS; } /* Generic command construction when we just take a key and value */ int redis_kv_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key; strlen_t key_len; zval *z_val; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &key, &key_len, &z_val)==FAILURE) { return FAILURE; } *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kv", key, key_len, z_val); return SUCCESS; } /* Generic command that takes a key and an unserialized value */ int redis_key_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key, *val; strlen_t key_len, val_len; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &key, &key_len, &val, &val_len)==FAILURE) { return FAILURE; } // Construct command *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ks", key, key_len, val, val_len); return SUCCESS; } /* Key, string, string without serialization (ZCOUNT, ZREMRANGEBYSCORE) */ int redis_key_str_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { char *k, *v1, *v2; strlen_t klen, v1len, v2len; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss", &k, &klen, &v1, &v1len, &v2, &v2len)==FAILURE) { return FAILURE; } *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", k, klen, v1, v1len, v2, v2len); // Success! return SUCCESS; } /* Generic command that takes two keys */ int redis_key_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { char *k1, *k2; strlen_t k1len, k2len; int k1free, k2free; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &k1, &k1len, &k2, &k2len)==FAILURE) { return FAILURE; } // Prefix both keys k1free = redis_key_prefix(redis_sock, &k1, &k1len); k2free = redis_key_prefix(redis_sock, &k2, &k2len); // If a slot is requested, we can test that they hash the same if(slot) { // Slots where these keys resolve short slot1 = cluster_hash_key(k1, k1len); short slot2 = cluster_hash_key(k2, k2len); // Check if Redis would give us a CROSSLOT error if(slot1 != slot2) { php_error_docref(0 TSRMLS_CC, E_WARNING, "Keys don't hash to the same slot"); if(k1free) efree(k1); if(k2free) efree(k2); return FAILURE; } // They're both the same *slot = slot1; } /* Send keys as normal strings because we manually prefixed to check against * cross slot error. */ *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ss", k1, k1len, k2, k2len); /* Clean keys up if we prefixed */ if (k1free) efree(k1); if (k2free) efree(k2); return SUCCESS; } /* Generic command construction where we take a key and a long */ int redis_key_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key; strlen_t keylen; zend_long lval; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl", &key, &keylen, &lval) ==FAILURE) { return FAILURE; } *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kl", key, keylen, lval); // Success! return SUCCESS; } /* key, long, long */ int redis_key_long_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key; strlen_t key_len; zend_long val1, val2; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sll", &key, &key_len, &val1, &val2)==FAILURE) { return FAILURE; } *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kll", key, key_len, val1, val2); return SUCCESS; } /* Generic command where we take a single key */ int redis_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key; strlen_t key_len; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len) ==FAILURE) { return FAILURE; } *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "k", key, key_len); return SUCCESS; } /* Generic command where we take a key and a double */ int redis_key_dbl_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key; strlen_t key_len; double val; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sd", &key, &key_len, &val)==FAILURE) { return FAILURE; } *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kf", key, key_len, val); return SUCCESS; } /* Generic to construct SCAN and variant commands */ int redis_fmt_scan_cmd(char **cmd, REDIS_SCAN_TYPE type, char *key, int key_len, long it, char *pat, int pat_len, long count) { static char *kw[] = {"SCAN","SSCAN","HSCAN","ZSCAN"}; int argc; smart_string cmdstr = {0}; // Figure out our argument count argc = 1 + (type!=TYPE_SCAN) + (pat_len>0?2:0) + (count>0?2:0); redis_cmd_init_sstr(&cmdstr, argc, kw[type], strlen(kw[type])); // Append our key if it's not a regular SCAN command if(type != TYPE_SCAN) { redis_cmd_append_sstr(&cmdstr, key, key_len); } // Append cursor redis_cmd_append_sstr_long(&cmdstr, it); // Append count if we've got one if(count) { redis_cmd_append_sstr(&cmdstr,"COUNT",sizeof("COUNT")-1); redis_cmd_append_sstr_long(&cmdstr, count); } // Append pattern if we've got one if(pat_len) { redis_cmd_append_sstr(&cmdstr,"MATCH",sizeof("MATCH")-1); redis_cmd_append_sstr(&cmdstr,pat,pat_len); } // Push command to the caller, return length *cmd = cmdstr.c; return cmdstr.len; } /* ZRANGE/ZREVRANGE */ int redis_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, int *withscores, short *slot, void **ctx) { char *key; strlen_t key_len; zend_long start, end; zend_bool ws=0; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sll|b", &key, &key_len, &start, &end, &ws)==FAILURE) { return FAILURE; } if(ws) { *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kdds", key, key_len, start, end, "WITHSCORES", sizeof("WITHSCORES") - 1); } else { *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kdd", key, key_len, start, end); } // Push out WITHSCORES option *withscores = ws; return SUCCESS; } /* ZRANGEBYSCORE/ZREVRANGEBYSCORE */ #define IS_WITHSCORES_ARG(s, l) \ (l == sizeof("withscores") - 1 && !strncasecmp(s, "withscores", l)) #define IS_LIMIT_ARG(s, l) \ (l == sizeof("limit") - 1 && !strncasecmp(s,"limit", l)) int redis_zrangebyscore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, int *withscores, short *slot, void **ctx) { char *key, *start, *end; int has_limit=0; long offset, count; strlen_t key_len, start_len, end_len; zval *z_opt=NULL, *z_ele; zend_string *zkey; ulong idx; HashTable *ht_opt; PHPREDIS_NOTUSED(idx); if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss|a", &key, &key_len, &start, &start_len, &end, &end_len, &z_opt) ==FAILURE) { return FAILURE; } // Check for an options array if(z_opt && Z_TYPE_P(z_opt)==IS_ARRAY) { ht_opt = Z_ARRVAL_P(z_opt); ZEND_HASH_FOREACH_KEY_VAL(ht_opt, idx, zkey, z_ele) { /* All options require a string key type */ if (!zkey) continue; ZVAL_DEREF(z_ele); /* Check for withscores and limit */ if (IS_WITHSCORES_ARG(ZSTR_VAL(zkey), ZSTR_LEN(zkey))) { *withscores = zval_is_true(z_ele); } else if (IS_LIMIT_ARG(ZSTR_VAL(zkey), ZSTR_LEN(zkey)) && Z_TYPE_P(z_ele) == IS_ARRAY) { HashTable *htlimit = Z_ARRVAL_P(z_ele); zval *zoff, *zcnt; /* We need two arguments (offset and count) */ if ((zoff = zend_hash_index_find(htlimit, 0)) != NULL && (zcnt = zend_hash_index_find(htlimit, 1)) != NULL ) { /* Set our limit if we can get valid longs from both args */ offset = zval_get_long(zoff); count = zval_get_long(zcnt); has_limit = 1; } } } ZEND_HASH_FOREACH_END(); } // Construct our command if (*withscores) { if (has_limit) { *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksssdds", key, key_len, start, start_len, end, end_len, "LIMIT", 5, offset, count, "WITHSCORES", 10); } else { *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksss", key, key_len, start, start_len, end, end_len, "WITHSCORES", 10); } } else { if (has_limit) { *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksssdd", key, key_len, start, start_len, end, end_len, "LIMIT", 5, offset, count); } else { *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", key, key_len, start, start_len, end, end_len); } } return SUCCESS; } /* ZUNIONSTORE, ZINTERSTORE */ int redis_zinter_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key, *agg_op=NULL; int key_free, argc = 2, keys_count; strlen_t key_len, agg_op_len = 0; zval *z_keys, *z_weights=NULL, *z_ele; HashTable *ht_keys, *ht_weights=NULL; smart_string cmdstr = {0}; // Parse args if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa|a!s", &key, &key_len, &z_keys, &z_weights, &agg_op, &agg_op_len)==FAILURE) { return FAILURE; } // Grab our keys ht_keys = Z_ARRVAL_P(z_keys); // Nothing to do if there aren't any if((keys_count = zend_hash_num_elements(ht_keys))==0) { return FAILURE; } else { argc += keys_count; } // Handle WEIGHTS if(z_weights != NULL) { ht_weights = Z_ARRVAL_P(z_weights); if(zend_hash_num_elements(ht_weights) != keys_count) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "WEIGHTS and keys array should be the same size!"); return FAILURE; } // "WEIGHTS" + key count argc += keys_count + 1; } // AGGREGATE option if(agg_op_len != 0) { if(strncasecmp(agg_op, "SUM", sizeof("SUM")) && strncasecmp(agg_op, "MIN", sizeof("MIN")) && strncasecmp(agg_op, "MAX", sizeof("MAX"))) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid AGGREGATE option provided!"); return FAILURE; } // "AGGREGATE" + type argc += 2; } // Prefix key key_free = redis_key_prefix(redis_sock, &key, &key_len); // Start building our command redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw)); redis_cmd_append_sstr(&cmdstr, key, key_len); redis_cmd_append_sstr_int(&cmdstr, keys_count); // Set our slot, free the key if we prefixed it CMD_SET_SLOT(slot,key,key_len); if(key_free) efree(key); // Process input keys ZEND_HASH_FOREACH_VAL(ht_keys, z_ele) { zend_string *zstr = zval_get_string(z_ele); char *key = ZSTR_VAL(zstr); strlen_t key_len = ZSTR_LEN(zstr); // Prefix key if necissary int key_free = redis_key_prefix(redis_sock, &key, &key_len); // If we're in Cluster mode, verify the slot is the same if(slot && *slot != cluster_hash_key(key,key_len)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "All keys don't hash to the same slot!"); efree(cmdstr.c); zend_string_release(zstr); if(key_free) efree(key); return FAILURE; } // Append this input set redis_cmd_append_sstr(&cmdstr, key, key_len); // Cleanup zend_string_release(zstr); if(key_free) efree(key); } ZEND_HASH_FOREACH_END(); // Weights if(ht_weights != NULL) { redis_cmd_append_sstr(&cmdstr, "WEIGHTS", sizeof("WEIGHTS")-1); // Process our weights ZEND_HASH_FOREACH_VAL(ht_weights, z_ele) { // Ignore non numeric args unless they're inf/-inf ZVAL_DEREF(z_ele); switch (Z_TYPE_P(z_ele)) { case IS_LONG: redis_cmd_append_sstr_long(&cmdstr, Z_LVAL_P(z_ele)); break; case IS_DOUBLE: redis_cmd_append_sstr_dbl(&cmdstr, Z_DVAL_P(z_ele)); break; case IS_STRING: { double dval; zend_long lval; zend_uchar type = is_numeric_string(Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele), &lval, &dval, 0); if (type == IS_LONG) { redis_cmd_append_sstr_long(&cmdstr, lval); break; } else if (type == IS_DOUBLE) { redis_cmd_append_sstr_dbl(&cmdstr, dval); break; } else if (strncasecmp(Z_STRVAL_P(z_ele), "-inf", sizeof("-inf") - 1) == 0 || strncasecmp(Z_STRVAL_P(z_ele), "+inf", sizeof("+inf") - 1) == 0 || strncasecmp(Z_STRVAL_P(z_ele), "inf", sizeof("inf") - 1) == 0 ) { redis_cmd_append_sstr(&cmdstr, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele)); break; } // fall through } default: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Weights must be numeric or '-inf','inf','+inf'"); efree(cmdstr.c); return FAILURE; } } ZEND_HASH_FOREACH_END(); } // AGGREGATE if(agg_op_len != 0) { redis_cmd_append_sstr(&cmdstr, "AGGREGATE", sizeof("AGGREGATE")-1); redis_cmd_append_sstr(&cmdstr, agg_op, agg_op_len); } // Push out values *cmd = cmdstr.c; *cmd_len = cmdstr.len; return SUCCESS; } /* SUBSCRIBE/PSUBSCRIBE */ int redis_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { zval *z_arr, *z_chan; HashTable *ht_chan; smart_string cmdstr = {0}; subscribeContext *sctx = emalloc(sizeof(subscribeContext)); strlen_t key_len; int key_free; char *key; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "af", &z_arr, &(sctx->cb), &(sctx->cb_cache))==FAILURE) { efree(sctx); return FAILURE; } ht_chan = Z_ARRVAL_P(z_arr); sctx->kw = kw; sctx->argc = zend_hash_num_elements(ht_chan); if(sctx->argc==0) { efree(sctx); return FAILURE; } // Start command construction redis_cmd_init_sstr(&cmdstr, sctx->argc, kw, strlen(kw)); // Iterate over channels ZEND_HASH_FOREACH_VAL(ht_chan, z_chan) { // We want to deal with strings here zend_string *zstr = zval_get_string(z_chan); // Grab channel name, prefix if required key = ZSTR_VAL(zstr); key_len = ZSTR_LEN(zstr); key_free = redis_key_prefix(redis_sock, &key, &key_len); // Add this channel redis_cmd_append_sstr(&cmdstr, key, key_len); zend_string_release(zstr); // Free our key if it was prefixed if(key_free) efree(key); } ZEND_HASH_FOREACH_END(); // Push values out *cmd_len = cmdstr.len; *cmd = cmdstr.c; *ctx = (void*)sctx; // Pick a slot at random CMD_RAND_SLOT(slot); return SUCCESS; } /* UNSUBSCRIBE/PUNSUBSCRIBE */ int redis_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { zval *z_arr, *z_chan; HashTable *ht_arr; smart_string cmdstr = {0}; subscribeContext *sctx = emalloc(sizeof(subscribeContext)); if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &z_arr)==FAILURE) { efree(sctx); return FAILURE; } ht_arr = Z_ARRVAL_P(z_arr); sctx->argc = zend_hash_num_elements(ht_arr); if(sctx->argc == 0) { efree(sctx); return FAILURE; } redis_cmd_init_sstr(&cmdstr, sctx->argc, kw, strlen(kw)); ZEND_HASH_FOREACH_VAL(ht_arr, z_chan) { char *key = Z_STRVAL_P(z_chan); strlen_t key_len = Z_STRLEN_P(z_chan); int key_free; key_free = redis_key_prefix(redis_sock, &key, &key_len); redis_cmd_append_sstr(&cmdstr, key, key_len); if(key_free) efree(key); } ZEND_HASH_FOREACH_END(); // Push out vals *cmd_len = cmdstr.len; *cmd = cmdstr.c; *ctx = (void*)sctx; return SUCCESS; } /* ZRANGEBYLEX/ZREVRANGEBYLEX */ int redis_zrangebylex_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key, *min, *max; strlen_t key_len, min_len, max_len; int argc = ZEND_NUM_ARGS(); zend_long offset, count; /* We need either 3 or 5 arguments for this to be valid */ if (argc != 3 && argc != 5) { php_error_docref(0 TSRMLS_CC, E_WARNING, "Must pass either 3 or 5 arguments"); return FAILURE; } if(zend_parse_parameters(argc TSRMLS_CC, "sss|ll", &key, &key_len, &min, &min_len, &max, &max_len, &offset, &count)==FAILURE) { return FAILURE; } /* min and max must start with '(' or '[', or be either '-' or '+' */ if (min_len < 1 || max_len < 1 || (min[0] != '(' && min[0] != '[' && (min[0] != '-' || min_len > 1) && (min[0] != '+' || min_len > 1)) || (max[0] != '(' && max[0] != '[' && (max[0] != '-' || max_len > 1) && (max[0] != '+' || max_len > 1))) { php_error_docref(0 TSRMLS_CC, E_WARNING, "min and max arguments must start with '[' or '('"); return FAILURE; } /* Construct command */ if (argc == 3) { *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", key, key_len, min, min_len, max, max_len); } else { *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksssll", key, key_len, min, min_len, max, max_len, "LIMIT", 5, offset, count); } return SUCCESS; } /* ZLEXCOUNT/ZREMRANGEBYLEX */ int redis_gen_zlex_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key, *min, *max; strlen_t key_len, min_len, max_len; /* Parse args */ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss", &key, &key_len, &min, &min_len, &max, &max_len)==FAILURE) { return FAILURE; } /* Quick sanity check on min/max */ if(min_len<1 || max_len<1 || (min[0]!='(' && min[0]!='[') || (max[0]!='(' && max[0]!='[')) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Min and Max arguments must begin with '(' or '['"); return FAILURE; } /* Construct command */ *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", key, key_len, min, min_len, max, max_len); return SUCCESS; } /* EVAL and EVALSHA */ int redis_eval_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { char *lua; int argc = 0; zval *z_arr = NULL, *z_ele; HashTable *ht_arr; zend_long num_keys = 0; smart_string cmdstr = {0}; strlen_t lua_len; zend_string *zstr; short prevslot = -1; /* Parse args */ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|al", &lua, &lua_len, &z_arr, &num_keys)==FAILURE) { return FAILURE; } /* Grab arg count */ if (z_arr != NULL) { ht_arr = Z_ARRVAL_P(z_arr); argc = zend_hash_num_elements(ht_arr); } /* EVAL[SHA] {script || sha1} {num keys} */ redis_cmd_init_sstr(&cmdstr, 2 + argc, kw, strlen(kw)); redis_cmd_append_sstr(&cmdstr, lua, lua_len); redis_cmd_append_sstr_long(&cmdstr, num_keys); // Iterate over our args if we have any if (argc > 0) { ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) { zstr = zval_get_string(z_ele); /* If we're still on a key, prefix it check slot */ if (num_keys-- > 0) { redis_cmd_append_sstr_key(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr), redis_sock, slot); /* If we have been passed a slot, all keys must match */ if (slot) { if (prevslot != -1 && prevslot != *slot) { zend_string_release(zstr); php_error_docref(0 TSRMLS_CC, E_WARNING, "All keys do not map to the same slot"); return FAILURE; } prevslot = *slot; } } else { redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr)); } zend_string_release(zstr); } ZEND_HASH_FOREACH_END(); } else { /* Any slot will do */ CMD_RAND_SLOT(slot); } *cmd = cmdstr.c; *cmd_len = cmdstr.len; return SUCCESS; } /* Commands that take a key followed by a variable list of serializable * values (RPUSH, LPUSH, SADD, SREM, etc...) */ int redis_key_varval_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { zval *z_args; smart_string cmdstr = {0}; strlen_t i; int argc = ZEND_NUM_ARGS(); // We at least need a key and one value if(argc < 2) { return FAILURE; } // Make sure we at least have a key, and we can get other args z_args = emalloc(argc * sizeof(zval)); if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) { efree(z_args); return FAILURE; } /* Initialize our command */ redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw)); /* Append key */ zend_string *zstr = zval_get_string(&z_args[0]); redis_cmd_append_sstr_key(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr), redis_sock, slot); zend_string_release(zstr); /* Add members */ for (i = 1; i < argc; i++ ){ redis_cmd_append_sstr_zval(&cmdstr, &z_args[i], redis_sock TSRMLS_CC); } // Push out values *cmd = cmdstr.c; *cmd_len = cmdstr.len; // Cleanup arg array efree(z_args); // Success! return SUCCESS; } /* Commands that take a key and then an array of values */ int redis_key_arr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { zval *z_arr, *z_val; HashTable *ht_arr; smart_string cmdstr = {0}; int key_free, val_free, argc = 1; strlen_t val_len, key_len; char *key, *val; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &key, &key_len, &z_arr) == FAILURE || zend_hash_num_elements(Z_ARRVAL_P(z_arr)) == 0) { return FAILURE; } /* Start constructing our command */ ht_arr = Z_ARRVAL_P(z_arr); argc += zend_hash_num_elements(ht_arr); redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw)); /* Prefix if required and append the key name */ key_free = redis_key_prefix(redis_sock, &key, &key_len); redis_cmd_append_sstr(&cmdstr, key, key_len); CMD_SET_SLOT(slot, key, key_len); if (key_free) efree(key); /* Iterate our hash table, serializing and appending values */ ZEND_HASH_FOREACH_VAL(ht_arr, z_val) { val_free = redis_serialize(redis_sock, z_val, &val, &val_len TSRMLS_CC); redis_cmd_append_sstr(&cmdstr, val, val_len); if (val_free) efree(val); } ZEND_HASH_FOREACH_END(); *cmd_len = cmdstr.len; *cmd = cmdstr.c; return SUCCESS; } /* Generic function that takes a variable number of keys, with an optional * timeout value. This can handle various SUNION/SUNIONSTORE/BRPOP type * commands. */ static int gen_varkey_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, int kw_len, int min_argc, int has_timeout, char **cmd, int *cmd_len, short *slot) { zval *z_args, *z_ele; HashTable *ht_arr; char *key; int key_free, i, tail; strlen_t key_len; int single_array = 0, argc = ZEND_NUM_ARGS(); smart_string cmdstr = {0}; long timeout = 0; short kslot = -1; zend_string *zstr; if(argc < min_argc) { zend_wrong_param_count(TSRMLS_C); return FAILURE; } // Allocate args z_args = emalloc(argc * sizeof(zval)); if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) { efree(z_args); return FAILURE; } // Handle our "single array" case if(has_timeout == 0) { single_array = argc==1 && Z_TYPE(z_args[0])==IS_ARRAY; } else { single_array = argc==2 && Z_TYPE(z_args[0])==IS_ARRAY && Z_TYPE(z_args[1])==IS_LONG; timeout = Z_LVAL(z_args[1]); } // If we're running a single array, rework args if(single_array) { ht_arr = Z_ARRVAL(z_args[0]); argc = zend_hash_num_elements(ht_arr); if(has_timeout) argc++; efree(z_args); z_args = NULL; /* If the array is empty, we can simply abort */ if (argc == 0) return FAILURE; } // Begin construction of our command redis_cmd_init_sstr(&cmdstr, argc, kw, kw_len); if(single_array) { ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) { zstr = zval_get_string(z_ele); key = ZSTR_VAL(zstr); key_len = ZSTR_LEN(zstr); key_free = redis_key_prefix(redis_sock, &key, &key_len); // Protect against CROSSLOT errors if(slot) { if(kslot == -1) { kslot = cluster_hash_key(key, key_len); } else if(cluster_hash_key(key,key_len)!=kslot) { zend_string_release(zstr); if(key_free) efree(key); php_error_docref(NULL TSRMLS_CC, E_WARNING, "Not all keys hash to the same slot!"); return FAILURE; } } // Append this key, free it if we prefixed redis_cmd_append_sstr(&cmdstr, key, key_len); zend_string_release(zstr); if(key_free) efree(key); } ZEND_HASH_FOREACH_END(); if(has_timeout) { redis_cmd_append_sstr_long(&cmdstr, timeout); } } else { if(has_timeout && Z_TYPE(z_args[argc-1])!=IS_LONG) { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Timeout value must be a LONG"); efree(z_args); return FAILURE; } tail = has_timeout ? argc-1 : argc; for(i=0;i= 2.6.12 set options */ if(z_opts && Z_TYPE_P(z_opts) != IS_LONG && Z_TYPE_P(z_opts) != IS_ARRAY && Z_TYPE_P(z_opts) != IS_NULL) { return FAILURE; } // Check for an options array if(z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) { HashTable *kt = Z_ARRVAL_P(z_opts); zend_string *zkey; ulong idx; zval *v; PHPREDIS_NOTUSED(idx); /* Iterate our option array */ ZEND_HASH_FOREACH_KEY_VAL(kt, idx, zkey, v) { ZVAL_DEREF(v); /* Detect PX or EX argument and validate timeout */ if (zkey && IS_EX_PX_ARG(ZSTR_VAL(zkey))) { /* Set expire type */ exp_type = ZSTR_VAL(zkey); /* Try to extract timeout */ if (Z_TYPE_P(v) == IS_LONG) { expire = Z_LVAL_P(v); } else if (Z_TYPE_P(v) == IS_STRING) { expire = atol(Z_STRVAL_P(v)); } /* Expiry can't be set < 1 */ if (expire < 1) { return FAILURE; } } else if (Z_TYPE_P(v) == IS_STRING && IS_NX_XX_ARG(Z_STRVAL_P(v))) { set_type = Z_STRVAL_P(v); } } ZEND_HASH_FOREACH_END(); } else if(z_opts && Z_TYPE_P(z_opts) == IS_LONG) { /* Grab expiry and fail if it's < 1 */ expire = Z_LVAL_P(z_opts); if (expire < 1) { return FAILURE; } } /* Now let's construct the command we want */ if(exp_type && set_type) { /* SET NX|XX PX|EX */ *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SET", "kvsls", key, key_len, z_value, exp_type, 2, expire, set_type, 2); } else if(exp_type) { /* SET PX|EX */ *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SET", "kvsl", key, key_len, z_value, exp_type, 2, expire); } else if(set_type) { /* SET NX|XX */ *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SET", "kvs", key, key_len, z_value, set_type, 2); } else if(expire > 0) { /* Backward compatible SETEX redirection */ *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SETEX", "klv", key, key_len, expire, z_value); } else { /* SET */ *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SET", "kv", key, key_len, z_value); } return SUCCESS; } /* BRPOPLPUSH */ int redis_brpoplpush_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key1, *key2; strlen_t key1_len, key2_len; int key1_free, key2_free; short slot1, slot2; zend_long timeout; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssl", &key1, &key1_len, &key2, &key2_len, &timeout)==FAILURE) { return FAILURE; } // Key prefixing key1_free = redis_key_prefix(redis_sock, &key1, &key1_len); key2_free = redis_key_prefix(redis_sock, &key2, &key2_len); // In cluster mode, verify the slots match if (slot) { slot1 = cluster_hash_key(key1, key1_len); slot2 = cluster_hash_key(key2, key2_len); if(slot1 != slot2) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Keys hash to different slots!"); if(key1_free) efree(key1); if(key2_free) efree(key2); return FAILURE; } // Both slots are the same *slot = slot1; } // Consistency with Redis, if timeout < 0 use RPOPLPUSH if(timeout < 0) { *cmd_len = REDIS_CMD_SPPRINTF(cmd, "RPOPLPUSH", "ss", key1, key1_len, key2, key2_len); } else { *cmd_len = REDIS_CMD_SPPRINTF(cmd, "BRPOPLPUSH", "ssd", key1, key1_len, key2, key2_len, timeout); } if (key1_free) efree(key1); if (key2_free) efree(key2); return SUCCESS; } /* To maintain backward compatibility with earlier versions of phpredis, we * allow for an optional "increment by" argument for INCR and DECR even though * that's not how Redis proper works */ #define TYPE_INCR 0 #define TYPE_DECR 1 /* Handle INCR(BY) and DECR(BY) depending on optional increment value */ static int redis_atomic_increment(INTERNAL_FUNCTION_PARAMETERS, int type, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key; strlen_t key_len; zend_long val = 1; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &key, &key_len, &val)==FAILURE) { return FAILURE; } /* If our value is 1 we use INCR/DECR. For other values, treat the call as * an INCRBY or DECRBY call */ if (type == TYPE_INCR) { if (val == 1) { *cmd_len = REDIS_CMD_SPPRINTF(cmd, "INCR", "k", key, key_len); } else { *cmd_len = REDIS_CMD_SPPRINTF(cmd, "INCRBY", "kl", key, key_len, val); } } else { if (val == 1) { *cmd_len = REDIS_CMD_SPPRINTF(cmd, "DECR", "k", key, key_len); } else { *cmd_len = REDIS_CMD_SPPRINTF(cmd, "DECRBY", "kl", key, key_len, val); } } /* Success */ return SUCCESS; } /* INCR */ int redis_incr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { return redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_INCR, redis_sock, cmd, cmd_len, slot, ctx); } /* DECR */ int redis_decr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { return redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_DECR, redis_sock, cmd, cmd_len, slot, ctx); } /* HINCRBY */ int redis_hincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key, *mem; strlen_t key_len, mem_len; zend_long byval; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssl", &key, &key_len, &mem, &mem_len, &byval)==FAILURE) { return FAILURE; } // Construct command *cmd_len = REDIS_CMD_SPPRINTF(cmd, "HINCRBY", "ksl", key, key_len, mem, mem_len, byval); // Success return SUCCESS; } /* HINCRBYFLOAT */ int redis_hincrbyfloat_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key, *mem; strlen_t key_len, mem_len; double byval; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssd", &key, &key_len, &mem, &mem_len, &byval)==FAILURE) { return FAILURE; } // Construct command *cmd_len = REDIS_CMD_SPPRINTF(cmd, "HINCRBYFLOAT", "ksf", key, key_len, mem, mem_len, byval); // Success return SUCCESS; } /* HMGET */ int redis_hmget_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key; zval *z_arr, *z_mems, *z_mem; int i, count, valid=0, key_free; strlen_t key_len; HashTable *ht_arr; smart_string cmdstr = {0}; // Parse arguments if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &key, &key_len, &z_arr)==FAILURE) { return FAILURE; } // Our HashTable ht_arr = Z_ARRVAL_P(z_arr); // We can abort if we have no elements if((count = zend_hash_num_elements(ht_arr))==0) { return FAILURE; } // Allocate memory for mems+1 so we can have a sentinel z_mems = ecalloc(count + 1, sizeof(zval)); // Iterate over our member array ZEND_HASH_FOREACH_VAL(ht_arr, z_mem) { ZVAL_DEREF(z_mem); // We can only handle string or long values here if ((Z_TYPE_P(z_mem) == IS_STRING && Z_STRLEN_P(z_mem) > 0) || Z_TYPE_P(z_mem) == IS_LONG ) { // Copy into our member array ZVAL_ZVAL(&z_mems[valid], z_mem, 1, 0); convert_to_string(&z_mems[valid]); // Increment the member count to actually send valid++; } } ZEND_HASH_FOREACH_END(); // If nothing was valid, fail if(valid == 0) { efree(z_mems); return FAILURE; } // Sentinel so we can free this even if it's used and then we discard // the transaction manually or there is a transaction failure ZVAL_NULL(&z_mems[valid]); // Start command construction redis_cmd_init_sstr(&cmdstr, valid+1, "HMGET", sizeof("HMGET")-1); // Prefix our key key_free = redis_key_prefix(redis_sock, &key, &key_len); redis_cmd_append_sstr(&cmdstr, key, key_len); // Iterate over members, appending as arguments for(i=0;i value array ZEND_HASH_FOREACH_KEY_VAL(ht_vals, idx, zkey, z_val) { char *mem, *val, kbuf[40]; strlen_t val_len; int val_free; unsigned int mem_len; // If the hash key is an integer, convert it to a string if (zkey) { mem_len = ZSTR_LEN(zkey); mem = ZSTR_VAL(zkey); } else { mem_len = snprintf(kbuf, sizeof(kbuf), "%ld", (long)idx); mem = (char*)kbuf; } // Serialize value (if directed) val_free = redis_serialize(redis_sock, z_val, &val, &val_len TSRMLS_CC); // Append the key and value to our command redis_cmd_append_sstr(&cmdstr, mem, mem_len); redis_cmd_append_sstr(&cmdstr, val, val_len); // Free our value if we serialized it if (val_free) efree(val); } ZEND_HASH_FOREACH_END(); // Set slot if directed CMD_SET_SLOT(slot,key,key_len); // Free our key if we prefixed it if(key_free) efree(key); // Push return pointers *cmd_len = cmdstr.len; *cmd = cmdstr.c; // Success! return SUCCESS; } /* HSTRLEN */ int redis_hstrlen_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key, *field; strlen_t key_len, field_len; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &key, &key_len, &field, &field_len) == FAILURE ) { return FAILURE; } *cmd_len = REDIS_CMD_SPPRINTF(cmd, "HSTRLEN", "ks", key, key_len, field, field_len); return SUCCESS; } /* BITPOS */ int redis_bitpos_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key; int argc; zend_long bit, start, end; strlen_t key_len; argc = ZEND_NUM_ARGS(); if(zend_parse_parameters(argc TSRMLS_CC, "sl|ll", &key, &key_len, &bit, &start, &end)==FAILURE) { return FAILURE; } // Prevalidate bit if(bit != 0 && bit != 1) { return FAILURE; } // Construct command based on arg count if(argc == 2) { *cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITPOS", "kd", key, key_len, bit); } else if(argc == 3) { *cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITPOS", "kdd", key, key_len, bit, start); } else { *cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITPOS", "kddd", key, key_len, bit, start, end); } return SUCCESS; } /* BITOP */ int redis_bitop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { zval *z_args; char *key; strlen_t key_len; int i, key_free, argc = ZEND_NUM_ARGS(); smart_string cmdstr = {0}; short kslot; zend_string *zstr; // Allocate space for args, parse them as an array z_args = emalloc(argc * sizeof(zval)); if (zend_get_parameters_array(ht, argc, z_args) == FAILURE || argc < 3 || Z_TYPE(z_args[0]) != IS_STRING) { efree(z_args); return FAILURE; } // If we were passed a slot pointer, init to a sentinel value if(slot) *slot = -1; // Initialize command construction, add our operation argument redis_cmd_init_sstr(&cmdstr, argc, "BITOP", sizeof("BITOP")-1); redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[0]), Z_STRLEN(z_args[0])); // Now iterate over our keys argument for(i=1;iauth) zend_string_release(redis_sock->auth); redis_sock->auth = zend_string_init(pw, pw_len, 0); // Success return SUCCESS; } /* SETBIT */ int redis_setbit_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key; strlen_t key_len; zend_long offset; zend_bool val; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "slb", &key, &key_len, &offset, &val)==FAILURE) { return FAILURE; } // Validate our offset if(offset < BITOP_MIN_OFFSET || offset > BITOP_MAX_OFFSET) { php_error_docref(0 TSRMLS_CC, E_WARNING, "Invalid OFFSET for bitop command (must be between 0-2^32-1)"); return FAILURE; } *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SETBIT", "kld", key, key_len, offset, (int)val); return SUCCESS; } /* LINSERT */ int redis_linsert_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key, *pos; strlen_t key_len, pos_len; zval *z_val, *z_pivot; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sszz", &key, &key_len, &pos, &pos_len, &z_pivot, &z_val)==FAILURE) { return FAILURE; } // Validate position if(strncasecmp(pos, "after", 5) && strncasecmp(pos, "before", 6)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Position must be either 'BEFORE' or 'AFTER'"); return FAILURE; } /* Construct command */ *cmd_len = REDIS_CMD_SPPRINTF(cmd, "LINSERT", "ksvv", key, key_len, pos, pos_len, z_pivot, z_val); // Success return SUCCESS; } /* LREM */ int redis_lrem_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key; strlen_t key_len; zend_long count = 0; zval *z_val; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|l", &key, &key_len, &z_val, &count)==FAILURE) { return FAILURE; } /* Construct command */ *cmd_len = REDIS_CMD_SPPRINTF(cmd, "LREM", "kdv", key, key_len, count, z_val); // Success! return SUCCESS; } int redis_smove_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { char *src, *dst; strlen_t src_len, dst_len; int src_free, dst_free; zval *z_val; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssz", &src, &src_len, &dst, &dst_len, &z_val)==FAILURE) { return FAILURE; } src_free = redis_key_prefix(redis_sock, &src, &src_len); dst_free = redis_key_prefix(redis_sock, &dst, &dst_len); // Protect against a CROSSSLOT error if (slot) { short slot1 = cluster_hash_key(src, src_len); short slot2 = cluster_hash_key(dst, dst_len); if(slot1 != slot2) { php_error_docref(0 TSRMLS_CC, E_WARNING, "Source and destination keys don't hash to the same slot!"); if(src_free) efree(src); if(dst_free) efree(dst); return FAILURE; } *slot = slot1; } // Construct command *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SMOVE", "ssv", src, src_len, dst, dst_len, z_val); // Cleanup if(src_free) efree(src); if(dst_free) efree(dst); // Succcess! return SUCCESS; } /* Generic command construction for HSET and HSETNX */ static int gen_hset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot) { char *key, *mem; strlen_t key_len, mem_len; zval *z_val; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssz", &key, &key_len, &mem, &mem_len, &z_val)==FAILURE) { return FAILURE; } /* Construct command */ *cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksv", key, key_len, mem, mem_len, z_val); // Success return SUCCESS; } /* HSET */ int redis_hset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { return gen_hset_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "HSET", cmd, cmd_len, slot); } /* HSETNX */ int redis_hsetnx_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { return gen_hset_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "HSETNX", cmd, cmd_len, slot); } /* SRANDMEMBER */ int redis_srandmember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx, short *have_count) { char *key; strlen_t key_len; zend_long count; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &key, &key_len, &count)==FAILURE) { return FAILURE; } // Set our have count flag *have_count = ZEND_NUM_ARGS() == 2; // Two args means we have the optional COUNT if (*have_count) { *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SRANDMEMBER", "kl", key, key_len, count); } else { *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SRANDMEMBER", "k", key, key_len); } return SUCCESS; } /* ZINCRBY */ int redis_zincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key; strlen_t key_len; double incrby; zval *z_val; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sdz", &key, &key_len, &incrby, &z_val)==FAILURE) { return FAILURE; } *cmd_len = REDIS_CMD_SPPRINTF(cmd, "ZINCRBY", "kfv", key, key_len, incrby, z_val); return SUCCESS; } /* SORT */ int redis_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, int *using_store, char **cmd, int *cmd_len, short *slot, void **ctx) { zval *z_opts=NULL, *z_ele, z_argv; char *key; HashTable *ht_opts; smart_string cmdstr = {0}; strlen_t key_len; int key_free; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &key, &key_len, &z_opts)==FAILURE) { return FAILURE; } // Default that we're not using store *using_store = 0; // If we don't have an options array, the command is quite simple if (!z_opts || zend_hash_num_elements(Z_ARRVAL_P(z_opts)) == 0) { // Construct command *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SORT", "k", key, key_len); /* Not storing */ *using_store = 0; return SUCCESS; } // Create our hash table to hold our sort arguments array_init(&z_argv); // SORT key_free = redis_key_prefix(redis_sock, &key, &key_len); add_next_index_stringl(&z_argv, key, key_len); if (key_free) efree(key); // Set slot CMD_SET_SLOT(slot,key,key_len); // Grab the hash table ht_opts = Z_ARRVAL_P(z_opts); // Handle BY pattern if (((z_ele = zend_hash_str_find(ht_opts, "by", sizeof("by") - 1)) != NULL || (z_ele = zend_hash_str_find(ht_opts, "BY", sizeof("BY") - 1)) != NULL ) && Z_TYPE_P(z_ele) == IS_STRING ) { // "BY" option is disabled in cluster if(slot) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "SORT BY option is not allowed in Redis Cluster"); zval_dtor(&z_argv); return FAILURE; } // ... BY add_next_index_stringl(&z_argv, "BY", sizeof("BY") - 1); add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele)); } // Handle ASC/DESC option if (((z_ele = zend_hash_str_find(ht_opts, "sort", sizeof("sort") - 1)) != NULL || (z_ele = zend_hash_str_find(ht_opts, "SORT", sizeof("SORT") - 1)) != NULL ) && Z_TYPE_P(z_ele) == IS_STRING ) { // 'asc'|'desc' add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele)); } // STORE option if (((z_ele = zend_hash_str_find(ht_opts, "store", sizeof("store") - 1)) != NULL || (z_ele = zend_hash_str_find(ht_opts, "STORE", sizeof("STORE") - 1)) != NULL ) && Z_TYPE_P(z_ele) == IS_STRING ) { // Slot verification int cross_slot = slot && *slot != cluster_hash_key( Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele)); if(cross_slot) { php_error_docref(0 TSRMLS_CC, E_WARNING, "Error, SORT key and STORE key have different slots!"); zval_dtor(&z_argv); return FAILURE; } // STORE add_next_index_stringl(&z_argv, "STORE", sizeof("STORE") - 1); add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele)); // We are using STORE *using_store = 1; } // GET option if (((z_ele = zend_hash_str_find(ht_opts, "get", sizeof("get") - 1)) != NULL || (z_ele = zend_hash_str_find(ht_opts, "GET", sizeof("GET") - 1)) != NULL ) && (Z_TYPE_P(z_ele) == IS_STRING || Z_TYPE_P(z_ele) == IS_ARRAY) ) { // Disabled in cluster if(slot) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "GET option for SORT disabled in Redis Cluster"); zval_dtor(&z_argv); return FAILURE; } // If it's a string just add it if (Z_TYPE_P(z_ele) == IS_STRING) { add_next_index_stringl(&z_argv, "GET", sizeof("GET") - 1); add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele)); } else { int added=0; zval *z_key; ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_ele), z_key) { // If we can't get the data, or it's not a string, skip if (z_key == NULL || Z_TYPE_P(z_key) != IS_STRING) { continue; } /* Add get per thing we're getting */ add_next_index_stringl(&z_argv, "GET", sizeof("GET") - 1); // Add this key to our argv array add_next_index_stringl(&z_argv, Z_STRVAL_P(z_key), Z_STRLEN_P(z_key)); added++; } ZEND_HASH_FOREACH_END(); // Make sure we were able to add at least one if(added==0) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Array of GET values requested, but none are valid"); zval_dtor(&z_argv); return FAILURE; } } } // ALPHA if (((z_ele = zend_hash_str_find(ht_opts, "alpha", sizeof("alpha") - 1)) != NULL || (z_ele = zend_hash_str_find(ht_opts, "ALPHA", sizeof("ALPHA") - 1)) != NULL) && zval_is_true(z_ele) ) { add_next_index_stringl(&z_argv, "ALPHA", sizeof("ALPHA") - 1); } // LIMIT if (((z_ele = zend_hash_str_find(ht_opts, "limit", sizeof("limit") - 1)) != NULL || (z_ele = zend_hash_str_find(ht_opts, "LIMIT", sizeof("LIMIT") - 1)) != NULL ) && Z_TYPE_P(z_ele) == IS_ARRAY ) { HashTable *ht_off = Z_ARRVAL_P(z_ele); zval *z_off, *z_cnt; if ((z_off = zend_hash_index_find(ht_off, 0)) != NULL && (z_cnt = zend_hash_index_find(ht_off, 1)) != NULL ) { if ((Z_TYPE_P(z_off) != IS_STRING && Z_TYPE_P(z_off) != IS_LONG) || (Z_TYPE_P(z_cnt) != IS_STRING && Z_TYPE_P(z_cnt) != IS_LONG) ) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "LIMIT options on SORT command must be longs or strings"); zval_dtor(&z_argv); return FAILURE; } // Add LIMIT argument add_next_index_stringl(&z_argv, "LIMIT", sizeof("LIMIT") - 1); long low, high; if (Z_TYPE_P(z_off) == IS_STRING) { low = atol(Z_STRVAL_P(z_off)); } else { low = Z_LVAL_P(z_off); } if (Z_TYPE_P(z_cnt) == IS_STRING) { high = atol(Z_STRVAL_P(z_cnt)); } else { high = Z_LVAL_P(z_cnt); } // Add our two LIMIT arguments add_next_index_long(&z_argv, low); add_next_index_long(&z_argv, high); } } // Start constructing our command HashTable *ht_argv = Z_ARRVAL_P(&z_argv); redis_cmd_init_sstr(&cmdstr, zend_hash_num_elements(ht_argv), "SORT", sizeof("SORT")-1); // Iterate through our arguments ZEND_HASH_FOREACH_VAL(ht_argv, z_ele) { // Args are strings or longs if (Z_TYPE_P(z_ele) == IS_STRING) { redis_cmd_append_sstr(&cmdstr,Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele)); } else { redis_cmd_append_sstr_long(&cmdstr, Z_LVAL_P(z_ele)); } } ZEND_HASH_FOREACH_END(); /* Clean up our arguments array. Note we don't have to free any prefixed * key as that we didn't duplicate the pointer if we prefixed */ zval_dtor(&z_argv); // Push our length and command *cmd_len = cmdstr.len; *cmd = cmdstr.c; // Success! return SUCCESS; } /* HDEL */ int redis_hdel_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { zval *z_args; smart_string cmdstr = {0}; char *arg; int arg_free, i; strlen_t arg_len; int argc = ZEND_NUM_ARGS(); zend_string *zstr; // We need at least KEY and one member if(argc < 2) { return FAILURE; } // Grab arguments as an array z_args = emalloc(argc * sizeof(zval)); if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) { efree(z_args); return FAILURE; } // Get first argument (the key) as a string zstr = zval_get_string(&z_args[0]); arg = ZSTR_VAL(zstr); arg_len = ZSTR_LEN(zstr); // Prefix arg_free = redis_key_prefix(redis_sock, &arg, &arg_len); // Start command construction redis_cmd_init_sstr(&cmdstr, argc, "HDEL", sizeof("HDEL")-1); redis_cmd_append_sstr(&cmdstr, arg, arg_len); // Set our slot, free key if we prefixed it CMD_SET_SLOT(slot,arg,arg_len); zend_string_release(zstr); if(arg_free) efree(arg); // Iterate through the members we're removing for(i=1;i 4) { // Only one score-element pair can be specified in this mode. efree(z_args); return FAILURE; } incr = 1; } } } ZEND_HASH_FOREACH_END(); argc = num - 1; if (exp_type) argc++; argc += ch + incr; i++; } else { argc = num; } // Prefix our key zstr = zval_get_string(&z_args[0]); key = ZSTR_VAL(zstr); key_len = ZSTR_LEN(zstr); key_free = redis_key_prefix(redis_sock, &key, &key_len); // Start command construction redis_cmd_init_sstr(&cmdstr, argc, "ZADD", sizeof("ZADD")-1); redis_cmd_append_sstr(&cmdstr, key, key_len); // Set our slot, free key if we prefixed it CMD_SET_SLOT(slot,key,key_len); zend_string_release(zstr); if(key_free) efree(key); if (exp_type) redis_cmd_append_sstr(&cmdstr, exp_type, 2); if (ch) redis_cmd_append_sstr(&cmdstr, "CH", 2); if (incr) redis_cmd_append_sstr(&cmdstr, "INCR", 4); // Now the rest of our arguments while (i < num) { // Append score and member if (Z_TYPE(z_args[i]) == IS_STRING && ( /* The score values should be the string representation of a double * precision floating point number. +inf and -inf values are valid * values as well. */ strncasecmp(Z_STRVAL(z_args[i]), "-inf", 4) == 0 || strncasecmp(Z_STRVAL(z_args[i]), "+inf", 4) == 0 )) { redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[i]), Z_STRLEN(z_args[i])); } else { redis_cmd_append_sstr_dbl(&cmdstr, zval_get_double(&z_args[i])); } // serialize value if requested val_free = redis_serialize(redis_sock, &z_args[i+1], &val, &val_len TSRMLS_CC); redis_cmd_append_sstr(&cmdstr, val, val_len); // Free value if we serialized if(val_free) efree(val); i += 2; } // Push output values *cmd = cmdstr.c; *cmd_len = cmdstr.len; // Cleanup args efree(z_args); return SUCCESS; } /* OBJECT */ int redis_object_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, REDIS_REPLY_TYPE *rtype, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key, *subcmd; strlen_t key_len, subcmd_len; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &subcmd, &subcmd_len, &key, &key_len)==FAILURE) { return FAILURE; } // Format our command *cmd_len = REDIS_CMD_SPPRINTF(cmd, "OBJECT", "sk", subcmd, subcmd_len, key, key_len); // Push the reply type to our caller if(subcmd_len == 8 && (!strncasecmp(subcmd,"refcount",8) || !strncasecmp(subcmd,"idletime",8))) { *rtype = TYPE_INT; } else if(subcmd_len == 8 && !strncasecmp(subcmd, "encoding", 8)) { *rtype = TYPE_BULK; } else { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid subcommand sent to OBJECT"); efree(*cmd); return FAILURE; } // Success return SUCCESS; } /* GEODIST */ int redis_geodist_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key, *source, *dest, *unit = NULL; strlen_t keylen, sourcelen, destlen, unitlen; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss|s", &key, &keylen, &source, &sourcelen, &dest, &destlen, &unit, &unitlen) == FAILURE) { return FAILURE; } /* Construct command */ if (unit != NULL) { *cmd_len = REDIS_CMD_SPPRINTF(cmd, "GEODIST", "ksss", key, keylen, source, sourcelen, dest, destlen, unit, unitlen); } else { *cmd_len = REDIS_CMD_SPPRINTF(cmd, "GEODIST", "kss", key, keylen, source, sourcelen, dest, destlen); } return SUCCESS; } /* Helper function to extract optional arguments for GEORADIUS and GEORADIUSBYMEMBER */ static void get_georadius_opts(HashTable *ht, int *withcoord, int *withdist, int *withhash, long *count, geoSortType *sort) { ulong idx; char *optstr; zend_string *zkey; zval *optval; PHPREDIS_NOTUSED(idx); /* Iterate over our argument array, collating which ones we have */ ZEND_HASH_FOREACH_KEY_VAL(ht, idx, zkey, optval) { ZVAL_DEREF(optval); /* If the key is numeric it's a non value option */ if (zkey) { if (ZSTR_LEN(zkey) == 5 && !strcasecmp(ZSTR_VAL(zkey), "count") && Z_TYPE_P(optval) == IS_LONG) { *count = Z_LVAL_P(optval); } } else { /* Option needs to be a string */ if (Z_TYPE_P(optval) != IS_STRING) continue; optstr = Z_STRVAL_P(optval); if (!strcasecmp(optstr, "withcoord")) { *withcoord = 1; } else if (!strcasecmp(optstr, "withdist")) { *withdist = 1; } else if (!strcasecmp(optstr, "withhash")) { *withhash = 1; } else if (!strcasecmp(optstr, "asc")) { *sort = SORT_ASC; } else if (!strcasecmp(optstr, "desc")) { *sort = SORT_DESC; } } } ZEND_HASH_FOREACH_END(); } /* Helper to append options to a GEORADIUS or GEORADIUSBYMEMBER command */ void append_georadius_opts(smart_string *str, int withcoord, int withdist, int withhash, long count, geoSortType sort) { /* WITHCOORD option */ if (withcoord) REDIS_CMD_APPEND_SSTR_STATIC(str, "WITHCOORD"); /* WITHDIST option */ if (withdist) REDIS_CMD_APPEND_SSTR_STATIC(str, "WITHDIST"); /* WITHHASH option */ if (withhash) REDIS_CMD_APPEND_SSTR_STATIC(str, "WITHHASH"); /* Append sort if it's not GEO_NONE */ if (sort == SORT_ASC) { REDIS_CMD_APPEND_SSTR_STATIC(str, "ASC"); } else if (sort == SORT_DESC) { REDIS_CMD_APPEND_SSTR_STATIC(str, "DESC"); } /* Append our count if we've got one */ if (count > 0) { REDIS_CMD_APPEND_SSTR_STATIC(str, "COUNT"); redis_cmd_append_sstr_long(str, count); } } /* GEORADIUS */ int redis_georadius_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key, *unit; strlen_t keylen, unitlen; int keyfree, withcoord = 0, withdist = 0, withhash = 0; long count = 0; geoSortType sort = SORT_NONE; double lng, lat, radius; zval *opts = NULL; smart_string cmdstr = {0}; int argc; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sddds|a", &key, &keylen, &lng, &lat, &radius, &unit, &unitlen, &opts) == FAILURE) { return FAILURE; } /* Parse any GEORADIUS options we have */ if (opts != NULL) { get_georadius_opts(Z_ARRVAL_P(opts), &withcoord, &withdist, &withhash, &count, &sort); } /* Calculate the number of arguments we're going to send, five required plus * options. */ argc = 5 + withcoord + withdist + withhash + (sort != SORT_NONE); if (count != 0) argc += 2; /* Begin construction of our command */ REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "GEORADIUS"); /* Apply any key prefix */ keyfree = redis_key_prefix(redis_sock, &key, &keylen); /* Append required arguments */ redis_cmd_append_sstr(&cmdstr, key, keylen); redis_cmd_append_sstr_dbl(&cmdstr, lng); redis_cmd_append_sstr_dbl(&cmdstr, lat); redis_cmd_append_sstr_dbl(&cmdstr, radius); redis_cmd_append_sstr(&cmdstr, unit, unitlen); /* Append optional arguments */ append_georadius_opts(&cmdstr, withcoord, withdist, withhash, count, sort); /* Free key if it was prefixed */ if (keyfree) efree(key); /* Set slot, command and len, and return */ CMD_SET_SLOT(slot, key, keylen); *cmd = cmdstr.c; *cmd_len = cmdstr.len; return SUCCESS; } /* GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] */ int redis_georadiusbymember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { char *key, *mem, *unit; strlen_t keylen, memlen, unitlen; int keyfree, argc, withcoord = 0, withdist = 0, withhash = 0; long count = 0; double radius; geoSortType sort = SORT_NONE; zval *opts = NULL; smart_string cmdstr = {0}; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssds|a", &key, &keylen, &mem, &memlen, &radius, &unit, &unitlen, &opts) == FAILURE) { return FAILURE; } if (opts != NULL) { get_georadius_opts(Z_ARRVAL_P(opts), &withcoord, &withdist, &withhash, &count, &sort); } /* Calculate argc */ argc = 4 + withcoord + withdist + withhash + (sort != SORT_NONE); if (count != 0) argc += 2; /* Begin command construction*/ REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "GEORADIUSBYMEMBER"); /* Prefix our key if we're prefixing */ keyfree = redis_key_prefix(redis_sock, &key, &keylen); /* Append required arguments */ redis_cmd_append_sstr(&cmdstr, key, keylen); redis_cmd_append_sstr(&cmdstr, mem, memlen); redis_cmd_append_sstr_long(&cmdstr, radius); redis_cmd_append_sstr(&cmdstr, unit, unitlen); /* Append options */ append_georadius_opts(&cmdstr, withcoord, withdist, withhash, count, sort); /* Free key if we prefixed */ if (keyfree) efree(key); CMD_SET_SLOT(slot, key, keylen); *cmd = cmdstr.c; *cmd_len = cmdstr.len; return SUCCESS; } /* MIGRATE */ int redis_migrate_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { smart_string cmdstr = {0}; char *host, *key; int argc, keyfree; zval *z_keys, *z_key; strlen_t hostlen, keylen; zend_long destdb, port, timeout; zend_bool copy = 0, replace = 0; zend_string *zstr; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "slzll|bb", &host, &hostlen, &port, &z_keys, &destdb, &timeout, ©, &replace) == FAILURE) { return FAILURE; } /* Protect against being passed an array with zero elements */ if (Z_TYPE_P(z_keys) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(z_keys)) == 0) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Keys array cannot be empty"); return FAILURE; } /* host, port, key|"", dest-db, timeout, [copy, replace] [KEYS key1..keyN] */ argc = 5 + copy + replace; if (Z_TYPE_P(z_keys) == IS_ARRAY) { /* +1 for the "KEYS" argument itself */ argc += 1 + zend_hash_num_elements(Z_ARRVAL_P(z_keys)); } /* Initialize MIGRATE command with host and port */ REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "MIGRATE"); redis_cmd_append_sstr(&cmdstr, host, hostlen); redis_cmd_append_sstr_long(&cmdstr, port); /* If passed a keys array the keys come later, otherwise pass the key to * migrate here */ if (Z_TYPE_P(z_keys) == IS_ARRAY) { REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, ""); } else { /* Grab passed value as a string */ zstr = zval_get_string(z_keys); /* We may need to prefix our string */ key = ZSTR_VAL(zstr); keylen = ZSTR_LEN(zstr); keyfree = redis_key_prefix(redis_sock, &key, &keylen); /* Add key to migrate */ redis_cmd_append_sstr(&cmdstr, key, keylen); zend_string_release(zstr); if (keyfree) efree(key); } redis_cmd_append_sstr_long(&cmdstr, destdb); redis_cmd_append_sstr_long(&cmdstr, timeout); REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, copy, "COPY"); REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, replace, "REPLACE"); /* Append actual keys if we've got a keys array */ if (Z_TYPE_P(z_keys) == IS_ARRAY) { REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "KEYS"); ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_keys), z_key) { zstr = zval_get_string(z_key); key = ZSTR_VAL(zstr); keylen = ZSTR_LEN(zstr); keyfree = redis_key_prefix(redis_sock, &key, &keylen); /* Append the key */ redis_cmd_append_sstr(&cmdstr, key, keylen); zend_string_release(zstr); if (keyfree) efree(key); } ZEND_HASH_FOREACH_END(); } *cmd = cmdstr.c; *cmd_len = cmdstr.len; return SUCCESS; } /* DEL */ int redis_del_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "DEL", sizeof("DEL")-1, 1, 0, cmd, cmd_len, slot); } /* WATCH */ int redis_watch_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "WATCH", sizeof("WATCH")-1, 1, 0, cmd, cmd_len, slot); } /* BLPOP */ int redis_blpop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "BLPOP", sizeof("BLPOP")-1, 2, 1, cmd, cmd_len, slot); } /* BRPOP */ int redis_brpop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "BRPOP", sizeof("BRPOP")-1, 1, 1, cmd, cmd_len, slot); } /* SINTER */ int redis_sinter_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "SINTER", sizeof("SINTER")-1, 1, 0, cmd, cmd_len, slot); } /* SINTERSTORE */ int redis_sinterstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "SINTERSTORE", sizeof("SINTERSTORE")-1, 1, 0, cmd, cmd_len, slot); } /* SUNION */ int redis_sunion_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "SUNION", sizeof("SUNION")-1, 1, 0, cmd, cmd_len, slot); } /* SUNIONSTORE */ int redis_sunionstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "SUNIONSTORE", sizeof("SUNIONSTORE")-1, 2, 0, cmd, cmd_len, slot); } /* SDIFF */ int redis_sdiff_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "SDIFF", sizeof("SDIFF")-1, 1, 0, cmd, cmd_len, slot); } /* SDIFFSTORE */ int redis_sdiffstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "SDIFFSTORE", sizeof("SDIFFSTORE")-1, 1, 0, cmd, cmd_len, slot); } /* COMMAND */ int redis_command_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { char *kw=NULL; zval *z_arg; strlen_t kw_len; /* Parse our args */ if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sz", &kw, &kw_len, &z_arg)==FAILURE) { return FAILURE; } /* Construct our command */ if(!kw) { *cmd_len = REDIS_CMD_SPPRINTF(cmd, "COMMAND", ""); } else if (!z_arg) { /* Sanity check */ if (strncasecmp(kw, "count", sizeof("count") - 1)) { return FAILURE; } /* COMMAND COUNT */ *cmd_len = REDIS_CMD_SPPRINTF(cmd, "COMMAND", "s", "COUNT", sizeof("COUNT") - 1); } else if (Z_TYPE_P(z_arg) == IS_STRING) { /* Sanity check */ if (strncasecmp(kw, "info", sizeof("info") - 1)) { return FAILURE; } /* COMMAND INFO */ *cmd_len = REDIS_CMD_SPPRINTF(cmd, "COMMAND", "ss", "INFO", sizeof("INFO") - 1, Z_STRVAL_P(z_arg), Z_STRLEN_P(z_arg)); } else { int arr_len; /* Sanity check on args */ if(strncasecmp(kw, "getkeys", sizeof("getkeys")-1) || Z_TYPE_P(z_arg)!=IS_ARRAY || (arr_len=zend_hash_num_elements(Z_ARRVAL_P(z_arg)))<1) { return FAILURE; } zval *z_ele; HashTable *ht_arr = Z_ARRVAL_P(z_arg); smart_string cmdstr = {0}; redis_cmd_init_sstr(&cmdstr, 1 + arr_len, "COMMAND", sizeof("COMMAND")-1); redis_cmd_append_sstr(&cmdstr, "GETKEYS", sizeof("GETKEYS")-1); ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) { zend_string *zstr = zval_get_string(z_ele); redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr)); zend_string_release(zstr); } ZEND_HASH_FOREACH_END(); *cmd = cmdstr.c; *cmd_len = cmdstr.len; } /* Any slot will do */ CMD_RAND_SLOT(slot); return SUCCESS; } /* * Redis commands that don't deal with the server at all. The RedisSock* * pointer is the only thing retreived differently, so we just take that * in additon to the standard INTERNAL_FUNCTION_PARAMETERS for arg parsing, * return value handling, and thread safety. */ void redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, redisCluster *c) { zend_long option; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &option) == FAILURE) { RETURN_FALSE; } // Return the requested option switch(option) { case REDIS_OPT_SERIALIZER: RETURN_LONG(redis_sock->serializer); case REDIS_OPT_PREFIX: if (redis_sock->prefix) { RETURN_STRINGL(ZSTR_VAL(redis_sock->prefix), ZSTR_LEN(redis_sock->prefix)); } RETURN_NULL(); case REDIS_OPT_READ_TIMEOUT: RETURN_DOUBLE(redis_sock->read_timeout); case REDIS_OPT_SCAN: RETURN_LONG(redis_sock->scan); case REDIS_OPT_FAILOVER: RETURN_LONG(c->failover); default: RETURN_FALSE; } } void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, redisCluster *c) { long val_long; zend_long option; char *val_str; struct timeval read_tv; strlen_t val_len; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ls", &option, &val_str, &val_len) == FAILURE) { RETURN_FALSE; } switch(option) { case REDIS_OPT_SERIALIZER: val_long = atol(val_str); if (val_long == REDIS_SERIALIZER_NONE || val_long == REDIS_SERIALIZER_PHP #ifdef HAVE_REDIS_IGBINARY || val_long == REDIS_SERIALIZER_IGBINARY #endif ) { redis_sock->serializer = val_long; RETURN_TRUE; } break; case REDIS_OPT_PREFIX: if (redis_sock->prefix) { zend_string_release(redis_sock->prefix); redis_sock->prefix = NULL; } if (val_str && val_len > 0) { redis_sock->prefix = zend_string_init(val_str, val_len, 0); } RETURN_TRUE; case REDIS_OPT_READ_TIMEOUT: redis_sock->read_timeout = atof(val_str); if(redis_sock->stream) { read_tv.tv_sec = (time_t)redis_sock->read_timeout; read_tv.tv_usec = (int)((redis_sock->read_timeout - read_tv.tv_sec) * 1000000); php_stream_set_option(redis_sock->stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &read_tv); } RETURN_TRUE; case REDIS_OPT_SCAN: val_long = atol(val_str); if(val_long==REDIS_SCAN_NORETRY || val_long==REDIS_SCAN_RETRY) { redis_sock->scan = val_long; RETURN_TRUE; } break; case REDIS_OPT_FAILOVER: val_long = atol(val_str); if (val_long == REDIS_FAILOVER_NONE || val_long == REDIS_FAILOVER_ERROR || val_long == REDIS_FAILOVER_DISTRIBUTE || val_long == REDIS_FAILOVER_DISTRIBUTE_SLAVES) { c->failover = val_long; RETURN_TRUE; } break; EMPTY_SWITCH_DEFAULT_CASE() } RETURN_FALSE; } void redis_prefix_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) { char *key; strlen_t key_len; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len) ==FAILURE) { RETURN_FALSE; } if (redis_sock->prefix) { int keyfree = redis_key_prefix(redis_sock, &key, &key_len); RETVAL_STRINGL(key, key_len); if (keyfree) efree(key); } else { RETURN_STRINGL(key, key_len); } } void redis_serialize_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) { zval *z_val; char *val; strlen_t val_len; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &z_val)==FAILURE) { RETURN_FALSE; } int val_free = redis_serialize(redis_sock, z_val, &val, &val_len TSRMLS_CC); RETVAL_STRINGL(val, val_len); if(val_free) efree(val); } void redis_unserialize_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zend_class_entry *ex) { char *value; strlen_t value_len; // Parse our arguments if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &value, &value_len) == FAILURE) { RETURN_FALSE; } // We only need to attempt unserialization if we have a serializer running if (redis_sock->serializer == REDIS_SERIALIZER_NONE) { // Just return the value that was passed to us RETURN_STRINGL(value, value_len); } zval zv, *z_ret = &zv; if (!redis_unserialize(redis_sock, value, value_len, z_ret TSRMLS_CC)) { // Badly formed input, throw an execption zend_throw_exception(ex, "Invalid serialized data, or unserialization error", 0 TSRMLS_CC); RETURN_FALSE; } RETURN_ZVAL(z_ret, 1, 0); } /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */ redis-3.1.6/redis_commands.h0000644000175000000120000002657013223116600016552 0ustar pyatsukhnenkowheel#ifndef REDIS_COMMANDS_H #define REDIS_COMMANDS_H #include "common.h" #include "library.h" #include "cluster_library.h" /* Pick a random slot, any slot (for stuff like publish/subscribe) */ #define CMD_RAND_SLOT(slot) \ if(slot) *slot = rand() % REDIS_CLUSTER_MOD /* Macro for setting the slot if we've been asked to */ #define CMD_SET_SLOT(slot,key,key_len) \ if (slot) *slot = cluster_hash_key(key,key_len); /* Simple container so we can push subscribe context out */ typedef struct subscribeContext { char *kw; int argc; zend_fcall_info cb; zend_fcall_info_cache cb_cache; } subscribeContext; /* Georadius sort type */ typedef enum geoSortType { SORT_NONE, SORT_ASC, SORT_DESC } geoSortType; /* Construct a raw command */ int redis_build_raw_cmd(zval *z_args, int argc, char **cmd, int *cmd_len TSRMLS_DC); /* Redis command generics. Many commands share common prototypes meaning that * we can write one function to handle all of them. For example, there are * many COMMAND key value commands, or COMMAND key commands. */ int redis_empty_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_key_long_val_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_key_long_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_kv_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_key_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_key_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_key_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_key_long_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_key_str_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_key_dbl_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_key_varval_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_key_arr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); /* Construct SCAN and similar commands, as well as check iterator */ int redis_scan_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, REDIS_SCAN_TYPE type, char **cmd, int *cmd_len); /* ZRANGE, ZREVRANGE, ZRANGEBYSCORE, and ZREVRANGEBYSCORE callback type */ typedef int (*zrange_cb)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *,char**,int*,int*,short*,void**); int redis_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, int *withscores, short *slot, void **ctx); int redis_zrangebyscore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, int *withscores, short *slot, void **ctx); int redis_zinter_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_zrangebylex_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_gen_zlex_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_eval_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); /* Commands which need a unique construction mechanism. This is either because * they don't share a signature with any other command, or because there is * specific processing we do (e.g. verifying subarguments) that make them * unique */ int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_brpoplpush_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_incr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_decr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_hincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_hincrbyfloat_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_hmget_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_hmset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_hstrlen_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_bitop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_bitcount_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_bitpos_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_pfcount_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_pfadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_pfmerge_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_auth_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_setbit_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_linsert_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_lrem_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_smove_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_hset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_hsetnx_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_srandmember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx, short *have_count); int redis_zincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, int *using_store, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_hdel_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_zadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_object_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, REDIS_REPLY_TYPE *rtype, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_del_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_watch_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_blpop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_brpop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_sinter_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_sinterstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_sunion_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_sunionstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_sdiff_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_sdiffstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_command_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_fmt_scan_cmd(char **cmd, REDIS_SCAN_TYPE type, char *key, int key_len, long it, char *pat, int pat_len, long count); int redis_geodist_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_georadius_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_georadiusbymember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_migrate_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); /* Commands that don't communicate with Redis at all (such as getOption, * setOption, _prefix, _serialize, etc). These can be handled in one place * with the method of grabbing our RedisSock* object in different ways * depending if this is a Redis object or a RedisCluster object. */ void redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, redisCluster *c); void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, redisCluster *c); void redis_prefix_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); void redis_serialize_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); void redis_unserialize_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zend_class_entry *ex); #endif /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */ redis-3.1.6/redis.c0000644000175000000120000032233413223116600014661 0ustar pyatsukhnenkowheel/* -*- Mode: C; tab-width: 4 -*- */ /* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2009 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Original author: Alfonso Jimenez | | Maintainer: Nicolas Favre-Felix | | Maintainer: Nasreddine Bouafif | | Maintainer: Michael Grunder | +----------------------------------------------------------------------+ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "common.h" #include "ext/standard/info.h" #include "php_redis.h" #include "redis_commands.h" #include "redis_array.h" #include "redis_cluster.h" #include #ifdef PHP_SESSION #include "ext/session/php_session.h" #endif #include "library.h" #ifdef PHP_SESSION extern ps_module ps_mod_redis; extern ps_module ps_mod_redis_cluster; #endif extern zend_class_entry *redis_array_ce; extern zend_class_entry *redis_cluster_ce; extern zend_class_entry *redis_cluster_exception_ce; zend_class_entry *redis_ce; zend_class_entry *redis_exception_ce; extern zend_function_entry redis_array_functions[]; extern zend_function_entry redis_cluster_functions[]; PHP_INI_BEGIN() /* redis arrays */ PHP_INI_ENTRY("redis.arrays.names", "", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.arrays.hosts", "", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.arrays.previous", "", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.arrays.functions", "", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.arrays.index", "", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.arrays.autorehash", "", PHP_INI_ALL, NULL) /* redis cluster */ PHP_INI_ENTRY("redis.clusters.seeds", "", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.clusters.timeout", "", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.clusters.read_timeout", "", PHP_INI_ALL, NULL) PHP_INI_END() /** {{{ Argument info for commands in redis 1.0 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_connect, 0, 0, 2) ZEND_ARG_INFO(0, host) ZEND_ARG_INFO(0, port) ZEND_ARG_INFO(0, timeout) ZEND_ARG_INFO(0, retry_interval) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_pconnect, 0, 0, 2) ZEND_ARG_INFO(0, host) ZEND_ARG_INFO(0, port) ZEND_ARG_INFO(0, timeout) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_mget, 0, 0, 1) ZEND_ARG_ARRAY_INFO(0, keys, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_del, 0, 0, 1) ZEND_ARG_INFO(0, key) #if PHP_VERSION_ID >= 50600 ZEND_ARG_VARIADIC_INFO(0, other_keys) #else ZEND_ARG_INFO(0, ...) #endif ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_keys, 0, 0, 1) ZEND_ARG_INFO(0, pattern) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_generic_sort, 0, 0, 1) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, pattern) ZEND_ARG_INFO(0, get) ZEND_ARG_INFO(0, start) ZEND_ARG_INFO(0, end) ZEND_ARG_INFO(0, getList) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_lrem, 0, 0, 3) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, value) ZEND_ARG_INFO(0, count) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_auth, 0, 0, 1) ZEND_ARG_INFO(0, password) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_select, 0, 0, 1) ZEND_ARG_INFO(0, dbindex) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_move, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, dbindex) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_slaveof, 0, 0, 0) ZEND_ARG_INFO(0, host) ZEND_ARG_INFO(0, port) ZEND_END_ARG_INFO() /* }}} */ ZEND_BEGIN_ARG_INFO_EX(arginfo_migrate, 0, 0, 5) ZEND_ARG_INFO(0, host) ZEND_ARG_INFO(0, port) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, db) ZEND_ARG_INFO(0, timeout) ZEND_ARG_INFO(0, copy) ZEND_ARG_INFO(0, replace) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_wait, 0, 0, 2) ZEND_ARG_INFO(0, numslaves) ZEND_ARG_INFO(0, timeout) ZEND_END_ARG_INFO() /** * Argument info for the SCAN proper */ ZEND_BEGIN_ARG_INFO_EX(arginfo_scan, 0, 0, 1) ZEND_ARG_INFO(1, i_iterator) ZEND_ARG_INFO(0, str_pattern) ZEND_ARG_INFO(0, i_count) ZEND_END_ARG_INFO() /** * Argument info for key scanning */ ZEND_BEGIN_ARG_INFO_EX(arginfo_kscan, 0, 0, 2) ZEND_ARG_INFO(0, str_key) ZEND_ARG_INFO(1, i_iterator) ZEND_ARG_INFO(0, str_pattern) ZEND_ARG_INFO(0, i_count) ZEND_END_ARG_INFO() #ifdef ZTS ZEND_DECLARE_MODULE_GLOBALS(redis) #endif static zend_function_entry redis_functions[] = { PHP_ME(Redis, __construct, NULL, ZEND_ACC_CTOR | ZEND_ACC_PUBLIC) PHP_ME(Redis, __destruct, NULL, ZEND_ACC_DTOR | ZEND_ACC_PUBLIC) PHP_ME(Redis, connect, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pconnect, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, close, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, ping, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, echo, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, get, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, set, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, setex, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, psetex, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, setnx, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getSet, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, randomKey, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, renameKey, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, renameNx, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getMultiple, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, exists, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, delete, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, incr, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, incrBy, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, incrByFloat, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, decr, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, decrBy, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, type, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, append, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getRange, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, setRange, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getBit, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, setBit, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, strlen, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getKeys, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sort, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sortAsc, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sortAscAlpha, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sortDesc, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sortDescAlpha, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lPush, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, rPush, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lPushx, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, rPushx, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lPop, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, rPop, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, blPop, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, brPop, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lSize, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lRemove, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, listTrim, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lGet, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lGetRange, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lSet, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lInsert, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sAdd, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sAddArray, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sSize, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sRemove, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sMove, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sPop, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sRandMember, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sContains, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sMembers, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sInter, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sInterStore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sUnion, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sUnionStore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sDiff, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, sDiffStore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, setTimeout, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, save, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, bgSave, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, lastSave, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, flushDB, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, flushAll, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, dbSize, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, auth, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, ttl, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pttl, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, persist, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, info, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, select, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, move, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, bgrewriteaof, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, slaveof, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, object, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, bitop, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, bitcount, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, bitpos, NULL, ZEND_ACC_PUBLIC) /* 1.1 */ PHP_ME(Redis, mset, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, msetnx, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, rpoplpush, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, brpoplpush, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zAdd, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zDelete, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRange, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRevRange, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRangeByScore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRevRangeByScore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRangeByLex, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRevRangeByLex, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zLexCount, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRemRangeByLex, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zCount, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zDeleteRangeByScore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zDeleteRangeByRank, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zCard, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zScore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRank, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRevRank, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zInter, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zUnion, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zIncrBy, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, expireAt, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pexpire, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pexpireAt, NULL, ZEND_ACC_PUBLIC) /* 1.2 */ PHP_ME(Redis, hGet, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hSet, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hSetNx, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hDel, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hLen, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hKeys, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hVals, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hGetAll, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hExists, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hIncrBy, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hIncrByFloat, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hMset, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hMget, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, hStrLen, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, multi, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, discard, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, exec, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pipeline, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, watch, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, unwatch, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, publish, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, subscribe, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, psubscribe, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, unsubscribe, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, punsubscribe, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, time, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, role, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, eval, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, evalsha, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, script, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, debug, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, dump, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, restore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, migrate, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getLastError, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, clearLastError, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, _prefix, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, _serialize, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, _unserialize, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, client, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, command, NULL, ZEND_ACC_PUBLIC) /* SCAN and friends */ PHP_ME(Redis, scan, arginfo_scan, ZEND_ACC_PUBLIC) PHP_ME(Redis, hscan, arginfo_kscan, ZEND_ACC_PUBLIC) PHP_ME(Redis, zscan, arginfo_kscan, ZEND_ACC_PUBLIC) PHP_ME(Redis, sscan, arginfo_kscan, ZEND_ACC_PUBLIC) /* HyperLogLog commands */ PHP_ME(Redis, pfadd, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pfcount, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pfmerge, NULL, ZEND_ACC_PUBLIC) /* options */ PHP_ME(Redis, getOption, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, setOption, NULL, ZEND_ACC_PUBLIC) /* config */ PHP_ME(Redis, config, NULL, ZEND_ACC_PUBLIC) /* slowlog */ PHP_ME(Redis, slowlog, NULL, ZEND_ACC_PUBLIC) /* Send a raw command and read raw results */ PHP_ME(Redis, rawcommand, NULL, ZEND_ACC_PUBLIC) /* geoadd and friends */ PHP_ME(Redis, geoadd, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, geohash, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, geopos, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, geodist, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, georadius, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, georadiusbymember, NULL, ZEND_ACC_PUBLIC) /* introspection */ PHP_ME(Redis, getHost, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getPort, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getDBNum, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getTimeout, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getReadTimeout, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getPersistentID, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getAuth, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, isConnected, NULL, ZEND_ACC_PUBLIC) /* TODO: document getMode() and wait() in README? */ PHP_ME(Redis, getMode, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, wait, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pubsub, NULL, ZEND_ACC_PUBLIC) /* aliases */ PHP_MALIAS(Redis, open, connect, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, popen, pconnect, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, lLen, lSize, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, sGetMembers, sMembers, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, mget, getMultiple, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, expire, setTimeout, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zunionstore, zUnion, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zinterstore, zInter, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zRemove, zDelete, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zRem, zDelete, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zRemoveRangeByScore, zDeleteRangeByScore, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zRemRangeByScore, zDeleteRangeByScore, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zRemRangeByRank, zDeleteRangeByRank, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zSize, zCard, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, substr, getRange, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, rename, renameKey, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, del, delete, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, keys, getKeys, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, lrem, lRemove, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, ltrim, listTrim, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, lindex, lGet, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, lrange, lGetRange, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, scard, sSize, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, srem, sRemove, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, sismember, sContains, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zReverseRange, zRevRange, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, sendEcho, echo, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, evaluate, eval, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, evaluateSha, evalsha, NULL, ZEND_ACC_PUBLIC) PHP_FE_END }; static const zend_module_dep redis_deps[] = { #ifdef HAVE_REDIS_IGBINARY ZEND_MOD_REQUIRED("igbinary") #endif ZEND_MOD_END }; zend_module_entry redis_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER_EX, NULL, redis_deps, #endif "redis", NULL, PHP_MINIT(redis), PHP_MSHUTDOWN(redis), NULL, NULL, PHP_MINFO(redis), #if ZEND_MODULE_API_NO >= 20010901 PHP_REDIS_VERSION, #endif STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_REDIS ZEND_GET_MODULE(redis) #endif /* Send a static DISCARD in case we're in MULTI mode. */ static int redis_send_discard(RedisSock *redis_sock TSRMLS_DC) { int result = FAILURE; char *cmd, *resp; int resp_len, cmd_len; /* format our discard command */ cmd_len = REDIS_SPPRINTF(&cmd, "DISCARD", ""); /* send our DISCARD command */ if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) >= 0 && (resp = redis_sock_read(redis_sock,&resp_len TSRMLS_CC)) != NULL) { /* success if we get OK */ result = (resp_len == 3 && strncmp(resp,"+OK", 3)==0) ? SUCCESS:FAILURE; /* free our response */ efree(resp); } /* free our command */ efree(cmd); /* return success/failure */ return result; } static void free_reply_callbacks(RedisSock *redis_sock) { fold_item *fi; for (fi = redis_sock->head; fi; ) { fold_item *fi_next = fi->next; free(fi); fi = fi_next; } redis_sock->head = NULL; redis_sock->current = NULL; } #if (PHP_MAJOR_VERSION < 7) void free_redis_object(void *object TSRMLS_DC) { redis_object *redis = (redis_object *)object; zend_object_std_dtor(&redis->std TSRMLS_CC); if (redis->sock) { redis_sock_disconnect(redis->sock TSRMLS_CC); redis_free_socket(redis->sock); } efree(redis); } zend_object_value create_redis_object(zend_class_entry *ce TSRMLS_DC) { zend_object_value retval; redis_object *redis = ecalloc(1, sizeof(redis_object)); memset(redis, 0, sizeof(redis_object)); zend_object_std_init(&redis->std, ce TSRMLS_CC); #if PHP_VERSION_ID < 50399 zval *tmp; zend_hash_copy(redis->std.properties, &ce->default_properties, (copy_ctor_func_t)zval_add_ref, (void *)&tmp, sizeof(zval *)); #else object_properties_init(&redis->std, ce); #endif retval.handle = zend_objects_store_put(redis, (zend_objects_store_dtor_t)zend_objects_destroy_object, (zend_objects_free_object_storage_t)free_redis_object, NULL TSRMLS_CC); retval.handlers = zend_get_std_object_handlers(); return retval; } #else zend_object_handlers redis_object_handlers; void free_redis_object(zend_object *object) { redis_object *redis = (redis_object *)((char *)(object) - XtOffsetOf(redis_object, std)); zend_object_std_dtor(&redis->std TSRMLS_CC); if (redis->sock) { redis_sock_disconnect(redis->sock TSRMLS_CC); redis_free_socket(redis->sock); } } zend_object * create_redis_object(zend_class_entry *ce TSRMLS_DC) { redis_object *redis = ecalloc(1, sizeof(redis_object) + zend_object_properties_size(ce)); redis->sock = NULL; zend_object_std_init(&redis->std, ce TSRMLS_CC); object_properties_init(&redis->std, ce); memcpy(&redis_object_handlers, zend_get_std_object_handlers(), sizeof(redis_object_handlers)); redis_object_handlers.offset = XtOffsetOf(redis_object, std); redis_object_handlers.free_obj = free_redis_object; redis->std.handlers = &redis_object_handlers; return &redis->std; } #endif static zend_always_inline RedisSock * redis_sock_get_instance(zval *id TSRMLS_DC, int no_throw) { redis_object *redis; if (Z_TYPE_P(id) == IS_OBJECT) { #if (PHP_MAJOR_VERSION < 7) redis = (redis_object *)zend_objects_get_address(id TSRMLS_CC); #else redis = (redis_object *)((char *)Z_OBJ_P(id) - XtOffsetOf(redis_object, std)); #endif if (redis->sock) { return redis->sock; } } // Throw an exception unless we've been requested not to if (!no_throw) { zend_throw_exception(redis_exception_ce, "Redis server went away", 0 TSRMLS_CC); } return NULL; } /** * redis_sock_get */ PHP_REDIS_API RedisSock * redis_sock_get(zval *id TSRMLS_DC, int no_throw) { RedisSock *redis_sock; if ((redis_sock = redis_sock_get_instance(id TSRMLS_CC, no_throw)) == NULL) { return NULL; } if (redis_sock->lazy_connect) { redis_sock->lazy_connect = 0; if (redis_sock_server_open(redis_sock TSRMLS_CC) < 0) { return NULL; } } return redis_sock; } /** * redis_sock_get_direct * Returns our attached RedisSock pointer if we're connected */ PHP_REDIS_API RedisSock *redis_sock_get_connected(INTERNAL_FUNCTION_PARAMETERS) { zval *object; RedisSock *redis_sock; // If we can't grab our object, or get a socket, or we're not connected, // return NULL if((zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) || (redis_sock = redis_sock_get(object TSRMLS_CC, 1)) == NULL || redis_sock->status != REDIS_SOCK_STATUS_CONNECTED) { return NULL; } /* Return our socket */ return redis_sock; } /* Redis and RedisCluster objects share serialization/prefixing settings so * this is a generic function to add class constants to either */ static void add_class_constants(zend_class_entry *ce, int is_cluster TSRMLS_DC) { zend_declare_class_constant_long(ce, ZEND_STRL("REDIS_NOT_FOUND"), REDIS_NOT_FOUND TSRMLS_CC); zend_declare_class_constant_long(ce, ZEND_STRL("REDIS_STRING"), REDIS_STRING TSRMLS_CC); zend_declare_class_constant_long(ce, ZEND_STRL("REDIS_SET"), REDIS_SET TSRMLS_CC); zend_declare_class_constant_long(ce, ZEND_STRL("REDIS_LIST"), REDIS_LIST TSRMLS_CC); zend_declare_class_constant_long(ce, ZEND_STRL("REDIS_ZSET"), REDIS_ZSET TSRMLS_CC); zend_declare_class_constant_long(ce, ZEND_STRL("REDIS_HASH"), REDIS_HASH TSRMLS_CC); /* Cluster doesn't support pipelining at this time */ if(!is_cluster) { zend_declare_class_constant_long(ce, ZEND_STRL("PIPELINE"), PIPELINE TSRMLS_CC); } /* Add common mode constants */ zend_declare_class_constant_long(ce, ZEND_STRL("ATOMIC"), ATOMIC TSRMLS_CC); zend_declare_class_constant_long(ce, ZEND_STRL("MULTI"), MULTI TSRMLS_CC); /* options */ zend_declare_class_constant_long(ce, ZEND_STRL("OPT_SERIALIZER"), REDIS_OPT_SERIALIZER TSRMLS_CC); zend_declare_class_constant_long(ce, ZEND_STRL("OPT_PREFIX"), REDIS_OPT_PREFIX TSRMLS_CC); zend_declare_class_constant_long(ce, ZEND_STRL("OPT_READ_TIMEOUT"), REDIS_OPT_READ_TIMEOUT TSRMLS_CC); /* serializer */ zend_declare_class_constant_long(ce, ZEND_STRL("SERIALIZER_NONE"), REDIS_SERIALIZER_NONE TSRMLS_CC); zend_declare_class_constant_long(ce, ZEND_STRL("SERIALIZER_PHP"), REDIS_SERIALIZER_PHP TSRMLS_CC); #ifdef HAVE_REDIS_IGBINARY zend_declare_class_constant_long(ce, ZEND_STRL("SERIALIZER_IGBINARY"), REDIS_SERIALIZER_IGBINARY TSRMLS_CC); #endif /* scan options*/ zend_declare_class_constant_long(ce, ZEND_STRL("OPT_SCAN"), REDIS_OPT_SCAN TSRMLS_CC); zend_declare_class_constant_long(ce, ZEND_STRL("SCAN_RETRY"), REDIS_SCAN_RETRY TSRMLS_CC); zend_declare_class_constant_long(ce, ZEND_STRL("SCAN_NORETRY"), REDIS_SCAN_NORETRY TSRMLS_CC); /* Cluster option to allow for slave failover */ if (is_cluster) { zend_declare_class_constant_long(ce, ZEND_STRL("OPT_SLAVE_FAILOVER"), REDIS_OPT_FAILOVER TSRMLS_CC); zend_declare_class_constant_long(ce, ZEND_STRL("FAILOVER_NONE"), REDIS_FAILOVER_NONE TSRMLS_CC); zend_declare_class_constant_long(ce, ZEND_STRL("FAILOVER_ERROR"), REDIS_FAILOVER_ERROR TSRMLS_CC); zend_declare_class_constant_long(ce, ZEND_STRL("FAILOVER_DISTRIBUTE"), REDIS_FAILOVER_DISTRIBUTE TSRMLS_CC); zend_declare_class_constant_long(ce, ZEND_STRL("FAILOVER_DISTRIBUTE_SLAVES"), REDIS_FAILOVER_DISTRIBUTE_SLAVES TSRMLS_CC); } zend_declare_class_constant_stringl(ce, "AFTER", 5, "after", 5 TSRMLS_CC); zend_declare_class_constant_stringl(ce, "BEFORE", 6, "before", 6 TSRMLS_CC); } /** * PHP_MINIT_FUNCTION */ PHP_MINIT_FUNCTION(redis) { struct timeval tv; zend_class_entry redis_class_entry; zend_class_entry redis_array_class_entry; zend_class_entry redis_cluster_class_entry; zend_class_entry redis_exception_class_entry; zend_class_entry redis_cluster_exception_class_entry; zend_class_entry *exception_ce = NULL; /* Seed random generator (for RedisCluster failover) */ gettimeofday(&tv, NULL); srand(tv.tv_usec * tv.tv_sec); REGISTER_INI_ENTRIES(); /* Redis class */ INIT_CLASS_ENTRY(redis_class_entry, "Redis", redis_functions); redis_ce = zend_register_internal_class(&redis_class_entry TSRMLS_CC); redis_ce->create_object = create_redis_object; /* RedisArray class */ INIT_CLASS_ENTRY(redis_array_class_entry, "RedisArray", redis_array_functions); redis_array_ce = zend_register_internal_class(&redis_array_class_entry TSRMLS_CC); redis_array_ce->create_object = create_redis_array_object; /* RedisCluster class */ INIT_CLASS_ENTRY(redis_cluster_class_entry, "RedisCluster", redis_cluster_functions); redis_cluster_ce = zend_register_internal_class(&redis_cluster_class_entry TSRMLS_CC); redis_cluster_ce->create_object = create_cluster_context; /* Base Exception class */ #if HAVE_SPL exception_ce = zend_hash_str_find_ptr(CG(class_table), "RuntimeException", sizeof("RuntimeException") - 1); #endif if (exception_ce == NULL) { #if (PHP_MAJOR_VERSION == 5) && (PHP_MINOR_VERSION < 2) exception_ce = zend_exception_get_default(); #else exception_ce = zend_exception_get_default(TSRMLS_C); #endif } /* RedisException class */ INIT_CLASS_ENTRY(redis_exception_class_entry, "RedisException", NULL); redis_exception_ce = zend_register_internal_class_ex( &redis_exception_class_entry, #if (PHP_MAJOR_VERSION < 7) exception_ce, NULL TSRMLS_CC #else exception_ce #endif ); /* RedisClusterException class */ INIT_CLASS_ENTRY(redis_cluster_exception_class_entry, "RedisClusterException", NULL); redis_cluster_exception_ce = zend_register_internal_class_ex( &redis_cluster_exception_class_entry, #if (PHP_MAJOR_VERSION < 7) exception_ce, NULL TSRMLS_CC #else exception_ce #endif ); /* Add shared class constants to Redis and RedisCluster objects */ add_class_constants(redis_ce, 0 TSRMLS_CC); add_class_constants(redis_cluster_ce, 1 TSRMLS_CC); #ifdef PHP_SESSION php_session_register_module(&ps_mod_redis); php_session_register_module(&ps_mod_redis_cluster); #endif return SUCCESS; } /** * PHP_MSHUTDOWN_FUNCTION */ PHP_MSHUTDOWN_FUNCTION(redis) { return SUCCESS; } /** * PHP_MINFO_FUNCTION */ PHP_MINFO_FUNCTION(redis) { php_info_print_table_start(); php_info_print_table_header(2, "Redis Support", "enabled"); php_info_print_table_row(2, "Redis Version", PHP_REDIS_VERSION); #ifdef HAVE_REDIS_IGBINARY php_info_print_table_row(2, "Available serializers", "php, igbinary"); #else php_info_print_table_row(2, "Available serializers", "php"); #endif php_info_print_table_end(); } /* {{{ proto Redis Redis::__construct() Public constructor */ PHP_METHOD(Redis, __construct) { if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") == FAILURE) { RETURN_FALSE; } } /* }}} */ /* {{{ proto Redis Redis::__destruct() Public Destructor */ PHP_METHOD(Redis,__destruct) { if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") == FAILURE) { RETURN_FALSE; } // Grab our socket RedisSock *redis_sock; if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 1)) == NULL) { RETURN_FALSE; } // If we think we're in MULTI mode, send a discard IF_MULTI() { IF_NOT_PIPELINE() { // Discard any multi commands, and free any callbacks that have been // queued redis_send_discard(redis_sock TSRMLS_CC); } free_reply_callbacks(redis_sock); } } /* {{{ proto boolean Redis::connect(string host, int port [, double timeout [, long retry_interval]]) */ PHP_METHOD(Redis, connect) { if (redis_connect(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0) == FAILURE) { RETURN_FALSE; } else { RETURN_TRUE; } } /* }}} */ /* {{{ proto boolean Redis::pconnect(string host, int port [, double timeout]) */ PHP_METHOD(Redis, pconnect) { if (redis_connect(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1) == FAILURE) { RETURN_FALSE; } else { RETURN_TRUE; } } /* }}} */ PHP_REDIS_API int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) { zval *object; char *host = NULL, *persistent_id = ""; zend_long port = -1, retry_interval = 0; strlen_t host_len, persistent_id_len; double timeout = 0.0, read_timeout = 0.0; redis_object *redis; #ifdef ZTS /* not sure how in threaded mode this works so disabled persistence at * first */ persistent = 0; #endif if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|ldsld", &object, redis_ce, &host, &host_len, &port, &timeout, &persistent_id, &persistent_id_len, &retry_interval, &read_timeout) == FAILURE) { return FAILURE; } else if (!persistent) { persistent_id = NULL; } if (timeout < 0L || timeout > INT_MAX) { zend_throw_exception(redis_exception_ce, "Invalid connect timeout", 0 TSRMLS_CC); return FAILURE; } if (read_timeout < 0L || read_timeout > INT_MAX) { zend_throw_exception(redis_exception_ce, "Invalid read timeout", 0 TSRMLS_CC); return FAILURE; } if (retry_interval < 0L || retry_interval > INT_MAX) { zend_throw_exception(redis_exception_ce, "Invalid retry interval", 0 TSRMLS_CC); return FAILURE; } /* If it's not a unix socket, set to default */ if(port == -1 && host_len && host[0] != '/') { port = 6379; } #if (PHP_MAJOR_VERSION < 7) redis = (redis_object *)zend_objects_get_address(object TSRMLS_CC); #else redis = (redis_object *)((char *)Z_OBJ_P(object) - XtOffsetOf(redis_object, std)); #endif /* if there is a redis sock already we have to remove it */ if (redis->sock) { redis_sock_disconnect(redis->sock TSRMLS_CC); redis_free_socket(redis->sock); } redis->sock = redis_sock_create(host, host_len, port, timeout, read_timeout, persistent, persistent_id, retry_interval, 0); if (redis_sock_server_open(redis->sock TSRMLS_CC) < 0) { redis_free_socket(redis->sock); redis->sock = NULL; return FAILURE; } return SUCCESS; } /* {{{ proto long Redis::bitop(string op, string key, ...) */ PHP_METHOD(Redis, bitop) { REDIS_PROCESS_CMD(bitop, redis_long_response); } /* }}} */ /* {{{ proto long Redis::bitcount(string key, [int start], [int end]) */ PHP_METHOD(Redis, bitcount) { REDIS_PROCESS_CMD(bitcount, redis_long_response); } /* }}} */ /* {{{ proto integer Redis::bitpos(string key, int bit, [int start, int end]) */ PHP_METHOD(Redis, bitpos) { REDIS_PROCESS_CMD(bitpos, redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::close() */ PHP_METHOD(Redis, close) { RedisSock *redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU); if (redis_sock && redis_sock_disconnect(redis_sock TSRMLS_CC)) { RETURN_TRUE; } RETURN_FALSE; } /* }}} */ /* {{{ proto boolean Redis::set(string key, mixed val, long timeout, * [array opt) */ PHP_METHOD(Redis, set) { REDIS_PROCESS_CMD(set, redis_boolean_response); } /* {{{ proto boolean Redis::setex(string key, long expire, string value) */ PHP_METHOD(Redis, setex) { REDIS_PROCESS_KW_CMD("SETEX", redis_key_long_val_cmd, redis_boolean_response); } /* {{{ proto boolean Redis::psetex(string key, long expire, string value) */ PHP_METHOD(Redis, psetex) { REDIS_PROCESS_KW_CMD("PSETEX", redis_key_long_val_cmd, redis_boolean_response); } /* {{{ proto boolean Redis::setnx(string key, string value) */ PHP_METHOD(Redis, setnx) { REDIS_PROCESS_KW_CMD("SETNX", redis_kv_cmd, redis_1_response); } /* }}} */ /* {{{ proto string Redis::getSet(string key, string value) */ PHP_METHOD(Redis, getSet) { REDIS_PROCESS_KW_CMD("GETSET", redis_kv_cmd, redis_string_response); } /* }}} */ /* {{{ proto string Redis::randomKey() */ PHP_METHOD(Redis, randomKey) { REDIS_PROCESS_KW_CMD("RANDOMKEY", redis_empty_cmd, redis_ping_response); } /* }}} */ /* {{{ proto string Redis::echo(string msg) */ PHP_METHOD(Redis, echo) { REDIS_PROCESS_KW_CMD("ECHO", redis_str_cmd, redis_string_response); } /* }}} */ /* {{{ proto string Redis::renameKey(string key_src, string key_dst) */ PHP_METHOD(Redis, renameKey) { REDIS_PROCESS_KW_CMD("RENAME", redis_key_key_cmd, redis_boolean_response); } /* }}} */ /* {{{ proto string Redis::renameNx(string key_src, string key_dst) */ PHP_METHOD(Redis, renameNx) { REDIS_PROCESS_KW_CMD("RENAMENX", redis_key_key_cmd, redis_1_response); } /* }}} */ /* }}} */ /* {{{ proto string Redis::get(string key) */ PHP_METHOD(Redis, get) { REDIS_PROCESS_KW_CMD("GET", redis_key_cmd, redis_string_response); } /* }}} */ /* {{{ proto string Redis::ping() */ PHP_METHOD(Redis, ping) { REDIS_PROCESS_KW_CMD("PING", redis_empty_cmd, redis_ping_response); } /* }}} */ /* {{{ proto boolean Redis::incr(string key [,int value]) */ PHP_METHOD(Redis, incr){ REDIS_PROCESS_CMD(incr, redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::incrBy(string key ,int value) */ PHP_METHOD(Redis, incrBy){ REDIS_PROCESS_KW_CMD("INCRBY", redis_key_long_cmd, redis_long_response); } /* }}} */ /* {{{ proto float Redis::incrByFloat(string key, float value) */ PHP_METHOD(Redis, incrByFloat) { REDIS_PROCESS_KW_CMD("INCRBYFLOAT", redis_key_dbl_cmd, redis_bulk_double_response); } /* }}} */ /* {{{ proto boolean Redis::decr(string key) */ PHP_METHOD(Redis, decr) { REDIS_PROCESS_CMD(decr, redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::decrBy(string key ,int value) */ PHP_METHOD(Redis, decrBy){ REDIS_PROCESS_KW_CMD("DECRBY", redis_key_long_cmd, redis_long_response); } /* }}} */ /* {{{ proto array Redis::getMultiple(array keys) */ PHP_METHOD(Redis, getMultiple) { zval *object, *z_args, *z_ele; HashTable *hash; RedisSock *redis_sock; smart_string cmd = {0}; int arg_count; /* Make sure we have proper arguments */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", &object, redis_ce, &z_args) == FAILURE) { RETURN_FALSE; } /* We'll need the socket */ if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } /* Grab our array */ hash = Z_ARRVAL_P(z_args); /* We don't need to do anything if there aren't any keys */ if((arg_count = zend_hash_num_elements(hash)) == 0) { RETURN_FALSE; } /* Build our command header */ redis_cmd_init_sstr(&cmd, arg_count, "MGET", 4); /* Iterate through and grab our keys */ ZEND_HASH_FOREACH_VAL(hash, z_ele) { zend_string *zstr = zval_get_string(z_ele); redis_cmd_append_sstr_key(&cmd, ZSTR_VAL(zstr), ZSTR_LEN(zstr), redis_sock, NULL); zend_string_release(zstr); } ZEND_HASH_FOREACH_END(); /* Kick off our command */ REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len); IF_ATOMIC() { if(redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } /* {{{ proto boolean Redis::exists(string key) */ PHP_METHOD(Redis, exists) { REDIS_PROCESS_KW_CMD("EXISTS", redis_key_cmd, redis_1_response); } /* }}} */ /* {{{ proto boolean Redis::delete(string key) */ PHP_METHOD(Redis, delete) { REDIS_PROCESS_CMD(del, redis_long_response); } /* }}} */ PHP_REDIS_API void redis_set_watch(RedisSock *redis_sock) { redis_sock->watching = 1; } PHP_REDIS_API void redis_watch_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, ctx, redis_set_watch); } /* {{{ proto boolean Redis::watch(string key1, string key2...) */ PHP_METHOD(Redis, watch) { REDIS_PROCESS_CMD(watch, redis_watch_response); } /* }}} */ PHP_REDIS_API void redis_clear_watch(RedisSock *redis_sock) { redis_sock->watching = 0; } PHP_REDIS_API void redis_unwatch_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, ctx, redis_clear_watch); } /* {{{ proto boolean Redis::unwatch() */ PHP_METHOD(Redis, unwatch) { REDIS_PROCESS_KW_CMD("UNWATCH", redis_empty_cmd, redis_unwatch_response); } /* }}} */ /* {{{ proto array Redis::getKeys(string pattern) */ PHP_METHOD(Redis, getKeys) { REDIS_PROCESS_KW_CMD("KEYS", redis_key_cmd, redis_mbulk_reply_raw); } /* }}} */ /* {{{ proto int Redis::type(string key) */ PHP_METHOD(Redis, type) { REDIS_PROCESS_KW_CMD("TYPE", redis_key_cmd, redis_type_response); } /* }}} */ /* {{{ proto long Redis::append(string key, string val) */ PHP_METHOD(Redis, append) { REDIS_PROCESS_KW_CMD("APPEND", redis_kv_cmd, redis_long_response); } /* }}} */ /* {{{ proto string Redis::GetRange(string key, long start, long end) */ PHP_METHOD(Redis, getRange) { REDIS_PROCESS_KW_CMD("GETRANGE", redis_key_long_long_cmd, redis_string_response); } /* }}} */ /* {{{ proto string Redis::setRange(string key, long start, string value) */ PHP_METHOD(Redis, setRange) { REDIS_PROCESS_KW_CMD("SETRANGE", redis_key_long_str_cmd, redis_long_response); } /* }}} */ /* {{{ proto long Redis::getbit(string key, long idx) */ PHP_METHOD(Redis, getBit) { REDIS_PROCESS_KW_CMD("GETBIT", redis_key_long_cmd, redis_long_response); } /* }}} */ /* {{{ proto long Redis::setbit(string key, long idx, bool|int value) */ PHP_METHOD(Redis, setBit) { REDIS_PROCESS_CMD(setbit, redis_long_response); } /* }}} */ /* {{{ proto long Redis::strlen(string key) */ PHP_METHOD(Redis, strlen) { REDIS_PROCESS_KW_CMD("STRLEN", redis_key_cmd, redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::lPush(string key , string value) */ PHP_METHOD(Redis, lPush) { REDIS_PROCESS_KW_CMD("LPUSH", redis_key_varval_cmd, redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::rPush(string key , string value) */ PHP_METHOD(Redis, rPush) { REDIS_PROCESS_KW_CMD("RPUSH", redis_key_varval_cmd, redis_long_response); } /* }}} */ PHP_METHOD(Redis, lInsert) { REDIS_PROCESS_CMD(linsert, redis_long_response); } /* {{{ proto long Redis::lPushx(string key, mixed value) */ PHP_METHOD(Redis, lPushx) { REDIS_PROCESS_KW_CMD("LPUSHX", redis_kv_cmd, redis_long_response); } /* }}} */ /* {{{ proto long Redis::rPushx(string key, mixed value) */ PHP_METHOD(Redis, rPushx) { REDIS_PROCESS_KW_CMD("RPUSHX", redis_kv_cmd, redis_long_response); } /* }}} */ /* {{{ proto string Redis::lPOP(string key) */ PHP_METHOD(Redis, lPop) { REDIS_PROCESS_KW_CMD("LPOP", redis_key_cmd, redis_string_response); } /* }}} */ /* {{{ proto string Redis::rPOP(string key) */ PHP_METHOD(Redis, rPop) { REDIS_PROCESS_KW_CMD("RPOP", redis_key_cmd, redis_string_response); } /* }}} */ /* {{{ proto string Redis::blPop(string key1, string key2, ..., int timeout) */ PHP_METHOD(Redis, blPop) { REDIS_PROCESS_CMD(blpop, redis_sock_read_multibulk_reply); } /* }}} */ /* {{{ proto string Redis::brPop(string key1, string key2, ..., int timeout) */ PHP_METHOD(Redis, brPop) { REDIS_PROCESS_CMD(brpop, redis_sock_read_multibulk_reply); } /* }}} */ /* {{{ proto int Redis::lSize(string key) */ PHP_METHOD(Redis, lSize) { REDIS_PROCESS_KW_CMD("LLEN", redis_key_cmd, redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::lRemove(string list, string value, int count = 0) */ PHP_METHOD(Redis, lRemove) { REDIS_PROCESS_CMD(lrem, redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::listTrim(string key , int start , int end) */ PHP_METHOD(Redis, listTrim) { REDIS_PROCESS_KW_CMD("LTRIM", redis_key_long_long_cmd, redis_boolean_response); } /* }}} */ /* {{{ proto string Redis::lGet(string key , int index) */ PHP_METHOD(Redis, lGet) { REDIS_PROCESS_KW_CMD("LINDEX", redis_key_long_cmd, redis_string_response); } /* }}} */ /* {{{ proto array Redis::lGetRange(string key, int start , int end) */ PHP_METHOD(Redis, lGetRange) { REDIS_PROCESS_KW_CMD("LRANGE", redis_key_long_long_cmd, redis_sock_read_multibulk_reply); } /* }}} */ /* {{{ proto long Redis::sAdd(string key , mixed value) */ PHP_METHOD(Redis, sAdd) { REDIS_PROCESS_KW_CMD("SADD", redis_key_varval_cmd, redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::sAddArray(string key, array $values) */ PHP_METHOD(Redis, sAddArray) { REDIS_PROCESS_KW_CMD("SADD", redis_key_arr_cmd, redis_long_response); } /* }}} */ /* {{{ proto int Redis::sSize(string key) */ PHP_METHOD(Redis, sSize) { REDIS_PROCESS_KW_CMD("SCARD", redis_key_cmd, redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::sRemove(string set, string value) */ PHP_METHOD(Redis, sRemove) { REDIS_PROCESS_KW_CMD("SREM", redis_key_varval_cmd, redis_long_response); } /* }}} */ /* {{{ proto boolean Redis::sMove(string src, string dst, mixed value) */ PHP_METHOD(Redis, sMove) { REDIS_PROCESS_CMD(smove, redis_1_response); } /* }}} */ /* {{{ proto string Redis::sPop(string key) */ PHP_METHOD(Redis, sPop) { if (ZEND_NUM_ARGS() == 1) { REDIS_PROCESS_KW_CMD("SPOP", redis_key_cmd, redis_string_response); } else if (ZEND_NUM_ARGS() == 2) { REDIS_PROCESS_KW_CMD("SPOP", redis_key_long_cmd, redis_sock_read_multibulk_reply); } else { ZEND_WRONG_PARAM_COUNT(); } } /* }}} */ /* {{{ proto string Redis::sRandMember(string key [int count]) */ PHP_METHOD(Redis, sRandMember) { char *cmd; int cmd_len; short have_count; RedisSock *redis_sock; // Grab our socket, validate call if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL || redis_srandmember_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &cmd, &cmd_len, NULL, NULL, &have_count)==FAILURE) { RETURN_FALSE; } REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); if(have_count) { IF_ATOMIC() { if(redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL)<0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } else { IF_ATOMIC() { redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_string_response); } } /* }}} */ /* {{{ proto boolean Redis::sContains(string set, string value) */ PHP_METHOD(Redis, sContains) { REDIS_PROCESS_KW_CMD("SISMEMBER", redis_kv_cmd, redis_1_response); } /* }}} */ /* {{{ proto array Redis::sMembers(string set) */ PHP_METHOD(Redis, sMembers) { REDIS_PROCESS_KW_CMD("SMEMBERS", redis_key_cmd, redis_sock_read_multibulk_reply); } /* }}} */ /* {{{ proto array Redis::sInter(string key0, ... string keyN) */ PHP_METHOD(Redis, sInter) { REDIS_PROCESS_CMD(sinter, redis_sock_read_multibulk_reply); } /* }}} */ /* {{{ proto array Redis::sInterStore(string dst, string key0,...string keyN) */ PHP_METHOD(Redis, sInterStore) { REDIS_PROCESS_CMD(sinterstore, redis_long_response); } /* }}} */ /* {{{ proto array Redis::sUnion(string key0, ... string keyN) */ PHP_METHOD(Redis, sUnion) { REDIS_PROCESS_CMD(sunion, redis_sock_read_multibulk_reply); } /* }}} */ /* {{{ proto array Redis::sUnionStore(string dst, string key0, ... keyN) */ PHP_METHOD(Redis, sUnionStore) { REDIS_PROCESS_CMD(sunionstore, redis_long_response); } /* }}} */ /* {{{ proto array Redis::sDiff(string key0, ... string keyN) */ PHP_METHOD(Redis, sDiff) { REDIS_PROCESS_CMD(sdiff, redis_sock_read_multibulk_reply); } /* }}} */ /* {{{ proto array Redis::sDiffStore(string dst, string key0, ... keyN) */ PHP_METHOD(Redis, sDiffStore) { REDIS_PROCESS_CMD(sdiffstore, redis_long_response); } /* }}} */ /* {{{ proto array Redis::sort(string key, array options) */ PHP_METHOD(Redis, sort) { char *cmd; int cmd_len, have_store; RedisSock *redis_sock; // Grab socket, handle command construction if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL || redis_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &have_store, &cmd, &cmd_len, NULL, NULL)==FAILURE) { RETURN_FALSE; } REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { if (redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_read_variant_reply); } static void generic_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, int desc, int alpha) { zval *object, *zele, *zget = NULL; RedisSock *redis_sock; zend_string *zpattern; char *key = NULL, *pattern = NULL, *store = NULL; strlen_t keylen, patternlen, storelen; zend_long offset = -1, count = -1; int argc = 1; /* SORT key is the simplest SORT command */ smart_string cmd = {0}; /* Parse myriad of sort arguments */ if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|s!z!lls", &object, redis_ce, &key, &keylen, &pattern, &patternlen, &zget, &offset, &count, &store, &storelen) == FAILURE) { RETURN_FALSE; } /* Ensure we're sorting something, and we can get context */ if (keylen == 0 || !(redis_sock = redis_sock_get(object TSRMLS_CC, 0))) RETURN_FALSE; /* Start calculating argc depending on input arguments */ if (pattern && patternlen) argc += 2; /* BY pattern */ if (offset >= 0 && count >= 0) argc += 3; /* LIMIT offset count */ if (alpha) argc += 1; /* ALPHA */ if (store) argc += 2; /* STORE destination */ if (desc) argc += 1; /* DESC (ASC is the default) */ /* GET is special. It can be 0 .. N arguments depending what we have */ if (zget) { if (Z_TYPE_P(zget) == IS_ARRAY) argc += zend_hash_num_elements(Z_ARRVAL_P(zget)); else if (Z_STRLEN_P(zget) > 0) { argc += 2; /* GET pattern */ } } /* Start constructing final command and append key */ redis_cmd_init_sstr(&cmd, argc, "SORT", 4); redis_cmd_append_sstr_key(&cmd, key, keylen, redis_sock, NULL); /* BY pattern */ if (pattern && patternlen) { redis_cmd_append_sstr(&cmd, "BY", sizeof("BY") - 1); redis_cmd_append_sstr(&cmd, pattern, patternlen); } /* LIMIT offset count */ if (offset >= 0 && count >= 0) { redis_cmd_append_sstr(&cmd, "LIMIT", sizeof("LIMIT") - 1); redis_cmd_append_sstr_long(&cmd, offset); redis_cmd_append_sstr_long(&cmd, count); } /* Handle any number of GET pattern arguments we've been passed */ if (zget != NULL) { if (Z_TYPE_P(zget) == IS_ARRAY) { ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zget), zele) { zpattern = zval_get_string(zele); redis_cmd_append_sstr(&cmd, "GET", sizeof("GET") - 1); redis_cmd_append_sstr(&cmd, ZSTR_VAL(zpattern), ZSTR_LEN(zpattern)); zend_string_release(zpattern); } ZEND_HASH_FOREACH_END(); } else { zpattern = zval_get_string(zget); redis_cmd_append_sstr(&cmd, "GET", sizeof("GET") - 1); redis_cmd_append_sstr(&cmd, ZSTR_VAL(zpattern), ZSTR_LEN(zpattern)); zend_string_release(zpattern); } } /* Append optional DESC and ALPHA modifiers */ if (desc) redis_cmd_append_sstr(&cmd, "DESC", sizeof("DESC") - 1); if (alpha) redis_cmd_append_sstr(&cmd, "ALPHA", sizeof("ALPHA") - 1); /* Finally append STORE if we've got it */ if (store && storelen) { redis_cmd_append_sstr(&cmd, "STORE", sizeof("STORE") - 1); redis_cmd_append_sstr_key(&cmd, store, storelen, redis_sock, NULL); } REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len); IF_ATOMIC() { if (redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_read_variant_reply); } /* {{{ proto array Redis::sortAsc(string key, string pattern, string get, * int start, int end, bool getList]) */ PHP_METHOD(Redis, sortAsc) { generic_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 0); } /* }}} */ /* {{{ proto array Redis::sortAscAlpha(string key, string pattern, string get, * int start, int end, bool getList]) */ PHP_METHOD(Redis, sortAscAlpha) { generic_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 1); } /* }}} */ /* {{{ proto array Redis::sortDesc(string key, string pattern, string get, * int start, int end, bool getList]) */ PHP_METHOD(Redis, sortDesc) { generic_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1, 0); } /* }}} */ /* {{{ proto array Redis::sortDescAlpha(string key, string pattern, string get, * int start, int end, bool getList]) */ PHP_METHOD(Redis, sortDescAlpha) { generic_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1, 1); } /* }}} */ /* {{{ proto array Redis::setTimeout(string key, int timeout) */ PHP_METHOD(Redis, setTimeout) { REDIS_PROCESS_KW_CMD("EXPIRE", redis_key_long_cmd, redis_1_response); } /* }}} */ /* {{{ proto bool Redis::pexpire(string key, long ms) */ PHP_METHOD(Redis, pexpire) { REDIS_PROCESS_KW_CMD("PEXPIRE", redis_key_long_cmd, redis_1_response); } /* }}} */ /* {{{ proto array Redis::expireAt(string key, int timestamp) */ PHP_METHOD(Redis, expireAt) { REDIS_PROCESS_KW_CMD("EXPIREAT", redis_key_long_cmd, redis_1_response); } /* }}} */ /* {{{ proto array Redis::pexpireAt(string key, int timestamp) */ PHP_METHOD(Redis, pexpireAt) { REDIS_PROCESS_KW_CMD("PEXPIREAT", redis_key_long_cmd, redis_1_response); } /* }}} */ /* {{{ proto array Redis::lSet(string key, int index, string value) */ PHP_METHOD(Redis, lSet) { REDIS_PROCESS_KW_CMD("LSET", redis_key_long_val_cmd, redis_boolean_response); } /* }}} */ /* {{{ proto string Redis::save() */ PHP_METHOD(Redis, save) { REDIS_PROCESS_KW_CMD("SAVE", redis_empty_cmd, redis_boolean_response); } /* }}} */ /* {{{ proto string Redis::bgSave() */ PHP_METHOD(Redis, bgSave) { REDIS_PROCESS_KW_CMD("BGSAVE", redis_empty_cmd, redis_boolean_response); } /* }}} */ /* {{{ proto integer Redis::lastSave() */ PHP_METHOD(Redis, lastSave) { REDIS_PROCESS_KW_CMD("LASTSAVE", redis_empty_cmd, redis_long_response); } /* }}} */ /* {{{ proto bool Redis::flushDB() */ PHP_METHOD(Redis, flushDB) { REDIS_PROCESS_KW_CMD("FLUSHDB", redis_empty_cmd, redis_boolean_response); } /* }}} */ /* {{{ proto bool Redis::flushAll() */ PHP_METHOD(Redis, flushAll) { REDIS_PROCESS_KW_CMD("FLUSHALL", redis_empty_cmd, redis_boolean_response); } /* }}} */ /* {{{ proto int Redis::dbSize() */ PHP_METHOD(Redis, dbSize) { REDIS_PROCESS_KW_CMD("DBSIZE", redis_empty_cmd, redis_long_response); } /* }}} */ /* {{{ proto bool Redis::auth(string passwd) */ PHP_METHOD(Redis, auth) { REDIS_PROCESS_CMD(auth, redis_boolean_response); } /* }}} */ /* {{{ proto long Redis::persist(string key) */ PHP_METHOD(Redis, persist) { REDIS_PROCESS_KW_CMD("PERSIST", redis_key_cmd, redis_1_response); } /* }}} */ /* {{{ proto long Redis::ttl(string key) */ PHP_METHOD(Redis, ttl) { REDIS_PROCESS_KW_CMD("TTL", redis_key_cmd, redis_long_response); } /* }}} */ /* {{{ proto long Redis::pttl(string key) */ PHP_METHOD(Redis, pttl) { REDIS_PROCESS_KW_CMD("PTTL", redis_key_cmd, redis_long_response); } /* }}} */ /* {{{ proto array Redis::info() */ PHP_METHOD(Redis, info) { zval *object; RedisSock *redis_sock; char *cmd, *opt = NULL; strlen_t opt_len; int cmd_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|s", &object, redis_ce, &opt, &opt_len) == FAILURE) { RETURN_FALSE; } if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } /* Build a standalone INFO command or one with an option */ if (opt != NULL) { cmd_len = REDIS_SPPRINTF(&cmd, "INFO", "s", opt, opt_len); } else { cmd_len = REDIS_SPPRINTF(&cmd, "INFO", ""); } REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_info_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_info_response); } /* }}} */ /* {{{ proto bool Redis::select(long dbNumber) */ PHP_METHOD(Redis, select) { zval *object; RedisSock *redis_sock; char *cmd; int cmd_len; zend_long dbNumber; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", &object, redis_ce, &dbNumber) == FAILURE) { RETURN_FALSE; } if (dbNumber < 0 || (redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } redis_sock->dbNumber = dbNumber; cmd_len = REDIS_SPPRINTF(&cmd, "SELECT", "d", dbNumber); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } /* }}} */ /* {{{ proto bool Redis::move(string key, long dbindex) */ PHP_METHOD(Redis, move) { REDIS_PROCESS_KW_CMD("MOVE", redis_key_long_cmd, redis_1_response); } /* }}} */ static void generic_mset(INTERNAL_FUNCTION_PARAMETERS, char *kw, ResultCallback fun) { RedisSock *redis_sock; smart_string cmd = {0}; zval *object, *z_array; HashTable *htargs; zend_string *zkey; zval *zmem; char buf[64]; size_t keylen; ulong idx; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", &object, redis_ce, &z_array) == FAILURE) { RETURN_FALSE; } /* Make sure we can get our socket, and we were not passed an empty array */ if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL || zend_hash_num_elements(Z_ARRVAL_P(z_array)) == 0) { RETURN_FALSE; } /* Initialize our command */ htargs = Z_ARRVAL_P(z_array); redis_cmd_init_sstr(&cmd, zend_hash_num_elements(htargs) * 2, kw, strlen(kw)); ZEND_HASH_FOREACH_KEY_VAL(htargs, idx, zkey, zmem) { /* Handle string or numeric keys */ if (zkey) { redis_cmd_append_sstr_key(&cmd, ZSTR_VAL(zkey), ZSTR_LEN(zkey), redis_sock, NULL); } else { keylen = snprintf(buf, sizeof(buf), "%ld", (long)idx); redis_cmd_append_sstr_key(&cmd, buf, (strlen_t)keylen, redis_sock, NULL); } /* Append our value */ redis_cmd_append_sstr_zval(&cmd, zmem, redis_sock TSRMLS_CC); } ZEND_HASH_FOREACH_END(); REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len); IF_ATOMIC() { fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(fun); } /* {{{ proto bool Redis::mset(array (key => value, ...)) */ PHP_METHOD(Redis, mset) { generic_mset(INTERNAL_FUNCTION_PARAM_PASSTHRU, "MSET", redis_boolean_response); } /* }}} */ /* {{{ proto bool Redis::msetnx(array (key => value, ...)) */ PHP_METHOD(Redis, msetnx) { generic_mset(INTERNAL_FUNCTION_PARAM_PASSTHRU, "MSETNX", redis_1_response); } /* }}} */ /* {{{ proto string Redis::rpoplpush(string srckey, string dstkey) */ PHP_METHOD(Redis, rpoplpush) { REDIS_PROCESS_KW_CMD("RPOPLPUSH", redis_key_key_cmd, redis_string_response); } /* }}} */ /* {{{ proto string Redis::brpoplpush(string src, string dst, int timeout) */ PHP_METHOD(Redis, brpoplpush) { REDIS_PROCESS_CMD(brpoplpush, redis_string_response); } /* }}} */ /* {{{ proto long Redis::zAdd(string key, int score, string value) */ PHP_METHOD(Redis, zAdd) { REDIS_PROCESS_CMD(zadd, redis_long_response); } /* }}} */ /* Handle ZRANGE and ZREVRANGE as they're the same except for keyword */ static void generic_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw, zrange_cb fun) { char *cmd; int cmd_len; RedisSock *redis_sock; int withscores=0; if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } if(fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, kw, &cmd, &cmd_len, &withscores, NULL, NULL)==FAILURE) { RETURN_FALSE; } REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); if(withscores) { IF_ATOMIC() { redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_keys_dbl); } else { IF_ATOMIC() { if(redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL)<0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); } } /* {{{ proto array Redis::zRange(string key,int start,int end,bool scores=0) */ PHP_METHOD(Redis, zRange) { generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZRANGE", redis_zrange_cmd); } /* {{{ proto array Redis::zRevRange(string k, long s, long e, bool scores=0) */ PHP_METHOD(Redis, zRevRange) { generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZREVRANGE", redis_zrange_cmd); } /* }}} */ /* {{{ proto array Redis::zRangeByScore(string k,string s,string e,array opt) */ PHP_METHOD(Redis, zRangeByScore) { generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZRANGEBYSCORE", redis_zrangebyscore_cmd); } /* }}} */ /* {{{ proto array Redis::zRevRangeByScore(string key, string start, string end, * array options) */ PHP_METHOD(Redis, zRevRangeByScore) { generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZREVRANGEBYSCORE", redis_zrangebyscore_cmd); } /* }}} */ /* {{{ proto array Redis::zRangeByLex(string key, string min, string max, [ * offset, limit]) */ PHP_METHOD(Redis, zRangeByLex) { REDIS_PROCESS_KW_CMD("ZRANGEBYLEX", redis_zrangebylex_cmd, redis_sock_read_multibulk_reply); } /* }}} */ PHP_METHOD(Redis, zRevRangeByLex) { REDIS_PROCESS_KW_CMD("ZREVRANGEBYLEX", redis_zrangebylex_cmd, redis_sock_read_multibulk_reply); } /* }}} */ /* {{{ proto long Redis::zLexCount(string key, string min, string max) */ PHP_METHOD(Redis, zLexCount) { REDIS_PROCESS_KW_CMD("ZLEXCOUNT", redis_gen_zlex_cmd, redis_long_response); } /* }}} */ /* {{{ proto long Redis::zRemRangeByLex(string key, string min, string max) */ PHP_METHOD(Redis, zRemRangeByLex) { REDIS_PROCESS_KW_CMD("ZREMRANGEBYLEX", redis_gen_zlex_cmd, redis_long_response); } /* }}} */ /* {{{ proto long Redis::zDelete(string key, string member) */ PHP_METHOD(Redis, zDelete) { REDIS_PROCESS_KW_CMD("ZREM", redis_key_varval_cmd, redis_long_response); } /* }}} */ /* {{{ proto long Redis::zDeleteRangeByScore(string k, string s, string e) */ PHP_METHOD(Redis, zDeleteRangeByScore) { REDIS_PROCESS_KW_CMD("ZREMRANGEBYSCORE", redis_key_str_str_cmd, redis_long_response); } /* }}} */ /* {{{ proto long Redis::zDeleteRangeByRank(string key, long start, long end) */ PHP_METHOD(Redis, zDeleteRangeByRank) { REDIS_PROCESS_KW_CMD("ZREMRANGEBYRANK", redis_key_long_long_cmd, redis_long_response); } /* }}} */ /* {{{ proto array Redis::zCount(string key, string start , string end) */ PHP_METHOD(Redis, zCount) { REDIS_PROCESS_KW_CMD("ZCOUNT", redis_key_str_str_cmd, redis_long_response); } /* }}} */ /* {{{ proto long Redis::zCard(string key) */ PHP_METHOD(Redis, zCard) { REDIS_PROCESS_KW_CMD("ZCARD", redis_key_cmd, redis_long_response); } /* }}} */ /* {{{ proto double Redis::zScore(string key, mixed member) */ PHP_METHOD(Redis, zScore) { REDIS_PROCESS_KW_CMD("ZSCORE", redis_kv_cmd, redis_bulk_double_response); } /* }}} */ /* {{{ proto long Redis::zRank(string key, string member) */ PHP_METHOD(Redis, zRank) { REDIS_PROCESS_KW_CMD("ZRANK", redis_kv_cmd, redis_long_response); } /* }}} */ /* {{{ proto long Redis::zRevRank(string key, string member) */ PHP_METHOD(Redis, zRevRank) { REDIS_PROCESS_KW_CMD("ZREVRANK", redis_kv_cmd, redis_long_response); } /* }}} */ /* {{{ proto double Redis::zIncrBy(string key, double value, mixed member) */ PHP_METHOD(Redis, zIncrBy) { REDIS_PROCESS_CMD(zincrby, redis_bulk_double_response); } /* }}} */ /* zInter */ PHP_METHOD(Redis, zInter) { REDIS_PROCESS_KW_CMD("ZINTERSTORE", redis_zinter_cmd, redis_long_response); } /* zUnion */ PHP_METHOD(Redis, zUnion) { REDIS_PROCESS_KW_CMD("ZUNIONSTORE", redis_zinter_cmd, redis_long_response); } /* hashes */ /* {{{ proto long Redis::hset(string key, string mem, string val) */ PHP_METHOD(Redis, hSet) { REDIS_PROCESS_CMD(hset, redis_long_response); } /* }}} */ /* {{{ proto bool Redis::hSetNx(string key, string mem, string val) */ PHP_METHOD(Redis, hSetNx) { REDIS_PROCESS_CMD(hsetnx, redis_1_response); } /* }}} */ /* {{{ proto string Redis::hget(string key, string mem) */ PHP_METHOD(Redis, hGet) { REDIS_PROCESS_KW_CMD("HGET", redis_key_str_cmd, redis_string_response); } /* }}} */ /* {{{ proto long Redis::hLen(string key) */ PHP_METHOD(Redis, hLen) { REDIS_PROCESS_KW_CMD("HLEN", redis_key_cmd, redis_long_response); } /* }}} */ /* {{{ proto long Redis::hDel(string key, string mem1, ... memN) */ PHP_METHOD(Redis, hDel) { REDIS_PROCESS_CMD(hdel, redis_long_response); } /* }}} */ /* {{{ proto bool Redis::hExists(string key, string mem) */ PHP_METHOD(Redis, hExists) { REDIS_PROCESS_KW_CMD("HEXISTS", redis_key_str_cmd, redis_1_response); } /* {{{ proto array Redis::hkeys(string key) */ PHP_METHOD(Redis, hKeys) { REDIS_PROCESS_KW_CMD("HKEYS", redis_key_cmd, redis_mbulk_reply_raw); } /* }}} */ /* {{{ proto array Redis::hvals(string key) */ PHP_METHOD(Redis, hVals) { REDIS_PROCESS_KW_CMD("HVALS", redis_key_cmd, redis_sock_read_multibulk_reply); } /* {{{ proto array Redis::hgetall(string key) */ PHP_METHOD(Redis, hGetAll) { REDIS_PROCESS_KW_CMD("HGETALL", redis_key_cmd, redis_mbulk_reply_zipped_vals); } /* }}} */ /* {{{ proto double Redis::hIncrByFloat(string k, string me, double v) */ PHP_METHOD(Redis, hIncrByFloat) { REDIS_PROCESS_CMD(hincrbyfloat, redis_bulk_double_response); } /* }}} */ /* {{{ proto long Redis::hincrby(string key, string mem, long byval) */ PHP_METHOD(Redis, hIncrBy) { REDIS_PROCESS_CMD(hincrby, redis_long_response); } /* }}} */ /* {{{ array Redis::hMget(string hash, array keys) */ PHP_METHOD(Redis, hMget) { REDIS_PROCESS_CMD(hmget, redis_mbulk_reply_assoc); } /* }}} */ /* {{{ proto bool Redis::hmset(string key, array keyvals) */ PHP_METHOD(Redis, hMset) { REDIS_PROCESS_CMD(hmset, redis_boolean_response); } /* }}} */ /* {{{ proto long Redis::hstrlen(string key, string field) */ PHP_METHOD(Redis, hStrLen) { REDIS_PROCESS_CMD(hstrlen, redis_long_response); } /* }}} */ /* flag : get, set {ATOMIC, MULTI, PIPELINE} */ PHP_METHOD(Redis, multi) { RedisSock *redis_sock; char *resp, *cmd; int resp_len, cmd_len; zval *object; zend_long multi_value = MULTI; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|l", &object, redis_ce, &multi_value) == FAILURE) { RETURN_FALSE; } /* if the flag is activated, send the command, the reply will be "QUEUED" * or -ERR */ if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } if (multi_value == PIPELINE) { /* Cannot enter pipeline mode in a MULTI block */ IF_MULTI() { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Can't activate pipeline in multi mode!"); RETURN_FALSE; } /* Enable PIPELINE if we're not already in one */ IF_ATOMIC() { free_reply_callbacks(redis_sock); REDIS_ENABLE_MODE(redis_sock, PIPELINE); } } else if (multi_value == MULTI) { /* Don't want to do anything if we're alredy in MULTI mode */ IF_NOT_MULTI() { cmd_len = REDIS_SPPRINTF(&cmd, "MULTI", ""); IF_PIPELINE() { PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len); efree(cmd); REDIS_SAVE_CALLBACK(NULL, NULL); REDIS_ENABLE_MODE(redis_sock, MULTI); } else { SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len) efree(cmd); if ((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC)) == NULL) { RETURN_FALSE; } else if (strncmp(resp, "+OK", 3) != 0) { efree(resp); RETURN_FALSE; } efree(resp); REDIS_ENABLE_MODE(redis_sock, MULTI); } } } else { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown mode sent to Redis::multi"); RETURN_FALSE; } RETURN_ZVAL(getThis(), 1, 0); } /* discard */ PHP_METHOD(Redis, discard) { RedisSock *redis_sock; zval *object; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { RETURN_FALSE; } if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } redis_sock->mode = ATOMIC; free_reply_callbacks(redis_sock); RETURN_BOOL(redis_send_discard(redis_sock TSRMLS_CC) == SUCCESS); } /* redis_sock_read_multibulk_multi_reply */ PHP_REDIS_API int redis_sock_read_multibulk_multi_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) { char inbuf[4096]; int numElems; size_t len; if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) { return - 1; } /* number of responses */ numElems = atoi(inbuf+1); if(numElems < 0) { return -1; } array_init(return_value); redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, return_value, numElems); return 0; } /* exec */ PHP_METHOD(Redis, exec) { RedisSock *redis_sock; char *cmd; int cmd_len, ret; zval *object; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE || (redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL ) { RETURN_FALSE; } IF_MULTI() { cmd_len = REDIS_SPPRINTF(&cmd, "EXEC", ""); IF_PIPELINE() { PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len); efree(cmd); REDIS_SAVE_CALLBACK(NULL, NULL); REDIS_DISABLE_MODE(redis_sock, MULTI); RETURN_ZVAL(getThis(), 1, 0); } SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len) efree(cmd); ret = redis_sock_read_multibulk_multi_reply( INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); free_reply_callbacks(redis_sock); REDIS_DISABLE_MODE(redis_sock, MULTI); redis_sock->watching = 0; if (ret < 0) { zval_dtor(return_value); RETURN_FALSE; } } IF_PIPELINE() { if (redis_sock->pipeline_cmd == NULL) { /* Empty array when no command was run. */ array_init(return_value); } else { if (redis_sock_write(redis_sock, redis_sock->pipeline_cmd, redis_sock->pipeline_len TSRMLS_CC) < 0) { ZVAL_FALSE(return_value); } else { array_init(return_value); redis_sock_read_multibulk_multi_reply_loop( INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, return_value, 0); } efree(redis_sock->pipeline_cmd); redis_sock->pipeline_cmd = NULL; redis_sock->pipeline_len = 0; } free_reply_callbacks(redis_sock); REDIS_DISABLE_MODE(redis_sock, PIPELINE); } } PHP_REDIS_API int redis_response_enqueued(RedisSock *redis_sock TSRMLS_DC) { char *resp; int resp_len, ret = FAILURE; if ((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC)) != NULL) { if (strncmp(resp, "+QUEUED", 7) == 0) { ret = SUCCESS; } efree(resp); } return ret; } PHP_REDIS_API int redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int numElems) { fold_item *fi; for (fi = redis_sock->head; fi; /* void */) { if (fi->fun) { fi->fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, fi->ctx TSRMLS_CC); fi = fi->next; continue; } size_t len; char inbuf[255]; if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) { } else if (strncmp(inbuf, "+OK", 3) != 0) { } while ((fi = fi->next) && fi->fun) { if (redis_response_enqueued(redis_sock TSRMLS_CC) == SUCCESS) { } else { } } if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) { } #if (PHP_MAJOR_VERSION < 7) zval *z_ret; MAKE_STD_ZVAL(z_ret); #else zval zv, *z_ret = &zv; #endif array_init(z_ret); add_next_index_zval(z_tab, z_ret); int num = atol(inbuf + 1); if (num > 0 && redis_read_multibulk_recursive(redis_sock, num, z_ret TSRMLS_CC) < 0) { } if (fi) fi = fi->next; } redis_sock->current = fi; return 0; } PHP_METHOD(Redis, pipeline) { RedisSock *redis_sock; zval *object; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE || (redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL ) { RETURN_FALSE; } /* User cannot enter MULTI mode if already in a pipeline */ IF_MULTI() { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Can't activate pipeline in multi mode!"); RETURN_FALSE; } /* Enable pipeline mode unless we're already in that mode in which case this * is just a NO OP */ IF_ATOMIC() { /* NB : we keep the function fold, to detect the last function. * We need the response format of the n - 1 command. So, we can delete * when n > 2, the { 1 .. n - 2} commands */ free_reply_callbacks(redis_sock); REDIS_ENABLE_MODE(redis_sock, PIPELINE); } RETURN_ZVAL(getThis(), 1, 0); } /* {{{ proto long Redis::publish(string channel, string msg) */ PHP_METHOD(Redis, publish) { REDIS_PROCESS_KW_CMD("PUBLISH", redis_key_str_cmd, redis_long_response); } /* }}} */ /* {{{ proto void Redis::psubscribe(Array(pattern1, pattern2, ... patternN)) */ PHP_METHOD(Redis, psubscribe) { REDIS_PROCESS_KW_CMD("PSUBSCRIBE", redis_subscribe_cmd, redis_subscribe_response); } /* {{{ proto void Redis::subscribe(Array(channel1, channel2, ... channelN)) */ PHP_METHOD(Redis, subscribe) { REDIS_PROCESS_KW_CMD("SUBSCRIBE", redis_subscribe_cmd, redis_subscribe_response); } /** * [p]unsubscribe channel_0 channel_1 ... channel_n * [p]unsubscribe(array(channel_0, channel_1, ..., channel_n)) * response format : * array( * channel_0 => TRUE|FALSE, * channel_1 => TRUE|FALSE, * ... * channel_n => TRUE|FALSE * ); **/ PHP_REDIS_API void generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *unsub_cmd) { zval *object, *array, *data; HashTable *arr_hash; RedisSock *redis_sock; char *cmd = "", *old_cmd = NULL; int cmd_len, array_count; int i; zval z_tab, *z_channel; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", &object, redis_ce, &array) == FAILURE) { RETURN_FALSE; } if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } arr_hash = Z_ARRVAL_P(array); array_count = zend_hash_num_elements(arr_hash); if (array_count == 0) { RETURN_FALSE; } ZEND_HASH_FOREACH_VAL(arr_hash, data) { ZVAL_DEREF(data); if (Z_TYPE_P(data) == IS_STRING) { char *old_cmd = NULL; if(*cmd) { old_cmd = cmd; } spprintf(&cmd, 0, "%s %s", cmd, Z_STRVAL_P(data)); if(old_cmd) { efree(old_cmd); } } } ZEND_HASH_FOREACH_END(); old_cmd = cmd; cmd_len = spprintf(&cmd, 0, "%s %s\r\n", unsub_cmd, cmd); efree(old_cmd); SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len) efree(cmd); array_init(return_value); for (i = 1; i <= array_count; i++) { redis_sock_read_multibulk_reply_zval( INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_tab); if (Z_TYPE(z_tab) == IS_ARRAY) { if ((z_channel = zend_hash_index_find(Z_ARRVAL(z_tab), 1)) == NULL) { RETURN_FALSE; } add_assoc_bool(return_value, Z_STRVAL_P(z_channel), 1); } else { //error zval_dtor(&z_tab); RETURN_FALSE; } zval_dtor(&z_tab); } } PHP_METHOD(Redis, unsubscribe) { REDIS_PROCESS_KW_CMD("UNSUBSCRIBE", redis_unsubscribe_cmd, redis_unsubscribe_response); } PHP_METHOD(Redis, punsubscribe) { REDIS_PROCESS_KW_CMD("PUNSUBSCRIBE", redis_unsubscribe_cmd, redis_unsubscribe_response); } /* {{{ proto string Redis::bgrewriteaof() */ PHP_METHOD(Redis, bgrewriteaof) { REDIS_PROCESS_KW_CMD("BGREWRITEAOF", redis_empty_cmd, redis_boolean_response); } /* }}} */ /* {{{ proto string Redis::slaveof([host, port]) */ PHP_METHOD(Redis, slaveof) { zval *object; RedisSock *redis_sock; char *cmd = "", *host = NULL; strlen_t host_len; zend_long port = 6379; int cmd_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|sl", &object, redis_ce, &host, &host_len, &port) == FAILURE) { RETURN_FALSE; } if (port < 0 || (redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } if (host && host_len) { cmd_len = REDIS_SPPRINTF(&cmd, "SLAVEOF", "sd", host, host_len, (int)port); } else { cmd_len = REDIS_SPPRINTF(&cmd, "SLAVEOF", "ss", "NO", 2, "ONE", 3); } REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } /* }}} */ /* {{{ proto string Redis::object(key) */ PHP_METHOD(Redis, object) { RedisSock *redis_sock; char *cmd; int cmd_len; REDIS_REPLY_TYPE rtype; if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } if(redis_object_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &rtype, &cmd, &cmd_len, NULL, NULL)==FAILURE) { RETURN_FALSE; } REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); if(rtype == TYPE_INT) { IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } else { IF_ATOMIC() { redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_string_response); } } /* }}} */ /* {{{ proto string Redis::getOption($option) */ PHP_METHOD(Redis, getOption) { RedisSock *redis_sock; if ((redis_sock = redis_sock_get_instance(getThis() TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } redis_getoption_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL); } /* }}} */ /* {{{ proto string Redis::setOption(string $option, mixed $value) */ PHP_METHOD(Redis, setOption) { RedisSock *redis_sock; if ((redis_sock = redis_sock_get_instance(getThis() TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } redis_setoption_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL); } /* }}} */ /* {{{ proto boolean Redis::config(string op, string key [, mixed value]) */ PHP_METHOD(Redis, config) { zval *object; RedisSock *redis_sock; char *key = NULL, *val = NULL, *cmd, *op = NULL; strlen_t key_len, val_len, op_len; enum {CFG_GET, CFG_SET} mode; int cmd_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss|s", &object, redis_ce, &op, &op_len, &key, &key_len, &val, &val_len) == FAILURE) { RETURN_FALSE; } /* op must be GET or SET */ if(strncasecmp(op, "GET", 3) == 0) { mode = CFG_GET; } else if(strncasecmp(op, "SET", 3) == 0) { mode = CFG_SET; } else { RETURN_FALSE; } if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } if (mode == CFG_GET && val == NULL) { cmd_len = REDIS_SPPRINTF(&cmd, "CONFIG", "ss", op, op_len, key, key_len); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len) IF_ATOMIC() { redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_raw); } else if(mode == CFG_SET && val != NULL) { cmd_len = REDIS_SPPRINTF(&cmd, "CONFIG", "sss", op, op_len, key, key_len, val, val_len); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len) IF_ATOMIC() { redis_boolean_response( INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_boolean_response); } else { RETURN_FALSE; } } /* }}} */ /* {{{ proto boolean Redis::slowlog(string arg, [int option]) */ PHP_METHOD(Redis, slowlog) { zval *object; RedisSock *redis_sock; char *arg, *cmd; int cmd_len; strlen_t arg_len; zend_long option = 0; enum {SLOWLOG_GET, SLOWLOG_LEN, SLOWLOG_RESET} mode; // Make sure we can get parameters if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|l", &object, redis_ce, &arg, &arg_len, &option) == FAILURE) { RETURN_FALSE; } /* Figure out what kind of slowlog command we're executing */ if(!strncasecmp(arg, "GET", 3)) { mode = SLOWLOG_GET; } else if(!strncasecmp(arg, "LEN", 3)) { mode = SLOWLOG_LEN; } else if(!strncasecmp(arg, "RESET", 5)) { mode = SLOWLOG_RESET; } else { /* This command is not valid */ RETURN_FALSE; } /* Make sure we can grab our redis socket */ if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } // Create our command. For everything except SLOWLOG GET (with an arg) it's // just two parts if (mode == SLOWLOG_GET && ZEND_NUM_ARGS() == 2) { cmd_len = REDIS_SPPRINTF(&cmd, "SLOWLOG", "sl", arg, arg_len, option); } else { cmd_len = REDIS_SPPRINTF(&cmd, "SLOWLOG", "s", arg, arg_len); } /* Kick off our command */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_read_variant_reply); } /* {{{ proto Redis::wait(int num_slaves, int ms) }}} */ PHP_METHOD(Redis, wait) { zval *object; RedisSock *redis_sock; zend_long num_slaves, timeout; char *cmd; int cmd_len; /* Make sure arguments are valid */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oll", &object, redis_ce, &num_slaves, &timeout) ==FAILURE) { RETURN_FALSE; } /* Don't even send this to Redis if our args are negative */ if(num_slaves < 0 || timeout < 0) { RETURN_FALSE; } /* Grab our socket */ if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } // Construct the command cmd_len = REDIS_SPPRINTF(&cmd, "WAIT", "ll", num_slaves, timeout); /* Kick it off */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_long_response); } /* Construct a PUBSUB command */ PHP_REDIS_API int redis_build_pubsub_cmd(RedisSock *redis_sock, char **ret, PUBSUB_TYPE type, zval *arg TSRMLS_DC) { HashTable *ht_chan; zval *z_ele; smart_string cmd = {0}; if (type == PUBSUB_CHANNELS) { if (arg) { /* With a pattern */ return REDIS_SPPRINTF(ret, "PUBSUB", "sk", "CHANNELS", sizeof("CHANNELS") - 1, Z_STRVAL_P(arg), Z_STRLEN_P(arg)); } else { /* No pattern */ return REDIS_SPPRINTF(ret, "PUBSUB", "s", "CHANNELS", sizeof("CHANNELS") - 1); } } else if (type == PUBSUB_NUMSUB) { ht_chan = Z_ARRVAL_P(arg); // Add PUBSUB and NUMSUB bits redis_cmd_init_sstr(&cmd, zend_hash_num_elements(ht_chan)+1, "PUBSUB", sizeof("PUBSUB")-1); redis_cmd_append_sstr(&cmd, "NUMSUB", sizeof("NUMSUB")-1); /* Iterate our elements */ ZEND_HASH_FOREACH_VAL(ht_chan, z_ele) { zend_string *zstr = zval_get_string(z_ele); redis_cmd_append_sstr_key(&cmd, ZSTR_VAL(zstr), ZSTR_LEN(zstr), redis_sock, NULL); zend_string_release(zstr); } ZEND_HASH_FOREACH_END(); /* Set return */ *ret = cmd.c; return cmd.len; } else if (type == PUBSUB_NUMPAT) { return REDIS_SPPRINTF(ret, "PUBSUB", "s", "NUMPAT", sizeof("NUMPAT") - 1); } /* Shouldn't ever happen */ return -1; } /* * {{{ proto Redis::pubsub("channels", pattern); * proto Redis::pubsub("numsub", Array channels); * proto Redis::pubsub("numpat"); }}} */ PHP_METHOD(Redis, pubsub) { zval *object; RedisSock *redis_sock; char *keyword, *cmd; int cmd_len; strlen_t kw_len; PUBSUB_TYPE type; zval *arg=NULL; // Parse arguments if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|z", &object, redis_ce, &keyword, &kw_len, &arg)==FAILURE) { RETURN_FALSE; } /* Validate our sub command keyword, and that we've got proper arguments */ if(!strncasecmp(keyword, "channels", sizeof("channels"))) { /* One (optional) string argument */ if(arg && Z_TYPE_P(arg) != IS_STRING) { RETURN_FALSE; } type = PUBSUB_CHANNELS; } else if(!strncasecmp(keyword, "numsub", sizeof("numsub"))) { /* One array argument */ if(ZEND_NUM_ARGS() < 2 || Z_TYPE_P(arg) != IS_ARRAY || zend_hash_num_elements(Z_ARRVAL_P(arg))==0) { RETURN_FALSE; } type = PUBSUB_NUMSUB; } else if(!strncasecmp(keyword, "numpat", sizeof("numpat"))) { type = PUBSUB_NUMPAT; } else { /* Invalid keyword */ RETURN_FALSE; } /* Grab our socket context object */ if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } /* Construct our "PUBSUB" command */ cmd_len = redis_build_pubsub_cmd(redis_sock, &cmd, type, arg TSRMLS_CC); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); if(type == PUBSUB_NUMSUB) { IF_ATOMIC() { if(redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL)<0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_keys_int); } else { IF_ATOMIC() { if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL)<0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_read_variant_reply); } } /* {{{ proto variant Redis::eval(string script, [array keys, long num_keys]) */ PHP_METHOD(Redis, eval) { REDIS_PROCESS_KW_CMD("EVAL", redis_eval_cmd, redis_read_variant_reply); } /* {{{ proto variant Redis::evalsha(string sha1, [array keys, long num_keys]) */ PHP_METHOD(Redis, evalsha) { REDIS_PROCESS_KW_CMD("EVALSHA", redis_eval_cmd, redis_read_variant_reply); } PHP_REDIS_API int redis_build_script_exists_cmd(char **ret, zval *argv, int argc) { smart_string cmd = {0}; zend_string *zstr; int i; // Start building our command REDIS_CMD_INIT_SSTR_STATIC(&cmd, 1 + argc, "SCRIPT"); redis_cmd_append_sstr(&cmd, "EXISTS", 6); for (i = 0; i < argc; i++) { zstr = zval_get_string(&argv[i]); redis_cmd_append_sstr(&cmd, ZSTR_VAL(zstr), ZSTR_LEN(zstr)); zend_string_release(zstr); } /* Success */ *ret = cmd.c; return cmd.len; } /* {{{ proto status Redis::script('flush') * {{{ proto status Redis::script('kill') * {{{ proto string Redis::script('load', lua_script) * {{{ proto int Reids::script('exists', script_sha1 [, script_sha2, ...]) */ PHP_METHOD(Redis, script) { zval *z_args; RedisSock *redis_sock; int cmd_len, argc; char *cmd; /* Attempt to grab our socket */ if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } /* Grab the number of arguments */ argc = ZEND_NUM_ARGS(); /* Allocate an array big enough to store our arguments */ z_args = emalloc(argc * sizeof(zval)); /* Make sure we can grab our arguments, we have a string directive */ if (zend_get_parameters_array(ht, argc, z_args) == FAILURE || (argc < 1 || Z_TYPE(z_args[0]) != IS_STRING)) { efree(z_args); RETURN_FALSE; } // Branch based on the directive if(!strcasecmp(Z_STRVAL(z_args[0]), "flush") || !strcasecmp(Z_STRVAL(z_args[0]), "kill")) { // Simple SCRIPT FLUSH, or SCRIPT_KILL command cmd_len = REDIS_SPPRINTF(&cmd, "SCRIPT", "s", Z_STRVAL(z_args[0]), Z_STRLEN(z_args[0])); } else if(!strcasecmp(Z_STRVAL(z_args[0]), "load")) { // Make sure we have a second argument, and it's not empty. If it is // empty, we can just return an empty array (which is what Redis does) if(argc < 2 || Z_TYPE(z_args[1]) != IS_STRING || Z_STRLEN(z_args[1]) < 1) { // Free our args efree(z_args); RETURN_FALSE; } // Format our SCRIPT LOAD command cmd_len = REDIS_SPPRINTF(&cmd, "SCRIPT", "ss", "LOAD", 4, Z_STRVAL(z_args[1]), Z_STRLEN(z_args[1])); } else if(!strcasecmp(Z_STRVAL(z_args[0]), "exists")) { /* Construct our SCRIPT EXISTS command */ cmd_len = redis_build_script_exists_cmd(&cmd, &(z_args[1]), argc-1); } else { /* Unknown directive */ efree(z_args); RETURN_FALSE; } /* Free our alocated arguments */ efree(z_args); // Kick off our request REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } REDIS_PROCESS_RESPONSE(redis_read_variant_reply); } /* {{{ proto DUMP key */ PHP_METHOD(Redis, dump) { REDIS_PROCESS_KW_CMD("DUMP", redis_key_cmd, redis_string_response); } /* }}} */ /* {{{ proto Redis::restore(ttl, key, value) */ PHP_METHOD(Redis, restore) { REDIS_PROCESS_KW_CMD("RESTORE", redis_key_long_val_cmd, redis_boolean_response); } /* }}} */ /* {{{ proto Redis::debug(string key) */ PHP_METHOD(Redis, debug) { REDIS_PROCESS_KW_CMD("DEBUG", redis_key_cmd, redis_string_response); } /* }}} */ /* {{{ proto Redis::migrate(host port key dest-db timeout [bool copy, * bool replace]) */ PHP_METHOD(Redis, migrate) { REDIS_PROCESS_CMD(migrate, redis_boolean_response); } /* {{{ proto Redis::_prefix(key) */ PHP_METHOD(Redis, _prefix) { RedisSock *redis_sock; if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } redis_prefix_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); } /* {{{ proto Redis::_serialize(value) */ PHP_METHOD(Redis, _serialize) { RedisSock *redis_sock; // Grab socket if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } redis_serialize_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); } /* {{{ proto Redis::_unserialize(value) */ PHP_METHOD(Redis, _unserialize) { RedisSock *redis_sock; // Grab socket if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } redis_unserialize_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, redis_exception_ce); } /* {{{ proto Redis::getLastError() */ PHP_METHOD(Redis, getLastError) { zval *object; RedisSock *redis_sock; // Grab our object if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { RETURN_FALSE; } // Grab socket if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } /* Return our last error or NULL if we don't have one */ if (redis_sock->err) { RETURN_STRINGL(ZSTR_VAL(redis_sock->err), ZSTR_LEN(redis_sock->err)); } RETURN_NULL(); } /* {{{ proto Redis::clearLastError() */ PHP_METHOD(Redis, clearLastError) { zval *object; RedisSock *redis_sock; // Grab our object if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { RETURN_FALSE; } // Grab socket if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } // Clear error message if (redis_sock->err) { zend_string_release(redis_sock->err); redis_sock->err = NULL; } RETURN_TRUE; } /* * {{{ proto long Redis::getMode() */ PHP_METHOD(Redis, getMode) { zval *object; RedisSock *redis_sock; /* Grab our object */ if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { RETURN_FALSE; } /* Grab socket */ if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } IF_PIPELINE() { RETVAL_LONG(PIPELINE); } else IF_MULTI() { RETVAL_LONG(MULTI); } else { RETVAL_LONG(ATOMIC); } } /* {{{ proto Redis::time() */ PHP_METHOD(Redis, time) { REDIS_PROCESS_KW_CMD("TIME", redis_empty_cmd, redis_mbulk_reply_raw); } /* {{{ proto array Redis::role() */ PHP_METHOD(Redis, role) { REDIS_PROCESS_KW_CMD("ROLE", redis_empty_cmd, redis_read_variant_reply); } /* * Introspection stuff */ /* {{{ proto Redis::IsConnected */ PHP_METHOD(Redis, isConnected) { RedisSock *redis_sock; if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) { RETURN_TRUE; } else { RETURN_FALSE; } } /* {{{ proto Redis::getHost() */ PHP_METHOD(Redis, getHost) { RedisSock *redis_sock; if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) { RETURN_STRINGL(ZSTR_VAL(redis_sock->host), ZSTR_LEN(redis_sock->host)); } else { RETURN_FALSE; } } /* {{{ proto Redis::getPort() */ PHP_METHOD(Redis, getPort) { RedisSock *redis_sock; if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) { /* Return our port */ RETURN_LONG(redis_sock->port); } else { RETURN_FALSE; } } /* {{{ proto Redis::getDBNum */ PHP_METHOD(Redis, getDBNum) { RedisSock *redis_sock; if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) { /* Return our db number */ RETURN_LONG(redis_sock->dbNumber); } else { RETURN_FALSE; } } /* {{{ proto Redis::getTimeout */ PHP_METHOD(Redis, getTimeout) { RedisSock *redis_sock; if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) { RETURN_DOUBLE(redis_sock->timeout); } else { RETURN_FALSE; } } /* {{{ proto Redis::getReadTimeout */ PHP_METHOD(Redis, getReadTimeout) { RedisSock *redis_sock; if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) { RETURN_DOUBLE(redis_sock->read_timeout); } else { RETURN_FALSE; } } /* {{{ proto Redis::getPersistentID */ PHP_METHOD(Redis, getPersistentID) { RedisSock *redis_sock; if ((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU)) == NULL) { RETURN_FALSE; } else if (redis_sock->persistent_id == NULL) { RETURN_NULL(); } RETURN_STRINGL(ZSTR_VAL(redis_sock->persistent_id), ZSTR_LEN(redis_sock->persistent_id)); } /* {{{ proto Redis::getAuth */ PHP_METHOD(Redis, getAuth) { RedisSock *redis_sock; if ((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU)) == NULL) { RETURN_FALSE; } else if (redis_sock->auth == NULL) { RETURN_NULL(); } RETURN_STRINGL(ZSTR_VAL(redis_sock->auth), ZSTR_LEN(redis_sock->auth)); } /* * $redis->client('list'); * $redis->client('kill', ); * $redis->client('setname', ); * $redis->client('getname'); */ PHP_METHOD(Redis, client) { zval *object; RedisSock *redis_sock; char *cmd, *opt=NULL, *arg=NULL; strlen_t opt_len, arg_len; int cmd_len; // Parse our method parameters if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|s", &object, redis_ce, &opt, &opt_len, &arg, &arg_len) == FAILURE) { RETURN_FALSE; } /* Grab our socket */ if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } /* Build our CLIENT command */ if (ZEND_NUM_ARGS() == 2) { cmd_len = REDIS_SPPRINTF(&cmd, "CLIENT", "ss", opt, opt_len, arg, arg_len); } else { cmd_len = REDIS_SPPRINTF(&cmd, "CLIENT", "s", opt, opt_len); } /* Execute our queue command */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); /* We handle CLIENT LIST with a custom response function */ if(!strncasecmp(opt, "list", 4)) { IF_ATOMIC() { redis_client_list_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,redis_sock, NULL); } REDIS_PROCESS_RESPONSE(redis_client_list_reply); } else { IF_ATOMIC() { redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,NULL,NULL); } REDIS_PROCESS_RESPONSE(redis_read_variant_reply); } } /* {{{ proto mixed Redis::rawcommand(string $command, [ $arg1 ... $argN]) */ PHP_METHOD(Redis, rawcommand) { int argc = ZEND_NUM_ARGS(), cmd_len; char *cmd = NULL; RedisSock *redis_sock; zval *z_args; /* Sanity check on arguments */ if (argc < 1) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Must pass at least one command keyword"); RETURN_FALSE; } z_args = emalloc(argc * sizeof(zval)); if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Internal PHP error parsing arguments"); efree(z_args); RETURN_FALSE; } else if (redis_build_raw_cmd(z_args, argc, &cmd, &cmd_len TSRMLS_CC) < 0 || (redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL ) { if (cmd) efree(cmd); efree(z_args); RETURN_FALSE; } /* Clean up command array */ efree(z_args); /* Execute our command */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,redis_sock,NULL,NULL); } REDIS_PROCESS_RESPONSE(redis_read_variant_reply); } /* }}} */ /* {{{ proto array Redis::command() * proto array Redis::command('info', string cmd) * proto array Redis::command('getkeys', array cmd_args) */ PHP_METHOD(Redis, command) { REDIS_PROCESS_CMD(command, redis_read_variant_reply); } /* }}} */ /* Helper to format any combination of SCAN arguments */ PHP_REDIS_API int redis_build_scan_cmd(char **cmd, REDIS_SCAN_TYPE type, char *key, int key_len, int iter, char *pattern, int pattern_len, int count) { smart_string cmdstr = {0}; char *keyword; int argc; /* Count our arguments +1 for key if it's got one, and + 2 for pattern */ /* or count given that they each carry keywords with them. */ argc = 1 + (key_len > 0) + (pattern_len > 0 ? 2 : 0) + (count > 0 ? 2 : 0); /* Turn our type into a keyword */ switch(type) { case TYPE_SCAN: keyword = "SCAN"; break; case TYPE_SSCAN: keyword = "SSCAN"; break; case TYPE_HSCAN: keyword = "HSCAN"; break; case TYPE_ZSCAN: default: keyword = "ZSCAN"; break; } /* Start the command */ redis_cmd_init_sstr(&cmdstr, argc, keyword, strlen(keyword)); if (key_len) redis_cmd_append_sstr(&cmdstr, key, key_len); redis_cmd_append_sstr_int(&cmdstr, iter); /* Append COUNT if we've got it */ if(count) { REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "COUNT"); redis_cmd_append_sstr_int(&cmdstr, count); } /* Append MATCH if we've got it */ if(pattern_len) { REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "MATCH"); redis_cmd_append_sstr(&cmdstr, pattern, pattern_len); } /* Return our command length */ *cmd = cmdstr.c; return cmdstr.len; } /* {{{ proto redis::scan(&$iterator, [pattern, [count]]) */ PHP_REDIS_API void generic_scan_cmd(INTERNAL_FUNCTION_PARAMETERS, REDIS_SCAN_TYPE type) { zval *object, *z_iter; RedisSock *redis_sock; HashTable *hash; char *pattern=NULL, *cmd, *key=NULL; int cmd_len, num_elements, key_free=0; strlen_t key_len = 0, pattern_len = 0; zend_long count=0, iter; /* Different prototype depending on if this is a key based scan */ if(type != TYPE_SCAN) { // Requires a key if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz/|s!l", &object, redis_ce, &key, &key_len, &z_iter, &pattern, &pattern_len, &count)==FAILURE) { RETURN_FALSE; } } else { // Doesn't require a key if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oz/|s!l", &object, redis_ce, &z_iter, &pattern, &pattern_len, &count) == FAILURE) { RETURN_FALSE; } } /* Grab our socket */ if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) { RETURN_FALSE; } /* Calling this in a pipeline makes no sense */ IF_NOT_ATOMIC() { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Can't call SCAN commands in multi or pipeline mode!"); RETURN_FALSE; } // The iterator should be passed in as NULL for the first iteration, but we // can treat any NON LONG value as NULL for these purposes as we've // seperated the variable anyway. if(Z_TYPE_P(z_iter) != IS_LONG || Z_LVAL_P(z_iter)<0) { /* Convert to long */ convert_to_long(z_iter); iter = 0; } else if(Z_LVAL_P(z_iter)!=0) { /* Update our iterator value for the next passthru */ iter = Z_LVAL_P(z_iter); } else { /* We're done, back to iterator zero */ RETURN_FALSE; } /* Prefix our key if we've got one and we have a prefix set */ if(key_len) { key_free = redis_key_prefix(redis_sock, &key, &key_len); } /** * Redis can return to us empty keys, especially in the case where there * are a large number of keys to scan, and we're matching against a * pattern. phpredis can be set up to abstract this from the user, by * setting OPT_SCAN to REDIS_SCAN_RETRY. Otherwise we will return empty * keys and the user will need to make subsequent calls with an updated * iterator. */ do { /* Free our previous reply if we're back in the loop. We know we are * if our return_value is an array */ if (Z_TYPE_P(return_value) == IS_ARRAY) { zval_dtor(return_value); ZVAL_NULL(return_value); } // Format our SCAN command cmd_len = redis_build_scan_cmd(&cmd, type, key, key_len, (int)iter, pattern, pattern_len, count); /* Execute our command getting our new iterator value */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); if(redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,type,&iter)<0) { if(key_free) efree(key); RETURN_FALSE; } /* Get the number of elements */ hash = Z_ARRVAL_P(return_value); num_elements = zend_hash_num_elements(hash); } while(redis_sock->scan == REDIS_SCAN_RETRY && iter != 0 && num_elements == 0); /* Free our key if it was prefixed */ if(key_free) efree(key); /* Update our iterator reference */ Z_LVAL_P(z_iter) = iter; } PHP_METHOD(Redis, scan) { generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_SCAN); } PHP_METHOD(Redis, hscan) { generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_HSCAN); } PHP_METHOD(Redis, sscan) { generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_SSCAN); } PHP_METHOD(Redis, zscan) { generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_ZSCAN); } /* * HyperLogLog based commands */ /* {{{ proto Redis::pfAdd(string key, array elements) }}} */ PHP_METHOD(Redis, pfadd) { REDIS_PROCESS_CMD(pfadd, redis_long_response); } /* {{{ proto Redis::pfCount(string key) }}}*/ PHP_METHOD(Redis, pfcount) { REDIS_PROCESS_CMD(pfcount, redis_long_response); } /* {{{ proto Redis::pfMerge(string dstkey, array keys) }}}*/ PHP_METHOD(Redis, pfmerge) { REDIS_PROCESS_CMD(pfmerge, redis_boolean_response); } /* * Geo commands */ PHP_METHOD(Redis, geoadd) { REDIS_PROCESS_KW_CMD("GEOADD", redis_key_varval_cmd, redis_long_response); } PHP_METHOD(Redis, geohash) { REDIS_PROCESS_KW_CMD("GEOHASH", redis_key_varval_cmd, redis_mbulk_reply_raw); } PHP_METHOD(Redis, geopos) { REDIS_PROCESS_KW_CMD("GEOPOS", redis_key_varval_cmd, redis_read_variant_reply); } PHP_METHOD(Redis, geodist) { REDIS_PROCESS_CMD(geodist, redis_bulk_double_response); } PHP_METHOD(Redis, georadius) { REDIS_PROCESS_CMD(georadius, redis_read_variant_reply); } PHP_METHOD(Redis, georadiusbymember) { REDIS_PROCESS_CMD(georadiusbymember, redis_read_variant_reply); } /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */ redis-3.1.6/crc16.h0000644000175000000120000001060313223116600014467 0ustar pyatsukhnenkowheel/* * Copyright 2001-2010 Georges Menie (www.menie.org) * Copyright 2010 Salvatore Sanfilippo (adapted to Redis coding style) * All rights reserved. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the University of California, Berkeley nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* CRC16 implementation according to CCITT standards. * * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the * following parameters: * * Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN" * Width : 16 bit * Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1) * Initialization : 0000 * Reflect Input byte : False * Reflect Output CRC : False * Xor constant to output CRC : 0000 * Output for "123456789" : 31C3 */ #include static const uint16_t crc16tab[256]= { 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6, 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485, 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4, 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823, 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12, 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41, 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70, 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f, 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e, 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d, 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c, 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab, 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a, 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9, 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8, 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 }; static inline uint16_t crc16(const char *buf, int len) { int counter; uint16_t crc = 0; for (counter = 0; counter < len; counter++) crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF]; return crc; } redis-3.1.6/redis_session.c0000644000175000000120000005746613223116600016437 0ustar pyatsukhnenkowheel/* -*- Mode: C; tab-width: 4 -*- */ /* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2009 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Original author: Alfonso Jimenez | | Maintainer: Nicolas Favre-Felix | | Maintainer: Nasreddine Bouafif | | Maintainer: Michael Grunder | +----------------------------------------------------------------------+ */ #include "common.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef PHP_SESSION #include "ext/standard/info.h" #include "php_redis.h" #include "redis_session.h" #include #include "library.h" #include "cluster_library.h" #include "php.h" #include "php_ini.h" #include "php_variables.h" #include "SAPI.h" #include "ext/standard/url.h" ps_module ps_mod_redis = { PS_MOD(redis) }; ps_module ps_mod_redis_cluster = { PS_MOD(rediscluster) }; typedef struct redis_pool_member_ { RedisSock *redis_sock; int weight; int database; char *prefix; size_t prefix_len; char *auth; size_t auth_len; struct redis_pool_member_ *next; } redis_pool_member; typedef struct { int totalWeight; int count; redis_pool_member *head; } redis_pool; PHP_REDIS_API redis_pool* redis_pool_new(TSRMLS_D) { return ecalloc(1, sizeof(redis_pool)); } PHP_REDIS_API void redis_pool_add(redis_pool *pool, RedisSock *redis_sock, int weight, int database, char *prefix, char *auth TSRMLS_DC) { redis_pool_member *rpm = ecalloc(1, sizeof(redis_pool_member)); rpm->redis_sock = redis_sock; rpm->weight = weight; rpm->database = database; rpm->prefix = prefix; rpm->prefix_len = (prefix?strlen(prefix):0); rpm->auth = auth; rpm->auth_len = (auth?strlen(auth):0); rpm->next = pool->head; pool->head = rpm; pool->totalWeight += weight; } PHP_REDIS_API void redis_pool_free(redis_pool *pool TSRMLS_DC) { redis_pool_member *rpm, *next; rpm = pool->head; while(rpm) { next = rpm->next; redis_sock_disconnect(rpm->redis_sock TSRMLS_CC); redis_free_socket(rpm->redis_sock); if(rpm->prefix) efree(rpm->prefix); if(rpm->auth) efree(rpm->auth); efree(rpm); rpm = next; } efree(pool); } static void redis_pool_member_auth(redis_pool_member *rpm TSRMLS_DC) { RedisSock *redis_sock = rpm->redis_sock; char *response, *cmd; int response_len, cmd_len; /* Short circuit if we don't have a password */ if(!rpm->auth || !rpm->auth_len) { return; } cmd_len = REDIS_SPPRINTF(&cmd, "AUTH", "s", rpm->auth, rpm->auth_len); if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) >= 0) { if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC))) { efree(response); } } efree(cmd); } static void redis_pool_member_select(redis_pool_member *rpm TSRMLS_DC) { RedisSock *redis_sock = rpm->redis_sock; char *response, *cmd; int response_len, cmd_len; cmd_len = REDIS_SPPRINTF(&cmd, "SELECT", "d", rpm->database); if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) >= 0) { if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC))) { efree(response); } } efree(cmd); } PHP_REDIS_API redis_pool_member * redis_pool_get_sock(redis_pool *pool, const char *key TSRMLS_DC) { unsigned int pos, i; memcpy(&pos, key, sizeof(pos)); pos %= pool->totalWeight; redis_pool_member *rpm = pool->head; for(i = 0; i < pool->totalWeight;) { if(pos >= i && pos < i + rpm->weight) { int needs_auth = 0; if(rpm->auth && rpm->auth_len && rpm->redis_sock->status != REDIS_SOCK_STATUS_CONNECTED) { needs_auth = 1; } redis_sock_server_open(rpm->redis_sock TSRMLS_CC); if(needs_auth) { redis_pool_member_auth(rpm TSRMLS_CC); } if(rpm->database >= 0) { /* default is -1 which leaves the choice to redis. */ redis_pool_member_select(rpm TSRMLS_CC); } return rpm; } i += rpm->weight; rpm = rpm->next; } return NULL; } /* {{{ PS_OPEN_FUNC */ PS_OPEN_FUNC(redis) { php_url *url; zval params, *param; int i, j, path_len; redis_pool *pool = redis_pool_new(TSRMLS_C); for (i=0,j=0,path_len=strlen(save_path); iquery != NULL) { array_init(¶ms); #if (PHP_VERSION_ID < 70300) sapi_module.treat_data(PARSE_STRING, estrdup(url->query), ¶ms TSRMLS_CC); #else sapi_module.treat_data(PARSE_STRING, estrndup(ZSTR_VAL(url->query), ZSTR_LEN(url->query)), ¶ms TSRMLS_CC); #endif if ((param = zend_hash_str_find(Z_ARRVAL(params), "weight", sizeof("weight") - 1)) != NULL) { weight = zval_get_long(param); } if ((param = zend_hash_str_find(Z_ARRVAL(params), "timeout", sizeof("timeout") - 1)) != NULL) { timeout = atof(Z_STRVAL_P(param)); } if ((param = zend_hash_str_find(Z_ARRVAL(params), "read_timeout", sizeof("read_timeout") - 1)) != NULL) { read_timeout = atof(Z_STRVAL_P(param)); } if ((param = zend_hash_str_find(Z_ARRVAL(params), "persistent", sizeof("persistent") - 1)) != NULL) { persistent = (atol(Z_STRVAL_P(param)) == 1 ? 1 : 0); } if ((param = zend_hash_str_find(Z_ARRVAL(params), "persistent_id", sizeof("persistent_id") - 1)) != NULL) { persistent_id = estrndup(Z_STRVAL_P(param), Z_STRLEN_P(param)); } if ((param = zend_hash_str_find(Z_ARRVAL(params), "prefix", sizeof("prefix") - 1)) != NULL) { prefix = estrndup(Z_STRVAL_P(param), Z_STRLEN_P(param)); } if ((param = zend_hash_str_find(Z_ARRVAL(params), "auth", sizeof("auth") - 1)) != NULL) { auth = estrndup(Z_STRVAL_P(param), Z_STRLEN_P(param)); } if ((param = zend_hash_str_find(Z_ARRVAL(params), "database", sizeof("database") - 1)) != NULL) { database = zval_get_long(param); } if ((param = zend_hash_str_find(Z_ARRVAL(params), "retry_interval", sizeof("retry_interval") - 1)) != NULL) { retry_interval = zval_get_long(param); } zval_dtor(¶ms); } if ((url->path == NULL && url->host == NULL) || weight <= 0 || timeout <= 0) { php_url_free(url); if (persistent_id) efree(persistent_id); if (prefix) efree(prefix); if (auth) efree(auth); redis_pool_free(pool TSRMLS_CC); PS_SET_MOD_DATA(NULL); return FAILURE; } RedisSock *redis_sock; if(url->host) { #if (PHP_VERSION_ID < 70300) redis_sock = redis_sock_create(url->host, strlen(url->host), url->port, timeout, read_timeout, persistent, persistent_id, retry_interval, 0); #else redis_sock = redis_sock_create(ZSTR_VAL(url->host), ZSTR_LEN(url->host), url->port, timeout, read_timeout, persistent, persistent_id, retry_interval, 0); #endif } else { /* unix */ #if (PHP_VERSION_ID < 70300) redis_sock = redis_sock_create(url->path, strlen(url->path), 0, timeout, read_timeout, persistent, persistent_id, retry_interval, 0); #else redis_sock = redis_sock_create(ZSTR_VAL(url->path), ZSTR_LEN(url->path), 0, timeout, read_timeout, persistent, persistent_id, retry_interval, 0); #endif } redis_pool_add(pool, redis_sock, weight, database, prefix, auth TSRMLS_CC); php_url_free(url); } } if (pool->head) { PS_SET_MOD_DATA(pool); return SUCCESS; } return FAILURE; } /* }}} */ /* {{{ PS_CLOSE_FUNC */ PS_CLOSE_FUNC(redis) { redis_pool *pool = PS_GET_MOD_DATA(); if(pool){ redis_pool_free(pool TSRMLS_CC); PS_SET_MOD_DATA(NULL); } return SUCCESS; } /* }}} */ static char * redis_session_key(redis_pool_member *rpm, const char *key, int key_len, int *session_len) { char *session; char default_prefix[] = "PHPREDIS_SESSION:"; char *prefix = default_prefix; size_t prefix_len = sizeof(default_prefix)-1; if(rpm->prefix) { prefix = rpm->prefix; prefix_len = rpm->prefix_len; } /* build session key */ *session_len = key_len + prefix_len; session = emalloc(*session_len); memcpy(session, prefix, prefix_len); memcpy(session + prefix_len, key, key_len); return session; } /* {{{ PS_READ_FUNC */ PS_READ_FUNC(redis) { char *resp, *cmd; int resp_len, cmd_len; redis_pool *pool = PS_GET_MOD_DATA(); #if (PHP_MAJOR_VERSION < 7) redis_pool_member *rpm = redis_pool_get_sock(pool, key TSRMLS_CC); #else redis_pool_member *rpm = redis_pool_get_sock(pool, ZSTR_VAL(key) TSRMLS_CC); #endif RedisSock *redis_sock = rpm?rpm->redis_sock:NULL; if(!rpm || !redis_sock){ return FAILURE; } /* send GET command */ #if (PHP_MAJOR_VERSION < 7) resp = redis_session_key(rpm, key, strlen(key), &resp_len); #else resp = redis_session_key(rpm, ZSTR_VAL(key), ZSTR_LEN(key), &resp_len); #endif cmd_len = REDIS_SPPRINTF(&cmd, "GET", "s", resp, resp_len); efree(resp); if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); return FAILURE; } efree(cmd); /* Read response from Redis. If we get a NULL response from redis_sock_read * this can indicate an error, OR a "NULL bulk" reply (empty session data) * in which case we can reply with success. */ if ((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC)) == NULL && resp_len != -1) { return FAILURE; } #if (PHP_MAJOR_VERSION < 7) if (resp_len < 0) { *val = STR_EMPTY_ALLOC(); *vallen = 0; } else { *val = resp; *vallen = resp_len; } #else if (resp_len < 0) { *val = ZSTR_EMPTY_ALLOC(); } else { *val = zend_string_init(resp, resp_len, 0); } efree(resp); #endif return SUCCESS; } /* }}} */ /* {{{ PS_WRITE_FUNC */ PS_WRITE_FUNC(redis) { char *cmd, *response, *session; int cmd_len, response_len, session_len; redis_pool *pool = PS_GET_MOD_DATA(); #if (PHP_MAJOR_VERSION < 7) redis_pool_member *rpm = redis_pool_get_sock(pool, key TSRMLS_CC); #else redis_pool_member *rpm = redis_pool_get_sock(pool, ZSTR_VAL(key) TSRMLS_CC); #endif RedisSock *redis_sock = rpm?rpm->redis_sock:NULL; if(!rpm || !redis_sock){ return FAILURE; } /* send SET command */ #if (PHP_MAJOR_VERSION < 7) session = redis_session_key(rpm, key, strlen(key), &session_len); cmd_len = REDIS_SPPRINTF(&cmd, "SETEX", "sds", session, session_len, INI_INT("session.gc_maxlifetime"), val, vallen); #else session = redis_session_key(rpm, ZSTR_VAL(key), ZSTR_LEN(key), &session_len); cmd_len = REDIS_SPPRINTF(&cmd, "SETEX", "sds", session, session_len, INI_INT("session.gc_maxlifetime"), ZSTR_VAL(val), ZSTR_LEN(val)); #endif efree(session); if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); return FAILURE; } efree(cmd); /* read response */ if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { return FAILURE; } if(response_len == 3 && strncmp(response, "+OK", 3) == 0) { efree(response); return SUCCESS; } else { efree(response); return FAILURE; } } /* }}} */ /* {{{ PS_DESTROY_FUNC */ PS_DESTROY_FUNC(redis) { char *cmd, *response, *session; int cmd_len, response_len, session_len; redis_pool *pool = PS_GET_MOD_DATA(); #if (PHP_MAJOR_VERSION < 7) redis_pool_member *rpm = redis_pool_get_sock(pool, key TSRMLS_CC); #else redis_pool_member *rpm = redis_pool_get_sock(pool, ZSTR_VAL(key) TSRMLS_CC); #endif RedisSock *redis_sock = rpm?rpm->redis_sock:NULL; if(!rpm || !redis_sock){ return FAILURE; } /* send DEL command */ #if (PHP_MAJOR_VERSION < 7) session = redis_session_key(rpm, key, strlen(key), &session_len); #else session = redis_session_key(rpm, ZSTR_VAL(key), ZSTR_LEN(key), &session_len); #endif cmd_len = REDIS_SPPRINTF(&cmd, "DEL", "s", session, session_len); efree(session); if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { efree(cmd); return FAILURE; } efree(cmd); /* read response */ if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { return FAILURE; } if(response_len == 2 && response[0] == ':' && (response[1] == '0' || response[1] == '1')) { efree(response); return SUCCESS; } else { efree(response); return FAILURE; } } /* }}} */ /* {{{ PS_GC_FUNC */ PS_GC_FUNC(redis) { return SUCCESS; } /* }}} */ /** * Redis Cluster session handler functions */ /* Helper to extract timeout values */ static void session_conf_timeout(HashTable *ht_conf, const char *key, int key_len, double *val) { zval *z_val; if ((z_val = zend_hash_str_find(ht_conf, key, key_len - 1)) != NULL && Z_TYPE_P(z_val) == IS_STRING ) { *val = atof(Z_STRVAL_P(z_val)); } } /* Simple helper to retreive a boolean (0 or 1) value from a string stored in our * session.save_path variable. This is so the user can use 0, 1, or 'true', * 'false' */ static void session_conf_bool(HashTable *ht_conf, char *key, int keylen, int *retval) { zval *z_val; char *str; int strlen; /* See if we have the option, and it's a string */ if ((z_val = zend_hash_str_find(ht_conf, key, keylen - 1)) != NULL && Z_TYPE_P(z_val) == IS_STRING ) { str = Z_STRVAL_P(z_val); strlen = Z_STRLEN_P(z_val); /* true/yes/1 are treated as true. Everything else is false */ *retval = (strlen == 4 && !strncasecmp(str, "true", 4)) || (strlen == 3 && !strncasecmp(str, "yes", 3)) || (strlen == 1 && !strncasecmp(str, "1", 1)); } } /* Prefix a session key */ static char *cluster_session_key(redisCluster *c, const char *key, int keylen, int *skeylen, short *slot) { char *skey; *skeylen = keylen + ZSTR_LEN(c->flags->prefix); skey = emalloc(*skeylen); memcpy(skey, ZSTR_VAL(c->flags->prefix), ZSTR_LEN(c->flags->prefix)); memcpy(skey + ZSTR_LEN(c->flags->prefix), key, keylen); *slot = cluster_hash_key(skey, *skeylen); return skey; } PS_OPEN_FUNC(rediscluster) { redisCluster *c; zval z_conf, *z_val; HashTable *ht_conf, *ht_seeds; double timeout = 0, read_timeout = 0; int persistent = 0; int retval, prefix_len, failover = REDIS_FAILOVER_NONE; char *prefix; /* Parse configuration for session handler */ array_init(&z_conf); sapi_module.treat_data(PARSE_STRING, estrdup(save_path), &z_conf TSRMLS_CC); /* Sanity check that we're able to parse and have a seeds array */ if (Z_TYPE(z_conf) != IS_ARRAY || (z_val = zend_hash_str_find(Z_ARRVAL(z_conf), "seed", sizeof("seed") - 1)) == NULL || Z_TYPE_P(z_val) != IS_ARRAY) { zval_dtor(&z_conf); return FAILURE; } /* Grab a copy of our config hash table and keep seeds array */ ht_conf = Z_ARRVAL(z_conf); ht_seeds = Z_ARRVAL_P(z_val); /* Grab timeouts if they were specified */ session_conf_timeout(ht_conf, "timeout", sizeof("timeout"), &timeout); session_conf_timeout(ht_conf, "read_timeout", sizeof("read_timeout"), &read_timeout); /* Grab persistent option */ session_conf_bool(ht_conf, "persistent", sizeof("persistent"), &persistent); /* Sanity check on our timeouts */ if (timeout < 0 || read_timeout < 0) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't set negative timeout values in session configuration"); zval_dtor(&z_conf); return FAILURE; } /* Look for a specific prefix */ if ((z_val = zend_hash_str_find(ht_conf, "prefix", sizeof("prefix") - 1)) != NULL && Z_TYPE_P(z_val) == IS_STRING && Z_STRLEN_P(z_val) > 0 ) { prefix = Z_STRVAL_P(z_val); prefix_len = Z_STRLEN_P(z_val); } else { prefix = "PHPREDIS_CLUSTER_SESSION:"; prefix_len = sizeof("PHPREDIS_CLUSTER_SESSION:")-1; } /* Look for a specific failover setting */ if ((z_val = zend_hash_str_find(ht_conf, "failover", sizeof("failover") - 1)) != NULL && Z_TYPE_P(z_val) == IS_STRING ) { if (!strcasecmp(Z_STRVAL_P(z_val), "error")) { failover = REDIS_FAILOVER_ERROR; } else if (!strcasecmp(Z_STRVAL_P(z_val), "distribute")) { failover = REDIS_FAILOVER_DISTRIBUTE; } } c = cluster_create(timeout, read_timeout, failover, persistent); if (!cluster_init_seeds(c, ht_seeds) && !cluster_map_keyspace(c TSRMLS_CC)) { /* Set up our prefix */ c->flags->prefix = zend_string_init(prefix, prefix_len, 0); PS_SET_MOD_DATA(c); retval = SUCCESS; } else { cluster_free(c); retval = FAILURE; } /* Cleanup */ zval_dtor(&z_conf); return retval; } /* {{{ PS_READ_FUNC */ PS_READ_FUNC(rediscluster) { redisCluster *c = PS_GET_MOD_DATA(); clusterReply *reply; char *cmd, *skey; int cmdlen, skeylen; short slot; /* Set up our command and slot information */ #if (PHP_MAJOR_VERSION < 7) skey = cluster_session_key(c, key, strlen(key), &skeylen, &slot); #else skey = cluster_session_key(c, ZSTR_VAL(key), ZSTR_LEN(key), &skeylen, &slot); #endif cmdlen = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "GET", "s", skey, skeylen); efree(skey); /* Attempt to kick off our command */ c->readonly = 1; if (cluster_send_command(c,slot,cmd,cmdlen TSRMLS_CC)<0 || c->err) { efree(cmd); return FAILURE; } /* Clean up command */ efree(cmd); /* Attempt to read reply */ reply = cluster_read_resp(c TSRMLS_CC); if (!reply || c->err) { if (reply) cluster_free_reply(reply, 1); return FAILURE; } /* Push reply value to caller */ #if (PHP_MAJOR_VERSION < 7) if (reply->str == NULL) { *val = STR_EMPTY_ALLOC(); *vallen = 0; } else { *val = reply->str; *vallen = reply->len; } #else if (reply->str == NULL) { *val = ZSTR_EMPTY_ALLOC(); } else { *val = zend_string_init(reply->str, reply->len, 0); } #endif /* Clean up */ cluster_free_reply(reply, 0); /* Success! */ return SUCCESS; } /* {{{ PS_WRITE_FUNC */ PS_WRITE_FUNC(rediscluster) { redisCluster *c = PS_GET_MOD_DATA(); clusterReply *reply; char *cmd, *skey; int cmdlen, skeylen; short slot; /* Set up command and slot info */ #if (PHP_MAJOR_VERSION < 7) skey = cluster_session_key(c, key, strlen(key), &skeylen, &slot); cmdlen = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "SETEX", "sds", skey, skeylen, INI_INT("session.gc_maxlifetime"), val, vallen); #else skey = cluster_session_key(c, ZSTR_VAL(key), ZSTR_LEN(key), &skeylen, &slot); cmdlen = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "SETEX", "sds", skey, skeylen, INI_INT("session.gc_maxlifetime"), ZSTR_VAL(val), ZSTR_LEN(val)); #endif efree(skey); /* Attempt to send command */ c->readonly = 0; if (cluster_send_command(c,slot,cmd,cmdlen TSRMLS_CC)<0 || c->err) { efree(cmd); return FAILURE; } /* Clean up our command */ efree(cmd); /* Attempt to read reply */ reply = cluster_read_resp(c TSRMLS_CC); if (!reply || c->err) { if (reply) cluster_free_reply(reply, 1); return FAILURE; } /* Clean up*/ cluster_free_reply(reply, 1); return SUCCESS; } /* {{{ PS_DESTROY_FUNC(rediscluster) */ PS_DESTROY_FUNC(rediscluster) { redisCluster *c = PS_GET_MOD_DATA(); clusterReply *reply; char *cmd, *skey; int cmdlen, skeylen; short slot; /* Set up command and slot info */ #if (PHP_MAJOR_VERSION < 7) skey = cluster_session_key(c, key, strlen(key), &skeylen, &slot); #else skey = cluster_session_key(c, ZSTR_VAL(key), ZSTR_LEN(key), &skeylen, &slot); #endif cmdlen = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "DEL", "s", skey, skeylen); efree(skey); /* Attempt to send command */ if (cluster_send_command(c,slot,cmd,cmdlen TSRMLS_CC)<0 || c->err) { efree(cmd); return FAILURE; } /* Clean up our command */ efree(cmd); /* Attempt to read reply */ reply = cluster_read_resp(c TSRMLS_CC); if (!reply || c->err) { if (reply) cluster_free_reply(reply, 1); return FAILURE; } /* Clean up our reply */ cluster_free_reply(reply, 1); return SUCCESS; } /* {{{ PS_CLOSE_FUNC */ PS_CLOSE_FUNC(rediscluster) { redisCluster *c = PS_GET_MOD_DATA(); if (c) { cluster_free(c); PS_SET_MOD_DATA(NULL); } return SUCCESS; } /* {{{ PS_GC_FUNC */ PS_GC_FUNC(rediscluster) { return SUCCESS; } #endif /* vim: set tabstop=4 expandtab: */ redis-3.1.6/redis_session.h0000644000175000000120000000064613223116600016430 0ustar pyatsukhnenkowheel#ifndef REDIS_SESSION_H #define REDIS_SESSION_H #ifdef PHP_SESSION #include "ext/session/php_session.h" PS_OPEN_FUNC(redis); PS_CLOSE_FUNC(redis); PS_READ_FUNC(redis); PS_WRITE_FUNC(redis); PS_DESTROY_FUNC(redis); PS_GC_FUNC(redis); PS_OPEN_FUNC(rediscluster); PS_CLOSE_FUNC(rediscluster); PS_READ_FUNC(rediscluster); PS_WRITE_FUNC(rediscluster); PS_DESTROY_FUNC(rediscluster); PS_GC_FUNC(rediscluster); #endif #endif