diff -Nru php-redis-2.2.4/debian/changelog php-redis-3.0.0/debian/changelog --- php-redis-2.2.4/debian/changelog 2014-03-07 20:43:44.000000000 +0000 +++ php-redis-3.0.0/debian/changelog 2016-09-20 13:28:33.000000000 +0000 @@ -1,15 +1,104 @@ -php-redis (2.2.4-1build2) trusty; urgency=medium +php-redis (3.0.0-0+syseleven1) trusty; urgency=medium - * No-change rebuild to generate correct description in dbgsym packages + * New upstream version + * Backport for Ubuntu 14.04 and PHP 7 - -- Iain Lane Fri, 07 Mar 2014 20:43:44 +0000 + -- Martin Loschwitz Tue, 20 Sep 2016 15:18:52 +0200 -php-redis (2.2.4-1build1) trusty; urgency=medium +php-redis (2.2.7-394-g892f928+2.2.7-4) unstable; urgency=medium - * No change rebuild for phpapi-20121212+lfs (LFS ABI break on 32-bit - arches). + * Move php-igbinary to Depends (Closes: #826407) - -- Robie Basak Wed, 19 Feb 2014 08:23:53 +0000 + -- Ondřej Surý Mon, 06 Jun 2016 11:48:21 +0200 + +php-redis (2.2.7-394-g892f928+2.2.7-3) unstable; urgency=medium + + * Enable igbinary support again + + -- Ondřej Surý Fri, 03 Jun 2016 15:34:49 +0200 + +php-redis (2.2.7-394-g892f928+2.2.7-2) unstable; urgency=medium + + * Move the default makefile snippet to dh-php and use a simple d/rules + with dh-php >= 0.12~ + + -- Ondřej Surý Fri, 29 Apr 2016 11:19:26 +0200 + +php-redis (2.2.7-394-g892f928+2.2.7-1) unstable; urgency=medium + + * Imported Upstream version 2.2.7-394-g892f928+2.2.7 + * Improve d/rules so it can be used with both single or multiple PECL + upstream versions, controlled by looking for package-MAJOR.MINOR.xml, + package-MAJOR.xml and package.xml in this order + + -- Ondřej Surý Thu, 28 Apr 2016 16:22:16 +0200 + +php-redis (2.2.7-389-g2887ad1+2.2.7-1) unstable; urgency=medium + + * Imported Upstream version 2.2.7-389-g2887ad1+2.2.7 + + -- Ondřej Surý Sun, 06 Mar 2016 21:00:52 +0100 + +php-redis (2.2.7-355-g799e0bc+2.2.7-4) unstable; urgency=medium + + * Force rebuild with dh_php >= 0.7 + + -- Ondřej Surý Mon, 29 Feb 2016 22:29:38 +0100 + +php-redis (2.2.7-355-g799e0bc+2.2.7-3) unstable; urgency=medium + + * Enable igbinary serializer in the redis extension build + * Format d/copyright using wrap-and-sort -a + * Add php-igbinary to Recommends instead of Depends since it's not + needed unless you use igbinary serializer + + -- Ondřej Surý Thu, 18 Feb 2016 14:31:29 +0100 + +php-redis (2.2.7-355-g799e0bc+2.2.7-2) unstable; urgency=medium + + * Re-upload with lintian override for PHP license + + -- Ondřej Surý Fri, 15 Jan 2016 14:52:58 +0100 + +php-redis (2.2.7-355-g799e0bc+2.2.7-1) unstable; urgency=medium + + * Imported Upstream version 2.2.7-355-g799e0bc+2.2.7 + * Dual source build for PHP 5.6 and PHP 7.0 + * Override wrong license-problem-php-license lintian check for packages + from PECL + + -- Ondřej Surý Thu, 17 Dec 2015 15:11:15 +0100 + +php-redis (2.2.7-355-g799e0bc-1) unstable; urgency=medium + + * Don't import ugly upstream debian/ directory + * Imported Upstream version 2.2.7-355-g799e0bc to fix building with + PHP 7.0 + + -- Ondřej Surý Wed, 09 Dec 2015 14:00:35 +0100 + +php-redis (2.2.7-2) unstable; urgency=medium + + * Convert package to PHP 7.0 + + -- Ondřej Surý Wed, 09 Dec 2015 13:52:48 +0100 + +php-redis (2.2.7-1) unstable; urgency=medium + + * New upstream version 2.2.7 + + -- Ondřej Surý Sat, 02 May 2015 13:29:53 +0200 + +php-redis (2.2.5-1) unstable; urgency=low + + * Team upload. + * Imported Upstream version 2.2.5 + * d/changelog: fixed too long line + * d/control: + - bumped up standards version to 3.9.5 (no changes need) + - added phppear:Debian-Depends in Depends + + -- Jonas Genannt Tue, 25 Mar 2014 12:28:52 +0100 php-redis (2.2.4-1) unstable; urgency=low @@ -17,6 +106,7 @@ * Move under PHP PECL team umbrella * Add Cyril Bouthors to Uploaders * Add myself into Uploaders - * Don't use ${phppear:Debian-Depends} to not pull strict php5 dependency, use ${php:Depends} instead + * Don't use ${phppear:Debian-Depends} to not pull strict php5 dependency, use + ${php:Depends} instead -- Ondřej Surý Mon, 21 Oct 2013 10:19:51 +0200 diff -Nru php-redis-2.2.4/debian/control php-redis-3.0.0/debian/control --- php-redis-2.2.4/debian/control 2013-10-21 08:20:21.000000000 +0000 +++ php-redis-3.0.0/debian/control 2016-09-20 19:49:19.000000000 +0000 @@ -3,22 +3,35 @@ Priority: optional Maintainer: Debian PHP PECL Maintainers Uploaders: Jonas Genannt , - Cyril Bouthors , - Ondřej Surý + Cyril Bouthors , + Ondřej Surý Build-Depends: debhelper (>= 9), - php5-dev, - pkg-php-tools (>= 1.6~), - dh-php5 -Standards-Version: 3.9.4 + dh-php (>= 0.12~), + php7.0-dev, + php7.0-json, + php7.1-dev, + php7.1-json, + php-igbinary +Build-Conflicts: php5.5-dev, + php5.5-json, + php5.5-common, + php5.6-dev, + php5.6-json, + php5.6-common +Standards-Version: 3.9.8 Vcs-Git: git://anonscm.debian.org/pkg-php/php-redis.git Vcs-Browser: http://anonscm.debian.org/gitweb?p=pkg-php/php-redis.git;a=summary Homepage: http://pecl.php.net/package/redis -Package: php5-redis +Package: php-redis Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, ${php:Depends} -Recommends: ${phppear:Debian-Recommends} -Breaks: ${phppear:Debian-Breaks} +Depends: php-igbinary, + ${misc:Depends}, + ${php:Depends}, + ${shlibs:Depends} Suggests: redis-server -Description: ${phppear:summary} - ${phppear:description} +Provides: ${php:Provides} +Description: PHP extension for interfacing with Redis + This extension allows php applications to communicate with the Redis + persistent key-value store. The php-redis module provides an easy + object oriented interface. diff -Nru php-redis-2.2.4/debian/copyright php-redis-3.0.0/debian/copyright --- php-redis-2.2.4/debian/copyright 2013-10-21 08:20:21.000000000 +0000 +++ php-redis-3.0.0/debian/copyright 2016-06-06 09:48:26.000000000 +0000 @@ -76,27 +76,27 @@ "This product includes PHP software, freely available from ". . - THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND + 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 + 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 + 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, + For more information on the PHP Group and the PHP project, please see . . PHP includes the Zend Engine, freely available at diff -Nru php-redis-2.2.4/debian/gbp.conf php-redis-3.0.0/debian/gbp.conf --- php-redis-2.2.4/debian/gbp.conf 2013-10-21 08:20:21.000000000 +0000 +++ php-redis-3.0.0/debian/gbp.conf 2016-06-06 09:48:26.000000000 +0000 @@ -1,6 +1,12 @@ [DEFAULT] -upstream-branch = upstream-sid -debian-branch = debian-sid +debian-branch = master +debian-tag = debian/%(version)s +upstream-branch = upstream +upstream-tag = upstream/%(version)s +pristine-tar = True -[git-buildpackage] -export-dir = ../build-area/ +[dch] +meta = 1 + +[import-orig] +filter = ['.gitignore','debian'] diff -Nru php-redis-2.2.4/debian/patches/package_description.patch php-redis-3.0.0/debian/patches/package_description.patch --- php-redis-2.2.4/debian/patches/package_description.patch 2013-10-21 08:20:21.000000000 +0000 +++ php-redis-3.0.0/debian/patches/package_description.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ -Description: Update long description for Debian -Author: Jonas Genannt -Bug: https://github.com/nicolasff/phpredis/issues/384 - -diff --git a/package.xml b/package.xml -index 11ba538..7ab6b38 100644 ---- a/package.xml -+++ b/package.xml -@@ -3,7 +3,7 @@ - redis - pecl.php.net - PHP extension for interfacing with Redis -- This extension provides an API for communicating with Redis servers. -+ This extension allows php applications to communicate with the Redis persistent key-value store. The php-redis module provides an easy object oriented interface. - - Nicolas Favre-Felix - nff diff -Nru php-redis-2.2.4/debian/patches/series php-redis-3.0.0/debian/patches/series --- php-redis-2.2.4/debian/patches/series 2013-10-21 08:20:21.000000000 +0000 +++ php-redis-3.0.0/debian/patches/series 2016-06-06 09:48:26.000000000 +0000 @@ -1 +0,0 @@ -package_description.patch diff -Nru php-redis-2.2.4/debian/php5-redis.php5 php-redis-3.0.0/debian/php5-redis.php5 --- php-redis-2.2.4/debian/php5-redis.php5 2013-10-21 08:20:21.000000000 +0000 +++ php-redis-3.0.0/debian/php5-redis.php5 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -mod debian/redis.ini diff -Nru php-redis-2.2.4/debian/php-redis.php php-redis-3.0.0/debian/php-redis.php --- php-redis-2.2.4/debian/php-redis.php 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/debian/php-redis.php 2016-06-06 09:48:26.000000000 +0000 @@ -0,0 +1 @@ +mod debian/redis.ini diff -Nru php-redis-2.2.4/debian/rules php-redis-3.0.0/debian/rules --- php-redis-2.2.4/debian/rules 2013-10-21 08:20:21.000000000 +0000 +++ php-redis-3.0.0/debian/rules 2016-06-06 09:48:26.000000000 +0000 @@ -1,7 +1,3 @@ #!/usr/bin/make -f - -export DH_VERBOSE=1 -export NO_INTERACTION=1 - -%: - dh $@ --buildsystem=phppear --with phppear --with php5 +include /usr/share/dh-php/pkg-pecl.mk +PECL_CONFIGURE_MAINT_APPEND = --enable-redis-igbinary diff -Nru php-redis-2.2.4/debian/source.lintian-overrides php-redis-3.0.0/debian/source.lintian-overrides --- php-redis-2.2.4/debian/source.lintian-overrides 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/debian/source.lintian-overrides 2016-06-06 09:48:26.000000000 +0000 @@ -0,0 +1,2 @@ +# Override invalid PHP license problem for PHP extensions coming from PECL.PHP.NET +php-redis source: license-problem-php-license * diff -Nru php-redis-2.2.4/.gitignore php-redis-3.0.0/.gitignore --- php-redis-2.2.4/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/.gitignore 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,16 @@ +*.deps +*.libs +Makefile* +ac*.m4 +config.* +*.o +install-sh +libtool +./*.sh +configure* +*.lo +build* +missing +autom4te.cache +mkinstalldirs +run-tests.php diff -Nru php-redis-2.2.4/package.xml php-redis-3.0.0/package.xml --- php-redis-2.2.4/package.xml 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/package.xml 2016-06-10 20:05:41.000000000 +0000 @@ -1,26 +1,30 @@ - - + + 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 - + + This extension provides an API for communicating with Redis servers. + Michael Grunder mgrunder michael.grunder@gmail.com yes - 2013-09-02 - + + Nicolas Favre-Felix + nff + n.favrefelix@gmail.com + yes + + 2016-06-10 - 2.2.4 - 2.2.4 + 3.0.0 + 3.0.0 stable @@ -28,70 +32,81 @@ PHP -** -** 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 + phpredis 3.0.0 -* 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 + This version of phpredis supports cluster and is intended for php versions + 7.0.0 and higher. To compile cluster-enabled phpredis for older versions + of php, please use the 2.2.8 pecl package. + + A huge thanks goes out to Sean DuBois for doing all the work required to get + phpredis working in php 7.0! + + -- Improvements --- + + * PHP 7 Support (Sean DuBois) [3159bd2, 567dc2f, daa4d9f, f2711e3, 9cb9d07, + 9d51c89, 9ff8f49, 33bb629, cbdf65a, f30b7fd, c687a51, 6b3e773, 2bf8241, + 771bd3d, 9221ca4, 4e00df6, e2407ca, 97fcfe6, 77e6200] + * Redis Cluster support + * Allow SINTERSTORE to take a single array argument again + * IPv6 support + + --- Fixes --- + + * config.w32 fix (Jan-E) [495d308, c9e0b682] + * Exception handling improvement (Jan-E) [314a2c3c] + * Unit test fix for max int value (Jan-E) [659ea2aa] + * unsigned long -> zend_ulong fix (Jan-E) [4d66e3d4] + * Visual Stuio 14 fixes (Jan-E) [ea98401c] + * Segfault fix when looking up our socket (ephemeralsnow) [0126481a] + * Allow '-' and '+' in ZRANGEBYLEX (Patrick Pokatilo) [8bfa2188] + * Documentation fixes (Ares) [54b9a0ec] + * php7 related memory leak fix (Stuart Carnie) [b75bf3b4] + * Potential segfault fix in cluster session (Sergei Lomakov) [661fb5b1] + * php7 related serialization leak fix (Adam Harvey) [c40fc1d8] - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - 5.2.0 - 6.0.0 + 7.0.0 + 7.1.0 6.0.0 @@ -100,70 +115,145 @@ redis - + - - - stable - stable - - - 2.2.4 - 2.2.4 - - 2013-09-01 + + stablestable + 3.0.03.0.0 + 2016-06-10 -** -** 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. + phpredis 3.0.0 -* 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). + -- Improvements --- -* Allow LONG and STRING keys in MGET/MSET + * 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 + + + + stablestable + 2.2.72.2.7 + 2015-03-03 + + phpredis 2.2.7 -* Extended SET options for Redis >= 2.6.12 + -- Improvements --- -* Persistent connections and UNIX SOCKET support for RedisArray + * 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 + + + + stablestable + 2.2.52.2.5 + 2014-03-15 + + phpredis 2.2.5 -* Allow aggregates for ZUNION/ZINTER without weights @mheijkoop + 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. -* Support for SLOWLOG command + A special thanks to everyone who helps the project by commenting on issues and + submitting pull requests! :) -* Reworked MGET algorithm to run in linear time regardless of key count. + [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 -* Reworked ZINTERSTORE/ZUNIONSTORE algorithm to run in linear time + [FIX] Handle the COPY and REPLACE arguments for the MIGRATE command -** -** Bug fixes -** + [DOC] Fix syntax error in documentation for the SET command (@mithunsatheesh) + [DOC] Homebrew documentation instructions (@mathias) -* 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 + + + + stablestable + 2.2.42.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 - + + stablestable + 2.2.32.2.3 2013-04-29 -First release to PECL + First release to PECL diff -Nru php-redis-2.2.4/redis-2.2.4/arrays.markdown php-redis-3.0.0/redis-2.2.4/arrays.markdown --- php-redis-2.2.4/redis-2.2.4/arrays.markdown 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/arrays.markdown 1970-01-01 00:00:00.000000000 +0000 @@ -1,149 +0,0 @@ -Redis 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)));
-
- -#### 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
-
- diff -Nru php-redis-2.2.4/redis-2.2.4/common.h php-redis-3.0.0/redis-2.2.4/common.h --- php-redis-2.2.4/redis-2.2.4/common.h 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/common.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,206 +0,0 @@ -#include "php.h" -#include "php_ini.h" -#include - -#ifndef REDIS_COMMON_H -#define REDIS_COMMON_H - -/* NULL check so Eclipse doesn't go crazy */ -#ifndef NULL -#define NULL ((void *) 0) -#endif - -#define redis_sock_name "Redis Socket Buffer" -#define REDIS_SOCK_STATUS_FAILED 0 -#define REDIS_SOCK_STATUS_DISCONNECTED 1 -#define REDIS_SOCK_STATUS_UNKNOWN 2 -#define REDIS_SOCK_STATUS_CONNECTED 3 - -#define redis_multi_access_type_name "Redis Multi type access" - -#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 - -/* reply types */ -typedef enum _REDIS_REPLY_TYPE { - TYPE_LINE = '+', - TYPE_INT = ':', - TYPE_ERR = '-', - TYPE_BULK = '$', - TYPE_MULTIBULK = '*' -} REDIS_REPLY_TYPE; - -/* options */ -#define REDIS_OPT_SERIALIZER 1 -#define REDIS_OPT_PREFIX 2 -#define REDIS_OPT_READ_TIMEOUT 3 - -/* serializers */ -#define REDIS_SERIALIZER_NONE 0 -#define REDIS_SERIALIZER_PHP 1 -#define REDIS_SERIALIZER_IGBINARY 2 - -#define IF_MULTI() if(redis_sock->mode == MULTI) -#define IF_MULTI_OR_ATOMIC() if(redis_sock->mode == MULTI || redis_sock->mode == ATOMIC)\ - -#define IF_MULTI_OR_PIPELINE() if(redis_sock->mode == MULTI || redis_sock->mode == PIPELINE) -#define IF_PIPELINE() if(redis_sock->mode == PIPELINE) -#define IF_NOT_MULTI() if(redis_sock->mode != MULTI) -#define IF_ATOMIC() if(redis_sock->mode == ATOMIC) -#define ELSE_IF_MULTI() else if(redis_sock->mode == MULTI) { \ - if(redis_response_enqueued(redis_sock TSRMLS_CC) == 1) {\ - RETURN_ZVAL(getThis(), 1, 0);\ - } else {\ - RETURN_FALSE;\ - } \ -} - -#define ELSE_IF_PIPELINE() else IF_PIPELINE() { \ - RETURN_ZVAL(getThis(), 1, 0);\ -} - - -#define MULTI_RESPONSE(callback) IF_MULTI_OR_PIPELINE() { \ - fold_item *f1, *current; \ - f1 = malloc(sizeof(fold_item)); \ - f1->fun = (void *)callback; \ - f1->next = NULL; \ - current = redis_sock->current;\ - if(current) current->next = f1; \ - redis_sock->current = f1; \ - } - -#define PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len) request_item *tmp; \ - struct request_item *current_request;\ - tmp = malloc(sizeof(request_item));\ - tmp->request_str = calloc(cmd_len, 1);\ - memcpy(tmp->request_str, cmd, cmd_len);\ - tmp->request_size = cmd_len;\ - tmp->next = NULL;\ - current_request = redis_sock->pipeline_current; \ - if(current_request) {\ - current_request->next = tmp;\ - } \ - redis_sock->pipeline_current = tmp; \ - if(NULL == redis_sock->pipeline_head) { \ - redis_sock->pipeline_head = redis_sock->pipeline_current;\ - } - -#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) IF_MULTI_OR_PIPELINE() { \ - fold_item *f1, *current; \ - f1 = malloc(sizeof(fold_item)); \ - f1->fun = (void *)callback; \ - f1->ctx = closure_context; \ - f1->next = NULL; \ - current = redis_sock->current;\ - if(current) current->next = f1; \ - redis_sock->current = f1; \ - if(NULL == redis_sock->head) { \ - redis_sock->head = redis_sock->current;\ - }\ -} - -#define REDIS_ELSE_IF_MULTI(function, closure_context) \ -else if(redis_sock->mode == MULTI) { \ - if(redis_response_enqueued(redis_sock TSRMLS_CC) == 1) {\ - REDIS_SAVE_CALLBACK(function, closure_context); \ - RETURN_ZVAL(getThis(), 1, 0);\ - } else {\ - RETURN_FALSE;\ - }\ -} - -#define REDIS_ELSE_IF_PIPELINE(function, closure_context) else IF_PIPELINE() { \ - REDIS_SAVE_CALLBACK(function, closure_context); \ - RETURN_ZVAL(getThis(), 1, 0);\ -} - -#define REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len) \ - IF_MULTI_OR_ATOMIC() { \ - SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len); \ - efree(cmd); \ - }\ - IF_PIPELINE() { \ - PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len); \ - efree(cmd); \ - } - -#define REDIS_PROCESS_RESPONSE_CLOSURE(function, closure_context) \ - REDIS_ELSE_IF_MULTI(function, closure_context) \ - REDIS_ELSE_IF_PIPELINE(function, closure_context); - -#define REDIS_PROCESS_RESPONSE(function) REDIS_PROCESS_RESPONSE_CLOSURE(function, NULL) - -/* 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)) - -typedef enum {ATOMIC, MULTI, PIPELINE} redis_mode; - -typedef struct fold_item { - zval * (*fun)(INTERNAL_FUNCTION_PARAMETERS, void *, ...); - void *ctx; - struct fold_item *next; -} fold_item; - -typedef struct request_item { - char *request_str; - int request_size; /* size_t */ - struct request_item *next; -} request_item; - -/* {{{ struct RedisSock */ -typedef struct { - php_stream *stream; - char *host; - short port; - char *auth; - double timeout; - double read_timeout; - long retry_interval; - int failed; - int status; - int persistent; - int watching; - char *persistent_id; - - int serializer; - long dbNumber; - - char *prefix; - int prefix_len; - - redis_mode mode; - fold_item *head; - fold_item *current; - - request_item *pipeline_head; - request_item *pipeline_current; - - char *err; - int err_len; - zend_bool lazy_connect; -} RedisSock; -/* }}} */ - -void -free_reply_callbacks(zval *z_this, RedisSock *redis_sock); - -#endif diff -Nru php-redis-2.2.4/redis-2.2.4/config.m4 php-redis-3.0.0/redis-2.2.4/config.m4 --- php-redis-2.2.4/redis-2.2.4/config.m4 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/config.m4 1970-01-01 00:00:00.000000000 +0000 @@ -1,103 +0,0 @@ -dnl $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 library.c redis_session.c redis_array.c redis_array_impl.c, $ext_shared) -fi diff -Nru php-redis-2.2.4/redis-2.2.4/config.w32 php-redis-3.0.0/redis-2.2.4/config.w32 --- php-redis-2.2.4/redis-2.2.4/config.w32 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/config.w32 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ -// vim: ft=javascript: - -ARG_ENABLE("redis", "whether to enable redis support", "yes"); -ARG_ENABLE("redis-session", "whether to enable sessions", "yes"); - -if (PHP_REDIS != "no") { - var sources = "redis.c library.c igbinary\\igbinary.c igbinary\\hash_si.c igbinary\\hash_function.c"; - if (PHP_REDIS_SESSION != "no") { - AC_DEFINE('PHP_SESSION', 1); - sources += " redis_session.c"; - } - - AC_DEFINE("PHP_EXPORTS", 1); - EXTENSION("redis", sources); -} diff -Nru php-redis-2.2.4/redis-2.2.4/COPYING php-redis-3.0.0/redis-2.2.4/COPYING --- php-redis-2.2.4/redis-2.2.4/COPYING 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/COPYING 1970-01-01 00:00:00.000000000 +0000 @@ -1,68 +0,0 @@ --------------------------------------------------------------------- - 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 -. diff -Nru php-redis-2.2.4/redis-2.2.4/CREDITS php-redis-3.0.0/redis-2.2.4/CREDITS --- php-redis-2.2.4/redis-2.2.4/CREDITS 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/CREDITS 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -Redis client extension for PHP -Alfonso Jimenez -Nasreddine Bouafif -Nicolas Favre-Felix diff -Nru php-redis-2.2.4/redis-2.2.4/library.c php-redis-3.0.0/redis-2.2.4/library.c --- php-redis-2.2.4/redis-2.2.4/library.c 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/library.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,1845 +0,0 @@ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif -#include "common.h" -#include "php_network.h" -#include -#ifndef _MSC_VER -#include /* TCP_NODELAY */ -#include -#endif -#include -#include -#ifdef HAVE_REDIS_IGBINARY -#include "igbinary/igbinary.h" -#endif -#include -#include "php_redis.h" -#include "library.h" -#include - -#define UNSERIALIZE_ONLY_VALUES 0 -#define UNSERIALIZE_ALL 1 - -extern zend_class_entry *redis_ce; -extern zend_class_entry *redis_exception_ce; -extern zend_class_entry *spl_ce_RuntimeException; - -PHPAPI 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); - } -} - -PHPAPI int redis_check_eof(RedisSock *redis_sock TSRMLS_DC) -{ - int eof; - int count = 0; - - if (!redis_sock->stream) { - return -1; - } - - eof = php_stream_eof(redis_sock->stream); - for (; eof; count++) { - if((MULTI == redis_sock->mode) || redis_sock->watching || count == 10) { /* too many failures */ - if(redis_sock->stream) { /* close stream if still here */ - 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; - } - zend_throw_exception(redis_exception_ce, "Connection lost", 0 TSRMLS_CC); - return -1; - } - if(redis_sock->stream) { /* close existing stream before reconnecting */ - redis_stream_close(redis_sock TSRMLS_CC); - redis_sock->stream = NULL; - redis_sock->mode = ATOMIC; - redis_sock->watching = 0; - } - // 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 : (random() % redis_sock->retry_interval)); - usleep(retry_interval); - } - redis_sock_connect(redis_sock TSRMLS_CC); /* reconnect */ - if(redis_sock->stream) { /* check for EOF again. */ - eof = php_stream_eof(redis_sock->stream); - } - } - - // Reselect the DB. - if (count && redis_sock->dbNumber) { - char *cmd, *response; - int cmd_len, response_len; - - cmd_len = redis_cmd_format_static(&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; -} - -PHPAPI zval *redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) { - char inbuf[1024]; - int numElems; - zval *z_tab; - - if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { - return NULL; - } - - if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { - redis_stream_close(redis_sock TSRMLS_CC); - redis_sock->stream = NULL; - redis_sock->status = REDIS_SOCK_STATUS_FAILED; - redis_sock->mode = ATOMIC; - redis_sock->watching = 0; - zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); - return NULL; - } - - if(inbuf[0] != '*') { - return NULL; - } - numElems = atoi(inbuf+1); - - MAKE_STD_ZVAL(z_tab); - array_init(z_tab); - - redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock, z_tab, numElems, 1, UNSERIALIZE_ALL); - return z_tab; -} - -/** - * redis_sock_read_bulk_reply - */ -PHPAPI char *redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes TSRMLS_DC) -{ - int offset = 0; - size_t got; - - char * reply; - - if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { - return NULL; - } - - if (bytes == -1) { - return NULL; - } else { - char c; - int i; - - reply = emalloc(bytes+1); - - while(offset < bytes) { - got = php_stream_read(redis_sock->stream, reply + offset, bytes-offset); - if (got <= 0) { - /* Error or EOF */ - zend_throw_exception(redis_exception_ce, "socket error on read socket", 0 TSRMLS_CC); - break; - } - offset += got; - } - for(i = 0; i < 2; i++) { - php_stream_read(redis_sock->stream, &c, 1); - } - } - - reply[bytes] = 0; - return reply; -} - -/** - * redis_sock_read - */ -PHPAPI char *redis_sock_read(RedisSock *redis_sock, int *buf_len TSRMLS_DC) -{ - char inbuf[1024]; - char *resp = NULL; - size_t err_len; - - if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { - return NULL; - } - - if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { - redis_stream_close(redis_sock TSRMLS_CC); - redis_sock->stream = NULL; - redis_sock->status = REDIS_SOCK_STATUS_FAILED; - redis_sock->mode = ATOMIC; - redis_sock->watching = 0; - zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); - return NULL; - } - - switch(inbuf[0]) { - case '-': - err_len = strlen(inbuf+1) - 2; - redis_sock_set_err(redis_sock, inbuf+1, err_len); - /* stale data */ - if(memcmp(inbuf + 1, "-ERR SYNC ", 10) == 0) { - zend_throw_exception(redis_exception_ce, "SYNC with master in progress", 0 TSRMLS_CC); - } - return NULL; - - case '$': - *buf_len = atoi(inbuf + 1); - resp = redis_sock_read_bulk_reply(redis_sock, *buf_len TSRMLS_CC); - return resp; - - 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 - /* :123\r\n */ - *buf_len = strlen(inbuf) - 2; - if(*buf_len >= 2) { - resp = emalloc(1+*buf_len); - memcpy(resp, inbuf, *buf_len); - resp[*buf_len] = 0; - return resp; - } - - default: - zend_throw_exception_ex( - redis_exception_ce, - 0 TSRMLS_CC, - "protocol error, got '%c' as reply type byte\n", - inbuf[0] - ); - } - - return NULL; -} - -void add_constant_long(zend_class_entry *ce, char *name, int value) { - zval *constval; - constval = pemalloc(sizeof(zval), 1); - INIT_PZVAL(constval); - ZVAL_LONG(constval, value); - zend_hash_add(&ce->constants_table, name, 1 + strlen(name), - (void*)&constval, sizeof(zval*), NULL); -} - -int -integer_length(int i) { - int sz = 0; - int ci = abs(i); - while (ci > 0) { - ci /= 10; - sz++; - } - if (i == 0) { /* log 0 doesn't make sense. */ - sz = 1; - } else if (i < 0) { /* allow for neg sign as well. */ - sz++; - } - return sz; -} - -int -redis_cmd_format_header(char **ret, char *keyword, int arg_count) { - // Our return buffer - smart_str buf = {0}; - - // Keyword length - int l = strlen(keyword); - - smart_str_appendc(&buf, '*'); - smart_str_append_long(&buf, arg_count + 1); - smart_str_appendl(&buf, _NL, sizeof(_NL) -1); - smart_str_appendc(&buf, '$'); - smart_str_append_long(&buf, l); - smart_str_appendl(&buf, _NL, sizeof(_NL) -1); - smart_str_appendl(&buf, keyword, l); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - - // Set our return pointer - *ret = buf.c; - - // Return the length - return buf.len; -} - -int -redis_cmd_format_static(char **ret, char *keyword, char *format, ...) { - - char *p = format; - va_list ap; - smart_str buf = {0}; - int l = strlen(keyword); - char *dbl_str; - int dbl_len; - - va_start(ap, format); - - /* add header */ - smart_str_appendc(&buf, '*'); - smart_str_append_long(&buf, strlen(format) + 1); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - smart_str_appendc(&buf, '$'); - smart_str_append_long(&buf, l); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - smart_str_appendl(&buf, keyword, l); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - - while (*p) { - smart_str_appendc(&buf, '$'); - - switch(*p) { - case 's': { - char *val = va_arg(ap, char*); - int val_len = va_arg(ap, int); - smart_str_append_long(&buf, val_len); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - smart_str_appendl(&buf, val, val_len); - } - break; - - case 'f': - case 'F': { - double d = va_arg(ap, double); - REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, d) - smart_str_append_long(&buf, dbl_len); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - smart_str_appendl(&buf, dbl_str, dbl_len); - efree(dbl_str); - } - break; - - case 'i': - case 'd': { - int i = va_arg(ap, int); - char tmp[32]; - int tmp_len = snprintf(tmp, sizeof(tmp), "%d", i); - smart_str_append_long(&buf, tmp_len); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - smart_str_appendl(&buf, tmp, tmp_len); - } - break; - case 'l': - case 'L': { - long l = va_arg(ap, long); - char tmp[32]; - int tmp_len = snprintf(tmp, sizeof(tmp), "%ld", l); - smart_str_append_long(&buf, tmp_len); - smart_str_appendl(&buf, _NL, sizeof(_NL) -1); - smart_str_appendl(&buf, tmp, tmp_len); - } - break; - } - p++; - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - } - smart_str_0(&buf); - - *ret = buf.c; - - return buf.len; -} - -/** - * This command behave somehow like printf, except that strings need 2 arguments: - * Their data and their size (strlen). - * Supported formats are: %d, %i, %s, %l - */ -int -redis_cmd_format(char **ret, char *format, ...) { - - smart_str buf = {0}; - va_list ap; - char *p = format; - char *dbl_str; - int dbl_len; - - va_start(ap, format); - - while (*p) { - if (*p == '%') { - switch (*(++p)) { - case 's': { - char *tmp = va_arg(ap, char*); - int tmp_len = va_arg(ap, int); - smart_str_appendl(&buf, tmp, tmp_len); - } - break; - - case 'F': - case 'f': { - double d = va_arg(ap, double); - REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, d) - smart_str_append_long(&buf, dbl_len); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - smart_str_appendl(&buf, dbl_str, dbl_len); - efree(dbl_str); - } - break; - - case 'd': - case 'i': { - int i = va_arg(ap, int); - char tmp[32]; - int tmp_len = snprintf(tmp, sizeof(tmp), "%d", i); - smart_str_appendl(&buf, tmp, tmp_len); - } - break; - } - } else { - smart_str_appendc(&buf, *p); - } - - p++; - } - - smart_str_0(&buf); - - *ret = buf.c; - - return buf.len; -} - -/* - * Append a command sequence to a Redis command - */ -int redis_cmd_append_str(char **cmd, int cmd_len, char *append, int append_len) { - // Smart string buffer - smart_str buf = {0}; - - // Append the current command to our smart_str - smart_str_appendl(&buf, *cmd, cmd_len); - - // Append our new command sequence - smart_str_appendc(&buf, '$'); - smart_str_append_long(&buf, append_len); - smart_str_appendl(&buf, _NL, sizeof(_NL) -1); - smart_str_appendl(&buf, append, append_len); - smart_str_appendl(&buf, _NL, sizeof(_NL) -1); - - // Free our old command - efree(*cmd); - - // Set our return pointer - *cmd = buf.c; - - // Return new command length - return buf.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_str *str, int num_args, char *keyword, int keyword_len) { - smart_str_appendc(str, '*'); - smart_str_append_long(str, num_args + 1); - smart_str_appendl(str, _NL, sizeof(_NL) -1); - smart_str_appendc(str, '$'); - smart_str_append_long(str, keyword_len); - smart_str_appendl(str, _NL, sizeof(_NL) - 1); - smart_str_appendl(str, keyword, keyword_len); - smart_str_appendl(str, _NL, sizeof(_NL) - 1); - return str->len; -} - -/* - * Append a command sequence to a smart_str - */ -int redis_cmd_append_sstr(smart_str *str, char *append, int append_len) { - smart_str_appendc(str, '$'); - smart_str_append_long(str, append_len); - smart_str_appendl(str, _NL, sizeof(_NL) - 1); - smart_str_appendl(str, append, append_len); - smart_str_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_str *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_str *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_str *str, double value) { - char *dbl_str; - int dbl_len; - - /// Convert to double - REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, value); - - // Append the string - int retval = redis_cmd_append_sstr(str, dbl_str, dbl_len); - - // Free our double string - efree(dbl_str); - - // Return new length - return retval; -} - -/* - * Append an integer command to a Redis command - */ -int redis_cmd_append_int(char **cmd, int cmd_len, int append) { - char int_buf[32]; - - // Conver to an int, capture length - int int_len = snprintf(int_buf, sizeof(int_buf), "%d", append); - - // Return the new length - return redis_cmd_append_str(cmd, cmd_len, int_buf, int_len); -} - -PHPAPI 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_MULTI_OR_PIPELINE() { - add_next_index_bool(z_tab, 0); - } else { - RETURN_FALSE; - } - return; - } - - ret = atof(response); - efree(response); - IF_MULTI_OR_PIPELINE() { - add_next_index_double(z_tab, ret); - } else { - RETURN_DOUBLE(ret); - } -} - -PHPAPI 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_MULTI_OR_PIPELINE() { - add_next_index_bool(z_tab, 0); - } 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_MULTI_OR_PIPELINE() { - add_next_index_long(z_tab, l); - } else { - RETURN_LONG(l); - } -} - -PHPAPI void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { - char *response; - int response_len; - char *pos, *cur; - char *key, *value, *p; - int is_numeric; - zval *z_multi_result; - - if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { - RETURN_FALSE; - } - - MAKE_STD_ZVAL(z_multi_result); - array_init(z_multi_result); /* pre-allocate array for multi's results. */ - /* response :: [response_line] - * response_line :: key ':' value CRLF - */ - - cur = response; - while(1) { - - /* skip comments and empty lines */ - if(*cur == '#' || *cur == '\r') { - if(!(cur = strchr(cur, '\n'))) - break; - cur++; - continue; - } - - /* key */ - pos = strchr(cur, ':'); - if(pos == NULL) { - break; - } - key = emalloc(pos - cur + 1); - memcpy(key, cur, pos-cur); - key[pos-cur] = 0; - - /* value */ - cur = pos + 1; - pos = strchr(cur, '\r'); - if(pos == NULL) { - break; - } - value = emalloc(pos - cur + 1); - memcpy(value, cur, pos-cur); - value[pos-cur] = 0; - pos += 2; /* \r, \n */ - cur = pos; - - is_numeric = 1; - for(p = value; *p; ++p) { - if(*p < '0' || *p > '9') { - is_numeric = 0; - break; - } - } - - if(is_numeric == 1) { - add_assoc_long(z_multi_result, key, atol(value)); - efree(value); - } else { - add_assoc_string(z_multi_result, key, value, 0); - } - efree(key); - } - efree(response); - - IF_MULTI_OR_PIPELINE() { - add_next_index_zval(z_tab, z_multi_result); - } else { - RETVAL_ZVAL(z_multi_result, 0, 1); - } -} - -/* - * Specialized handling of the CLIENT LIST output so it comes out in a simple way for PHP userland code - * to handle. - */ -PHPAPI void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab) { - char *resp; - int resp_len; - zval *z_result, *z_sub_result; - - // Make sure we can read a response from Redis - if((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC)) == NULL) { - RETURN_FALSE; - } - - // Allocate memory for our response - MAKE_STD_ZVAL(z_result); - array_init(z_result); - - // Allocate memory for one user (there should be at least one, namely us!) - ALLOC_INIT_ZVAL(z_sub_result); - array_init(z_sub_result); - - // Pointers for parsing - char *p = resp, *lpos = resp, *kpos = NULL, *vpos = NULL, *p2, *key, *value; - - // Key length, done flag - int klen, done = 0, is_numeric; - - // 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 = emalloc(klen + 1); - strncpy(key, kpos, klen); - key[klen] = 0; - - // Allocate, copy in our value - value = emalloc(p-lpos+1); - strncpy(value,lpos,p-lpos+1); - value[p-lpos]=0; - - // Treat numbers as numbers, strings as strings - is_numeric = 1; - for(p2 = value; *p; ++p) { - if(*p < '0' || *p > '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)); - efree(value); - } else { - add_assoc_string(z_sub_result, key, value, 0); - } - - // 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_result, z_sub_result); - - // If we have another user, make another one - if(*(p+1) != '\0') { - ALLOC_INIT_ZVAL(z_sub_result); - array_init(z_sub_result); - } - } - - // Free our key - efree(key); - } else { - // Something is wrong - efree(resp); - RETURN_FALSE; - } - - // 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++; - } - - // Free our respoonse - efree(resp); - - IF_MULTI_OR_PIPELINE() { - add_next_index_zval(z_tab, z_result); - } else { - RETVAL_ZVAL(z_result, 0, 1); - } -} - -PHPAPI void redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx, SuccessCallback success_callback) { - - char *response; - int response_len; - char ret; - - if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { - IF_MULTI_OR_PIPELINE() { - add_next_index_bool(z_tab, 0); - return; - } - RETURN_FALSE; - } - ret = response[0]; - efree(response); - - IF_MULTI_OR_PIPELINE() { - if (ret == '+') { - if (success_callback != NULL) { - success_callback(redis_sock); - } - add_next_index_bool(z_tab, 1); - } else { - add_next_index_bool(z_tab, 0); - } - } else { - if (ret == '+') { - if (success_callback != NULL) { - success_callback(redis_sock); - } - RETURN_TRUE; - } else { - RETURN_FALSE; - } - } -} - -PHPAPI 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); -} - -PHPAPI 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_MULTI_OR_PIPELINE() { - add_next_index_bool(z_tab, 0); - return; - } else { - RETURN_FALSE; - } - } - - if(response[0] == ':') { - long long ret = atoll(response + 1); - IF_MULTI_OR_PIPELINE() { - if(ret > LONG_MAX) { /* overflow */ - add_next_index_stringl(z_tab, response+1, response_len-1, 1); - } else { - efree(response); - add_next_index_long(z_tab, (long)ret); - } - } else { - if(ret > LONG_MAX) { /* overflow */ - RETURN_STRINGL(response+1, response_len-1, 1); - } else { - efree(response); - RETURN_LONG((long)ret); - } - } - } else { - efree(response); - IF_MULTI_OR_PIPELINE() { - add_next_index_null(z_tab); - } else { - RETURN_FALSE; - } - } -} - - - -PHPAPI int redis_sock_read_multibulk_reply_zipped_with_flag(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int flag) { - - /* - int ret = redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab TSRMLS_CC); - array_zip_values_and_scores(return_value, 0); - */ - - char inbuf[1024]; - int numElems; - zval *z_multi_result; - - if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { - return -1; - } - if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { - redis_stream_close(redis_sock TSRMLS_CC); - redis_sock->stream = NULL; - redis_sock->status = REDIS_SOCK_STATUS_FAILED; - redis_sock->mode = ATOMIC; - redis_sock->watching = 0; - zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); - return -1; - } - - if(inbuf[0] != '*') { - IF_MULTI_OR_PIPELINE() { - add_next_index_bool(z_tab, 0); - } else { - RETVAL_FALSE; - } - return -1; - } - numElems = atoi(inbuf+1); - MAKE_STD_ZVAL(z_multi_result); - array_init(z_multi_result); /* pre-allocate array for multi's results. */ - - redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock, z_multi_result, numElems, 1, flag ? UNSERIALIZE_ALL : UNSERIALIZE_ONLY_VALUES); - - array_zip_values_and_scores(redis_sock, z_multi_result, 0 TSRMLS_CC); - - IF_MULTI_OR_PIPELINE() { - add_next_index_zval(z_tab, z_multi_result); - } else { - *return_value = *z_multi_result; - zval_copy_ctor(return_value); - zval_dtor(z_multi_result); - efree(z_multi_result); - } - - return 0; -} - -PHPAPI int redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { - - return redis_sock_read_multibulk_reply_zipped_with_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, 1); -} - -PHPAPI int redis_sock_read_multibulk_reply_zipped_strings(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { - return redis_sock_read_multibulk_reply_zipped_with_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, 0); -} - -PHPAPI void redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { - - char *response; - int response_len; - char ret; - - if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { - IF_MULTI_OR_PIPELINE() { - add_next_index_bool(z_tab, 0); - return; - } else { - RETURN_FALSE; - } - } - ret = response[1]; - efree(response); - - IF_MULTI_OR_PIPELINE() { - if(ret == '1') { - add_next_index_bool(z_tab, 1); - } else { - add_next_index_bool(z_tab, 0); - } - } else { - if (ret == '1') { - RETURN_TRUE; - } else { - RETURN_FALSE; - } - } -} - -PHPAPI 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_MULTI_OR_PIPELINE() { - add_next_index_bool(z_tab, 0); - return; - } - RETURN_FALSE; - } - IF_MULTI_OR_PIPELINE() { - zval *z = NULL; - if(redis_unserialize(redis_sock, response, response_len, &z TSRMLS_CC) == 1) { - efree(response); - add_next_index_zval(z_tab, z); - } else { - add_next_index_stringl(z_tab, response, response_len, 0); - } - } else { - if(redis_unserialize(redis_sock, response, response_len, &return_value TSRMLS_CC) == 0) { - RETURN_STRINGL(response, response_len, 0); - } else { - efree(response); - } - } -} - -/* like string response, but never unserialized. */ -PHPAPI 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_MULTI_OR_PIPELINE() { - add_next_index_bool(z_tab, 0); - return; - } - RETURN_FALSE; - } - IF_MULTI_OR_PIPELINE() { - add_next_index_stringl(z_tab, response, response_len, 0); - } else { - RETURN_STRINGL(response, response_len, 0); - } -} - - -/** - * redis_sock_create - */ -PHPAPI RedisSock* redis_sock_create(char *host, int host_len, unsigned short port, - double 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 = estrndup(host, host_len); - 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; - - if(persistent_id) { - size_t persistent_id_len = strlen(persistent_id); - redis_sock->persistent_id = ecalloc(persistent_id_len + 1, 1); - memcpy(redis_sock->persistent_id, persistent_id, persistent_id_len); - } else { - redis_sock->persistent_id = NULL; - } - - memcpy(redis_sock->host, host, host_len); - redis_sock->host[host_len] = '\0'; - - redis_sock->port = port; - redis_sock->timeout = timeout; - redis_sock->read_timeout = timeout; - - redis_sock->serializer = REDIS_SERIALIZER_NONE; - redis_sock->mode = ATOMIC; - redis_sock->head = NULL; - redis_sock->current = NULL; - redis_sock->pipeline_head = NULL; - redis_sock->pipeline_current = NULL; - - redis_sock->err = NULL; - redis_sock->err_len = 0; - - return redis_sock; -} - -/** - * redis_sock_connect - */ -PHPAPI int redis_sock_connect(RedisSock *redis_sock TSRMLS_DC) -{ - struct timeval tv, read_tv, *tv_ptr = NULL; - char *host = NULL, *persistent_id = NULL, *errstr = NULL; - int host_len, 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(redis_sock->host[0] == '/' && redis_sock->port < 1) { - host_len = spprintf(&host, 0, "unix://%s", redis_sock->host); - } else { - if(redis_sock->port == 0) - redis_sock->port = 6379; - host_len = spprintf(&host, 0, "%s:%d", redis_sock->host, redis_sock->port); - } - - if (redis_sock->persistent) { - if (redis_sock->persistent_id) { - spprintf(&persistent_id, 0, "phpredis:%s:%s", host, 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, ENFORCE_SAFE_MODE, - STREAM_XPORT_CLIENT - | STREAM_XPORT_CONNECT, - persistent_id, tv_ptr, NULL, &errstr, &err - ); - - if (persistent_id) { - efree(persistent_id); - } - - efree(host); - - if (!redis_sock->stream) { - efree(errstr); - return -1; - } - - /* set TCP_NODELAY */ - sock = (php_netstream_data_t*)redis_sock->stream->abstract; - setsockopt(sock->socket, IPPROTO_TCP, TCP_NODELAY, (char *) &tcp_flag, sizeof(int)); - - php_stream_auto_cleanup(redis_sock->stream); - - if(tv.tv_sec != 0 || 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 - */ -PHPAPI int redis_sock_server_open(RedisSock *redis_sock, int force_connect 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; - case REDIS_SOCK_STATUS_UNKNOWN: - if (force_connect > 0 && redis_sock_connect(redis_sock TSRMLS_CC) < 0) { - res = -1; - } else { - res = 0; - - redis_sock->status = REDIS_SOCK_STATUS_CONNECTED; - } - break; - } - - return res; -} - -/** - * redis_sock_disconnect - */ -PHPAPI int redis_sock_disconnect(RedisSock *redis_sock TSRMLS_DC) -{ - if (redis_sock == NULL) { - return 1; - } - - redis_sock->dbNumber = 0; - if (redis_sock->stream != NULL) { - if (!redis_sock->persistent) { - redis_sock_write(redis_sock, "QUIT", sizeof("QUIT") - 1 TSRMLS_CC); - } - - redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED; - redis_sock->watching = 0; - if(redis_sock->stream && !redis_sock->persistent) { /* still valid after the write? */ - php_stream_close(redis_sock->stream); - } - redis_sock->stream = NULL; - - return 1; - } - - return 0; -} - -PHPAPI void redis_send_discard(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) -{ - char *cmd; - int response_len, cmd_len; - char * response; - - cmd_len = redis_cmd_format_static(&cmd, "DISCARD", ""); - - if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { - efree(cmd); - RETURN_FALSE; - } - efree(cmd); - - if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { - RETURN_FALSE; - } - - if(response_len == 3 && strncmp(response, "+OK", 3) == 0) { - RETURN_TRUE; - } - RETURN_FALSE; -} - -/** - * redis_sock_set_err - */ -PHPAPI int redis_sock_set_err(RedisSock *redis_sock, const char *msg, int msg_len) { - // Allocate/Reallocate our last error member - if(msg != NULL && msg_len > 0) { - if(redis_sock->err == NULL) { - redis_sock->err = emalloc(msg_len + 1); - } else if(msg_len > redis_sock->err_len) { - redis_sock->err = erealloc(redis_sock->err, msg_len +1); - } - - // Copy in our new error message, set new length, and null terminate - memcpy(redis_sock->err, msg, msg_len); - redis_sock->err[msg_len] = '\0'; - redis_sock->err_len = msg_len; - } else { - // Free our last error - if(redis_sock->err != NULL) { - efree(redis_sock->err); - } - - // Set to null, with zero length - redis_sock->err = NULL; - redis_sock->err_len = 0; - } - - // Success - return 0; -} - -/** - * redis_sock_read_multibulk_reply - */ -PHPAPI int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) -{ - char inbuf[1024]; - int numElems; - zval *z_multi_result; - - if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { - return -1; - } - if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { - redis_stream_close(redis_sock TSRMLS_CC); - redis_sock->stream = NULL; - redis_sock->status = REDIS_SOCK_STATUS_FAILED; - redis_sock->mode = ATOMIC; - redis_sock->watching = 0; - zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); - return -1; - } - - if(inbuf[0] != '*') { - IF_MULTI_OR_PIPELINE() { - add_next_index_bool(z_tab, 0); - } else { - RETVAL_FALSE; - } - return -1; - } - numElems = atoi(inbuf+1); - MAKE_STD_ZVAL(z_multi_result); - array_init(z_multi_result); /* pre-allocate array for multi's results. */ - - redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock, z_multi_result, numElems, 1, UNSERIALIZE_ALL); - - IF_MULTI_OR_PIPELINE() { - add_next_index_zval(z_tab, z_multi_result); - } else { - *return_value = *z_multi_result; - efree(z_multi_result); - } - //zval_copy_ctor(return_value); - return 0; -} - -/** - * Like multibulk reply, but don't touch the values, they won't be compressed. (this is used by HKEYS). - */ -PHPAPI int redis_sock_read_multibulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) -{ - char inbuf[1024]; - int numElems; - zval *z_multi_result; - - if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { - return -1; - } - if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { - redis_stream_close(redis_sock TSRMLS_CC); - redis_sock->stream = NULL; - redis_sock->status = REDIS_SOCK_STATUS_FAILED; - redis_sock->mode = ATOMIC; - redis_sock->watching = 0; - zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); - return -1; - } - - if(inbuf[0] != '*') { - IF_MULTI_OR_PIPELINE() { - add_next_index_bool(z_tab, 0); - } else { - RETVAL_FALSE; - } - return -1; - } - numElems = atoi(inbuf+1); - MAKE_STD_ZVAL(z_multi_result); - array_init(z_multi_result); /* pre-allocate array for multi's results. */ - - redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock, z_multi_result, numElems, 0, UNSERIALIZE_ALL); - - IF_MULTI_OR_PIPELINE() { - add_next_index_zval(z_tab, z_multi_result); - } else { - *return_value = *z_multi_result; - efree(z_multi_result); - } - //zval_copy_ctor(return_value); - return 0; -} - -PHPAPI int -redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, - zval *z_tab, int numElems, int unwrap_key, int unserialize_even_only) -{ - char *response; - int response_len; - - while(numElems > 0) { - response = redis_sock_read(redis_sock, &response_len TSRMLS_CC); - if(response != NULL) { - zval *z = NULL; - int can_unserialize = unwrap_key; - if(unserialize_even_only == UNSERIALIZE_ONLY_VALUES && numElems % 2 == 0) - can_unserialize = 0; - - if(can_unserialize && redis_unserialize(redis_sock, response, response_len, &z TSRMLS_CC) == 1) { - efree(response); - add_next_index_zval(z_tab, z); - } else { - add_next_index_stringl(z_tab, response, response_len, 0); - } - } else { - add_next_index_bool(z_tab, 0); - } - numElems --; - } - return 0; -} - -/** - * redis_sock_read_multibulk_reply_assoc - */ -PHPAPI int redis_sock_read_multibulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) -{ - char inbuf[1024], *response; - int response_len; - int i, numElems; - zval *z_multi_result; - - zval **z_keys = ctx; - - if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { - return -1; - } - if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { - redis_stream_close(redis_sock TSRMLS_CC); - redis_sock->stream = NULL; - redis_sock->status = REDIS_SOCK_STATUS_FAILED; - redis_sock->mode = ATOMIC; - redis_sock->watching = 0; - zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); - return -1; - } - - if(inbuf[0] != '*') { - IF_MULTI_OR_PIPELINE() { - add_next_index_bool(z_tab, 0); - } else { - RETVAL_FALSE; - } - return -1; - } - numElems = atoi(inbuf+1); - MAKE_STD_ZVAL(z_multi_result); - 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 *z = NULL; - if(redis_unserialize(redis_sock, response, response_len, &z TSRMLS_CC) == 1) { - efree(response); - add_assoc_zval_ex(z_multi_result, Z_STRVAL_P(z_keys[i]), 1+Z_STRLEN_P(z_keys[i]), z); - } else { - add_assoc_stringl_ex(z_multi_result, Z_STRVAL_P(z_keys[i]), 1+Z_STRLEN_P(z_keys[i]), response, response_len, 0); - } - } else { - add_assoc_bool_ex(z_multi_result, Z_STRVAL_P(z_keys[i]), 1+Z_STRLEN_P(z_keys[i]), 0); - } - zval_dtor(z_keys[i]); - efree(z_keys[i]); - } - efree(z_keys); - - IF_MULTI_OR_PIPELINE() { - add_next_index_zval(z_tab, z_multi_result); - } else { - *return_value = *z_multi_result; - zval_copy_ctor(return_value); - INIT_PZVAL(return_value); - zval_dtor(z_multi_result); - efree(z_multi_result); - } - return 0; -} - -/** - * redis_sock_write - */ -PHPAPI 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); - return -1; - } - if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { - return -1; - } - return php_stream_write(redis_sock->stream, cmd, sz); -} - -/** - * redis_free_socket - */ -PHPAPI void redis_free_socket(RedisSock *redis_sock) -{ - if(redis_sock->prefix) { - efree(redis_sock->prefix); - } - if(redis_sock->err) { - efree(redis_sock->err); - } - if(redis_sock->auth) { - efree(redis_sock->auth); - } - if(redis_sock->persistent_id) { - efree(redis_sock->persistent_id); - } - efree(redis_sock->host); - efree(redis_sock); -} - -PHPAPI int -redis_serialize(RedisSock *redis_sock, zval *z, char **val, int *val_len TSRMLS_DC) { -#if ZEND_MODULE_API_NO >= 20100000 - php_serialize_data_t ht; -#else - HashTable ht; -#endif - smart_str sstr = {0}; - zval *z_copy; - size_t sz; - uint8_t *val8; - - 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); - return 0; - - case IS_OBJECT: - MAKE_STD_ZVAL(z_copy); - ZVAL_STRINGL(z_copy, "Object", 6, 1); - break; - - case IS_ARRAY: - MAKE_STD_ZVAL(z_copy); - ZVAL_STRINGL(z_copy, "Array", 5, 1); - break; - - default: /* copy */ - MAKE_STD_ZVAL(z_copy); - *z_copy = *z; - zval_copy_ctor(z_copy); - break; - } - - /* return string */ - convert_to_string(z_copy); - *val = Z_STRVAL_P(z_copy); - *val_len = Z_STRLEN_P(z_copy); - efree(z_copy); - return 1; - - 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 TSRMLS_CC); - *val = sstr.c; - *val_len = (int)sstr.len; -#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) { /* ok */ - *val = (char*)val8; - *val_len = (int)sz; - return 1; - } -#endif - return 0; - } - return 0; -} - -PHPAPI int -redis_unserialize(RedisSock *redis_sock, const char *val, int val_len, zval **return_value TSRMLS_DC) { - - php_unserialize_data_t var_hash; - int ret, rv_free = 0; - - switch(redis_sock->serializer) { - case REDIS_SERIALIZER_NONE: - return 0; - - case REDIS_SERIALIZER_PHP: - if(!*return_value) { - MAKE_STD_ZVAL(*return_value); - rv_free = 1; - } -#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(return_value, (const unsigned char**)&val, - (const unsigned char*)val + val_len, &var_hash TSRMLS_CC)) { - if(rv_free==1) efree(*return_value); - ret = 0; - } else { - ret = 1; - } -#if ZEND_MODULE_API_NO >= 20100000 - PHP_VAR_UNSERIALIZE_DESTROY(var_hash); -#else - var_destroy(&var_hash); -#endif - - return ret; - - case REDIS_SERIALIZER_IGBINARY: -#ifdef HAVE_REDIS_IGBINARY - if(!*return_value) { - MAKE_STD_ZVAL(*return_value); - } - if(igbinary_unserialize((const uint8_t *)val, (size_t)val_len, return_value TSRMLS_CC) == 0) { - return 1; - } - efree(*return_value); -#endif - return 0; - break; - } - return 0; -} - -PHPAPI int -redis_key_prefix(RedisSock *redis_sock, char **key, int *key_len TSRMLS_DC) { - int ret_len; - char *ret; - - if(redis_sock->prefix == NULL || redis_sock->prefix_len == 0) { - return 0; - } - - ret_len = redis_sock->prefix_len + *key_len; - ret = ecalloc(1 + ret_len, 1); - memcpy(ret, redis_sock->prefix, redis_sock->prefix_len); - memcpy(ret + redis_sock->prefix_len, *key, *key_len); - - *key = ret; - *key_len = ret_len; - return 1; -} - -/* - * Processing for variant reply types (think EVAL) - */ - -PHPAPI 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 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(redis_sock TSRMLS_CC); - redis_sock->stream = NULL; - redis_sock->status = REDIS_SOCK_STATUS_FAILED; - redis_sock->mode = ATOMIC; - redis_sock->watching = 0; - - // Throw a read error exception - zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); - } - - // We don't need \r\n - *line_size-=2; - buf[*line_size]='\0'; - - - // Success! - return 0; -} - -PHPAPI int -redis_read_reply_type(RedisSock *redis_sock, REDIS_REPLY_TYPE *reply_type, int *reply_info TSRMLS_DC) { - // Make sure we haven't lost the connection, even trying to reconnect - if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { - // Failure - 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); - } - - // 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)) < 0) { - return -1; - } - - // Set our size response - *reply_info = atoi(inbuf); - } - - // Success! - return 0; -} - -/* - * Read a single line response, having already consumed the reply-type byte - */ -PHPAPI 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[1024]; - 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) { - if(memcmp(inbuf, "ERR SYNC", 9) == 0) { - zend_throw_exception(redis_exception_ce, "SYNC with master in progress", 0 TSRMLS_CC); - } - - // Set our last error - redis_sock_set_err(redis_sock, inbuf, line_size); - - // Set our response to FALSE - ZVAL_FALSE(*z_ret); - } else { - // Set our response to TRUE - ZVAL_TRUE(*z_ret); - } - - return 0; -} - -PHPAPI 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; - } else { - ZVAL_STRINGL(*z_ret, bulk_resp, size, 0); - return 0; - } -} - -PHPAPI int -redis_read_multibulk_recursive(RedisSock *redis_sock, int elements, zval **z_ret TSRMLS_DC) { - int reply_info; - REDIS_REPLY_TYPE reply_type; - zval *z_subelem; - - // 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: - ALLOC_INIT_ZVAL(z_subelem); - 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 - ALLOC_INIT_ZVAL(z_subelem); - 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 - ALLOC_INIT_ZVAL(z_subelem); - 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; - } - - // Decrement our element counter - elements--; - } - - return 0; -} - -PHPAPI int -redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab) { - // Reply type, and reply size vars - REDIS_REPLY_TYPE reply_type; - int reply_info; - //char *bulk_resp; - zval *z_ret; - - // Attempt to read our header - if(redis_read_reply_type(redis_sock, &reply_type, &reply_info TSRMLS_CC) < 0) { - return -1; - } - - // Our return ZVAL - MAKE_STD_ZVAL(z_ret); - - // 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 respoinse recursively - if(reply_info > -1) { - redis_read_multibulk_recursive(redis_sock, reply_info, &z_ret TSRMLS_CC); - } - break; - default: - // Protocol error - zend_throw_exception_ex(redis_exception_ce, 0 TSRMLS_CC, "protocol error, got '%c' as reply-type byte\n", reply_type); - break; - } - - IF_MULTI_OR_PIPELINE() { - add_next_index_zval(z_tab, z_ret); - } else { - // Set our return value - *return_value = *z_ret; - zval_copy_ctor(return_value); - zval_dtor(z_ret); - efree(z_ret); - } - - // Success - return 0; -} - -/* vim: set tabstop=4 softtabstop=4 noexpandtab shiftwidth=4: */ - diff -Nru php-redis-2.2.4/redis-2.2.4/library.h php-redis-3.0.0/redis-2.2.4/library.h --- php-redis-2.2.4/redis-2.2.4/library.h 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/library.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,77 +0,0 @@ -void add_constant_long(zend_class_entry *ce, char *name, int value); -int integer_length(int i); -int redis_cmd_format(char **ret, char *format, ...); -int redis_cmd_format_static(char **ret, char *keyword, char *format, ...); -int redis_cmd_format_header(char **ret, char *keyword, int arg_count); -int redis_cmd_append_str(char **cmd, int cmd_len, char *append, int append_len); -int redis_cmd_init_sstr(smart_str *str, int num_args, char *keyword, int keyword_len); -int redis_cmd_append_sstr(smart_str *str, char *append, int append_len); -int redis_cmd_append_sstr_int(smart_str *str, int append); -int redis_cmd_append_sstr_long(smart_str *str, long append); -int redis_cmd_append_int(char **cmd, int cmd_len, int append); -int redis_cmd_append_sstr_dbl(smart_str *str, double value); - -PHPAPI char * redis_sock_read(RedisSock *redis_sock, int *buf_len TSRMLS_DC); - -PHPAPI void redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHPAPI void redis_long_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval* z_tab, void *ctx); -typedef void (*SuccessCallback)(RedisSock *redis_sock); -PHPAPI void redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx, SuccessCallback success_callback); -PHPAPI void redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHPAPI void redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHPAPI void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHPAPI void redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHPAPI void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHPAPI void redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHPAPI RedisSock* redis_sock_create(char *host, int host_len, unsigned short port, double timeout, int persistent, char *persistent_id, long retry_interval, zend_bool lazy_connect); -PHPAPI int redis_sock_connect(RedisSock *redis_sock TSRMLS_DC); -PHPAPI int redis_sock_server_open(RedisSock *redis_sock, int force_connect TSRMLS_DC); -PHPAPI int redis_sock_disconnect(RedisSock *redis_sock TSRMLS_DC); -PHPAPI zval *redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); -PHPAPI char *redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes TSRMLS_DC); -PHPAPI int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *_z_tab, void *ctx); -PHPAPI int redis_sock_read_multibulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHPAPI int redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int numElems, int unwrap_key, int unserialize_even_only); -PHPAPI int redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHPAPI int redis_sock_read_multibulk_reply_zipped_strings(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHPAPI int redis_sock_read_multibulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHPAPI int redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz TSRMLS_DC); -PHPAPI void redis_stream_close(RedisSock *redis_sock TSRMLS_DC); -PHPAPI int redis_check_eof(RedisSock *redis_sock TSRMLS_DC); -//PHPAPI int redis_sock_get(zval *id, RedisSock **redis_sock TSRMLS_DC); -PHPAPI void redis_free_socket(RedisSock *redis_sock); -PHPAPI void redis_send_discard(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); -PHPAPI int redis_sock_set_err(RedisSock *redis_sock, const char *msg, int msg_len); - -PHPAPI int -redis_serialize(RedisSock *redis_sock, zval *z, char **val, int *val_len TSRMLS_DC); -PHPAPI int -redis_key_prefix(RedisSock *redis_sock, char **key, int *key_len TSRMLS_DC); - -PHPAPI int -redis_unserialize(RedisSock *redis_sock, const char *val, int val_len, zval **return_value TSRMLS_DC); - - -/* -* Variant Read methods, mostly to implement eval -*/ - -PHPAPI int redis_read_reply_type(RedisSock *redis_sock, REDIS_REPLY_TYPE *reply_type, int *reply_info TSRMLS_DC); -PHPAPI int redis_read_variant_line(RedisSock *redis_sock, REDIS_REPLY_TYPE reply_type, zval **z_ret TSRMLS_DC); -PHPAPI int redis_read_variant_bulk(RedisSock *redis_sock, int size, zval **z_ret TSRMLS_DC); -PHPAPI int redis_read_multibulk_recursive(RedisSock *redis_sock, int elements, zval **z_ret TSRMLS_DC); -PHPAPI int redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab); - -PHPAPI void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab); - -#if ZEND_MODULE_API_NO >= 20100000 -#define REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, dbl) \ - char dbl_decsep; \ - dbl_decsep = '.'; \ - dbl_str = _php_math_number_format_ex(dbl, 16, &dbl_decsep, 1, NULL, 0); \ - dbl_len = strlen(dbl_str); -#else -#define REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, dbl) \ - dbl_str = _php_math_number_format(dbl, 16, '.', '\x00'); \ - dbl_len = strlen(dbl_str); -#endif diff -Nru php-redis-2.2.4/redis-2.2.4/php_redis.h php-redis-3.0.0/redis-2.2.4/php_redis.h --- php-redis-2.2.4/redis-2.2.4/php_redis.h 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/php_redis.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,271 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | 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 | - +----------------------------------------------------------------------+ -*/ - -#include "common.h" - -#ifndef PHP_REDIS_H -#define PHP_REDIS_H - -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, 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, resetStat); -PHP_METHOD(Redis, select); -PHP_METHOD(Redis, move); -PHP_METHOD(Redis, zAdd); -PHP_METHOD(Redis, zDelete); -PHP_METHOD(Redis, zRange); -PHP_METHOD(Redis, zReverseRange); -PHP_METHOD(Redis, zRangeByScore); -PHP_METHOD(Redis, zRevRangeByScore); -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, eval); -PHP_METHOD(Redis, evalsha); -PHP_METHOD(Redis, script); -PHP_METHOD(Redis, dump); -PHP_METHOD(Redis, restore); -PHP_METHOD(Redis, migrate); - -PHP_METHOD(Redis, time); - -PHP_METHOD(Redis, getLastError); -PHP_METHOD(Redis, clearLastError); -PHP_METHOD(Redis, _prefix); -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, 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, client); - -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); - -#ifdef PHP_WIN32 -#define PHP_REDIS_API __declspec(dllexport) -#else -#define PHP_REDIS_API -#endif - -#ifdef ZTS -#include "TSRM.h" -#endif - -PHP_MINIT_FUNCTION(redis); -PHP_MSHUTDOWN_FUNCTION(redis); -PHP_RINIT_FUNCTION(redis); -PHP_RSHUTDOWN_FUNCTION(redis); -PHP_MINFO_FUNCTION(redis); - -PHPAPI int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent); -PHPAPI void redis_atomic_increment(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int count); -PHPAPI int generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len, - int min_argc, RedisSock **redis_sock, int has_timeout, int all_keys, int can_serialize); -PHPAPI void generic_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sort, int use_alpha); -typedef void (*ResultCallback)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHPAPI void generic_empty_cmd_impl(INTERNAL_FUNCTION_PARAMETERS, char *cmd, int cmd_len, ResultCallback result_callback); -PHPAPI void generic_empty_cmd(INTERNAL_FUNCTION_PARAMETERS, char *cmd, int cmd_len, ...); -PHPAPI void generic_empty_long_cmd(INTERNAL_FUNCTION_PARAMETERS, char *cmd, int cmd_len, ...); - -PHPAPI void generic_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sub_cmd); -PHPAPI void generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *unsub_cmd); - -PHPAPI void array_zip_values_and_scores(RedisSock *redis_sock, zval *z_tab, int use_atof TSRMLS_DC); -PHPAPI int redis_response_enqueued(RedisSock *redis_sock TSRMLS_DC); - -PHPAPI int get_flag(zval *object TSRMLS_DC); -PHPAPI void set_flag(zval *object, int new_flag TSRMLS_DC); - -PHPAPI int redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int numElems); - -/* pipeline */ -PHPAPI request_item* get_pipeline_head(zval *object); -PHPAPI void set_pipeline_head(zval *object, request_item *head); -PHPAPI request_item* get_pipeline_current(zval *object); -PHPAPI void set_pipeline_current(zval *object, request_item *current); - -#ifndef _MSC_VER -ZEND_BEGIN_MODULE_GLOBALS(redis) -ZEND_END_MODULE_GLOBALS(redis) -#endif - -struct redis_queued_item { - - /* reading function */ - zval * (*fun)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, ...); - - char *cmd; - int cmd_len; - - struct redis_queued_item *next; -}; - -extern zend_module_entry redis_module_entry; -#define redis_module_ptr &redis_module_entry - -#define phpext_redis_ptr redis_module_ptr - -#define PHP_REDIS_VERSION "2.2.4" - -#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 - */ diff -Nru php-redis-2.2.4/redis-2.2.4/README.markdown php-redis-3.0.0/redis-2.2.4/README.markdown --- php-redis-2.2.4/redis-2.2.4/README.markdown 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/README.markdown 1970-01-01 00:00:00.000000000 +0000 @@ -1,3119 +0,0 @@ -# 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/nicolasff/phpredis/issues) or to n.favrefelix@gmail.com ([@yowgi](http://twitter.com/yowgi)). - - -# 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) - * [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). - - -## 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" -~~~ - -`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/nicolasff/phpredis/issues/213#issuecomment-11361242) on how to build phpredis on Windows. - - -## Distributed Redis Array - -See [dedicated page](https://github.com/nicolasff/phpredis/blob/master/arrays.markdown#readme). - - - -# 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) - -##### *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 to 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) - -##### *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 the 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 -~~~ - - -### 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 -* [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); /* 3.5 */ -~~~ - -### 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`, 'value2', `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*'); -~~~ - - - -### 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. -##### *Parameters* -*host* string. The destination host -*port* integer. The TCP port to connect to. -*key* string. The key to migrate. -*destination-db* integer. The target DB. -*timeout* integer. The maximum amount of time given to this transfer. -##### *Examples* -~~~ -$redis->migrate('backup', 6379, 'foo', 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 - -### hSet ------ -_**Description**_: Adds a value to the hash stored at key. If this value is already in the hash, `FALSE` is returned. -##### *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* -*hashKey* - -##### *Return value* -*BOOL* `TRUE` in case of success, `FALSE` in case of failure - - -### 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') */ -~~~ - - - -## 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. -Il 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 a random member from a set -* [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 - -### 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* -##### *Return value* -*String* "popped" value -*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'}*/ -$redis->sPop('key1'); /* 'member1', 'key1' => {'member3', 'member2'} */ -$redis->sPop('key1'); /* 'member3', 'key1' => {'member2'} */ -~~~ - -### 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" -} -~~~ - - -## 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 -* [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 - -### 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) */ -~~~ - -### 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') */ -~~~ - -## Pub/sub - -* [psubscribe](#psubscribe) - Subscribe to channels by pattern -* [publish](#publish) - Post a message to a channel -* [subscribe](#subscribe) - Subscribe to channels - -### 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) - -##### *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. - -##### *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 -~~~ - - -## 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*: a list of keys - -##### *Example* -~~~ -$redis->watch('x'); -/* 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](#) - Evaluate a LUA script serverside -* [evalSha](#) - Evaluate a LUA script serverside, from the SHA1 hash of the script instead of the script itself -* [script](#) - Execute the Redis SCRIPT command to perform various operations on the scripting subsystem -* [getLastError](#) - The last error message (if any) -* [clearLastError](#) - Clear the last error message -* [_prefix](#) - A utility method to prefix the value with the prefix setting for phpredis -* [_unserialize](#) - A utility method to unserialize 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' -~~~ - -### _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 phpreids - -##### *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_TIMOUT) 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 diff -Nru php-redis-2.2.4/redis-2.2.4/redis_array.c php-redis-3.0.0/redis-2.2.4/redis_array.c --- php-redis-2.2.4/redis-2.2.4/redis_array.c 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/redis_array.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,1329 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | 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 "redis_array.h" -#include - -#include "library.h" -#include "redis_array.h" -#include "redis_array_impl.h" - -extern zend_class_entry *redis_ce; -zend_class_entry *redis_array_ce; - -ZEND_BEGIN_ARG_INFO_EX(__redis_array_call_args, 0, 0, 2) - ZEND_ARG_INFO(0, function_name) - ZEND_ARG_INFO(0, arguments) -ZEND_END_ARG_INFO() - -zend_function_entry redis_array_functions[] = { - PHP_ME(RedisArray, __construct, NULL, ZEND_ACC_PUBLIC) - PHP_ME(RedisArray, __call, __redis_array_call_args, 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) - - /* 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) - {NULL, NULL, NULL} -}; - -int le_redis_array; -void redis_destructor_redis_array(zend_rsrc_list_entry * rsrc TSRMLS_DC) -{ - int i; - RedisArray *ra = (RedisArray*)rsrc->ptr; - - /* delete Redis objects */ - for(i = 0; i < ra->count; ++i) { - zval_dtor(ra->redis[i]); - efree(ra->redis[i]); - - /* remove host too */ - efree(ra->hosts[i]); - } - efree(ra->redis); - efree(ra->hosts); - - /* delete function */ - if(ra->z_fun) { - zval_dtor(ra->z_fun); - efree(ra->z_fun); - } - - /* delete distributor */ - if(ra->z_dist) { - zval_dtor(ra->z_dist); - efree(ra->z_dist); - } - - /* delete list of pure commands */ - zval_dtor(ra->z_pure_cmds); - efree(ra->z_pure_cmds); - - /* free container */ - efree(ra); -} - -/** - * redis_array_get - */ -PHPAPI int redis_array_get(zval *id, RedisArray **ra TSRMLS_DC) -{ - - zval **socket; - int resource_type; - - if (Z_TYPE_P(id) != IS_OBJECT || zend_hash_find(Z_OBJPROP_P(id), "socket", - sizeof("socket"), (void **) &socket) == FAILURE) { - return -1; - } - - *ra = (RedisArray *) zend_list_find(Z_LVAL_PP(socket), &resource_type); - - if (!*ra || resource_type != le_redis_array) { - return -1; - } - - return Z_LVAL_PP(socket); -} - -uint32_t rcrc32(const char *s, size_t sz) { - - static const uint32_t table[256] = { - 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535, - 0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD, - 0xE7B82D07,0x90BF1D91,0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D, - 0x6DDDE4EB,0xF4D4B551,0x83D385C7,0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC, - 0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,0x3B6E20C8,0x4C69105E,0xD56041E4, - 0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C, - 0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,0x26D930AC, - 0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F, - 0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB, - 0xB6662D3D,0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F, - 0x9FBFE4A5,0xE8B8D433,0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB, - 0x086D3D2D,0x91646C97,0xE6635C01,0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E, - 0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,0x65B0D9C6,0x12B7E950,0x8BBEB8EA, - 0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,0x4DB26158,0x3AB551CE, - 0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,0x4369E96A, - 0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9, - 0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409, - 0xCE61E49F,0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81, - 0xB7BD5C3B,0xC0BA6CAD,0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739, - 0x9DD277AF,0x04DB2615,0x73DC1683,0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8, - 0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,0xF00F9344,0x8708A3D2,0x1E01F268, - 0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,0xFED41B76,0x89D32BE0, - 0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,0xD6D6A3E8, - 0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B, - 0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF, - 0x4669BE79,0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703, - 0x220216B9,0x5505262F,0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7, - 0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A, - 0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,0x95BF4A82,0xE2B87A14,0x7BB12BAE, - 0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,0x86D3D2D4,0xF1D4E242, - 0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6, - 0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45, - 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D, - 0x3E6E77DB,0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5, - 0x47B2CF7F,0x30B5FFE9,0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605, - 0xCDD70693,0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94, - 0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D}; - - unsigned long ret = 0xffffffff; - size_t i; - - for (i = 0; i < sz; i++) { - ret = (ret >> 8) ^ table[ (ret ^ ((unsigned char)s[i])) & 0xFF ]; - } - return (ret ^ 0xFFFFFFFF); - -} - -/* {{{ proto RedisArray RedisArray::__construct() - Public constructor */ -PHP_METHOD(RedisArray, __construct) -{ - zval *z0, *z_fun = NULL, *z_dist = NULL, **zpData, *z_opts = NULL; - int id; - 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; - - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|a", &z0, &z_opts) == FAILURE) { - RETURN_FALSE; - } - - /* extract options */ - if(z_opts) { - - hOpts = Z_ARRVAL_P(z_opts); - - /* extract previous ring. */ - if(FAILURE != zend_hash_find(hOpts, "previous", sizeof("previous"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_ARRAY - && zend_hash_num_elements(Z_ARRVAL_PP(zpData)) != 0) { - /* consider previous array as non-existent if empty. */ - hPrev = Z_ARRVAL_PP(zpData); - } - - /* extract function name. */ - if(FAILURE != zend_hash_find(hOpts, "function", sizeof("function"), (void**)&zpData)) { - MAKE_STD_ZVAL(z_fun); - *z_fun = **zpData; - zval_copy_ctor(z_fun); - } - - /* extract function name. */ - if(FAILURE != zend_hash_find(hOpts, "distributor", sizeof("distributor"), (void**)&zpData)) { - MAKE_STD_ZVAL(z_dist); - *z_dist = **zpData; - zval_copy_ctor(z_dist); - } - - /* extract index option. */ - if(FAILURE != zend_hash_find(hOpts, "index", sizeof("index"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_BOOL) { - b_index = Z_BVAL_PP(zpData); - } - - /* extract autorehash option. */ - if(FAILURE != zend_hash_find(hOpts, "autorehash", sizeof("autorehash"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_BOOL) { - b_autorehash = Z_BVAL_PP(zpData); - } - - /* pconnect */ - if(FAILURE != zend_hash_find(hOpts, "pconnect", sizeof("pconnect"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_BOOL) { - b_pconnect = Z_BVAL_PP(zpData); - } - - /* extract retry_interval option. */ - zval **z_retry_interval_pp; - if (FAILURE != zend_hash_find(hOpts, "retry_interval", sizeof("retry_interval"), (void**)&z_retry_interval_pp)) { - if (Z_TYPE_PP(z_retry_interval_pp) == IS_LONG || Z_TYPE_PP(z_retry_interval_pp) == IS_STRING) { - if (Z_TYPE_PP(z_retry_interval_pp) == IS_LONG) { - l_retry_interval = Z_LVAL_PP(z_retry_interval_pp); - } - else { - l_retry_interval = atol(Z_STRVAL_PP(z_retry_interval_pp)); - } - } - } - - /* extract lazy connect option. */ - if(FAILURE != zend_hash_find(hOpts, "lazy_connect", sizeof("lazy_connect"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_BOOL) { - b_lazy_connect = Z_BVAL_PP(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 TSRMLS_CC); - break; - - default: - WRONG_PARAM_COUNT; - break; - } - - if(ra) { - ra->auto_rehash = b_autorehash; -#if PHP_VERSION_ID >= 50400 - id = zend_list_insert(ra, le_redis_array TSRMLS_CC); -#else - id = zend_list_insert(ra, le_redis_array); -#endif - add_property_resource(getThis(), "socket", id); - } -} - -static void -ra_forward_call(INTERNAL_FUNCTION_PARAMETERS, RedisArray *ra, const char *cmd, int cmd_len, zval *z_args, zval *z_new_target) { - - zval **zp_tmp, z_tmp; - char *key = NULL; // set to avoid "unused-but-set-variable" - int key_len; - int i; - zval *redis_inst; - zval z_fun, **z_callargs; - HashPosition pointer; - HashTable *h_args; - - int argc; - int failed; - zend_bool b_write_cmd = 0; - - h_args = Z_ARRVAL_P(z_args); - argc = zend_hash_num_elements(h_args); - - if(ra->z_multi_exec) { - redis_inst = ra->z_multi_exec; /* we already have the instance */ - } else { - /* extract key and hash it. */ - if(!(key = ra_find_key(ra, z_args, cmd, &key_len))) { - php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not find key"); - RETURN_FALSE; - } - - /* 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; - } - } - - /* check if write cmd */ - b_write_cmd = ra_is_write_cmd(ra, cmd, cmd_len); - - if(ra->index && b_write_cmd && !ra->z_multi_exec) { /* add MULTI + SADD */ - ra_index_multi(redis_inst, MULTI TSRMLS_CC); - } - - /* pass call through */ - ZVAL_STRING(&z_fun, cmd, 0); /* method name */ - z_callargs = emalloc(argc * sizeof(zval*)); - - /* copy args to array */ - for (i = 0, zend_hash_internal_pointer_reset_ex(h_args, &pointer); - zend_hash_get_current_data_ex(h_args, (void**) &zp_tmp, - &pointer) == SUCCESS; - ++i, zend_hash_move_forward_ex(h_args, &pointer)) { - - z_callargs[i] = *zp_tmp; - } - - /* 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 TSRMLS_CC); - efree(z_callargs); - RETURN_ZVAL(getThis(), 1, 0); - } - - /* CALL! */ - if(ra->index && b_write_cmd) { - /* call using discarded temp value and extract exec results after. */ - call_user_function(&redis_ce->function_table, &redis_inst, &z_fun, &z_tmp, argc, z_callargs TSRMLS_CC); - zval_dtor(&z_tmp); - - /* 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 TSRMLS_CC); - - failed = 0; - if((Z_TYPE_P(return_value) == IS_BOOL && Z_BVAL_P(return_value) == 0) || - (Z_TYPE_P(return_value) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(return_value)) == 0) || - (Z_TYPE_P(return_value) == IS_LONG && Z_LVAL_P(return_value) == 0 && !strcasecmp(cmd, "TYPE"))) - - { - failed = 1; - } - - /* check if we have an error. */ - if(failed && ra->prev && !b_write_cmd) { /* there was an error reading, try with prev ring. */ - /* 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); - } - - if(!failed && !b_write_cmd && z_new_target && ra->auto_rehash) { /* move key from old ring to new ring */ - ra_move_key(key, key_len, redis_inst, z_new_target TSRMLS_CC); - } - } - - /* cleanup */ - efree(z_callargs); -} - -PHP_METHOD(RedisArray, __call) -{ - zval *object; - RedisArray *ra; - zval *z_args; - - char *cmd; - int 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 (redis_array_get(object, &ra TSRMLS_CC) < 0) { - 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 (redis_array_get(object, &ra TSRMLS_CC) < 0) { - RETURN_FALSE; - } - - array_init(return_value); - for(i = 0; i < ra->count; ++i) { - add_next_index_string(return_value, ra->hosts[i], 1); - } -} - -PHP_METHOD(RedisArray, _target) -{ - zval *object; - RedisArray *ra; - char *key; - int key_len, i; - zval *redis_inst; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_array_ce, &key, &key_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_array_get(object, &ra TSRMLS_CC) < 0) { - RETURN_FALSE; - } - - redis_inst = ra_find_node(ra, key, key_len, &i TSRMLS_CC); - if(redis_inst) { - ZVAL_STRING(return_value, ra->hosts[i], 1); - } else { - RETURN_NULL(); - } -} - -PHP_METHOD(RedisArray, _instance) -{ - zval *object; - RedisArray *ra; - char *target; - int 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 (redis_array_get(object, &ra TSRMLS_CC) < 0) { - 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; - RedisArray *ra; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", - &object, redis_array_ce) == FAILURE) { - RETURN_FALSE; - } - - if (redis_array_get(object, &ra TSRMLS_CC) < 0) { - RETURN_FALSE; - } - - if(ra->z_fun) { - *return_value = *ra->z_fun; - zval_copy_ctor(return_value); - } else { - RETURN_NULL(); - } -} - -PHP_METHOD(RedisArray, _distributor) -{ - zval *object; - RedisArray *ra; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", - &object, redis_array_ce) == FAILURE) { - RETURN_FALSE; - } - - if (redis_array_get(object, &ra TSRMLS_CC) < 0) { - RETURN_FALSE; - } - - if(ra->z_fun) { - *return_value = *ra->z_fun; - zval_copy_ctor(return_value); - } else { - RETURN_NULL(); - } -} - -PHP_METHOD(RedisArray, _rehash) -{ - zval *object; - RedisArray *ra; - zend_fcall_info z_cb; - zend_fcall_info_cache z_cb_cache; - - 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 (redis_array_get(object, &ra TSRMLS_CC) < 0) { - 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, *z_tmp; - int i; - RedisArray *ra; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", - &object, redis_array_ce) == FAILURE) { - RETURN_FALSE; - } - - if (redis_array_get(object, &ra TSRMLS_CC) < 0) { - RETURN_FALSE; - } - - /* prepare call */ - ZVAL_STRING(&z_fun, method_name, 0); - - array_init(return_value); - for(i = 0; i < ra->count; ++i) { - - MAKE_STD_ZVAL(z_tmp); - - /* Call each node in turn */ - call_user_function(&redis_ce->function_table, &ra->redis[i], - &z_fun, z_tmp, 0, NULL TSRMLS_CC); - - add_assoc_zval(return_value, ra->hosts[i], z_tmp); - } -} - -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, keys) -{ - zval *object, *z_args[1], *z_tmp, z_fun; - RedisArray *ra; - char *pattern; - int pattern_len, 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(redis_array_get(object, &ra TSRMLS_CC) < 0) { - RETURN_FALSE; - } - - // Set up our function call (KEYS) - ZVAL_STRING(&z_fun, "KEYS", 0); - - // We will be passing with one string argument (the pattern) - MAKE_STD_ZVAL(z_args[0]); - ZVAL_STRINGL(z_args[0], pattern, pattern_len, 0); - - // Init our array return - array_init(return_value); - - // Iterate our RedisArray nodes - for(i=0; icount; ++i) { - // Return for this node - MAKE_STD_ZVAL(z_tmp); - - // Call KEYS on each node - call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 1, z_args TSRMLS_CC); - - // Add the result for this host - add_assoc_zval(return_value, ra->hosts[i], z_tmp); - } - - // Free arg array - efree(z_args[0]); -} - -PHP_METHOD(RedisArray, getOption) -{ - zval *object, z_fun, *z_tmp, *z_args[1]; - int i; - RedisArray *ra; - long opt; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", - &object, redis_array_ce, &opt) == FAILURE) { - RETURN_FALSE; - } - - if (redis_array_get(object, &ra TSRMLS_CC) < 0) { - RETURN_FALSE; - } - - /* prepare call */ - ZVAL_STRING(&z_fun, "getOption", 0); - - /* copy arg */ - MAKE_STD_ZVAL(z_args[0]); - ZVAL_LONG(z_args[0], opt); - - array_init(return_value); - for(i = 0; i < ra->count; ++i) { - - MAKE_STD_ZVAL(z_tmp); - - /* Call each node in turn */ - call_user_function(&redis_ce->function_table, &ra->redis[i], - &z_fun, z_tmp, 1, z_args TSRMLS_CC); - - add_assoc_zval(return_value, ra->hosts[i], z_tmp); - } - - /* cleanup */ - efree(z_args[0]); -} - -PHP_METHOD(RedisArray, setOption) -{ - zval *object, z_fun, *z_tmp, *z_args[2]; - int i; - RedisArray *ra; - long opt; - char *val_str; - int 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 (redis_array_get(object, &ra TSRMLS_CC) < 0) { - RETURN_FALSE; - } - - /* prepare call */ - ZVAL_STRING(&z_fun, "setOption", 0); - - /* copy args */ - MAKE_STD_ZVAL(z_args[0]); - ZVAL_LONG(z_args[0], opt); - MAKE_STD_ZVAL(z_args[1]); - ZVAL_STRINGL(z_args[1], val_str, val_len, 0); - - array_init(return_value); - for(i = 0; i < ra->count; ++i) { - - MAKE_STD_ZVAL(z_tmp); - - /* Call each node in turn */ - call_user_function(&redis_ce->function_table, &ra->redis[i], - &z_fun, z_tmp, 2, z_args TSRMLS_CC); - - add_assoc_zval(return_value, ra->hosts[i], z_tmp); - } - - /* cleanup */ - efree(z_args[0]); - efree(z_args[1]); -} - -PHP_METHOD(RedisArray, select) -{ - zval *object, z_fun, *z_tmp, *z_args[2]; - int i; - RedisArray *ra; - long opt; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", - &object, redis_array_ce, &opt) == FAILURE) { - RETURN_FALSE; - } - - if (redis_array_get(object, &ra TSRMLS_CC) < 0) { - RETURN_FALSE; - } - - /* prepare call */ - ZVAL_STRING(&z_fun, "select", 0); - - /* copy args */ - MAKE_STD_ZVAL(z_args[0]); - ZVAL_LONG(z_args[0], opt); - - array_init(return_value); - for(i = 0; i < ra->count; ++i) { - - MAKE_STD_ZVAL(z_tmp); - - /* Call each node in turn */ - call_user_function(&redis_ce->function_table, &ra->redis[i], - &z_fun, z_tmp, 1, z_args TSRMLS_CC); - - add_assoc_zval(return_value, ra->hosts[i], z_tmp); - } - - /* cleanup */ - efree(z_args[0]); -} - -#define HANDLE_MULTI_EXEC(cmd) do {\ - if (redis_array_get(getThis(), &ra TSRMLS_CC) >= 0 && ra->z_multi_exec) {\ - int i, num_varargs;\ - zval ***varargs = NULL;\ - zval 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;\ - MAKE_STD_ZVAL(z_tmp);\ - *z_tmp = **varargs[i];\ - zval_copy_ctor(z_tmp);\ - INIT_PZVAL(z_tmp);\ - 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);\ - if(varargs) {\ - efree(varargs);\ - }\ - return;\ - }\ -}while(0) - -/* MGET will distribute the call to several nodes and regroup the values. */ -PHP_METHOD(RedisArray, mget) -{ - zval *object, *z_keys, z_fun, *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; - HashPosition pointer; - zval **redis_instances, **argv; - - /* Multi/exec support */ - HANDLE_MULTI_EXEC("MGET"); - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", - &object, redis_array_ce, &z_keys) == FAILURE) { - RETURN_FALSE; - } - - if (redis_array_get(object, &ra TSRMLS_CC) < 0) { - RETURN_FALSE; - } - - /* prepare call */ - ZVAL_STRING(&z_fun, "MGET", 0); - - /* init data structures */ - h_keys = Z_ARRVAL_P(z_keys); - argc = zend_hash_num_elements(h_keys); - argv = emalloc(argc * sizeof(zval*)); - pos = emalloc(argc * sizeof(int)); - redis_instances = emalloc(argc * sizeof(zval*)); - memset(redis_instances, 0, argc * sizeof(zval*)); - - argc_each = emalloc(ra->count * sizeof(int)); - memset(argc_each, 0, ra->count * sizeof(int)); - - /* associate each key to a redis node */ - for (i = 0, zend_hash_internal_pointer_reset_ex(h_keys, &pointer); - zend_hash_get_current_data_ex(h_keys, (void**) &data, - &pointer) == SUCCESS; - zend_hash_move_forward_ex(h_keys, &pointer), ++i) - { - /* 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_PP(data) != IS_STRING && Z_TYPE_PP(data) != IS_LONG) { - php_error_docref(NULL TSRMLS_CC, E_ERROR, "MGET: all keys must be strings or longs"); - efree(argv); - efree(pos); - efree(redis_instances); - efree(argc_each); - RETURN_FALSE; - } - - /* Convert to a string for hash lookup if it isn't one */ - if(Z_TYPE_PP(data) == IS_STRING) { - key_len = Z_STRLEN_PP(data); - key_lookup = Z_STRVAL_PP(data); - } else { - key_len = snprintf(kbuf, sizeof(kbuf), "%ld", Z_LVAL_PP(data)); - key_lookup = (char*)kbuf; - } - - /* Find our node */ - redis_instances[i] = ra_find_node(ra, key_lookup, key_len, &pos[i] TSRMLS_CC); - - argc_each[pos[i]]++; /* count number of keys per node */ - argv[i] = *data; - } - - /* prepare return value */ - array_init(return_value); - MAKE_STD_ZVAL(z_tmp_array); - array_init(z_tmp_array); - - /* calls */ - for(n = 0; n < ra->count; ++n) { /* for each node */ - /* copy args for MGET call on node. */ - MAKE_STD_ZVAL(z_argarray); - array_init(z_argarray); - - for(i = 0; i < argc; ++i) { - if(pos[i] != n) continue; - - MAKE_STD_ZVAL(z_tmp); - *z_tmp = *argv[i]; - zval_copy_ctor(z_tmp); - INIT_PZVAL(z_tmp); - add_next_index_zval(z_argarray, z_tmp); - } - - /* call MGET on the node */ - MAKE_STD_ZVAL(z_ret); - call_user_function(&redis_ce->function_table, &ra->redis[n], - &z_fun, z_ret, 1, &z_argarray TSRMLS_CC); - - /* cleanup args array */ - zval_ptr_dtor(&z_argarray); - - for(i = 0, j = 0; i < argc; ++i) { - /* Error out if we didn't get a proper response */ - if(Z_TYPE_P(z_ret) != IS_ARRAY) { - /* cleanup */ - zval_dtor(z_ret); - efree(z_ret); - zval_ptr_dtor(&z_tmp_array); - efree(pos); - efree(redis_instances); - efree(argc_each); - - /* failure */ - RETURN_FALSE; - } - - if(pos[i] != n) continue; - - zend_hash_quick_find(Z_ARRVAL_P(z_ret), NULL, 0, j, (void**)&z_cur); - j++; - - MAKE_STD_ZVAL(z_tmp); - *z_tmp = **z_cur; - zval_copy_ctor(z_tmp); - INIT_PZVAL(z_tmp); - add_index_zval(z_tmp_array, i, z_tmp); - } - zval_dtor(z_ret); - efree(z_ret); - } - - /* copy temp array in the right order to return_value */ - for(i = 0; i < argc; ++i) { - zend_hash_quick_find(Z_ARRVAL_P(z_tmp_array), NULL, 0, i, (void**)&z_cur); - - MAKE_STD_ZVAL(z_tmp); - *z_tmp = **z_cur; - zval_copy_ctor(z_tmp); - INIT_PZVAL(z_tmp); - add_next_index_zval(return_value, z_tmp); - } - - /* cleanup */ - zval_ptr_dtor(&z_tmp_array); - efree(argv); - efree(pos); - efree(redis_instances); - efree(argc_each); -} - - -/* MSET will distribute the call to several nodes and regroup the values. */ -PHP_METHOD(RedisArray, mset) -{ - zval *object, *z_keys, z_fun, *z_argarray, **data, z_ret; - int i, n; - RedisArray *ra; - int *pos, argc, *argc_each; - HashTable *h_keys; - zval **redis_instances, *redis_inst, **argv; - char *key, **keys, **key_free, kbuf[40]; - unsigned int key_len, free_idx = 0; - int type, *key_lens; - unsigned long idx; - - /* Multi/exec support */ - HANDLE_MULTI_EXEC("MSET"); - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", - &object, redis_array_ce, &z_keys) == FAILURE) { - RETURN_FALSE; - } - - if (redis_array_get(object, &ra TSRMLS_CC) < 0) { - RETURN_FALSE; - } - - /* init data structures */ - h_keys = Z_ARRVAL_P(z_keys); - argc = zend_hash_num_elements(h_keys); - argv = emalloc(argc * sizeof(zval*)); - pos = emalloc(argc * sizeof(int)); - keys = emalloc(argc * sizeof(char*)); - key_lens = emalloc(argc * sizeof(int)); - redis_instances = emalloc(argc * sizeof(zval*)); - memset(redis_instances, 0, argc * sizeof(zval*)); - - /* Allocate an array holding the indexes of any keys that need freeing */ - key_free = emalloc(argc * sizeof(char*)); - - argc_each = emalloc(ra->count * sizeof(int)); - memset(argc_each, 0, ra->count * sizeof(int)); - - /* associate each key to a redis node */ - for(i = 0, zend_hash_internal_pointer_reset(h_keys); - zend_hash_has_more_elements(h_keys) == SUCCESS; - zend_hash_move_forward(h_keys), i++) - { - /* We have to skip the element if we can't get the array value */ - if(zend_hash_get_current_data(h_keys, (void**)&data) == FAILURE) { - continue; - } - - /* Grab our key */ - type = zend_hash_get_current_key_ex(h_keys, &key, &key_len, &idx, 0, NULL); - - /* If the key isn't a string, make a string representation of it */ - if(type != HASH_KEY_IS_STRING) { - key_len = snprintf(kbuf, sizeof(kbuf), "%ld", (long)idx); - key = estrndup(kbuf, key_len); - key_free[free_idx++]=key; - } else { - key_len--; /* We don't want the null terminator */ - } - - redis_instances[i] = ra_find_node(ra, key, (int)key_len, &pos[i] TSRMLS_CC); - argc_each[pos[i]]++; /* count number of keys per node */ - argv[i] = *data; - keys[i] = key; - key_lens[i] = (int)key_len; - } - - - /* calls */ - for(n = 0; n < ra->count; ++n) { /* for each node */ - - /* prepare call */ - ZVAL_STRING(&z_fun, "MSET", 0); - redis_inst = ra->redis[n]; - - /* copy args */ - int found = 0; - MAKE_STD_ZVAL(z_argarray); - array_init(z_argarray); - for(i = 0; i < argc; ++i) { - if(pos[i] != n) continue; - - zval *z_tmp; - ALLOC_ZVAL(z_tmp); - *z_tmp = *argv[i]; - zval_copy_ctor(z_tmp); - INIT_PZVAL(z_tmp); - - add_assoc_zval_ex(z_argarray, keys[i], key_lens[i] + 1, z_tmp); /* +1 to count the \0 here */ - found++; - } - - if(!found) - { - zval_dtor(z_argarray); - efree(z_argarray); - continue; /* don't run empty MSETs */ - } - - if(ra->index) { /* add MULTI */ - ra_index_multi(redis_inst, MULTI TSRMLS_CC); - } - - /* call */ - call_user_function(&redis_ce->function_table, &ra->redis[n], - &z_fun, &z_ret, 1, &z_argarray TSRMLS_CC); - - if(ra->index) { - ra_index_keys(z_argarray, redis_inst TSRMLS_CC); /* use SADD to add keys to node index */ - ra_index_exec(redis_inst, NULL, 0 TSRMLS_CC); /* run EXEC */ - } - - zval_dtor(&z_ret); - - zval_ptr_dtor(&z_argarray); - } - - /* Free any keys that we needed to allocate memory for, because they weren't strings */ - for(i=0; icount * sizeof(int)); - memset(argc_each, 0, ra->count * sizeof(int)); - - /* associate each key to a redis node */ - for (i = 0, zend_hash_internal_pointer_reset_ex(h_keys, &pointer); - zend_hash_get_current_data_ex(h_keys, (void**) &data, - &pointer) == SUCCESS; - zend_hash_move_forward_ex(h_keys, &pointer), ++i) { - - if (Z_TYPE_PP(data) != IS_STRING) { - php_error_docref(NULL TSRMLS_CC, E_ERROR, "DEL: all keys must be string."); - efree(pos); - RETURN_FALSE; - } - - redis_instances[i] = ra_find_node(ra, Z_STRVAL_PP(data), Z_STRLEN_PP(data), &pos[i] TSRMLS_CC); - argc_each[pos[i]]++; /* count number of keys per node */ - argv[i] = *data; - } - - /* calls */ - for(n = 0; n < ra->count; ++n) { /* for each node */ - - int found = 0; - redis_inst = ra->redis[n]; - - /* copy args */ - MAKE_STD_ZVAL(z_argarray); - array_init(z_argarray); - for(i = 0; i < argc; ++i) { - if(pos[i] != n) continue; - - MAKE_STD_ZVAL(z_tmp); - *z_tmp = *argv[i]; - zval_copy_ctor(z_tmp); - INIT_PZVAL(z_tmp); - - add_next_index_zval(z_argarray, z_tmp); - found++; - } - - if(!found) { // don't run empty DELs - zval_dtor(z_argarray); - efree(z_argarray); - continue; - } - - if(ra->index) { /* add MULTI */ - ra_index_multi(redis_inst, MULTI TSRMLS_CC); - } - - /* call */ - MAKE_STD_ZVAL(z_ret); - call_user_function(&redis_ce->function_table, &redis_inst, - &z_fun, z_ret, 1, &z_argarray TSRMLS_CC); - - if(ra->index) { - ra_index_del(z_argarray, redis_inst TSRMLS_CC); /* use SREM to remove keys from node index */ - ra_index_exec(redis_inst, z_tmp, 0 TSRMLS_CC); /* run EXEC */ - total += Z_LVAL_P(z_tmp); /* increment total from multi/exec block */ - } else { - total += Z_LVAL_P(z_ret); /* increment total from single command */ - } - - zval_dtor(z_ret); - efree(z_ret); - - zval_dtor(z_argarray); - efree(z_argarray); - } - - /* cleanup */ - efree(argv); - efree(pos); - efree(redis_instances); - efree(argc_each); - - if(free_zkeys) { - zval_dtor(z_keys); - efree(z_keys); - } - - efree(z_args); - RETURN_LONG(total); -} - -PHP_METHOD(RedisArray, multi) -{ - zval *object; - RedisArray *ra; - zval *z_redis; - char *host; - int host_len; - 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 (redis_array_get(object, &ra TSRMLS_CC) < 0) { - 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 (redis_array_get(object, &ra TSRMLS_CC) < 0 || !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 (redis_array_get(object, &ra TSRMLS_CC) < 0 || !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 (redis_array_get(object, &ra TSRMLS_CC) < 0 || !ra->z_multi_exec) { - RETURN_FALSE; - } - - /* unwatch keys, stay in multi/exec mode. */ - ra_index_unwatch(ra->z_multi_exec, return_value TSRMLS_CC); -} diff -Nru php-redis-2.2.4/redis-2.2.4/redis_array.h php-redis-3.0.0/redis-2.2.4/redis_array.h --- php-redis-2.2.4/redis-2.2.4/redis_array.h 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/redis_array.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,55 +0,0 @@ -#ifndef REDIS_ARRAY_H -#define REDIS_ARRAY_H - -#include -#include "common.h" - -void redis_destructor_redis_array(zend_rsrc_list_entry * rsrc TSRMLS_DC); - -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, 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 */ - zval *z_pure_cmds; /* hash table */ - - struct RedisArray_ *prev; -} RedisArray; - -uint32_t rcrc32(const char *s, size_t sz); - - -#endif diff -Nru php-redis-2.2.4/redis-2.2.4/redis_array_impl.c php-redis-3.0.0/redis-2.2.4/redis_array_impl.c --- php-redis-2.2.4/redis-2.2.4/redis_array_impl.c 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/redis_array_impl.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,1241 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | 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" - -#define PHPREDIS_INDEX_NAME "__phpredis_array_index__" - -extern int le_redis_sock; -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, host_len, id; - int count = zend_hash_num_elements(hosts); - char *host, *p; - short port; - zval **zpData, z_cons, z_ret; - RedisSock *redis_sock = NULL; - - /* function calls on the Redis object */ - ZVAL_STRING(&z_cons, "__construct", 0); - - /* init connections */ - for(i = 0; i < count; ++i) { - if(FAILURE == zend_hash_quick_find(hosts, NULL, 0, i, (void**)&zpData)) { - efree(ra); - return NULL; - } - - ra->hosts[i] = estrdup(Z_STRVAL_PP(zpData)); - - /* default values */ - host = Z_STRVAL_PP(zpData); - host_len = Z_STRLEN_PP(zpData); - port = 6379; - - if((p = strchr(host, ':'))) { /* found port */ - host_len = p - host; - port = (short)atoi(p+1); - } else if(strchr(host,'/') != NULL) { /* unix socket */ - port = -1; - } - - /* create Redis object */ - MAKE_STD_ZVAL(ra->redis[i]); - object_init_ex(ra->redis[i], redis_ce); - INIT_PZVAL(ra->redis[i]); - call_user_function(&redis_ce->function_table, &ra->redis[i], &z_cons, &z_ret, 0, NULL TSRMLS_CC); - - /* create socket */ - redis_sock = redis_sock_create(host, host_len, port, 0, ra->pconnect, NULL, retry_interval, b_lazy_connect); - - if (!b_lazy_connect) - { - /* connect */ - redis_sock_server_open(redis_sock, 1 TSRMLS_CC); - } - - /* attach */ -#if PHP_VERSION_ID >= 50400 - id = zend_list_insert(redis_sock, le_redis_sock TSRMLS_CC); -#else - id = zend_list_insert(redis_sock, le_redis_sock); -#endif - add_property_resource(ra->redis[i], "socket", id); - } - - return ra; -} - -/* List pure functions */ -void ra_init_function_table(RedisArray *ra) { - - MAKE_STD_ZVAL(ra->z_pure_cmds); - array_init(ra->z_pure_cmds); - - add_assoc_bool(ra->z_pure_cmds, "HGET", 1); - add_assoc_bool(ra->z_pure_cmds, "HGETALL", 1); - add_assoc_bool(ra->z_pure_cmds, "HKEYS", 1); - add_assoc_bool(ra->z_pure_cmds, "HLEN", 1); - add_assoc_bool(ra->z_pure_cmds, "SRANDMEMBER", 1); - add_assoc_bool(ra->z_pure_cmds, "HMGET", 1); - add_assoc_bool(ra->z_pure_cmds, "STRLEN", 1); - add_assoc_bool(ra->z_pure_cmds, "SUNION", 1); - add_assoc_bool(ra->z_pure_cmds, "HVALS", 1); - add_assoc_bool(ra->z_pure_cmds, "TYPE", 1); - add_assoc_bool(ra->z_pure_cmds, "EXISTS", 1); - add_assoc_bool(ra->z_pure_cmds, "LINDEX", 1); - add_assoc_bool(ra->z_pure_cmds, "SCARD", 1); - add_assoc_bool(ra->z_pure_cmds, "LLEN", 1); - add_assoc_bool(ra->z_pure_cmds, "SDIFF", 1); - add_assoc_bool(ra->z_pure_cmds, "ZCARD", 1); - add_assoc_bool(ra->z_pure_cmds, "ZCOUNT", 1); - add_assoc_bool(ra->z_pure_cmds, "LRANGE", 1); - add_assoc_bool(ra->z_pure_cmds, "ZRANGE", 1); - add_assoc_bool(ra->z_pure_cmds, "ZRANK", 1); - add_assoc_bool(ra->z_pure_cmds, "GET", 1); - add_assoc_bool(ra->z_pure_cmds, "GETBIT", 1); - add_assoc_bool(ra->z_pure_cmds, "SINTER", 1); - add_assoc_bool(ra->z_pure_cmds, "GETRANGE", 1); - add_assoc_bool(ra->z_pure_cmds, "ZREVRANGE", 1); - add_assoc_bool(ra->z_pure_cmds, "SISMEMBER", 1); - add_assoc_bool(ra->z_pure_cmds, "ZREVRANGEBYSCORE", 1); - add_assoc_bool(ra->z_pure_cmds, "ZREVRANK", 1); - add_assoc_bool(ra->z_pure_cmds, "HEXISTS", 1); - add_assoc_bool(ra->z_pure_cmds, "ZSCORE", 1); - add_assoc_bool(ra->z_pure_cmds, "HGET", 1); - add_assoc_bool(ra->z_pure_cmds, "OBJECT", 1); - add_assoc_bool(ra->z_pure_cmds, "SMEMBERS", 1); -} - -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_params_hosts, **z_hosts; - zval *z_params_prev, **z_prev; - zval *z_params_funs, **z_data_pp, *z_fun = NULL, *z_dist = NULL; - zval *z_params_index; - zval *z_params_autorehash; - zval *z_params_retry_interval; - zval *z_params_pconnect; - 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; - HashTable *hHosts = NULL, *hPrev = NULL; - - /* find entry */ - if(!ra_find_name(name)) - return ra; - - /* find hosts */ - MAKE_STD_ZVAL(z_params_hosts); - array_init(z_params_hosts); - sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.hosts")), z_params_hosts TSRMLS_CC); - if (zend_hash_find(Z_ARRVAL_P(z_params_hosts), name, strlen(name) + 1, (void **) &z_hosts) != FAILURE) { - hHosts = Z_ARRVAL_PP(z_hosts); - } - - /* find previous hosts */ - MAKE_STD_ZVAL(z_params_prev); - array_init(z_params_prev); - sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.previous")), z_params_prev TSRMLS_CC); - if (zend_hash_find(Z_ARRVAL_P(z_params_prev), name, strlen(name) + 1, (void **) &z_prev) != FAILURE) { - hPrev = Z_ARRVAL_PP(z_prev); - } - - /* find function */ - MAKE_STD_ZVAL(z_params_funs); - array_init(z_params_funs); - sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.functions")), z_params_funs TSRMLS_CC); - if (zend_hash_find(Z_ARRVAL_P(z_params_funs), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { - MAKE_STD_ZVAL(z_fun); - *z_fun = **z_data_pp; - zval_copy_ctor(z_fun); - } - - /* find distributor */ - MAKE_STD_ZVAL(z_params_funs); - array_init(z_params_funs); - sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.distributor")), z_params_funs TSRMLS_CC); - if (zend_hash_find(Z_ARRVAL_P(z_params_funs), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { - MAKE_STD_ZVAL(z_dist); - *z_dist = **z_data_pp; - zval_copy_ctor(z_dist); - } - - /* find index option */ - MAKE_STD_ZVAL(z_params_index); - array_init(z_params_index); - sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.index")), z_params_index TSRMLS_CC); - if (zend_hash_find(Z_ARRVAL_P(z_params_index), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { - if(Z_TYPE_PP(z_data_pp) == IS_STRING && strncmp(Z_STRVAL_PP(z_data_pp), "1", 1) == 0) { - b_index = 1; - } - } - - /* find autorehash option */ - MAKE_STD_ZVAL(z_params_autorehash); - array_init(z_params_autorehash); - sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.autorehash")), z_params_autorehash TSRMLS_CC); - if (zend_hash_find(Z_ARRVAL_P(z_params_autorehash), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { - if(Z_TYPE_PP(z_data_pp) == IS_STRING && strncmp(Z_STRVAL_PP(z_data_pp), "1", 1) == 0) { - b_autorehash = 1; - } - } - - /* find retry interval option */ - MAKE_STD_ZVAL(z_params_retry_interval); - array_init(z_params_retry_interval); - sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.retryinterval")), z_params_retry_interval TSRMLS_CC); - if (zend_hash_find(Z_ARRVAL_P(z_params_retry_interval), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { - if (Z_TYPE_PP(z_data_pp) == IS_LONG || Z_TYPE_PP(z_data_pp) == IS_STRING) { - if (Z_TYPE_PP(z_data_pp) == IS_LONG) { - l_retry_interval = Z_LVAL_PP(z_data_pp); - } - else { - l_retry_interval = atol(Z_STRVAL_PP(z_data_pp)); - } - } - } - - /* find pconnect option */ - MAKE_STD_ZVAL(z_params_pconnect); - array_init(z_params_pconnect); - sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.pconnect")), z_params_pconnect TSRMLS_CC); - if (zend_hash_find(Z_ARRVAL_P(z_params_pconnect), name, strlen(name) + 1, (void**) &z_data_pp) != FAILURE) { - if(Z_TYPE_PP(z_data_pp) == IS_STRING && strncmp(Z_STRVAL_PP(z_data_pp), "1", 1) == 0) { - b_pconnect = 1; - } - } - /* find retry interval option */ - MAKE_STD_ZVAL(z_params_lazy_connect); - array_init(z_params_lazy_connect); - sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.lazyconnect")), z_params_lazy_connect TSRMLS_CC); - if (zend_hash_find(Z_ARRVAL_P(z_params_lazy_connect), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { - if(Z_TYPE_PP(z_data_pp) == IS_STRING && strncmp(Z_STRVAL_PP(z_data_pp), "1", 1) == 0) { - b_lazy_connect = 1; - } - } - - /* create RedisArray object */ - ra = ra_make_array(hHosts, z_fun, z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect TSRMLS_CC); - ra->auto_rehash = b_autorehash; - - /* cleanup */ - zval_dtor(z_params_hosts); - efree(z_params_hosts); - zval_dtor(z_params_prev); - efree(z_params_prev); - zval_dtor(z_params_funs); - efree(z_params_funs); - zval_dtor(z_params_index); - efree(z_params_index); - zval_dtor(z_params_autorehash); - efree(z_params_autorehash); - zval_dtor(z_params_retry_interval); - efree(z_params_retry_interval); - zval_dtor(z_params_pconnect); - efree(z_params_pconnect); - zval_dtor(z_params_lazy_connect); - efree(z_params_lazy_connect); - - 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 TSRMLS_DC) { - - int count = zend_hash_num_elements(hosts); - - /* create object */ - RedisArray *ra = emalloc(sizeof(RedisArray)); - ra->hosts = emalloc(count * sizeof(char*)); - ra->redis = emalloc(count * sizeof(zval*)); - ra->count = count; - ra->z_fun = NULL; - ra->z_dist = NULL; - ra->z_multi_exec = NULL; - ra->index = b_index; - ra->auto_rehash = 0; - - /* init array data structures */ - ra_init_function_table(ra); - - if(NULL == ra_load_hosts(ra, hosts, retry_interval, b_lazy_connect TSRMLS_CC)) { - 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 TSRMLS_CC) : NULL; - - /* copy function if provided */ - if(z_fun) { - MAKE_STD_ZVAL(ra->z_fun); - *ra->z_fun = *z_fun; - zval_copy_ctor(ra->z_fun); - } - - /* copy distributor if provided */ - if(z_dist) { - MAKE_STD_ZVAL(ra->z_dist); - *ra->z_dist = *z_dist; - zval_copy_ctor(ra->z_dist); - } - - 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; - zval z_ret; - zval *z_argv0; - - /* check that we can call the extractor function */ - if(!zend_is_callable_ex(ra->z_fun, NULL, 0, NULL, NULL, NULL, NULL TSRMLS_CC)) { - php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not call extractor function"); - return NULL; - } - //convert_to_string(ra->z_fun); - - /* call extraction function */ - MAKE_STD_ZVAL(z_argv0); - ZVAL_STRINGL(z_argv0, key, key_len, 0); - call_user_function(EG(function_table), NULL, ra->z_fun, &z_ret, 1, &z_argv0 TSRMLS_CC); - efree(z_argv0); - - if(Z_TYPE(z_ret) != IS_STRING) { - zval_dtor(&z_ret); - return NULL; - } - - *out_len = Z_STRLEN(z_ret); - out = emalloc(*out_len + 1); - out[*out_len] = 0; - memcpy(out, Z_STRVAL(z_ret), *out_len); - - 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; - *out_len = key_len; - - if(ra->z_fun) - return ra_call_extractor(ra, key, key_len, out_len TSRMLS_CC); - - /* look for '{' */ - start = strchr(key, '{'); - if(!start) return estrndup(key, key_len); - - /* look for '}' */ - end = strchr(start + 1, '}'); - if(!end) return estrndup(key, key_len); - - /* found substring */ - *out_len = end - start - 1; - out = emalloc(*out_len + 1); - out[*out_len] = 0; - memcpy(out, start+1, *out_len); - - return out; -} - -/* call userland key distributor function */ -zend_bool -ra_call_distributor(RedisArray *ra, const char *key, int key_len, int *pos TSRMLS_DC) { - - zval z_ret; - zval *z_argv0; - - /* check that we can call the extractor function */ - if(!zend_is_callable_ex(ra->z_dist, NULL, 0, NULL, NULL, NULL, NULL TSRMLS_CC)) { - php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not call distributor function"); - return 0; - } - //convert_to_string(ra->z_fun); - - /* call extraction function */ - MAKE_STD_ZVAL(z_argv0); - ZVAL_STRINGL(z_argv0, key, key_len, 0); - call_user_function(EG(function_table), NULL, ra->z_dist, &z_ret, 1, &z_argv0 TSRMLS_CC); - efree(z_argv0); - - if(Z_TYPE(z_ret) != IS_LONG) { - zval_dtor(&z_ret); - return 0; - } - - *pos = Z_LVAL(z_ret); - zval_dtor(&z_ret); - return 1; -} - -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(ra->z_dist) { - if (!ra_call_distributor(ra, key, key_len, &pos TSRMLS_CC)) { - return NULL; - } - } - else { - /* hash */ - hash = rcrc32(out, out_len); - efree(out); - - /* get position on ring */ - uint64_t h64 = hash; - h64 *= ra->count; - h64 /= 0xffffffff; - pos = (int)h64; - } - 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; -} - - -char * -ra_find_key(RedisArray *ra, zval *z_args, const char *cmd, int *key_len) { - - zval **zp_tmp; - int key_pos = 0; /* TODO: change this depending on the command */ - - if( zend_hash_num_elements(Z_ARRVAL_P(z_args)) == 0 - || zend_hash_quick_find(Z_ARRVAL_P(z_args), NULL, 0, key_pos, (void**)&zp_tmp) == FAILURE - || Z_TYPE_PP(zp_tmp) != IS_STRING) { - - return NULL; - } - - *key_len = Z_STRLEN_PP(zp_tmp); - return Z_STRVAL_PP(zp_tmp); -} - -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_STRING(&z_fun_multi, "MULTI", 0); - MAKE_STD_ZVAL(z_args[0]); - ZVAL_LONG(z_args[0], multi_value); - call_user_function(&redis_ce->function_table, &z_redis, &z_fun_multi, &z_ret, 1, z_args TSRMLS_CC); - efree(z_args[0]); - //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 = emalloc(argc * sizeof(zval*)); - - /* prepare first parameters */ - ZVAL_STRING(&z_fun, cmd, 0); - MAKE_STD_ZVAL(z_args[0]); - ZVAL_STRING(z_args[0], PHPREDIS_INDEX_NAME, 0); - - /* prepare keys */ - for(i = 0; i < argc - 1; ++i) { - zval **zpp; - zend_hash_quick_find(Z_ARRVAL_P(z_keys), NULL, 0, i, (void**)&zpp); - z_args[i+1] = *zpp; - } - - /* run cmd */ - call_user_function(&redis_ce->function_table, &z_redis, &z_fun, &z_ret, argc, z_args TSRMLS_CC); - - /* don't dtor z_ret, since we're returning z_redis */ - efree(z_args[0]); /* free index name zval */ - 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) { - - /* Initialize key array */ - zval *z_keys, **z_entry_pp; - MAKE_STD_ZVAL(z_keys); -#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 - HashPosition pos; - - /* Go through input array and add values to the key array */ - zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(z_pairs), &pos); - while (zend_hash_get_current_data_ex(Z_ARRVAL_P(z_pairs), (void **)&z_entry_pp, &pos) == SUCCESS) { - char *key; - unsigned int key_len; - unsigned long num_key; - zval *z_new; - MAKE_STD_ZVAL(z_new); - - switch (zend_hash_get_current_key_ex(Z_ARRVAL_P(z_pairs), &key, &key_len, &num_key, 1, &pos)) { - case HASH_KEY_IS_STRING: - ZVAL_STRINGL(z_new, key, (int)key_len - 1, 0); - zend_hash_next_index_insert(Z_ARRVAL_P(z_keys), &z_new, sizeof(zval *), NULL); - break; - - case HASH_KEY_IS_LONG: - Z_TYPE_P(z_new) = IS_LONG; - Z_LVAL_P(z_new) = (long)num_key; - zend_hash_next_index_insert(Z_ARRVAL_P(z_keys), &z_new, sizeof(zval *), NULL); - break; - } - zend_hash_move_forward_ex(Z_ARRVAL_P(z_pairs), &pos); - } - - /* add keys to index */ - ra_index_change_keys("SADD", z_keys, z_redis TSRMLS_CC); - - /* cleanup */ - zval_dtor(z_keys); - efree(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]; - MAKE_STD_ZVAL(z_args[0]); - MAKE_STD_ZVAL(z_args[1]); - - /* prepare args */ - ZVAL_STRINGL(&z_fun_sadd, "SADD", 4, 0); - - ZVAL_STRING(z_args[0], PHPREDIS_INDEX_NAME, 0); - ZVAL_STRINGL(z_args[1], key, key_len, 1); - - /* run SADD */ - call_user_function(&redis_ce->function_table, &z_redis, &z_fun_sadd, &z_ret, 2, z_args TSRMLS_CC); - - /* don't dtor z_ret, since we're returning z_redis */ - efree(z_args[0]); - efree(z_args[1]); -} - -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_STRING(&z_fun_exec, "EXEC", 0); - call_user_function(&redis_ce->function_table, &z_redis, &z_fun_exec, &z_ret, 0, NULL TSRMLS_CC); - - - /* extract first element of exec array and put into return_value. */ - if(Z_TYPE(z_ret) == IS_ARRAY) { - if(return_value) { - if(keep_all) { - *return_value = z_ret; - zval_copy_ctor(return_value); - } else if(zend_hash_quick_find(Z_ARRVAL(z_ret), NULL, 0, 0, (void**)&zp_tmp) != FAILURE) { - *return_value = **zp_tmp; - zval_copy_ctor(return_value); - } - } - 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_STRING(&z_fun_discard, "DISCARD", 0); - call_user_function(&redis_ce->function_table, &z_redis, &z_fun_discard, &z_ret, 0, NULL TSRMLS_CC); - - 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_STRING(&z_fun_unwatch, "UNWATCH", 0); - call_user_function(&redis_ce->function_table, &z_redis, &z_fun_unwatch, &z_ret, 0, NULL TSRMLS_CC); - - 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_exists(Z_ARRVAL_P(ra->z_pure_cmds), cmd_up, cmd_len+1); - - efree(cmd_up); - return !ret; -} - -/* list keys from array index */ -static long -ra_rehash_scan(zval *z_redis, char ***keys, int **key_lens, const char *cmd, const char *arg TSRMLS_DC) { - - long count, i; - zval z_fun_smembers, z_ret, *z_arg, **z_data_pp; - HashTable *h_keys; - HashPosition pointer; - char *key; - int key_len; - - /* arg */ - MAKE_STD_ZVAL(z_arg); - ZVAL_STRING(z_arg, arg, 0); - - /* run SMEMBERS */ - ZVAL_STRING(&z_fun_smembers, cmd, 0); - call_user_function(&redis_ce->function_table, &z_redis, &z_fun_smembers, &z_ret, 1, &z_arg TSRMLS_CC); - efree(z_arg); - if(Z_TYPE(z_ret) != IS_ARRAY) { /* failure */ - return -1; /* TODO: log error. */ - } - h_keys = Z_ARRVAL(z_ret); - - /* allocate key array */ - count = zend_hash_num_elements(h_keys); - *keys = emalloc(count * sizeof(char*)); - *key_lens = emalloc(count * sizeof(int)); - - for (i = 0, zend_hash_internal_pointer_reset_ex(h_keys, &pointer); - zend_hash_get_current_data_ex(h_keys, (void**) &z_data_pp, &pointer) == SUCCESS; - zend_hash_move_forward_ex(h_keys, &pointer), ++i) { - - key = Z_STRVAL_PP(z_data_pp); - key_len = Z_STRLEN_PP(z_data_pp); - - /* copy key and length */ - (*keys)[i] = emalloc(1 + key_len); - memcpy((*keys)[i], key, key_len); - (*key_lens)[i] = key_len; - (*keys)[i][key_len] = 0; /* null-terminate string */ - } - - /* cleanup */ - zval_dtor(&z_ret); - - return count; -} - -static long -ra_rehash_scan_index(zval *z_redis, char ***keys, int **key_lens TSRMLS_DC) { - return ra_rehash_scan(z_redis, keys, key_lens, "SMEMBERS", PHPREDIS_INDEX_NAME TSRMLS_CC); -} - -/* list keys using KEYS command */ -static long -ra_rehash_scan_keys(zval *z_redis, char ***keys, int **key_lens TSRMLS_DC) { - return ra_rehash_scan(z_redis, keys, key_lens, "KEYS", "*" TSRMLS_CC); -} - -/* 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; - zval z_fun_type, z_ret, *z_arg; - zval **z_data; - long success = 1; - - MAKE_STD_ZVAL(z_arg); - /* Pipelined */ - ra_index_multi(z_from, PIPELINE TSRMLS_CC); - - /* prepare args */ - ZVAL_STRINGL(&z_fun_type, "TYPE", 4, 0); - ZVAL_STRINGL(z_arg, key, key_len, 0); - /* run TYPE */ - call_user_function(&redis_ce->function_table, &z_redis, &z_fun_type, &z_ret, 1, &z_arg TSRMLS_CC); - - ZVAL_STRINGL(&z_fun_type, "TTL", 3, 0); - ZVAL_STRINGL(z_arg, key, key_len, 0); - /* run TYPE */ - call_user_function(&redis_ce->function_table, &z_redis, &z_fun_type, &z_ret, 1, &z_arg TSRMLS_CC); - - /* cleanup */ - efree(z_arg); - - /* Get the result from the pipeline. */ - ra_index_exec(z_from, &z_ret, 1 TSRMLS_CC); - if(Z_TYPE(z_ret) == IS_ARRAY) { - HashTable *retHash = Z_ARRVAL(z_ret); - for(i = 0, zend_hash_internal_pointer_reset(retHash); - zend_hash_has_more_elements(retHash) == SUCCESS; - zend_hash_move_forward(retHash)) { - - if(zend_hash_get_current_data(retHash, (void**)&z_data) == FAILURE) { - success = 0; - break; - } - if(Z_TYPE_PP(z_data) != IS_LONG) { - success = 0; - break; - } - /* Get the result - Might change in the future to handle doubles as well */ - res[i] = Z_LVAL_PP(z_data); - i++; - } - } - 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, 0); - MAKE_STD_ZVAL(z_args[0]); - ZVAL_STRING(z_args[0], PHPREDIS_INDEX_NAME, 0); - MAKE_STD_ZVAL(z_args[1]); - ZVAL_STRINGL(z_args[1], key, key_len, 0); - - call_user_function(&redis_ce->function_table, &z_redis, &z_fun_srem, &z_ret, 2, z_args TSRMLS_CC); - - /* cleanup */ - efree(z_args[0]); - efree(z_args[1]); -} - - -/* 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; - - /* in a transaction */ - ra_index_multi(z_from, MULTI TSRMLS_CC); - - /* run DEL on source */ - MAKE_STD_ZVAL(z_args); - ZVAL_STRINGL(&z_fun_del, "DEL", 3, 0); - ZVAL_STRINGL(z_args, key, key_len, 0); - call_user_function(&redis_ce->function_table, &z_from, &z_fun_del, &z_ret, 1, &z_args TSRMLS_CC); - efree(z_args); - - /* 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 */ - MAKE_STD_ZVAL(z_args[0]); - MAKE_STD_ZVAL(z_args[1]); - ZVAL_STRINGL(&z_fun_expire, "EXPIRE", 6, 0); - ZVAL_STRINGL(z_args[0], key, key_len, 0); - ZVAL_LONG(z_args[1], ttl); - call_user_function(&redis_ce->function_table, &z_to, &z_fun_expire, &z_ret, 2, z_args TSRMLS_CC); - /* cleanup */ - efree(z_args[0]); - efree(z_args[1]); - } - - 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_args[4], **z_zadd_args, **z_score_pp; - int count; - HashTable *h_zset_vals; - char *val; - unsigned int val_len; - int i; - unsigned long idx; - - /* run ZRANGE key 0 -1 WITHSCORES on source */ - ZVAL_STRINGL(&z_fun_zrange, "ZRANGE", 6, 0); - for(i = 0; i < 4; ++i) { - MAKE_STD_ZVAL(z_args[i]); - } - ZVAL_STRINGL(z_args[0], key, key_len, 0); - ZVAL_STRINGL(z_args[1], "0", 1, 0); - ZVAL_STRINGL(z_args[2], "-1", 2, 0); - ZVAL_BOOL(z_args[3], 1); - call_user_function(&redis_ce->function_table, &z_from, &z_fun_zrange, &z_ret, 4, z_args TSRMLS_CC); - - /* cleanup zrange args */ - for(i = 0; i < 4; ++i) { - efree(z_args[i]); /* FIXME */ - } - - if(Z_TYPE(z_ret) != IS_ARRAY) { /* key not found or replaced */ - /* TODO: report? */ - 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 = emalloc((1 + 2*count) * sizeof(zval*)); - - for(i = 1, zend_hash_internal_pointer_reset(h_zset_vals); - zend_hash_has_more_elements(h_zset_vals) == SUCCESS; - zend_hash_move_forward(h_zset_vals)) { - - if(zend_hash_get_current_data(h_zset_vals, (void**)&z_score_pp) == FAILURE) { - continue; - } - - /* add score */ - convert_to_double(*z_score_pp); - MAKE_STD_ZVAL(z_zadd_args[i]); - ZVAL_DOUBLE(z_zadd_args[i], Z_DVAL_PP(z_score_pp)); - - /* add value */ - MAKE_STD_ZVAL(z_zadd_args[i+1]); - switch (zend_hash_get_current_key_ex(h_zset_vals, &val, &val_len, &idx, 0, NULL)) { - case HASH_KEY_IS_STRING: - ZVAL_STRINGL(z_zadd_args[i+1], val, (int)val_len-1, 0); /* we have to remove 1 because it is an array key. */ - break; - case HASH_KEY_IS_LONG: - ZVAL_LONG(z_zadd_args[i+1], (long)idx); - break; - default: - return -1; // Todo: log error - break; - } - i += 2; - } - - /* run ZADD on target */ - ZVAL_STRINGL(&z_fun_zadd, "ZADD", 4, 0); - MAKE_STD_ZVAL(z_zadd_args[0]); - ZVAL_STRINGL(z_zadd_args[0], key, key_len, 0); - call_user_function(&redis_ce->function_table, &z_to, &z_fun_zadd, &z_ret, 1 + 2 * count, z_zadd_args TSRMLS_CC); - - /* Expire if needed */ - ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC); - - /* cleanup */ - for(i = 0; i < 1 + 2 * count; ++i) { - efree(z_zadd_args[i]); - } - - 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 */ - MAKE_STD_ZVAL(z_args[0]); - ZVAL_STRINGL(&z_fun_get, "GET", 3, 0); - ZVAL_STRINGL(z_args[0], key, key_len, 0); - call_user_function(&redis_ce->function_table, &z_from, &z_fun_get, &z_ret, 1, z_args TSRMLS_CC); - - if(Z_TYPE(z_ret) != IS_STRING) { /* key not found or replaced */ - /* TODO: report? */ - efree(z_args[0]); - return 0; - } - - /* run SET on target */ - MAKE_STD_ZVAL(z_args[1]); - if (ttl > 0) { - MAKE_STD_ZVAL(z_args[2]); - ZVAL_STRINGL(&z_fun_set, "SETEX", 5, 0); - ZVAL_STRINGL(z_args[0], key, key_len, 0); - ZVAL_LONG(z_args[1], ttl); - ZVAL_STRINGL(z_args[2], Z_STRVAL(z_ret), Z_STRLEN(z_ret), 1); /* copy z_ret to arg 1 */ - call_user_function(&redis_ce->function_table, &z_to, &z_fun_set, &z_ret, 3, z_args TSRMLS_CC); - /* cleanup */ - efree(z_args[1]); - zval_dtor(z_args[2]); - efree(z_args[2]); - } - else { - ZVAL_STRINGL(&z_fun_set, "SET", 3, 0); - ZVAL_STRINGL(z_args[0], key, key_len, 0); - ZVAL_STRINGL(z_args[1], Z_STRVAL(z_ret), Z_STRLEN(z_ret), 1); /* copy z_ret to arg 1 */ - call_user_function(&redis_ce->function_table, &z_to, &z_fun_set, &z_ret, 2, z_args TSRMLS_CC); - /* cleanup */ - zval_dtor(z_args[1]); - efree(z_args[1]); - } - - /* cleanup */ - efree(z_args[0]); - - 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, *z_args[2]; - - /* run HGETALL on source */ - MAKE_STD_ZVAL(z_args[0]); - ZVAL_STRINGL(&z_fun_hgetall, "HGETALL", 7, 0); - ZVAL_STRINGL(z_args[0], key, key_len, 0); - call_user_function(&redis_ce->function_table, &z_from, &z_fun_hgetall, &z_ret, 1, z_args TSRMLS_CC); - - if(Z_TYPE(z_ret) != IS_ARRAY) { /* key not found or replaced */ - /* TODO: report? */ - efree(z_args[0]); - return 0; - } - - /* run HMSET on target */ - ZVAL_STRINGL(&z_fun_hmset, "HMSET", 5, 0); - ZVAL_STRINGL(z_args[0], key, key_len, 0); - z_args[1] = &z_ret; /* copy z_ret to arg 1 */ - call_user_function(&redis_ce->function_table, &z_to, &z_fun_hmset, &z_ret, 2, z_args TSRMLS_CC); - - /* Expire if needed */ - ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC); - - /* cleanup */ - efree(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_pp; - int count, i; - HashTable *h_set_vals; - - /* run retrieval command on source */ - z_retrieve_args = emalloc((1+list_count) * sizeof(zval*)); - ZVAL_STRING(&z_fun_retrieve, cmd_list[0], 0); /* set the command */ - - /* set the key */ - MAKE_STD_ZVAL(z_retrieve_args[0]); - ZVAL_STRINGL(z_retrieve_args[0], key, key_len, 0); - - /* possibly add some other args if they were provided. */ - for(i = 1; i < list_count; ++i) { - MAKE_STD_ZVAL(z_retrieve_args[i]); - ZVAL_STRING(z_retrieve_args[i], cmd_list[i], 0); - } - - call_user_function(&redis_ce->function_table, &z_from, &z_fun_retrieve, &z_ret, list_count, z_retrieve_args TSRMLS_CC); - - /* cleanup */ - for(i = 0; i < list_count; ++i) { - efree(z_retrieve_args[i]); - } - efree(z_retrieve_args); - - if(Z_TYPE(z_ret) != IS_ARRAY) { /* key not found or replaced */ - /* TODO: report? */ - return 0; - } - - /* run SADD/RPUSH on target */ - h_set_vals = Z_ARRVAL(z_ret); - count = zend_hash_num_elements(h_set_vals); - z_sadd_args = emalloc((1 + count) * sizeof(zval*)); - ZVAL_STRING(&z_fun_sadd, cmd_add[0], 0); - MAKE_STD_ZVAL(z_sadd_args[0]); /* add key */ - ZVAL_STRINGL(z_sadd_args[0], key, key_len, 0); - - for(i = 0, zend_hash_internal_pointer_reset(h_set_vals); - zend_hash_has_more_elements(h_set_vals) == SUCCESS; - zend_hash_move_forward(h_set_vals), i++) { - - if(zend_hash_get_current_data(h_set_vals, (void**)&z_data_pp) == FAILURE) { - continue; - } - - /* add set elements */ - MAKE_STD_ZVAL(z_sadd_args[i+1]); - *(z_sadd_args[i+1]) = **z_data_pp; - zval_copy_ctor(z_sadd_args[i+1]); - } - call_user_function(&redis_ce->function_table, &z_to, &z_fun_sadd, &z_ret, count+1, z_sadd_args TSRMLS_CC); - - /* Expire if needed */ - ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC); - - /* cleanup */ - efree(z_sadd_args[0]); /* no dtor at [0] */ - - for(i = 0; i < count; ++i) { - zval_dtor(z_sadd_args[i + 1]); - efree(z_sadd_args[i + 1]); - } - efree(z_sadd_args); - - 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], 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 *z_ret = NULL, **z_args[2]; - zval *z_host, *z_count; - - z_cb->retval_ptr_ptr = &z_ret; - z_cb->params = (struct _zval_struct ***)&z_args; - z_cb->param_count = 2; - z_cb->no_separation = 0; - - /* run cb(hostname, count) */ - MAKE_STD_ZVAL(z_host); - ZVAL_STRING(z_host, hostname, 0); - z_args[0] = &z_host; - MAKE_STD_ZVAL(z_count); - ZVAL_LONG(z_count, count); - z_args[1] = &z_count; - - zend_call_function(z_cb, z_cb_cache TSRMLS_CC); - - /* cleanup */ - efree(z_host); - efree(z_count); - if(z_ret) - efree(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) { - - char **keys; - int *key_lens; - long count, i; - int target_pos; - zval *z_target; - - /* list all keys */ - if(b_index) { - count = ra_rehash_scan_index(z_redis, &keys, &key_lens TSRMLS_CC); - } else { - count = ra_rehash_scan_keys(z_redis, &keys, &key_lens TSRMLS_CC); - } - - /* callback */ - if(z_cb && z_cb_cache) { - zval_rehash_callback(z_cb, z_cb_cache, hostname, count TSRMLS_CC); - } - - /* for each key, redistribute */ - for(i = 0; i < count; ++i) { - - /* check that we're not moving to the same node. */ - z_target = ra_find_node(ra, keys[i], key_lens[i], &target_pos TSRMLS_CC); - - if(strcmp(hostname, ra->hosts[target_pos])) { /* different host */ - /* php_printf("move [%s] from [%s] to [%s]\n", keys[i], hostname, ra->hosts[target_pos]); */ - ra_move_key(keys[i], key_lens[i], z_redis, z_target TSRMLS_CC); - } - } - - /* cleanup */ - for(i = 0; i < count; ++i) { - efree(keys[i]); - } - efree(keys); - efree(key_lens); -} - -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); - } -} - diff -Nru php-redis-2.2.4/redis-2.2.4/redis_array_impl.h php-redis-3.0.0/redis-2.2.4/redis_array_impl.h --- php-redis-2.2.4/redis-2.2.4/redis_array_impl.h 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/redis_array_impl.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,29 +0,0 @@ -#ifndef REDIS_ARRAY_IMPL_H -#define REDIS_ARRAY_IMPL_H - -#include -#include "common.h" -#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 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); -char * ra_find_key(RedisArray *ra, zval *z_args, const char *cmd, int *key_len); -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 diff -Nru php-redis-2.2.4/redis-2.2.4/redis.c php-redis-3.0.0/redis-2.2.4/redis.c --- php-redis-2.2.4/redis-2.2.4/redis.c 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/redis.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,6626 +0,0 @@ -/* -*- 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_ini.h" -#include "php_redis.h" -#include "redis_array.h" -#include - -#ifdef PHP_SESSION -#include "ext/session/php_session.h" -#endif - -#include -#include -#include - -#include "library.h" - -#define R_SUB_CALLBACK_CLASS_TYPE 1 -#define R_SUB_CALLBACK_FT_TYPE 2 -#define R_SUB_CLOSURE_TYPE 3 - -int le_redis_sock; -extern int le_redis_array; - -#ifdef PHP_SESSION -extern ps_module ps_mod_redis; -#endif - -extern zend_class_entry *redis_array_ce; -zend_class_entry *redis_ce; -zend_class_entry *redis_exception_ce; -zend_class_entry *spl_ce_RuntimeException = NULL; - -extern zend_function_entry redis_array_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) -PHP_INI_END() - -ZEND_DECLARE_MODULE_GLOBALS(redis) - -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, 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, resetStat, 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) - - /* 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, zReverseRange, NULL, ZEND_ACC_PUBLIC) - PHP_ME(Redis, zRangeByScore, NULL, ZEND_ACC_PUBLIC) - PHP_ME(Redis, zRevRangeByScore, 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, 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, eval, NULL, ZEND_ACC_PUBLIC) - PHP_ME(Redis, evalsha, NULL, ZEND_ACC_PUBLIC) - PHP_ME(Redis, script, 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, _unserialize, NULL, ZEND_ACC_PUBLIC) - - PHP_ME(Redis, client, 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) - - /* 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) - - /* 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, zrevrange, zReverseRange, 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) - {NULL, NULL, NULL} -}; - -zend_module_entry redis_module_entry = { -#if ZEND_MODULE_API_NO >= 20010901 - STANDARD_MODULE_HEADER, -#endif - "redis", - NULL, - PHP_MINIT(redis), - PHP_MSHUTDOWN(redis), - PHP_RINIT(redis), - PHP_RSHUTDOWN(redis), - 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 - -PHPAPI zend_class_entry *redis_get_exception_base(int root TSRMLS_DC) -{ -#if HAVE_SPL - if (!root) { - if (!spl_ce_RuntimeException) { - zend_class_entry **pce; - - if (zend_hash_find(CG(class_table), "runtimeexception", - sizeof("RuntimeException"), (void **) &pce) == SUCCESS) { - spl_ce_RuntimeException = *pce; - return *pce; - } - } else { - return spl_ce_RuntimeException; - } - } -#endif -#if (PHP_MAJOR_VERSION == 5) && (PHP_MINOR_VERSION < 2) - return zend_exception_get_default(); -#else - return zend_exception_get_default(TSRMLS_C); -#endif -} - -/** - * Send a static DISCARD in case we're in MULTI mode. - */ -static int send_discard_static(RedisSock *redis_sock TSRMLS_DC) { - - int result = FAILURE; - char *cmd, *response; - int response_len, cmd_len; - - /* format our discard command */ - cmd_len = redis_cmd_format_static(&cmd, "DISCARD", ""); - - /* send our DISCARD command */ - if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) >= 0 && - (response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) != NULL) { - - /* success if we get OK */ - result = (response_len == 3 && strncmp(response,"+OK", 3) == 0) ? SUCCESS : FAILURE; - - /* free our response */ - efree(response); - } - - /* free our command */ - efree(cmd); - - /* return success/failure */ - return result; -} - -/** - * redis_destructor_redis_sock - */ -static void redis_destructor_redis_sock(zend_rsrc_list_entry * rsrc TSRMLS_DC) -{ - RedisSock *redis_sock = (RedisSock *) rsrc->ptr; - redis_sock_disconnect(redis_sock TSRMLS_CC); - redis_free_socket(redis_sock); -} -/** - * redis_sock_get - */ -PHPAPI int redis_sock_get(zval *id, RedisSock **redis_sock TSRMLS_DC, int no_throw) -{ - - zval **socket; - int resource_type; - - if (Z_TYPE_P(id) != IS_OBJECT || zend_hash_find(Z_OBJPROP_P(id), "socket", - sizeof("socket"), (void **) &socket) == FAILURE) { - // 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 -1; - } - - *redis_sock = (RedisSock *) zend_list_find(Z_LVAL_PP(socket), &resource_type); - - if (!*redis_sock || resource_type != le_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 -1; - } - if ((*redis_sock)->lazy_connect) - { - (*redis_sock)->lazy_connect = 0; - if (redis_sock_server_open(*redis_sock, 1 TSRMLS_CC) < 0) { - return -1; - } - } - - return Z_LVAL_PP(socket); -} - -/** - * redis_sock_get_direct - * Returns our attached RedisSock pointer if we're connected - */ -PHPAPI 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_get(object, &redis_sock TSRMLS_CC, 1) < 0) || redis_sock->status != REDIS_SOCK_STATUS_CONNECTED) - { - return NULL; - } - - // Return our socket - return redis_sock; -} - - -/** - * PHP_MINIT_FUNCTION - */ -PHP_MINIT_FUNCTION(redis) -{ - zend_class_entry redis_class_entry; - zend_class_entry redis_array_class_entry; - zend_class_entry redis_exception_class_entry; - - 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); - - /* 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); - - le_redis_array = zend_register_list_destructors_ex( - redis_destructor_redis_array, - NULL, - "Redis Array", module_number - ); - - /* RedisException class */ - INIT_CLASS_ENTRY(redis_exception_class_entry, "RedisException", NULL); - redis_exception_ce = zend_register_internal_class_ex( - &redis_exception_class_entry, - redis_get_exception_base(0 TSRMLS_CC), - NULL TSRMLS_CC - ); - - le_redis_sock = zend_register_list_destructors_ex( - redis_destructor_redis_sock, - NULL, - redis_sock_name, module_number - ); - - add_constant_long(redis_ce, "REDIS_NOT_FOUND", REDIS_NOT_FOUND); - add_constant_long(redis_ce, "REDIS_STRING", REDIS_STRING); - add_constant_long(redis_ce, "REDIS_SET", REDIS_SET); - add_constant_long(redis_ce, "REDIS_LIST", REDIS_LIST); - add_constant_long(redis_ce, "REDIS_ZSET", REDIS_ZSET); - add_constant_long(redis_ce, "REDIS_HASH", REDIS_HASH); - - add_constant_long(redis_ce, "ATOMIC", ATOMIC); - add_constant_long(redis_ce, "MULTI", MULTI); - add_constant_long(redis_ce, "PIPELINE", PIPELINE); - - /* options */ - add_constant_long(redis_ce, "OPT_SERIALIZER", REDIS_OPT_SERIALIZER); - add_constant_long(redis_ce, "OPT_PREFIX", REDIS_OPT_PREFIX); - add_constant_long(redis_ce, "OPT_READ_TIMEOUT", REDIS_OPT_READ_TIMEOUT); - - /* serializer */ - add_constant_long(redis_ce, "SERIALIZER_NONE", REDIS_SERIALIZER_NONE); - add_constant_long(redis_ce, "SERIALIZER_PHP", REDIS_SERIALIZER_PHP); -#ifdef HAVE_REDIS_IGBINARY - add_constant_long(redis_ce, "SERIALIZER_IGBINARY", REDIS_SERIALIZER_IGBINARY); -#endif - - zend_declare_class_constant_stringl(redis_ce, "AFTER", 5, "after", 5 TSRMLS_CC); - zend_declare_class_constant_stringl(redis_ce, "BEFORE", 6, "before", 6 TSRMLS_CC); - -#ifdef PHP_SESSION - /* declare session handler */ - php_session_register_module(&ps_mod_redis); -#endif - - return SUCCESS; -} - -/** - * PHP_MSHUTDOWN_FUNCTION - */ -PHP_MSHUTDOWN_FUNCTION(redis) -{ - return SUCCESS; -} - -/** - * PHP_RINIT_FUNCTION - */ -PHP_RINIT_FUNCTION(redis) -{ - return SUCCESS; -} - -/** - * PHP_RSHUTDOWN_FUNCTION - */ -PHP_RSHUTDOWN_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); - 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_get(getThis(), &redis_sock TSRMLS_CC, 1) < 0) { - RETURN_FALSE; - } - - // If we think we're in MULTI mode, send a discard - if(redis_sock->mode == MULTI) { - // Discard any multi commands, and free any callbacks that have been queued - send_discard_static(redis_sock TSRMLS_CC); - free_reply_callbacks(getThis(), 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 { - /* reset multi/exec state if there is one. */ - RedisSock *redis_sock; - if (redis_sock_get(getThis(), &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - RETURN_TRUE; - } -} -/* }}} */ - -PHPAPI int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) { - zval *object; - zval **socket; - int host_len, id; - char *host = NULL; - long port = -1; - long retry_interval = 0; - - char *persistent_id = NULL; - int persistent_id_len = -1; - - double timeout = 0.0; - RedisSock *redis_sock = NULL; - -#ifdef ZTS - /* not sure how in threaded mode this works so disabled persistents at first */ - persistent = 0; -#endif - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|ldsl", - &object, redis_ce, &host, &host_len, &port, - &timeout, &persistent_id, &persistent_id_len, - &retry_interval) == FAILURE) { - return FAILURE; - } - - if (timeout < 0L || timeout > INT_MAX) { - zend_throw_exception(redis_exception_ce, "Invalid 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(port == -1 && host_len && host[0] != '/') { /* not unix socket, set to default value */ - port = 6379; - } - - /* if there is a redis sock already we have to remove it from the list */ - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 1) > 0) { - if (zend_hash_find(Z_OBJPROP_P(object), "socket", - sizeof("socket"), (void **) &socket) == FAILURE) - { - /* maybe there is a socket but the id isn't known.. what to do? */ - } else { - zend_list_delete(Z_LVAL_PP(socket)); /* the refcount should be decreased and the detructor called */ - } - } - - redis_sock = redis_sock_create(host, host_len, port, timeout, persistent, persistent_id, retry_interval, 0); - - if (redis_sock_server_open(redis_sock, 1 TSRMLS_CC) < 0) { - redis_free_socket(redis_sock); - return FAILURE; - } - -#if PHP_VERSION_ID >= 50400 - id = zend_list_insert(redis_sock, le_redis_sock TSRMLS_CC); -#else - id = zend_list_insert(redis_sock, le_redis_sock); -#endif - add_property_resource(object, "socket", id); - - return SUCCESS; -} - -/* {{{ proto boolean Redis::bitop(string op, string key, ...) - */ -PHP_METHOD(Redis, bitop) -{ - char *cmd; - int cmd_len; - - zval **z_args; - char **keys; - int *keys_len; - int argc = ZEND_NUM_ARGS(), i; - RedisSock *redis_sock = NULL; - smart_str buf = {0}; - int key_free = 0; - - /* get redis socket */ - if (redis_sock_get(getThis(), &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - /* fetch args */ - z_args = emalloc(argc * sizeof(zval*)); - if(zend_get_parameters_array(ht, argc, z_args) == FAILURE - || argc < 3 /* 3 args min. */ - || Z_TYPE_P(z_args[0]) != IS_STRING /* operation must be a string. */ - ) { - efree(z_args); - RETURN_FALSE; - } - - - keys = emalloc(argc * sizeof(char*)); - keys_len = emalloc(argc * sizeof(int)); - - /* prefix keys */ - for(i = 0; i < argc; ++i) { - convert_to_string(z_args[i]); - - keys[i] = Z_STRVAL_P(z_args[i]); - keys_len[i] = Z_STRLEN_P(z_args[i]); - if(i != 0) - key_free = redis_key_prefix(redis_sock, &keys[i], &keys_len[i] TSRMLS_CC); - } - - /* start building the command */ - smart_str_appendc(&buf, '*'); - smart_str_append_long(&buf, argc + 1); /* +1 for BITOP command */ - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - - /* add command name */ - smart_str_appendc(&buf, '$'); - smart_str_append_long(&buf, 5); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - smart_str_appendl(&buf, "BITOP", 5); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - - /* add keys */ - for(i = 0; i < argc; ++i) { - smart_str_appendc(&buf, '$'); - smart_str_append_long(&buf, keys_len[i]); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - smart_str_appendl(&buf, keys[i], keys_len[i]); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - } - /* end string */ - smart_str_0(&buf); - cmd = buf.c; - cmd_len = buf.len; - - /* cleanup */ - if(key_free) - for(i = 1; i < argc; ++i) { - efree(keys[i]); - } - efree(keys); - efree(keys_len); - efree(z_args); - - /* send */ - 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); -} -/* }}} */ - -/* {{{ proto boolean Redis::bitcount(string key, [int start], [int end]) - */ -PHP_METHOD(Redis, bitcount) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - long start = 0, end = -1; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|ll", - &object, redis_ce, - &key, &key_len, &start, &end) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - /* BITCOUNT key start end */ - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "BITCOUNT", "sdd", key, key_len, (int)start, (int)end); - if(key_free) efree(key); - - 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); - -} -/* }}} */ - -/* {{{ proto boolean Redis::close() - */ -PHP_METHOD(Redis, close) -{ - zval *object; - RedisSock *redis_sock = NULL; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", - &object, redis_ce) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - if (redis_sock_disconnect(redis_sock TSRMLS_CC)) { - RETURN_TRUE; - } - - RETURN_FALSE; -} -/* }}} */ - -/* {{{ proto boolean Redis::set(string key, mixed value, long timeout | array options) */ -PHP_METHOD(Redis, set) { - zval *object; - RedisSock *redis_sock; - char *key = NULL, *val = NULL, *cmd, *exp_type = NULL, *set_type = NULL; - int key_len, val_len, cmd_len; - long expire = -1; - int val_free = 0, key_free = 0; - zval *z_value, *z_opts = NULL; - - // Make sure the arguments are correct - if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz|z", - &object, redis_ce, &key, &key_len, &z_value, - &z_opts) == FAILURE) - { - RETURN_FALSE; - } - - // Ensure we can grab our redis socket - if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - /* Our optional argument can either be a long (to support legacy SETEX */ - /* redirection), or an array with Redis >= 2.6.12 set options */ - if(z_opts && Z_TYPE_P(z_opts) != IS_LONG && Z_TYPE_P(z_opts) != IS_ARRAY) { - RETURN_FALSE; - } - - /* Serialization, key prefixing */ - val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - - if(z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) { - HashTable *kt = Z_ARRVAL_P(z_opts); - int type; - unsigned int ht_key_len; - unsigned long idx; - char *k; - zval **v; - - /* Iterate our option array */ - for(zend_hash_internal_pointer_reset(kt); - zend_hash_has_more_elements(kt) == SUCCESS; - zend_hash_move_forward(kt)) - { - // Grab key and value - type = zend_hash_get_current_key_ex(kt, &k, &ht_key_len, &idx, 0, NULL); - zend_hash_get_current_data(kt, (void**)&v); - - if(type == HASH_KEY_IS_STRING && (Z_TYPE_PP(v) == IS_LONG) && - (Z_LVAL_PP(v) > 0) && IS_EX_PX_ARG(k)) - { - exp_type = k; - expire = Z_LVAL_PP(v); - } else if(Z_TYPE_PP(v) == IS_STRING && IS_NX_XX_ARG(Z_STRVAL_PP(v))) { - set_type = Z_STRVAL_PP(v); - } - } - } else if(z_opts && Z_TYPE_P(z_opts) == IS_LONG) { - expire = Z_LVAL_P(z_opts); - } - - /* Now let's construct the command we want */ - if(exp_type && set_type) { - /* SET NX|XX PX|EX */ - cmd_len = redis_cmd_format_static(&cmd, "SET", "ssssl", key, key_len, - val, val_len, set_type, 2, exp_type, - 2, expire); - } else if(exp_type) { - /* SET PX|EX */ - cmd_len = redis_cmd_format_static(&cmd, "SET", "sssl", key, key_len, - val, val_len, exp_type, 2, expire); - } else if(set_type) { - /* SET NX|XX */ - cmd_len = redis_cmd_format_static(&cmd, "SET", "sss", key, key_len, - val, val_len, set_type, 2); - } else if(expire > 0) { - /* Backward compatible SETEX redirection */ - cmd_len = redis_cmd_format_static(&cmd, "SETEX", "sds", key, key_len, - expire, val, val_len); - } else { - /* SET */ - cmd_len = redis_cmd_format_static(&cmd, "SET", "ss", key, key_len, - val, val_len); - } - - /* Free our key or value if we prefixed/serialized */ - if(key_free) efree(key); - if(val_free) efree(val); - - /* Kick off the command */ - 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); -} - -PHPAPI void redis_generic_setex(INTERNAL_FUNCTION_PARAMETERS, char *keyword) { - - zval *object; - RedisSock *redis_sock; - char *key = NULL, *val = NULL, *cmd; - int key_len, val_len, cmd_len; - long expire; - int val_free = 0, key_free = 0; - zval *z_value; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oslz", - &object, redis_ce, &key, &key_len, - &expire, &z_value) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, keyword, "sds", key, key_len, expire, val, val_len); - if(val_free) efree(val); - if(key_free) efree(key); - - 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 boolean Redis::setex(string key, long expire, string value) - */ -PHP_METHOD(Redis, setex) -{ - redis_generic_setex(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SETEX"); -} - -/* {{{ proto boolean Redis::psetex(string key, long expire, string value) - */ -PHP_METHOD(Redis, psetex) -{ - redis_generic_setex(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PSETEX"); -} - -/* {{{ proto boolean Redis::setnx(string key, string value) - */ -PHP_METHOD(Redis, setnx) -{ - - zval *object; - RedisSock *redis_sock; - char *key = NULL, *val = NULL, *cmd; - int key_len, val_len, cmd_len; - int val_free = 0, key_free = 0; - zval *z_value; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz", - &object, redis_ce, &key, &key_len, - &z_value) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "SETNX", "ss", key, key_len, val, val_len); - if(val_free) efree(val); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - - IF_ATOMIC() { - redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - - REDIS_PROCESS_RESPONSE(redis_1_response); - -} -/* }}} */ -/* {{{ proto string Redis::getSet(string key, string value) - */ -PHP_METHOD(Redis, getSet) -{ - - zval *object; - RedisSock *redis_sock; - char *key = NULL, *val = NULL, *cmd; - int key_len, val_len, cmd_len; - int val_free = 0, key_free = 0; - zval *z_value; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz", - &object, redis_ce, &key, &key_len, - &z_value) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "GETSET", "ss", key, key_len, val, val_len); - if(val_free) efree(val); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_string_response); - -} -/* }}} */ - -/* {{{ proto string Redis::randomKey() - */ -PHP_METHOD(Redis, randomKey) -{ - - zval *object; - RedisSock *redis_sock; - char *cmd; - int cmd_len; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", - &object, redis_ce) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - cmd_len = redis_cmd_format(&cmd, "*1" _NL "$9" _NL "RANDOMKEY" _NL); - /* TODO: remove prefix from key */ - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_ping_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_ping_response); -} -/* }}} */ - -/* {{{ proto string Redis::echo(string key) - */ -PHP_METHOD(Redis, echo) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len; - int key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_ce, - &key, &key_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "ECHO", "s", key, key_len); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_string_response); - -} -/* }}} */ - -/* {{{ proto string Redis::renameKey(string key_src, string key_dst) - */ -PHP_METHOD(Redis, renameKey) -{ - - zval *object; - RedisSock *redis_sock; - char *cmd, *src, *dst; - int cmd_len, src_len, dst_len; - int src_free, dst_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", - &object, redis_ce, - &src, &src_len, - &dst, &dst_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - src_free = redis_key_prefix(redis_sock, &src, &src_len TSRMLS_CC); - dst_free = redis_key_prefix(redis_sock, &dst, &dst_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "RENAME", "ss", src, src_len, dst, dst_len); - if(src_free) efree(src); - if(dst_free) efree(dst); - - 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::renameNx(string key_src, string key_dst) - */ -PHP_METHOD(Redis, renameNx) -{ - - zval *object; - RedisSock *redis_sock; - char *cmd, *src, *dst; - int cmd_len, src_len, dst_len; - int src_free, dst_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", - &object, redis_ce, - &src, &src_len, - &dst, &dst_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - src_free = redis_key_prefix(redis_sock, &src, &src_len TSRMLS_CC); - dst_free = redis_key_prefix(redis_sock, &dst, &dst_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "RENAMENX", "ss", src, src_len, dst, dst_len); - if(src_free) efree(src); - if(dst_free) efree(dst); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_1_response); - -} -/* }}} */ - -/* {{{ proto string Redis::get(string key) - */ -PHP_METHOD(Redis, get) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len; - int key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_ce, - &key, &key_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "GET", "s", key, key_len); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_string_response); - -} -/* }}} */ - - -/* {{{ proto string Redis::ping() - */ -PHP_METHOD(Redis, ping) -{ - zval *object; - RedisSock *redis_sock; - char *cmd; - int cmd_len; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", - &object, redis_ce) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - cmd_len = redis_cmd_format_static(&cmd, "PING", ""); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_ping_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_ping_response); -} -/* }}} */ - -PHPAPI void redis_atomic_increment(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int count) { - - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len; - long val = 1; - int key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|l", - &object, redis_ce, - &key, &key_len, &val) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - if (val == 1) { - cmd_len = redis_cmd_format_static(&cmd, keyword, "s", key, key_len); - } else { - cmd_len = redis_cmd_format_static(&cmd, keyword, "sl", key, key_len, val); - } - if(key_free) efree(key); - - 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); -} - -/* {{{ proto boolean Redis::incr(string key [,int value]) - */ -PHP_METHOD(Redis, incr){ - - zval *object; - char *key = NULL; - int key_len; - long val = 1; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|l", - &object, redis_ce, - &key, &key_len, &val) == FAILURE) { - RETURN_FALSE; - } - - if(val == 1) { - redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, "INCR", 1); - } else { - redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, "INCRBY", val); - } -} -/* }}} */ - -/* {{{ proto boolean Redis::incrBy(string key ,int value) - */ -PHP_METHOD(Redis, incrBy){ - - zval *object; - char *key = NULL; - int key_len; - long val = 1; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osl", - &object, redis_ce, - &key, &key_len, &val) == FAILURE) { - RETURN_FALSE; - } - - if(val == 1) { - redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, "INCR", 1); - } else { - redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, "INCRBY", val); - } -} -/* }}} */ - -/* {{{ proto float Redis::incrByFloat(string key, float value) - */ -PHP_METHOD(Redis, incrByFloat) { - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - double val; - - if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osd", - &object, redis_ce, &key, &key_len, &val) == FAILURE) { - RETURN_FALSE; - } - - if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - // Prefix our key, free it if we have - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - if(key_free) efree(key); - - // Format our INCRBYFLOAT command - cmd_len = redis_cmd_format_static(&cmd, "INCRBYFLOAT", "sf", key, key_len, val); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_bulk_double_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_bulk_double_response); -} - -/* {{{ proto boolean Redis::decr(string key [,int value]) - */ -PHP_METHOD(Redis, decr) -{ - zval *object; - char *key = NULL; - int key_len; - long val = 1; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|l", - &object, redis_ce, - &key, &key_len, &val) == FAILURE) { - RETURN_FALSE; - } - - if(val == 1) { - redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DECR", 1); - } else { - redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DECRBY", val); - } -} -/* }}} */ - -/* {{{ proto boolean Redis::decrBy(string key ,int value) - */ -PHP_METHOD(Redis, decrBy){ - - zval *object; - char *key = NULL; - int key_len; - long val = 1; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osl", - &object, redis_ce, - &key, &key_len, &val) == FAILURE) { - RETURN_FALSE; - } - - if(val == 1) { - redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DECR", 1); - } else { - redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DECRBY", val); - } -} -/* }}} */ - -/* {{{ proto array Redis::getMultiple(array keys) - */ -PHP_METHOD(Redis, getMultiple) -{ - zval *object, *z_args, **z_ele; - HashTable *hash; - HashPosition ptr; - RedisSock *redis_sock; - smart_str 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - 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 - for(zend_hash_internal_pointer_reset_ex(hash, &ptr); - zend_hash_get_current_data_ex(hash, (void**)&z_ele, &ptr) == SUCCESS; - zend_hash_move_forward_ex(hash, &ptr)) - { - char *key; - int key_len, key_free; - zval *z_tmp = NULL; - - // If the key isn't a string, turn it into one - if(Z_TYPE_PP(z_ele) == IS_STRING) { - key = Z_STRVAL_PP(z_ele); - key_len = Z_STRLEN_PP(z_ele); - } else { - MAKE_STD_ZVAL(z_tmp); - *z_tmp = **z_ele; - zval_copy_ctor(z_tmp); - convert_to_string(z_tmp); - - key = Z_STRVAL_P(z_tmp); - key_len = Z_STRLEN_P(z_tmp); - } - - // Apply key prefix if necissary - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - - // Append this key to our command - redis_cmd_append_sstr(&cmd, key, key_len); - - // Free our key if it was prefixed - if(key_free) efree(key); - - // Free oour temporary ZVAL if we converted from a non-string - if(z_tmp) { - zval_dtor(z_tmp); - efree(z_tmp); - z_tmp = NULL; - } - } - - // 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) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len; - int key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_ce, - &key, &key_len) == FAILURE) { - RETURN_FALSE; - } - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "EXISTS", "s", key, key_len); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_1_response); - -} -/* }}} */ - -/* {{{ proto boolean Redis::delete(string key) - */ -PHP_METHOD(Redis, delete) -{ - RedisSock *redis_sock; - - if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, - "DEL", sizeof("DEL") - 1, - 1, &redis_sock, 0, 1, 1)) - return; - - IF_ATOMIC() { - redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_long_response); - -} -/* }}} */ - -PHPAPI void redis_set_watch(RedisSock *redis_sock) -{ - redis_sock->watching = 1; -} - -PHPAPI 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) -{ - RedisSock *redis_sock; - - generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, - "WATCH", sizeof("WATCH") - 1, - 1, &redis_sock, 0, 1, 1); - redis_sock->watching = 1; - IF_ATOMIC() { - redis_watch_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_watch_response); - -} -/* }}} */ - -PHPAPI void redis_clear_watch(RedisSock *redis_sock) -{ - redis_sock->watching = 0; -} - -PHPAPI 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) -{ - char cmd[] = "*1" _NL "$7" _NL "UNWATCH" _NL; - generic_empty_cmd_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, estrdup(cmd), sizeof(cmd)-1, redis_unwatch_response); - -} -/* }}} */ - -/* {{{ proto array Redis::getKeys(string pattern) - */ -PHP_METHOD(Redis, getKeys) -{ - zval *object; - RedisSock *redis_sock; - char *pattern = NULL, *cmd; - int pattern_len, cmd_len, pattern_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_ce, - &pattern, &pattern_len) == FAILURE) { - RETURN_NULL(); - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - pattern_free = redis_key_prefix(redis_sock, &pattern, &pattern_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "KEYS", "s", pattern, pattern_len); - if(pattern_free) efree(pattern); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - if (redis_sock_read_multibulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock, NULL, NULL) < 0) { - RETURN_FALSE; - } - } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_raw); -} -/* }}} */ - -/* {{{ proto int Redis::type(string key) - */ -PHP_METHOD(Redis, type) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_ce, - &key, &key_len) == FAILURE) { - RETURN_NULL(); - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "TYPE", "s", key, key_len); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_type_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_type_response); -} -/* }}} */ - -PHP_METHOD(Redis, append) -{ - zval *object; - RedisSock *redis_sock; - char *cmd; - int cmd_len, key_len, val_len, key_free; - char *key, *val; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", - &object, redis_ce, - &key, &key_len, &val, &val_len) == FAILURE) { - RETURN_NULL(); - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "APPEND", "ss", key, key_len, val, val_len); - if(key_free) efree(key); - - 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); -} - -PHP_METHOD(Redis, getRange) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - long start, end; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osll", - &object, redis_ce, &key, &key_len, - &start, &end) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "GETRANGE", "sdd", key, key_len, (int)start, (int)end); - if(key_free) efree(key); - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_string_response); -} - -PHP_METHOD(Redis, setRange) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *val, *cmd; - int key_len, val_len, cmd_len, key_free; - long offset; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osls", - &object, redis_ce, &key, &key_len, - &offset, &val, &val_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "SETRANGE", "sds", key, key_len, (int)offset, val, val_len); - if(key_free) efree(key); - 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); -} - -PHP_METHOD(Redis, getBit) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - long offset; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osl", - &object, redis_ce, &key, &key_len, - &offset) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "GETBIT", "sd", key, key_len, (int)offset); - if(key_free) efree(key); - 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); -} - -PHP_METHOD(Redis, setBit) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - long offset; - zend_bool val; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oslb", - &object, redis_ce, &key, &key_len, - &offset, &val) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "SETBIT", "sdd", key, key_len, (int)offset, (int)val); - if(key_free) efree(key); - 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); -} - - -PHP_METHOD(Redis, strlen) -{ - zval *object; - RedisSock *redis_sock; - char *cmd; - int cmd_len, key_len, key_free; - char *key; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_ce, - &key, &key_len) == FAILURE) { - RETURN_NULL(); - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "STRLEN", "s", key, key_len); - if(key_free) efree(key); - - 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); -} - -PHPAPI void -generic_push_function(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len) { - zval *object; - RedisSock *redis_sock; - char *cmd, *key, *val; - int cmd_len, key_len, val_len; - zval *z_value; - int val_free, key_free = 0; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz", - &object, redis_ce, - &key, &key_len, &z_value) == FAILURE) { - RETURN_NULL(); - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, keyword, "ss", key, key_len, val, val_len); - if(val_free) efree(val); - if(key_free) efree(key); - - 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); -} - -/* {{{ proto boolean Redis::lPush(string key , string value) - */ -PHP_METHOD(Redis, lPush) -{ - RedisSock *redis_sock; - - if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, - "LPUSH", sizeof("LPUSH") - 1, - 2, &redis_sock, 0, 0, 1)) - return; - - IF_ATOMIC() { - redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_long_response); -} -/* }}} */ - -/* {{{ proto boolean Redis::rPush(string key , string value) - */ -PHP_METHOD(Redis, rPush) -{ - RedisSock *redis_sock; - - if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, - "RPUSH", sizeof("RPUSH") - 1, - 2, &redis_sock, 0, 0, 1)) - return; - - IF_ATOMIC() { - redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_long_response); -} -/* }}} */ - -PHP_METHOD(Redis, lInsert) -{ - - zval *object; - RedisSock *redis_sock; - char *pivot, *position, *key, *val, *cmd; - int pivot_len, position_len, key_len, val_len, cmd_len; - int val_free, pivot_free, key_free; - zval *z_value, *z_pivot; - - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osszz", - &object, redis_ce, - &key, &key_len, - &position, &position_len, - &z_pivot, - &z_value) == FAILURE) { - RETURN_NULL(); - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - if(strncasecmp(position, "after", 5) == 0 || strncasecmp(position, "before", 6) == 0) { - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); - pivot_free = redis_serialize(redis_sock, z_pivot, &pivot, &pivot_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "LINSERT", "ssss", key, key_len, position, position_len, pivot, pivot_len, val, val_len); - if(val_free) efree(val); - if(key_free) efree(key); - if(pivot_free) efree(pivot); - - 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); - } else { - php_error_docref(NULL TSRMLS_CC, E_ERROR, "Error on position"); - } - -} - -PHP_METHOD(Redis, lPushx) -{ - generic_push_function(INTERNAL_FUNCTION_PARAM_PASSTHRU, "LPUSHX", sizeof("LPUSHX")-1); -} - -PHP_METHOD(Redis, rPushx) -{ - generic_push_function(INTERNAL_FUNCTION_PARAM_PASSTHRU, "RPUSHX", sizeof("RPUSHX")-1); -} - -PHPAPI void -generic_pop_function(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len) { - - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_ce, - &key, &key_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, keyword, "s", key, key_len); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_string_response); -} - -/* {{{ proto string Redis::lPOP(string key) - */ -PHP_METHOD(Redis, lPop) -{ - generic_pop_function(INTERNAL_FUNCTION_PARAM_PASSTHRU, "LPOP", sizeof("LPOP")-1); -} -/* }}} */ - -/* {{{ proto string Redis::rPOP(string key) - */ -PHP_METHOD(Redis, rPop) -{ - generic_pop_function(INTERNAL_FUNCTION_PARAM_PASSTHRU, "RPOP", sizeof("RPOP")-1); -} -/* }}} */ - -/* {{{ proto string Redis::blPop(string key1, string key2, ..., int timeout) - */ -PHP_METHOD(Redis, blPop) -{ - - RedisSock *redis_sock; - - if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, - "BLPOP", sizeof("BLPOP") - 1, - 2, &redis_sock, 1, 1, 1)) - return; - - 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 string Redis::brPop(string key1, string key2, ..., int timeout) - */ -PHP_METHOD(Redis, brPop) -{ - RedisSock *redis_sock; - - if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, - "BRPOP", sizeof("BRPOP") - 1, - 2, &redis_sock, 1, 1, 1)) - return; - - 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 int Redis::lSize(string key) - */ -PHP_METHOD(Redis, lSize) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_ce, - &key, &key_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "LLEN", "s", key, key_len); - if(key_free) efree(key); - - 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); - -} -/* }}} */ - -/* {{{ proto boolean Redis::lRemove(string list, string value, int count = 0) - */ -PHP_METHOD(Redis, lRemove) -{ - zval *object; - RedisSock *redis_sock; - char *cmd; - int cmd_len, key_len, val_len; - char *key, *val; - long count = 0; - zval *z_value; - int val_free, key_free = 0; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz|l", - &object, redis_ce, - &key, &key_len, &z_value, &count) == FAILURE) { - RETURN_NULL(); - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - - /* LREM key count value */ - val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "LREM", "sds", key, key_len, count, val, val_len); - if(val_free) efree(val); - if(key_free) efree(key); - - 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); -} -/* }}} */ - -/* {{{ proto boolean Redis::listTrim(string key , int start , int end) - */ -PHP_METHOD(Redis, listTrim) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - long start, end; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osll", - &object, redis_ce, &key, &key_len, - &start, &end) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "LTRIM", "sdd", key, key_len, (int)start, (int)end); - if(key_free) efree(key); - - 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::lGet(string key , int index) - */ -PHP_METHOD(Redis, lGet) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - long index; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osl", - &object, redis_ce, - &key, &key_len, &index) == FAILURE) { - RETURN_NULL(); - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - /* LINDEX key pos */ - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "LINDEX", "sd", key, key_len, (int)index); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_string_response); -} -/* }}} */ - -/* {{{ proto array Redis::lGetRange(string key, int start , int end) - */ -PHP_METHOD(Redis, lGetRange) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - long start, end; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osll", - &object, redis_ce, - &key, &key_len, &start, &end) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - /* LRANGE key start end */ - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "LRANGE", "sdd", key, key_len, (int)start, (int)end); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); - -} -/* }}} */ - -/* {{{ proto boolean Redis::sAdd(string key , mixed value) - */ -PHP_METHOD(Redis, sAdd) -{ - RedisSock *redis_sock; - - if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, - "SADD", sizeof("SADD") - 1, - 2, &redis_sock, 0, 0, 1)) - return; - - IF_ATOMIC() { - redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_long_response); -} -/* }}} */ - -/* {{{ proto int Redis::sSize(string key) - */ -PHP_METHOD(Redis, sSize) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_ce, - &key, &key_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "SCARD", "s", key, key_len); - if(key_free) efree(key); - - 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); -} -/* }}} */ - -/* {{{ proto boolean Redis::sRemove(string set, string value) - */ -PHP_METHOD(Redis, sRemove) -{ - RedisSock *redis_sock; - - if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, - "SREM", sizeof("SREM") - 1, - 2, &redis_sock, 0, 0, 1)) - return; - - IF_ATOMIC() { - redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_long_response); -} -/* }}} */ -/* {{{ proto boolean Redis::sMove(string set_src, string set_dst, mixed value) - */ -PHP_METHOD(Redis, sMove) -{ - zval *object; - RedisSock *redis_sock; - char *src = NULL, *dst = NULL, *val = NULL, *cmd; - int src_len, dst_len, val_len, cmd_len; - int val_free, src_free, dst_free; - zval *z_value; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ossz", - &object, redis_ce, - &src, &src_len, - &dst, &dst_len, - &z_value) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); - src_free = redis_key_prefix(redis_sock, &src, &src_len TSRMLS_CC); - dst_free = redis_key_prefix(redis_sock, &dst, &dst_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "SMOVE", "sss", src, src_len, dst, dst_len, val, val_len); - if(val_free) efree(val); - if(src_free) efree(src); - if(dst_free) efree(dst); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_1_response); -} -/* }}} */ - -/* }}} */ -/* {{{ proto string Redis::sPop(string key) - */ -PHP_METHOD(Redis, sPop) -{ - generic_pop_function(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SPOP", 4); -} -/* }}} */ - -/* }}} */ -/* {{{ proto string Redis::sRandMember(string key [int count]) - */ -PHP_METHOD(Redis, sRandMember) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free = 0; - long count; - - // Parse our params - if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|l", - &object, redis_ce, &key, &key_len, &count) == FAILURE) { - RETURN_FALSE; - } - - // Get our redis socket - if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - // Prefix our key if necissary - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - - // If we have two arguments, we're running with an optional COUNT, which will return - // a multibulk reply. Without the argument we'll return a string response - if(ZEND_NUM_ARGS() == 2) { - // Construct our command with count - cmd_len = redis_cmd_format_static(&cmd, "SRANDMEMBER", "sl", key, key_len, count); - } else { - // Construct our command - cmd_len = redis_cmd_format_static(&cmd, "SRANDMEMBER", "s", key, key_len); - } - - // Free our key if we prefixed it - if(key_free) efree(key); - - // Process our command - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - - // Process our reply - IF_ATOMIC() { - // This will be bulk or multi-bulk depending if we passed the optional [COUNT] argument - if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL) < 0) { - RETURN_FALSE; - } - } - REDIS_PROCESS_RESPONSE(redis_read_variant_reply); -} -/* }}} */ - -/* {{{ proto boolean Redis::sContains(string set, string value) - */ -PHP_METHOD(Redis, sContains) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *val = NULL, *cmd; - int key_len, val_len, cmd_len; - int val_free, key_free = 0; - zval *z_value; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz", - &object, redis_ce, - &key, &key_len, &z_value) == FAILURE) { - return; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "SISMEMBER", "ss", key, key_len, val, val_len); - if(val_free) efree(val); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_1_response); -} -/* }}} */ - -/* {{{ proto array Redis::sMembers(string set) - */ -PHP_METHOD(Redis, sMembers) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_ce, - &key, &key_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "SMEMBERS", "s", key, key_len); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, 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); -} -/* }}} */ - -PHPAPI int generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len, - int min_argc, RedisSock **out_sock, int has_timeout, int all_keys, int can_serialize) -{ - zval **z_args, *z_array; - char **keys, *cmd; - int cmd_len, *keys_len, *keys_to_free; - int i, j, argc = ZEND_NUM_ARGS(), real_argc = 0; - int single_array = 0; - int timeout = 0; - int pos; - int array_size; - - RedisSock *redis_sock; - - if(argc < min_argc) { - zend_wrong_param_count(TSRMLS_C); - ZVAL_BOOL(return_value, 0); - return FAILURE; - } - - /* get redis socket */ - if (redis_sock_get(getThis(), out_sock TSRMLS_CC, 0) < 0) { - ZVAL_BOOL(return_value, 0); - return FAILURE; - } - redis_sock = *out_sock; - - z_args = emalloc(argc * sizeof(zval*)); - if(zend_get_parameters_array(ht, argc, z_args) == FAILURE) { - efree(z_args); - ZVAL_BOOL(return_value, 0); - return FAILURE; - } - - /* case of a single array */ - if(has_timeout == 0) { - if(argc == 1 && Z_TYPE_P(z_args[0]) == IS_ARRAY) { - single_array = 1; - z_array = z_args[0]; - efree(z_args); - z_args = NULL; - - /* new count */ - argc = zend_hash_num_elements(Z_ARRVAL_P(z_array)); - } - } else if(has_timeout == 1) { - if(argc == 2 && Z_TYPE_P(z_args[0]) == IS_ARRAY && Z_TYPE_P(z_args[1]) == IS_LONG) { - single_array = 1; - z_array = z_args[0]; - timeout = Z_LVAL_P(z_args[1]); - efree(z_args); - z_args = NULL; - /* new count */ - argc = zend_hash_num_elements(Z_ARRVAL_P(z_array)); - } - } - - /* prepare an array for the keys, one for their lengths, one to mark the keys to free. */ - array_size = argc; - if(has_timeout) - array_size++; - - keys = emalloc(array_size * sizeof(char*)); - keys_len = emalloc(array_size * sizeof(int)); - keys_to_free = emalloc(array_size * sizeof(int)); - memset(keys_to_free, 0, array_size * sizeof(int)); - - - cmd_len = 1 + integer_length(keyword_len) + 2 +keyword_len + 2; /* start computing the command length */ - - if(single_array) { /* loop over the array */ - HashTable *keytable = Z_ARRVAL_P(z_array); - - for(j = 0, zend_hash_internal_pointer_reset(keytable); - zend_hash_has_more_elements(keytable) == SUCCESS; - zend_hash_move_forward(keytable)) { - - char *key; - unsigned int key_len; - unsigned long idx; - zval **z_value_pp; - - zend_hash_get_current_key_ex(keytable, &key, &key_len, &idx, 0, NULL); - if(zend_hash_get_current_data(keytable, (void**)&z_value_pp) == FAILURE) { - continue; /* this should never happen, according to the PHP people. */ - } - - - if(!all_keys && j != 0) { /* not just operating on keys */ - - if(can_serialize) { - keys_to_free[j] = redis_serialize(redis_sock, *z_value_pp, &keys[j], &keys_len[j] TSRMLS_CC); - } else { - convert_to_string(*z_value_pp); - keys[j] = Z_STRVAL_PP(z_value_pp); - keys_len[j] = Z_STRLEN_PP(z_value_pp); - keys_to_free[j] = 0; - } - - } else { - - /* only accept strings */ - if(Z_TYPE_PP(z_value_pp) != IS_STRING) { - convert_to_string(*z_value_pp); - } - - /* get current value */ - keys[j] = Z_STRVAL_PP(z_value_pp); - keys_len[j] = Z_STRLEN_PP(z_value_pp); - - keys_to_free[j] = redis_key_prefix(redis_sock, &keys[j], &keys_len[j] TSRMLS_CC); /* add optional prefix */ - } - - cmd_len += 1 + integer_length(keys_len[j]) + 2 + keys_len[j] + 2; /* $ + size + NL + string + NL */ - j++; - real_argc++; - } - if(has_timeout) { - keys_len[j] = spprintf(&keys[j], 0, "%d", timeout); - cmd_len += 1 + integer_length(keys_len[j]) + 2 + keys_len[j] + 2; // $ + size + NL + string + NL - j++; - real_argc++; - } - } else { - if(has_timeout && Z_TYPE_P(z_args[argc - 1]) != IS_LONG) { - php_error_docref(NULL TSRMLS_CC, E_ERROR, "Syntax error on timeout"); - } - - for(i = 0, j = 0; i < argc; ++i) { /* store each key */ - if(!all_keys && j != 0) { /* not just operating on keys */ - - if(can_serialize) { - keys_to_free[j] = redis_serialize(redis_sock, z_args[i], &keys[j], &keys_len[j] TSRMLS_CC); - } else { - convert_to_string(z_args[i]); - keys[j] = Z_STRVAL_P(z_args[i]); - keys_len[j] = Z_STRLEN_P(z_args[i]); - keys_to_free[j] = 0; - } - - } else { - - if(Z_TYPE_P(z_args[i]) != IS_STRING) { - convert_to_string(z_args[i]); - } - - keys[j] = Z_STRVAL_P(z_args[i]); - keys_len[j] = Z_STRLEN_P(z_args[i]); - - // If we have a timeout it should be the last argument, which we do not want to prefix - if(!has_timeout || i < argc-1) { - keys_to_free[j] = redis_key_prefix(redis_sock, &keys[j], &keys_len[j] TSRMLS_CC); /* add optional prefix TSRMLS_CC*/ - } - } - - cmd_len += 1 + integer_length(keys_len[j]) + 2 + keys_len[j] + 2; /* $ + size + NL + string + NL */ - j++; - real_argc++; - } - } - - cmd_len += 1 + integer_length(real_argc+1) + 2; // *count NL - cmd = emalloc(cmd_len+1); - - sprintf(cmd, "*%d" _NL "$%d" _NL "%s" _NL, 1+real_argc, keyword_len, keyword); - - pos = 1 +integer_length(real_argc + 1) + 2 - + 1 + integer_length(keyword_len) + 2 - + keyword_len + 2; - - /* copy each key to its destination */ - for(i = 0; i < real_argc; ++i) { - sprintf(cmd + pos, "$%d" _NL, keys_len[i]); // size - pos += 1 + integer_length(keys_len[i]) + 2; - memcpy(cmd + pos, keys[i], keys_len[i]); - pos += keys_len[i]; - memcpy(cmd + pos, _NL, 2); - pos += 2; - } - - /* cleanup prefixed keys. */ - for(i = 0; i < real_argc + (has_timeout?-1:0); ++i) { - if(keys_to_free[i]) - efree(keys[i]); - } - if(single_array && has_timeout) { /* cleanup string created to contain timeout value */ - efree(keys[real_argc-1]); - } - - efree(keys); - efree(keys_len); - efree(keys_to_free); - - if(z_args) efree(z_args); - - /* call REDIS_PROCESS_REQUEST and skip void returns */ - IF_MULTI_OR_ATOMIC() { - if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { - efree(cmd); - return FAILURE; - } - efree(cmd); - } - IF_PIPELINE() { - PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len); - efree(cmd); - } - - return SUCCESS; -} - -/* {{{ proto array Redis::sInter(string key0, ... string keyN) - */ -PHP_METHOD(Redis, sInter) { - - RedisSock *redis_sock; - - if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, - "SINTER", sizeof("SINTER") - 1, - 0, &redis_sock, 0, 1, 1)) - return; - - 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::sInterStore(string destination, string key0, ... string keyN) - */ -PHP_METHOD(Redis, sInterStore) { - - RedisSock *redis_sock; - - if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, - "SINTERSTORE", sizeof("SINTERSTORE") - 1, - 1, &redis_sock, 0, 1, 1)) - return; - - IF_ATOMIC() { - redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_long_response); - - -} -/* }}} */ - -/* {{{ proto array Redis::sUnion(string key0, ... string keyN) - */ -PHP_METHOD(Redis, sUnion) { - - RedisSock *redis_sock; - - if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, - "SUNION", sizeof("SUNION") - 1, - 0, &redis_sock, 0, 1, 1)) - return; - - 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::sUnionStore(string destination, string key0, ... string keyN) - */ -PHP_METHOD(Redis, sUnionStore) { - - RedisSock *redis_sock; - - if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, - "SUNIONSTORE", sizeof("SUNIONSTORE") - 1, - 1, &redis_sock, 0, 1, 1)) - return; - - IF_ATOMIC() { - redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_long_response); -} - -/* }}} */ - -/* {{{ proto array Redis::sDiff(string key0, ... string keyN) - */ -PHP_METHOD(Redis, sDiff) { - - RedisSock *redis_sock; - - if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, - "SDIFF", sizeof("SDIFF") - 1, - 0, &redis_sock, 0, 1, 1)) - return; - - IF_ATOMIC() { - /* read multibulk reply */ - 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::sDiffStore(string destination, string key0, ... string keyN) - */ -PHP_METHOD(Redis, sDiffStore) { - - RedisSock *redis_sock; - - if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, - "SDIFFSTORE", sizeof("SDIFFSTORE") - 1, - 1, &redis_sock, 0, 1, 1)) - return; - - IF_ATOMIC() { - redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_long_response); -} -/* }}} */ - -PHP_METHOD(Redis, sort) { - - zval *object = getThis(), *z_array = NULL, **z_cur; - char *cmd, *old_cmd = NULL, *key; - int cmd_len, elements = 2, key_len, key_free; - int using_store = 0; - RedisSock *redis_sock; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|a", - &object, redis_ce, - &key, &key_len, &z_array) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format(&cmd, "$4" _NL "SORT" _NL "$%d" _NL "%s" _NL, key_len, key, key_len); - if(key_free) efree(key); - - if(z_array) { - if ((zend_hash_find(Z_ARRVAL_P(z_array), "by", sizeof("by"), (void **) &z_cur) == SUCCESS - || zend_hash_find(Z_ARRVAL_P(z_array), "BY", sizeof("BY"), (void **) &z_cur) == SUCCESS) - && Z_TYPE_PP(z_cur) == IS_STRING) { - - old_cmd = cmd; - cmd_len = redis_cmd_format(&cmd, "%s" - "$2" _NL - "BY" _NL - "$%d" _NL - "%s" _NL - , cmd, cmd_len - , Z_STRLEN_PP(z_cur), Z_STRVAL_PP(z_cur), Z_STRLEN_PP(z_cur)); - elements += 2; - efree(old_cmd); - - } - - if ((zend_hash_find(Z_ARRVAL_P(z_array), "sort", sizeof("sort"), (void **) &z_cur) == SUCCESS - || zend_hash_find(Z_ARRVAL_P(z_array), "SORT", sizeof("SORT"), (void **) &z_cur) == SUCCESS) - && Z_TYPE_PP(z_cur) == IS_STRING) { - - old_cmd = cmd; - cmd_len = redis_cmd_format(&cmd, "%s" - "$%d" _NL - "%s" _NL - , cmd, cmd_len - , Z_STRLEN_PP(z_cur), Z_STRVAL_PP(z_cur), Z_STRLEN_PP(z_cur)); - elements += 1; - efree(old_cmd); - } - - if ((zend_hash_find(Z_ARRVAL_P(z_array), "store", sizeof("store"), (void **) &z_cur) == SUCCESS - || zend_hash_find(Z_ARRVAL_P(z_array), "STORE", sizeof("STORE"), (void **) &z_cur) == SUCCESS) - && Z_TYPE_PP(z_cur) == IS_STRING) { - - using_store = 1; - old_cmd = cmd; - cmd_len = redis_cmd_format(&cmd, "%s" - "$5" _NL - "STORE" _NL - "$%d" _NL - "%s" _NL - , cmd, cmd_len - , Z_STRLEN_PP(z_cur), Z_STRVAL_PP(z_cur), Z_STRLEN_PP(z_cur)); - elements += 2; - efree(old_cmd); - } - - if ((zend_hash_find(Z_ARRVAL_P(z_array), "get", sizeof("get"), (void **) &z_cur) == SUCCESS - || zend_hash_find(Z_ARRVAL_P(z_array), "GET", sizeof("GET"), (void **) &z_cur) == SUCCESS) - && (Z_TYPE_PP(z_cur) == IS_STRING || Z_TYPE_PP(z_cur) == IS_ARRAY)) { - - if(Z_TYPE_PP(z_cur) == IS_STRING) { - old_cmd = cmd; - cmd_len = redis_cmd_format(&cmd, "%s" - "$3" _NL - "GET" _NL - "$%d" _NL - "%s" _NL - , cmd, cmd_len - , Z_STRLEN_PP(z_cur), Z_STRVAL_PP(z_cur), Z_STRLEN_PP(z_cur)); - elements += 2; - efree(old_cmd); - } else if(Z_TYPE_PP(z_cur) == IS_ARRAY) { // loop over the strings in that array and add them as patterns - - HashTable *keytable = Z_ARRVAL_PP(z_cur); - for(zend_hash_internal_pointer_reset(keytable); - zend_hash_has_more_elements(keytable) == SUCCESS; - zend_hash_move_forward(keytable)) { - - char *key; - unsigned int key_len; - unsigned long idx; - zval **z_value_pp; - - zend_hash_get_current_key_ex(keytable, &key, &key_len, &idx, 0, NULL); - if(zend_hash_get_current_data(keytable, (void**)&z_value_pp) == FAILURE) { - continue; /* this should never happen, according to the PHP people. */ - } - - if(Z_TYPE_PP(z_value_pp) == IS_STRING) { - old_cmd = cmd; - cmd_len = redis_cmd_format(&cmd, "%s" - "$3" _NL - "GET" _NL - "$%d" _NL - "%s" _NL - , cmd, cmd_len - , Z_STRLEN_PP(z_value_pp), Z_STRVAL_PP(z_value_pp), Z_STRLEN_PP(z_value_pp)); - elements += 2; - efree(old_cmd); - } - } - } - } - - if ((zend_hash_find(Z_ARRVAL_P(z_array), "alpha", sizeof("alpha"), (void **) &z_cur) == SUCCESS - || zend_hash_find(Z_ARRVAL_P(z_array), "ALPHA", sizeof("ALPHA"), (void **) &z_cur) == SUCCESS) - && Z_TYPE_PP(z_cur) == IS_BOOL && Z_BVAL_PP(z_cur) == 1) { - - old_cmd = cmd; - cmd_len = redis_cmd_format(&cmd, "%s" - "$5" _NL - "ALPHA" _NL - , cmd, cmd_len); - elements += 1; - efree(old_cmd); - } - - if ((zend_hash_find(Z_ARRVAL_P(z_array), "limit", sizeof("limit"), (void **) &z_cur) == SUCCESS - || zend_hash_find(Z_ARRVAL_P(z_array), "LIMIT", sizeof("LIMIT"), (void **) &z_cur) == SUCCESS) - && Z_TYPE_PP(z_cur) == IS_ARRAY) { - - if(zend_hash_num_elements(Z_ARRVAL_PP(z_cur)) == 2) { - zval **z_offset_pp, **z_count_pp; - // get the two values from the table, check that they are indeed of LONG type - if(SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(z_cur), 0, (void**)&z_offset_pp) && - SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(z_cur), 1, (void**)&z_count_pp)) { - - long limit_low, limit_high; - if((Z_TYPE_PP(z_offset_pp) == IS_LONG || Z_TYPE_PP(z_offset_pp) == IS_STRING) && - (Z_TYPE_PP(z_count_pp) == IS_LONG || Z_TYPE_PP(z_count_pp) == IS_STRING)) { - - - if(Z_TYPE_PP(z_offset_pp) == IS_LONG) { - limit_low = Z_LVAL_PP(z_offset_pp); - } else { - limit_low = atol(Z_STRVAL_PP(z_offset_pp)); - } - if(Z_TYPE_PP(z_count_pp) == IS_LONG) { - limit_high = Z_LVAL_PP(z_count_pp); - } else { - limit_high = atol(Z_STRVAL_PP(z_count_pp)); - } - - old_cmd = cmd; - cmd_len = redis_cmd_format(&cmd, "%s" - "$5" _NL - "LIMIT" _NL - "$%d" _NL - "%d" _NL - "$%d" _NL - "%d" _NL - , cmd, cmd_len - , integer_length(limit_low), limit_low - , integer_length(limit_high), limit_high); - elements += 3; - efree(old_cmd); - } - } - } - } - - } - - /* complete with prefix */ - - old_cmd = cmd; - cmd_len = redis_cmd_format(&cmd, "*%d" _NL "%s", elements, cmd, cmd_len); - efree(old_cmd); - - /* run command */ - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - if(using_store) { - IF_ATOMIC() { - redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_long_response); - } 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); - } -} - -PHPAPI void generic_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sort, int use_alpha) { - - zval *object; - RedisSock *redis_sock; - char *key = NULL, *pattern = NULL, *get = NULL, *store = NULL, *cmd; - int key_len, pattern_len = -1, get_len = -1, store_len = -1, cmd_len, key_free; - long sort_start = -1, sort_count = -1; - - int cmd_elements; - - char *cmd_lines[30]; - int cmd_sizes[30]; - - int sort_len; - int i, pos; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|sslls", - &object, redis_ce, - &key, &key_len, &pattern, &pattern_len, - &get, &get_len, &sort_start, &sort_count, &store, &store_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - if(key_len == 0) { - RETURN_FALSE; - } - - /* first line, sort. */ - cmd_lines[1] = estrdup("$4"); - cmd_sizes[1] = 2; - cmd_lines[2] = estrdup("SORT"); - cmd_sizes[2] = 4; - - // Prefix our key if we need to - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - - /* second line, key */ - cmd_sizes[3] = redis_cmd_format(&cmd_lines[3], "$%d", key_len); - cmd_lines[4] = emalloc(key_len + 1); - memcpy(cmd_lines[4], key, key_len); - cmd_lines[4][key_len] = 0; - cmd_sizes[4] = key_len; - - // If we prefixed our key, free it - if(key_free) efree(key); - - cmd_elements = 5; - if(pattern && pattern_len) { - /* BY */ - cmd_lines[cmd_elements] = estrdup("$2"); - cmd_sizes[cmd_elements] = 2; - cmd_elements++; - cmd_lines[cmd_elements] = estrdup("BY"); - cmd_sizes[cmd_elements] = 2; - cmd_elements++; - - /* pattern */ - cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], "$%d", pattern_len); - cmd_elements++; - cmd_lines[cmd_elements] = emalloc(pattern_len + 1); - memcpy(cmd_lines[cmd_elements], pattern, pattern_len); - cmd_lines[cmd_elements][pattern_len] = 0; - cmd_sizes[cmd_elements] = pattern_len; - cmd_elements++; - } - if(sort_start >= 0 && sort_count >= 0) { - /* LIMIT */ - cmd_lines[cmd_elements] = estrdup("$5"); - cmd_sizes[cmd_elements] = 2; - cmd_elements++; - cmd_lines[cmd_elements] = estrdup("LIMIT"); - cmd_sizes[cmd_elements] = 5; - cmd_elements++; - - /* start */ - cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], "$%d", integer_length(sort_start)); - cmd_elements++; - cmd_sizes[cmd_elements] = spprintf(&cmd_lines[cmd_elements], 0, "%d", (int)sort_start); - cmd_elements++; - - /* count */ - cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], "$%d", integer_length(sort_count)); - cmd_elements++; - cmd_sizes[cmd_elements] = spprintf(&cmd_lines[cmd_elements], 0, "%d", (int)sort_count); - cmd_elements++; - } - if(get && get_len) { - /* GET */ - cmd_lines[cmd_elements] = estrdup("$3"); - cmd_sizes[cmd_elements] = 2; - cmd_elements++; - cmd_lines[cmd_elements] = estrdup("GET"); - cmd_sizes[cmd_elements] = 3; - cmd_elements++; - - /* pattern */ - cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], "$%d", get_len); - cmd_elements++; - cmd_lines[cmd_elements] = emalloc(get_len + 1); - memcpy(cmd_lines[cmd_elements], get, get_len); - cmd_lines[cmd_elements][get_len] = 0; - cmd_sizes[cmd_elements] = get_len; - cmd_elements++; - } - - /* add ASC or DESC */ - sort_len = strlen(sort); - cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], "$%d", sort_len); - cmd_elements++; - cmd_lines[cmd_elements] = emalloc(sort_len + 1); - memcpy(cmd_lines[cmd_elements], sort, sort_len); - cmd_lines[cmd_elements][sort_len] = 0; - cmd_sizes[cmd_elements] = sort_len; - cmd_elements++; - - if(use_alpha) { - /* ALPHA */ - cmd_lines[cmd_elements] = estrdup("$5"); - cmd_sizes[cmd_elements] = 2; - cmd_elements++; - cmd_lines[cmd_elements] = estrdup("ALPHA"); - cmd_sizes[cmd_elements] = 5; - cmd_elements++; - } - if(store && store_len) { - /* STORE */ - cmd_lines[cmd_elements] = estrdup("$5"); - cmd_sizes[cmd_elements] = 2; - cmd_elements++; - cmd_lines[cmd_elements] = estrdup("STORE"); - cmd_sizes[cmd_elements] = 5; - cmd_elements++; - - /* store key */ - cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], "$%d", store_len); - cmd_elements++; - cmd_lines[cmd_elements] = emalloc(store_len + 1); - memcpy(cmd_lines[cmd_elements], store, store_len); - cmd_lines[cmd_elements][store_len] = 0; - cmd_sizes[cmd_elements] = store_len; - cmd_elements++; - } - - /* first line has the star */ - cmd_sizes[0] = spprintf(&cmd_lines[0], 0, "*%d", (cmd_elements-1)/2); - - /* compute the command size */ - cmd_len = 0; - for(i = 0; i < cmd_elements; ++i) { - cmd_len += cmd_sizes[i] + sizeof(_NL) - 1; /* each line followeb by _NL */ - } - - /* copy all lines into the final command. */ - cmd = emalloc(1 + cmd_len); - pos = 0; - for(i = 0; i < cmd_elements; ++i) { - memcpy(cmd + pos, cmd_lines[i], cmd_sizes[i]); - pos += cmd_sizes[i]; - memcpy(cmd + pos, _NL, sizeof(_NL) - 1); - pos += sizeof(_NL) - 1; - - /* free every line */ - efree(cmd_lines[i]); - } - - REDIS_PROCESS_REQUEST(redis_sock, cmd, 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 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, "ASC", 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, "ASC", 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, "DESC", 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, "DESC", 1); -} -/* }}} */ - -PHPAPI void generic_expire_cmd(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len) { - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd, *t; - int key_len, cmd_len, key_free, t_len; - int i; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", - &object, redis_ce, &key, &key_len, - &t, &t_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - /* check that we have a number */ - for(i = 0; i < t_len; ++i) - if(t[i] < '0' || t[i] > '9') - RETURN_FALSE; - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, keyword, "ss", key, key_len, t, t_len); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_1_response); -} - -/* {{{ proto array Redis::setTimeout(string key, int timeout) - */ -PHP_METHOD(Redis, setTimeout) { - generic_expire_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "EXPIRE", sizeof("EXPIRE")-1); -} - -PHP_METHOD(Redis, pexpire) { - generic_expire_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PEXPIRE", sizeof("PEXPIRE")-1); -} -/* }}} */ - -/* {{{ proto array Redis::expireAt(string key, int timestamp) - */ -PHP_METHOD(Redis, expireAt) { - generic_expire_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "EXPIREAT", sizeof("EXPIREAT")-1); -} -/* }}} */ - -/* {{{ proto array Redis::pexpireAt(string key, int timestamp) - */ -PHP_METHOD(Redis, pexpireAt) { - generic_expire_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PEXPIREAT", sizeof("PEXPIREAT")-1); -} -/* }}} */ - - -/* {{{ proto array Redis::lSet(string key, int index, string value) - */ -PHP_METHOD(Redis, lSet) { - - zval *object; - RedisSock *redis_sock; - - char *cmd; - int cmd_len, key_len, val_len; - long index; - char *key, *val; - int val_free, key_free = 0; - zval *z_value; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oslz", - &object, redis_ce, &key, &key_len, &index, &z_value) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "LSET", "sds", key, key_len, index, val, val_len); - if(val_free) efree(val); - if(key_free) efree(key); - - 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); -} -/* }}} */ - -PHPAPI void generic_empty_cmd_impl(INTERNAL_FUNCTION_PARAMETERS, char *cmd, int cmd_len, ResultCallback result_callback) { - zval *object; - RedisSock *redis_sock; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", - &object, redis_ce) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - result_callback(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(result_callback); -} - -PHPAPI void generic_empty_cmd(INTERNAL_FUNCTION_PARAMETERS, char *cmd, int cmd_len, ...) { - generic_empty_cmd_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len, redis_boolean_response); -} - -/* {{{ proto string Redis::save() - */ -PHP_METHOD(Redis, save) -{ - char *cmd; - int cmd_len = redis_cmd_format_static(&cmd, "SAVE", ""); - generic_empty_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len); - -} -/* }}} */ - -/* {{{ proto string Redis::bgSave() - */ -PHP_METHOD(Redis, bgSave) -{ - char *cmd; - int cmd_len = redis_cmd_format_static(&cmd, "BGSAVE", ""); - generic_empty_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len); - -} -/* }}} */ - -PHPAPI void generic_empty_long_cmd(INTERNAL_FUNCTION_PARAMETERS, char *cmd, int cmd_len, ...) { - - zval *object; - RedisSock *redis_sock; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", - &object, redis_ce) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - 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); -} - -/* {{{ proto integer Redis::lastSave() - */ -PHP_METHOD(Redis, lastSave) -{ - char *cmd; - int cmd_len = redis_cmd_format_static(&cmd, "LASTSAVE", ""); - generic_empty_long_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len); -} -/* }}} */ - - -/* {{{ proto bool Redis::flushDB() - */ -PHP_METHOD(Redis, flushDB) -{ - char *cmd; - int cmd_len = redis_cmd_format_static(&cmd, "FLUSHDB", ""); - generic_empty_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len); -} -/* }}} */ - -/* {{{ proto bool Redis::flushAll() - */ -PHP_METHOD(Redis, flushAll) -{ - char *cmd; - int cmd_len = redis_cmd_format_static(&cmd, "FLUSHALL", ""); - generic_empty_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len); -} -/* }}} */ - -/* {{{ proto int Redis::dbSize() - */ -PHP_METHOD(Redis, dbSize) -{ - char *cmd; - int cmd_len = redis_cmd_format_static(&cmd, "DBSIZE", ""); - generic_empty_long_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len); -} -/* }}} */ - -/* {{{ proto bool Redis::auth(string passwd) - */ -PHP_METHOD(Redis, auth) { - - zval *object; - RedisSock *redis_sock; - - char *cmd, *password; - int cmd_len, password_len; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_ce, &password, &password_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - cmd_len = redis_cmd_format_static(&cmd, "AUTH", "s", password, password_len); - - // Free previously stored auth if we have one, and store this password - if(redis_sock->auth) efree(redis_sock->auth); - redis_sock->auth = estrndup(password, password_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); -} -/* }}} */ - -/* {{{ proto long Redis::persist(string key) - */ -PHP_METHOD(Redis, persist) { - - zval *object; - RedisSock *redis_sock; - - char *cmd, *key; - int cmd_len, key_len, key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_ce, &key, &key_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "PERSIST", "s", key, key_len); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_1_response); -} -/* }}} */ - -PHPAPI void generic_ttl(INTERNAL_FUNCTION_PARAMETERS, char *keyword) { - zval *object; - RedisSock *redis_sock; - - char *cmd, *key; - int cmd_len, key_len, key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_ce, &key, &key_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, keyword, "s", key, key_len); - if(key_free) efree(key); - - 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); -} - -/* {{{ proto long Redis::ttl(string key) - */ -PHP_METHOD(Redis, ttl) { - generic_ttl(INTERNAL_FUNCTION_PARAM_PASSTHRU, "TTL"); -} -/* }}} */ - -/* {{{ proto long Redis::pttl(string key) - */ -PHP_METHOD(Redis, pttl) { - generic_ttl(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PTTL"); -} -/* }}} */ - -/* {{{ proto array Redis::info() - */ -PHP_METHOD(Redis, info) { - - zval *object; - RedisSock *redis_sock; - char *cmd, *opt = NULL; - int cmd_len, opt_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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - // Build a standalone INFO command or one with an option - if(opt != NULL) { - cmd_len = redis_cmd_format_static(&cmd, "INFO", "s", opt, opt_len); - } else { - cmd_len = redis_cmd_format_static(&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 string Redis::resetStat() - */ -PHP_METHOD(Redis, resetStat) -{ - char *cmd; - int cmd_len = redis_cmd_format_static(&cmd, "CONFIG", "s", "RESETSTAT", 9); - generic_empty_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len); -} -/* }}} */ - -/* {{{ proto bool Redis::select(long dbNumber) - */ -PHP_METHOD(Redis, select) { - - zval *object; - RedisSock *redis_sock; - - char *cmd; - int cmd_len; - long dbNumber; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", - &object, redis_ce, &dbNumber) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - redis_sock->dbNumber = dbNumber; - - cmd_len = redis_cmd_format_static(&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) { - - zval *object; - RedisSock *redis_sock; - - char *cmd, *key; - int cmd_len, key_len, key_free; - long dbNumber; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osl", - &object, redis_ce, &key, &key_len, &dbNumber) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "MOVE", "sd", key, key_len, dbNumber); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_1_response); - -} -/* }}} */ - -PHPAPI void -generic_mset(INTERNAL_FUNCTION_PARAMETERS, char *kw, void (*fun)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *, zval *, void *)) { - - zval *object; - RedisSock *redis_sock; - - char *cmd = NULL, *p = NULL; - int cmd_len = 0, argc = 0, kw_len = strlen(kw); - int step = 0; // 0: compute size; 1: copy strings. - zval *z_array; - - HashTable *keytable; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", - &object, redis_ce, &z_array) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - if(zend_hash_num_elements(Z_ARRVAL_P(z_array)) == 0) { - RETURN_FALSE; - } - - for(step = 0; step < 2; ++step) { - if(step == 1) { - cmd_len += 1 + integer_length(1 + 2 * argc) + 2; /* star + arg count + NL */ - cmd_len += 1 + integer_length(kw_len) + 2; /* dollar + strlen(kw) + NL */ - cmd_len += kw_len + 2; /* kw + NL */ - - p = cmd = emalloc(cmd_len + 1); /* alloc */ - p += sprintf(cmd, "*%d" _NL "$%d" _NL "%s" _NL, 1 + 2 * argc, kw_len, kw); /* copy header */ - } - - keytable = Z_ARRVAL_P(z_array); - for(zend_hash_internal_pointer_reset(keytable); - zend_hash_has_more_elements(keytable) == SUCCESS; - zend_hash_move_forward(keytable)) { - - char *key, *val; - unsigned int key_len; - int val_len; - unsigned long idx; - int type; - zval **z_value_pp; - int val_free, key_free; - char buf[32]; - - type = zend_hash_get_current_key_ex(keytable, &key, &key_len, &idx, 0, NULL); - if(zend_hash_get_current_data(keytable, (void**)&z_value_pp) == FAILURE) { - continue; /* this should never happen, according to the PHP people. */ - } - - // If the key isn't a string, use the index value returned when grabbing the - // key. We typecast to long, because they could actually be negative. - if(type != HASH_KEY_IS_STRING) { - // Create string representation of our index - key_len = snprintf(buf, sizeof(buf), "%ld", (long)idx); - key = (char*)buf; - } else if(key_len > 0) { - // When not an integer key, the length will include the \0 - key_len--; - } - - if(step == 0) - argc++; /* found a valid arg */ - - val_free = redis_serialize(redis_sock, *z_value_pp, &val, &val_len TSRMLS_CC); - key_free = redis_key_prefix(redis_sock, &key, (int*)&key_len TSRMLS_CC); - - if(step == 0) { /* counting */ - cmd_len += 1 + integer_length(key_len) + 2 - + key_len + 2 - + 1 + integer_length(val_len) + 2 - + val_len + 2; - } else { - p += sprintf(p, "$%d" _NL, key_len); /* key len */ - memcpy(p, key, key_len); p += key_len; /* key */ - memcpy(p, _NL, 2); p += 2; - - p += sprintf(p, "$%d" _NL, val_len); /* val len */ - memcpy(p, val, val_len); p += val_len; /* val */ - memcpy(p, _NL, 2); p += 2; - } - - if(val_free) efree(val); - if(key_free) efree(key); - } - } - - REDIS_PROCESS_REQUEST(redis_sock, cmd, 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); -} -/* }}} */ - -PHPAPI void common_rpoplpush(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, - char *srckey, int srckey_len, char *dstkey, int dstkey_len, int timeout) { - - char *cmd; - int cmd_len; - - int srckey_free = redis_key_prefix(redis_sock, &srckey, &srckey_len TSRMLS_CC); - int dstkey_free = redis_key_prefix(redis_sock, &dstkey, &dstkey_len TSRMLS_CC); - if(timeout < 0) { - cmd_len = redis_cmd_format_static(&cmd, "RPOPLPUSH", "ss", srckey, srckey_len, dstkey, dstkey_len); - } else { - cmd_len = redis_cmd_format_static(&cmd, "BRPOPLPUSH", "ssd", srckey, srckey_len, dstkey, dstkey_len, timeout); - } - if(srckey_free) efree(srckey); - if(dstkey_free) efree(dstkey); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_string_response); - -} - -/* {{{ proto string Redis::rpoplpush(string srckey, string dstkey) - */ -PHP_METHOD(Redis, rpoplpush) -{ - zval *object; - RedisSock *redis_sock; - char *srckey = NULL, *dstkey = NULL; - int srckey_len, dstkey_len; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", - &object, redis_ce, &srckey, &srckey_len, - &dstkey, &dstkey_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - common_rpoplpush(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, srckey, srckey_len, dstkey, dstkey_len, -1); -} -/* }}} */ - -/* {{{ proto string Redis::brpoplpush(string srckey, string dstkey) - */ -PHP_METHOD(Redis, brpoplpush) -{ - zval *object; - RedisSock *redis_sock; - char *srckey = NULL, *dstkey = NULL; - int srckey_len, dstkey_len; - long timeout = 0; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ossl", - &object, redis_ce, &srckey, &srckey_len, - &dstkey, &dstkey_len, &timeout) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - common_rpoplpush(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, srckey, srckey_len, dstkey, dstkey_len, timeout); -} -/* }}} */ - -/* {{{ proto long Redis::zAdd(string key, int score, string value) - */ -PHP_METHOD(Redis, zAdd) { - - RedisSock *redis_sock; - - char *cmd; - int cmd_len, key_len, val_len; - double score; - char *key, *val; - int val_free, key_free = 0; - char *dbl_str; - int dbl_len; - - zval **z_args; - int argc = ZEND_NUM_ARGS(), i; - - /* get redis socket */ - if (redis_sock_get(getThis(), &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - z_args = emalloc(argc * sizeof(zval*)); - if(zend_get_parameters_array(ht, argc, z_args) == FAILURE) { - efree(z_args); - RETURN_FALSE; - } - - /* need key, score, value, [score, value...] */ - if(argc > 1) { - convert_to_string(z_args[0]); // required string - } - if(argc < 3 || Z_TYPE_P(z_args[0]) != IS_STRING || (argc-1) % 2 != 0) { - efree(z_args); - RETURN_FALSE; - } - - /* possibly serialize key */ - key = Z_STRVAL_P(z_args[0]); - key_len = Z_STRLEN_P(z_args[0]); - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - - /* start building the command */ - smart_str buf = {0}; - smart_str_appendc(&buf, '*'); - smart_str_append_long(&buf, argc + 1); /* +1 for ZADD command */ - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - - /* add command name */ - smart_str_appendc(&buf, '$'); - smart_str_append_long(&buf, 4); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - smart_str_appendl(&buf, "ZADD", 4); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - - /* add key */ - smart_str_appendc(&buf, '$'); - smart_str_append_long(&buf, key_len); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - smart_str_appendl(&buf, key, key_len); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - - for(i = 1; i < argc; i +=2) { - convert_to_double(z_args[i]); // convert score to double - val_free = redis_serialize(redis_sock, z_args[i+1], &val, &val_len TSRMLS_CC); // possibly serialize value. - - /* add score */ - score = Z_DVAL_P(z_args[i]); - REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, score) - smart_str_appendc(&buf, '$'); - smart_str_append_long(&buf, dbl_len); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - smart_str_appendl(&buf, dbl_str, dbl_len); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - efree(dbl_str); - - /* add value */ - smart_str_appendc(&buf, '$'); - smart_str_append_long(&buf, val_len); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - smart_str_appendl(&buf, val, val_len); - smart_str_appendl(&buf, _NL, sizeof(_NL) - 1); - - if(val_free) efree(val); - } - - /* end string */ - smart_str_0(&buf); - cmd = buf.c; - cmd_len = buf.len; - if(key_free) efree(key); - - /* cleanup */ - efree(z_args); - - 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); -} -/* }}} */ -/* {{{ proto array Redis::zRange(string key, int start , int end, bool withscores = FALSE) - */ -PHP_METHOD(Redis, zRange) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - long start, end; - long withscores = 0; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osll|b", - &object, redis_ce, - &key, &key_len, &start, &end, &withscores) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - if(withscores) { - cmd_len = redis_cmd_format_static(&cmd, "ZRANGE", "sdds", key, key_len, start, end, "WITHSCORES", 10); - } else { - cmd_len = redis_cmd_format_static(&cmd, "ZRANGE", "sdd", key, key_len, start, end); - } - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - if(withscores) { - IF_ATOMIC() { - redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_zipped); - } 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 long Redis::zDelete(string key, string member) - */ -PHP_METHOD(Redis, zDelete) -{ - RedisSock *redis_sock; - - if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, - "ZREM", sizeof("ZREM") - 1, - 2, &redis_sock, 0, 0, 1)) - return; - - IF_ATOMIC() { - redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_long_response); -} -/* }}} */ -/* {{{ proto long Redis::zDeleteRangeByScore(string key, string start, string end) - */ -PHP_METHOD(Redis, zDeleteRangeByScore) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - char *start, *end; - int start_len, end_len; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osss", - &object, redis_ce, - &key, &key_len, &start, &start_len, &end, &end_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "ZREMRANGEBYSCORE", "sss", key, key_len, start, start_len, end, end_len); - if(key_free) efree(key); - - 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); - -} -/* }}} */ - -/* {{{ proto long Redis::zDeleteRangeByRank(string key, long start, long end) - */ -PHP_METHOD(Redis, zDeleteRangeByRank) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - long start, end; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osll", - &object, redis_ce, - &key, &key_len, &start, &end) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "ZREMRANGEBYRANK", "sdd", key, key_len, (int)start, (int)end); - if(key_free) efree(key); - - 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); - -} -/* }}} */ - -/* {{{ proto array Redis::zReverseRange(string key, int start , int end, bool withscores = FALSE) - */ -PHP_METHOD(Redis, zReverseRange) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - long start, end; - long withscores = 0; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osll|b", - &object, redis_ce, - &key, &key_len, &start, &end, &withscores) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - if(withscores) { - cmd_len = redis_cmd_format_static(&cmd, "ZREVRANGE", "sdds", key, key_len, start, end, "WITHSCORES", 10); - } else { - cmd_len = redis_cmd_format_static(&cmd, "ZREVRANGE", "sdd", key, key_len, start, end); - } - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - if(withscores) { - IF_ATOMIC() { - redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_zipped); - } 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); - } -} -/* }}} */ - -PHPAPI void -redis_generic_zrange_by_score(INTERNAL_FUNCTION_PARAMETERS, char *keyword) { - - zval *object, *z_options = NULL, **z_limit_val_pp = NULL, **z_withscores_val_pp = NULL; - - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - zend_bool withscores = 0; - char *start, *end; - int start_len, end_len; - int has_limit = 0; - long limit_low, limit_high; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osss|a", - &object, redis_ce, - &key, &key_len, - &start, &start_len, - &end, &end_len, - &z_options) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - /* options */ - if (z_options && Z_TYPE_P(z_options) == IS_ARRAY) { - /* add scores */ - zend_hash_find(Z_ARRVAL_P(z_options), "withscores", sizeof("withscores"), (void**)&z_withscores_val_pp); - withscores = (z_withscores_val_pp ? Z_BVAL_PP(z_withscores_val_pp) : 0); - - /* limit offset, count: - z_limit_val_pp points to an array($longFrom, $longCount) - */ - if(zend_hash_find(Z_ARRVAL_P(z_options), "limit", sizeof("limit"), (void**)&z_limit_val_pp)== SUCCESS) {; - if(zend_hash_num_elements(Z_ARRVAL_PP(z_limit_val_pp)) == 2) { - zval **z_offset_pp, **z_count_pp; - // get the two values from the table, check that they are indeed of LONG type - if(SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(z_limit_val_pp), 0, (void**)&z_offset_pp) && - SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(z_limit_val_pp), 1, (void**)&z_count_pp) && - Z_TYPE_PP(z_offset_pp) == IS_LONG && - Z_TYPE_PP(z_count_pp) == IS_LONG) { - - has_limit = 1; - limit_low = Z_LVAL_PP(z_offset_pp); - limit_high = Z_LVAL_PP(z_count_pp); - } - } - } - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - if(withscores) { - if(has_limit) { - cmd_len = redis_cmd_format_static(&cmd, keyword, "ssssdds", - key, key_len, start, start_len, end, end_len, "LIMIT", 5, limit_low, limit_high, "WITHSCORES", 10); - } else { - cmd_len = redis_cmd_format_static(&cmd, keyword, "ssss", - key, key_len, start, start_len, end, end_len, "WITHSCORES", 10); - } - } else { - if(has_limit) { - cmd_len = redis_cmd_format_static(&cmd, keyword, "ssssdd", - key, key_len, start, start_len, end, end_len, "LIMIT", 5, limit_low, limit_high); - } else { - cmd_len = redis_cmd_format_static(&cmd, keyword, "sss", key, key_len, start, start_len, end, end_len); - } - } - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - if(withscores) { - /* with scores! we have to transform the return array. - * return_value currently holds this: [elt0, val0, elt1, val1 ... ] - * we want [elt0 => val0, elt1 => val1], etc. - */ - IF_ATOMIC() { - if(redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { - RETURN_FALSE; - } - } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_zipped); - } 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::zRangeByScore(string key, string start , string end [,array options = NULL]) - */ -PHP_METHOD(Redis, zRangeByScore) -{ - redis_generic_zrange_by_score(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZRANGEBYSCORE"); -} -/* }}} */ -/* {{{ proto array Redis::zRevRangeByScore(string key, string start , string end [,array options = NULL]) - */ -PHP_METHOD(Redis, zRevRangeByScore) -{ - redis_generic_zrange_by_score(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZREVRANGEBYSCORE"); -} - -/* {{{ proto array Redis::zCount(string key, string start , string end) - */ -PHP_METHOD(Redis, zCount) -{ - zval *object; - - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - char *start, *end; - int start_len, end_len; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osss", - &object, redis_ce, - &key, &key_len, - &start, &start_len, - &end, &end_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "ZCOUNT", "sss", key, key_len, start, start_len, end, end_len); - if(key_free) efree(key); - - 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); -} -/* }}} */ - -/* {{{ proto long Redis::zCard(string key) - */ -PHP_METHOD(Redis, zCard) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_ce, - &key, &key_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "ZCARD", "s", key, key_len); - if(key_free) efree(key); - - 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); - -} -/* }}} */ - -/* {{{ proto double Redis::zScore(string key, mixed member) - */ -PHP_METHOD(Redis, zScore) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *val = NULL, *cmd; - int key_len, val_len, cmd_len; - int val_free, key_free = 0; - zval *z_value; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz", - &object, redis_ce, &key, &key_len, - &z_value) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "ZSCORE", "ss", key, key_len, val, val_len); - if(val_free) efree(val); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_bulk_double_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_bulk_double_response); -} -/* }}} */ - - -PHPAPI void generic_rank_method(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len) { - zval *object; - RedisSock *redis_sock; - char *key = NULL, *val = NULL, *cmd; - int key_len, val_len, cmd_len; - int val_free, key_free = 0; - zval *z_value; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz", - &object, redis_ce, &key, &key_len, - &z_value) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, keyword, "ss", key, key_len, val, val_len); - if(val_free) efree(val); - if(key_free) efree(key); - - 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); -} - - -/* {{{ proto long Redis::zRank(string key, string member) - */ -PHP_METHOD(Redis, zRank) { - - generic_rank_method(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZRANK", 5); -} -/* }}} */ - -/* {{{ proto long Redis::zRevRank(string key, string member) - */ -PHP_METHOD(Redis, zRevRank) { - - generic_rank_method(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZREVRANK", 8); -} -/* }}} */ - -PHPAPI void generic_incrby_method(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len) { - zval *object; - RedisSock *redis_sock; - - char *key = NULL, *cmd, *val; - int key_len, val_len, cmd_len; - double add; - int val_free, key_free; - zval *z_value; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osdz", - &object, redis_ce, - &key, &key_len, &add, &z_value) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, keyword, "sfs", key, key_len, add, val, val_len); - if(val_free) efree(val); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_bulk_double_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_bulk_double_response); - -} - -/* {{{ proto double Redis::zIncrBy(string key, double value, mixed member) - */ -PHP_METHOD(Redis, zIncrBy) -{ - generic_incrby_method(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZINCRBY", sizeof("ZINCRBY")-1); -} -/* }}} */ - -PHPAPI void generic_z_command(INTERNAL_FUNCTION_PARAMETERS, char *command, int command_len) { - zval *object, *z_keys, *z_weights = NULL, **z_data; - HashTable *ht_keys, *ht_weights = NULL; - RedisSock *redis_sock; - smart_str cmd = {0}; - HashPosition ptr; - char *store_key, *agg_op = NULL; - int cmd_arg_count = 2, store_key_len, agg_op_len = 0, keys_count; - - // Grab our parameters - if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa|a!s", - &object, redis_ce, &store_key, &store_key_len, - &z_keys, &z_weights, &agg_op, &agg_op_len) == FAILURE) - { - RETURN_FALSE; - } - - // We'll need our socket - if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - // Grab our keys argument as an array - ht_keys = Z_ARRVAL_P(z_keys); - - // Nothing to do if there aren't any keys - if((keys_count = zend_hash_num_elements(ht_keys)) == 0) { - RETURN_FALSE; - } else { - // Increment our overall argument count - cmd_arg_count += keys_count; - } - - // Grab and validate our weights array - if(z_weights != NULL) { - ht_weights = Z_ARRVAL_P(z_weights); - - // This command is invalid if the weights array isn't the same size - // as our keys array. - if(zend_hash_num_elements(ht_weights) != keys_count) { - RETURN_FALSE; - } - - // Increment our overall argument count by the number of keys - // plus one, for the "WEIGHTS" argument itself - cmd_arg_count += keys_count + 1; - } - - // AGGREGATE option - if(agg_op_len != 0) { - // Verify our aggregation option - if(strncasecmp(agg_op, "SUM", sizeof("SUM")) && - strncasecmp(agg_op, "MIN", sizeof("MIN")) && - strncasecmp(agg_op, "MAX", sizeof("MAX"))) - { - RETURN_FALSE; - } - - // Two more arguments: "AGGREGATE" and agg_op - cmd_arg_count += 2; - } - - // Command header - redis_cmd_init_sstr(&cmd, cmd_arg_count, command, command_len); - - // Prefix our key if necessary and add the output key - int key_free = redis_key_prefix(redis_sock, &store_key, &store_key_len TSRMLS_CC); - redis_cmd_append_sstr(&cmd, store_key, store_key_len); - if(key_free) efree(store_key); - - // Number of input keys argument - redis_cmd_append_sstr_int(&cmd, keys_count); - - // Process input keys - for(zend_hash_internal_pointer_reset_ex(ht_keys, &ptr); - zend_hash_get_current_data_ex(ht_keys, (void**)&z_data, &ptr)==SUCCESS; - zend_hash_move_forward_ex(ht_keys, &ptr)) - { - char *key; - int key_free, key_len; - zval *z_tmp = NULL; - - if(Z_TYPE_PP(z_data) == IS_STRING) { - key = Z_STRVAL_PP(z_data); - key_len = Z_STRLEN_PP(z_data); - } else { - MAKE_STD_ZVAL(z_tmp); - *z_tmp = **z_data; - convert_to_string(z_tmp); - - key = Z_STRVAL_P(z_tmp); - key_len = Z_STRLEN_P(z_tmp); - } - - // Apply key prefix if necessary - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - - // Append this input set - redis_cmd_append_sstr(&cmd, key, key_len); - - // Free our key if it was prefixed - if(key_free) efree(key); - - // Free our temporary z_val if it was converted - if(z_tmp) { - zval_dtor(z_tmp); - efree(z_tmp); - z_tmp = NULL; - } - } - - // Weights - if(ht_weights != NULL) { - // Append "WEIGHTS" argument - redis_cmd_append_sstr(&cmd, "WEIGHTS", sizeof("WEIGHTS") - 1); - - // Process weights - for(zend_hash_internal_pointer_reset_ex(ht_weights, &ptr); - zend_hash_get_current_data_ex(ht_weights, (void**)&z_data, &ptr)==SUCCESS; - zend_hash_move_forward_ex(ht_weights, &ptr)) - { - // Ignore non numeric arguments, unless they're special Redis numbers - if (Z_TYPE_PP(z_data) != IS_LONG && Z_TYPE_PP(z_data) != IS_DOUBLE && - strncasecmp(Z_STRVAL_PP(z_data), "inf", sizeof("inf")) != 0 && - strncasecmp(Z_STRVAL_PP(z_data), "-inf", sizeof("-inf")) != 0 && - strncasecmp(Z_STRVAL_PP(z_data), "+inf", sizeof("+inf")) != 0) - { - // We should abort if we have an invalid weight, rather than pass - // a different number of weights than the user is expecting - efree(cmd.c); - RETURN_FALSE; - } - - // Append the weight based on the input type - switch(Z_TYPE_PP(z_data)) { - case IS_LONG: - redis_cmd_append_sstr_long(&cmd, Z_LVAL_PP(z_data)); - break; - case IS_DOUBLE: - redis_cmd_append_sstr_dbl(&cmd, Z_DVAL_PP(z_data)); - break; - case IS_STRING: - redis_cmd_append_sstr(&cmd, Z_STRVAL_PP(z_data), Z_STRLEN_PP(z_data)); - break; - } - } - } - - // Aggregation options, if we have them - if(agg_op_len != 0) { - redis_cmd_append_sstr(&cmd, "AGGREGATE", sizeof("AGGREGATE") - 1); - redis_cmd_append_sstr(&cmd, agg_op, agg_op_len); - } - - // Kick off our request - REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len); - IF_ATOMIC() { - redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_long_response); -} - -/* zInter */ -PHP_METHOD(Redis, zInter) { - generic_z_command(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZINTERSTORE", 11); -} - -/* zUnion */ -PHP_METHOD(Redis, zUnion) { - generic_z_command(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZUNIONSTORE", 11); -} - -/* hashes */ - -PHPAPI void -generic_hset(INTERNAL_FUNCTION_PARAMETERS, char *kw, void (*fun)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *, zval *, void *)) { - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd, *member, *val; - int key_len, member_len, cmd_len, val_len; - int val_free, key_free = 0; - zval *z_value; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ossz", - &object, redis_ce, - &key, &key_len, &member, &member_len, &z_value) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, kw, "sss", key, key_len, member, member_len, val, val_len); - if(val_free) efree(val); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(fun); -} -/* hSet */ -PHP_METHOD(Redis, hSet) -{ - generic_hset(INTERNAL_FUNCTION_PARAM_PASSTHRU, "HSET", redis_long_response); -} -/* }}} */ -/* hSetNx */ -PHP_METHOD(Redis, hSetNx) -{ - generic_hset(INTERNAL_FUNCTION_PARAM_PASSTHRU, "HSETNX", redis_1_response); -} -/* }}} */ - - -/* hGet */ -PHP_METHOD(Redis, hGet) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd, *member; - int key_len, member_len, cmd_len, key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", - &object, redis_ce, - &key, &key_len, &member, &member_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "HGET", "ss", key, key_len, member, member_len); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_string_response); - -} -/* }}} */ - -/* hLen */ -PHP_METHOD(Redis, hLen) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_ce, - &key, &key_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "HLEN", "s", key, key_len); - if(key_free) efree(key); - - 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); - -} -/* }}} */ - -PHPAPI RedisSock* -generic_hash_command_2(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len, char **out_cmd, int *out_len) { - - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd, *member; - int key_len, cmd_len, member_len, key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", - &object, redis_ce, - &key, &key_len, &member, &member_len) == FAILURE) { - ZVAL_BOOL(return_value, 0); - return NULL; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - ZVAL_BOOL(return_value, 0); - return NULL; - } - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, keyword, "ss", key, key_len, member, member_len); - if(key_free) efree(key); - - *out_cmd = cmd; - *out_len = cmd_len; - return redis_sock; -} - -/* hDel */ -PHP_METHOD(Redis, hDel) -{ - RedisSock *redis_sock; - - if(FAILURE == generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, - "HDEL", sizeof("HDEL") - 1, - 2, &redis_sock, 0, 0, 0)) - return; - - IF_ATOMIC() { - redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_long_response); -} - -/* hExists */ -PHP_METHOD(Redis, hExists) -{ - char *cmd; - int cmd_len; - RedisSock *redis_sock = generic_hash_command_2(INTERNAL_FUNCTION_PARAM_PASSTHRU, "HEXISTS", 7, &cmd, &cmd_len); - if(!redis_sock) - RETURN_FALSE; - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_1_response); - -} - -PHPAPI RedisSock* -generic_hash_command_1(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len) { - - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", - &object, redis_ce, - &key, &key_len) == FAILURE) { - ZVAL_BOOL(return_value, 0); - return NULL; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - ZVAL_BOOL(return_value, 0); - return NULL; - } - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, keyword, "s", key, key_len); - if(key_free) efree(key); - - /* call REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len) without breaking the return value */ - IF_MULTI_OR_ATOMIC() { - if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { - efree(cmd); - return NULL; - } - efree(cmd); - } - IF_PIPELINE() { - PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len); - efree(cmd); - } - return redis_sock; -} - -/* hKeys */ -PHP_METHOD(Redis, hKeys) -{ - RedisSock *redis_sock = generic_hash_command_1(INTERNAL_FUNCTION_PARAM_PASSTHRU, "HKEYS", sizeof("HKEYS")-1); - if(!redis_sock) - RETURN_FALSE; - - IF_ATOMIC() { - if (redis_sock_read_multibulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock, NULL, NULL) < 0) { - RETURN_FALSE; - } - } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_raw); - - -} -/* hVals */ -PHP_METHOD(Redis, hVals) -{ - RedisSock *redis_sock = generic_hash_command_1(INTERNAL_FUNCTION_PARAM_PASSTHRU, "HVALS", sizeof("HVALS")-1); - if(!redis_sock) - RETURN_FALSE; - - 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); - -} - - -PHP_METHOD(Redis, hGetAll) { - - RedisSock *redis_sock = generic_hash_command_1(INTERNAL_FUNCTION_PARAM_PASSTHRU, "HGETALL", sizeof("HGETALL")-1); - if(!redis_sock) - RETURN_FALSE; - - IF_ATOMIC() { - if (redis_sock_read_multibulk_reply_zipped_strings(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock, NULL, NULL) < 0) { - RETURN_FALSE; - } - } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_zipped_strings); -} - -PHPAPI void array_zip_values_and_scores(RedisSock *redis_sock, zval *z_tab, int use_atof TSRMLS_DC) { - - zval *z_ret; - HashTable *keytable; - - MAKE_STD_ZVAL(z_ret); - 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)) { - - char *tablekey, *hkey, *hval; - unsigned int tablekey_len; - int hkey_len; - unsigned long idx; - zval **z_key_pp, **z_value_pp; - - zend_hash_get_current_key_ex(keytable, &tablekey, &tablekey_len, &idx, 0, NULL); - if(zend_hash_get_current_data(keytable, (void**)&z_key_pp) == FAILURE) { - continue; /* this should never happen, according to the PHP people. */ - } - - /* get current value, a key */ - convert_to_string(*z_key_pp); - hkey = Z_STRVAL_PP(z_key_pp); - hkey_len = Z_STRLEN_PP(z_key_pp); - - /* move forward */ - zend_hash_move_forward(keytable); - - /* fetch again */ - zend_hash_get_current_key_ex(keytable, &tablekey, &tablekey_len, &idx, 0, NULL); - if(zend_hash_get_current_data(keytable, (void**)&z_value_pp) == FAILURE) { - continue; /* this should never happen, according to the PHP people. */ - } - - /* get current value, a hash value now. */ - hval = Z_STRVAL_PP(z_value_pp); - - if(use_atof) { /* zipping a score */ - add_assoc_double_ex(z_ret, hkey, 1+hkey_len, atof(hval)); - } else { /* add raw copy */ - zval *z = NULL; - MAKE_STD_ZVAL(z); - *z = **z_value_pp; - zval_copy_ctor(z); - add_assoc_zval_ex(z_ret, hkey, 1+hkey_len, z); - } - } - /* replace */ - zval_dtor(z_tab); - *z_tab = *z_ret; - zval_copy_ctor(z_tab); - zval_dtor(z_ret); - - efree(z_ret); -} - -PHP_METHOD(Redis, hIncrByFloat) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd, *member; - int key_len, member_len, cmd_len, key_free; - double val; - - // Validate we have the right number of arguments - if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ossd", - &object, redis_ce, - &key, &key_len, &member, &member_len, &val) == FAILURE) { - RETURN_FALSE; - } - - // Grab our socket - if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "HINCRBYFLOAT", "ssf", key, key_len, member, member_len, val); - if(key_free) efree(key); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_bulk_double_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_bulk_double_response); -} - -PHP_METHOD(Redis, hIncrBy) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd, *member, *val; - int key_len, member_len, cmd_len, val_len, key_free; - int i; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osss", - &object, redis_ce, - &key, &key_len, &member, &member_len, &val, &val_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - /* check for validity of numeric string */ - i = 0; - if(val_len && val[0] == '-') { /* negative case */ - i++; - } - for(; i < val_len; ++i) { - if(val[i] < '0' || val[i] > '9') { - RETURN_FALSE; - } - } - - /* HINCRBY key member amount */ - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "HINCRBY", "sss", key, key_len, member, member_len, val, val_len); - if(key_free) efree(key); - - 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); - -} - - -PHP_METHOD(Redis, hMget) { - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd; - int key_len, cmd_len, key_free; - zval *z_array; - zval **z_keys; - int nb_fields, i; - char *old_cmd = NULL; - - zval **data; - HashTable *arr_hash; - HashPosition pointer; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa", - &object, redis_ce, - &key, &key_len, &z_array) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - nb_fields = zend_hash_num_elements(Z_ARRVAL_P(z_array)); - - if( nb_fields == 0) { - RETURN_FALSE; - } - - z_keys = ecalloc(nb_fields, sizeof(zval *)); - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - - cmd_len = redis_cmd_format(&cmd, - "*%d" _NL - "$5" _NL - "HMGET" _NL - - "$%d" _NL /* key */ - "%s" _NL - , nb_fields + 2 - , key_len, key, key_len); - if(key_free) efree(key); - - arr_hash = Z_ARRVAL_P(z_array); - - for (i = 0, zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); - zend_hash_get_current_data_ex(arr_hash, (void**) &data, - &pointer) == SUCCESS; - zend_hash_move_forward_ex(arr_hash, &pointer)) { - - if (Z_TYPE_PP(data) == IS_LONG || Z_TYPE_PP(data) == IS_STRING) { - - old_cmd = cmd; - if (Z_TYPE_PP(data) == IS_LONG) { - cmd_len = redis_cmd_format(&cmd, "%s" "$%d" _NL "%d" _NL - , cmd, cmd_len - , integer_length(Z_LVAL_PP(data)), (int)Z_LVAL_PP(data)); - } else if (Z_TYPE_PP(data) == IS_STRING) { - cmd_len = redis_cmd_format(&cmd, "%s" "$%d" _NL "%s" _NL - , cmd, cmd_len - , Z_STRLEN_PP(data), Z_STRVAL_PP(data), Z_STRLEN_PP(data)); - } - efree(old_cmd); - /* save context */ - MAKE_STD_ZVAL(z_keys[i]); - *z_keys[i] = **data; - zval_copy_ctor(z_keys[i]); - convert_to_string(z_keys[i]); - - i++; - } - } - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_sock_read_multibulk_reply_assoc(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, z_keys); - } - REDIS_PROCESS_RESPONSE_CLOSURE(redis_sock_read_multibulk_reply_assoc, z_keys); -} - -PHP_METHOD(Redis, hMset) -{ - zval *object; - RedisSock *redis_sock; - char *key = NULL, *cmd, *old_cmd = NULL; - int key_len, cmd_len, key_free, i, element_count = 2; - zval *z_hash; - HashTable *ht_hash; - smart_str set_cmds = {0}; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa", - &object, redis_ce, - &key, &key_len, &z_hash) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - ht_hash = Z_ARRVAL_P(z_hash); - - if (zend_hash_num_elements(ht_hash) == 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format(&cmd, - "$5" _NL "HMSET" _NL - "$%d" _NL "%s" _NL - , key_len, key, key_len); - if(key_free) efree(key); - - /* looping on each item of the array */ - for(i =0, zend_hash_internal_pointer_reset(ht_hash); - zend_hash_has_more_elements(ht_hash) == SUCCESS; - i++, zend_hash_move_forward(ht_hash)) { - - char *hkey, hkey_str[40]; - unsigned int hkey_len; - unsigned long idx; - int type; - zval **z_value_p; - - char *hval; - int hval_len, hval_free; - - type = zend_hash_get_current_key_ex(ht_hash, &hkey, &hkey_len, &idx, 0, NULL); - - if(zend_hash_get_current_data(ht_hash, (void**)&z_value_p) == FAILURE) { - continue; /* this should never happen */ - } - - if(type != HASH_KEY_IS_STRING) { /* convert to string */ - hkey_len = 1 + sprintf(hkey_str, "%ld", idx); - hkey = (char*)hkey_str; - } - element_count += 2; - - /* key is set. */ - hval_free = redis_serialize(redis_sock, *z_value_p, &hval, &hval_len TSRMLS_CC); - - // Append our member and value in place - redis_cmd_append_sstr(&set_cmds, hkey, hkey_len - 1); - redis_cmd_append_sstr(&set_cmds, hval, hval_len); - - if(hval_free) efree(hval); - } - - // Now construct the entire command - old_cmd = cmd; - cmd_len = redis_cmd_format(&cmd, "*%d" _NL "%s%s", element_count, cmd, cmd_len, set_cmds.c, set_cmds.len); - efree(old_cmd); - - // Free the HMSET bits - efree(set_cmds.c); - - 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); -} - - -PHPAPI int redis_response_enqueued(RedisSock *redis_sock TSRMLS_DC) { - - char *response; - int response_len, ret = 0; - - if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { - return 0; - } - - if(strncmp(response, "+QUEUED", 7) == 0) { - ret = 1; - } - efree(response); - return ret; -} - -/* flag : get, set {ATOMIC, MULTI, PIPELINE} */ - -PHP_METHOD(Redis, multi) -{ - - RedisSock *redis_sock; - char *cmd; - int response_len, cmd_len; - char * response; - zval *object; - 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - if(multi_value == MULTI || multi_value == PIPELINE) { - redis_sock->mode = multi_value; - } else { - RETURN_FALSE; - } - - redis_sock->current = NULL; - - IF_MULTI() { - cmd_len = redis_cmd_format_static(&cmd, "MULTI", ""); - - if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { - efree(cmd); - RETURN_FALSE; - } - efree(cmd); - - if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { - RETURN_FALSE; - } - - if(strncmp(response, "+OK", 3) == 0) { - efree(response); - RETURN_ZVAL(getThis(), 1, 0); - } - efree(response); - RETURN_FALSE; - } - IF_PIPELINE() { - free_reply_callbacks(getThis(), redis_sock); - 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - redis_sock->mode = ATOMIC; - redis_send_discard(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); -} - -PHPAPI int redis_sock_read_multibulk_pipeline_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) -{ - zval *z_tab; - MAKE_STD_ZVAL(z_tab); - array_init(z_tab); - - redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock, z_tab, 0); - - *return_value = *z_tab; - efree(z_tab); - - /* free allocated function/request memory */ - free_reply_callbacks(getThis(), redis_sock); - - return 0; - -} -/* redis_sock_read_multibulk_multi_reply */ -PHPAPI int redis_sock_read_multibulk_multi_reply(INTERNAL_FUNCTION_PARAMETERS, - RedisSock *redis_sock) -{ - - char inbuf[1024]; - int numElems; - zval *z_tab; - - redis_check_eof(redis_sock TSRMLS_CC); - - php_stream_gets(redis_sock->stream, inbuf, 1024); - if(inbuf[0] != '*') { - return -1; - } - - /* number of responses */ - numElems = atoi(inbuf+1); - - if(numElems < 0) { - return -1; - } - - zval_dtor(return_value); - - MAKE_STD_ZVAL(z_tab); - array_init(z_tab); - - redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock, z_tab, numElems); - - *return_value = *z_tab; - efree(z_tab); - - return 0; -} - -void -free_reply_callbacks(zval *z_this, RedisSock *redis_sock) { - - fold_item *fi; - fold_item *head = redis_sock->head; - request_item *ri; - - for(fi = head; fi; ) { - fold_item *fi_next = fi->next; - free(fi); - fi = fi_next; - } - redis_sock->head = NULL; - redis_sock->current = NULL; - - for(ri = redis_sock->pipeline_head; ri; ) { - struct request_item *ri_next = ri->next; - free(ri->request_str); - free(ri); - ri = ri_next; - } - redis_sock->pipeline_head = NULL; - redis_sock->pipeline_current = NULL; -} - -/* exec */ -PHP_METHOD(Redis, exec) -{ - - RedisSock *redis_sock; - char *cmd; - int cmd_len; - zval *object; - struct request_item *ri; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", - &object, redis_ce) == FAILURE) { - RETURN_FALSE; - } - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - IF_MULTI() { - - cmd_len = redis_cmd_format_static(&cmd, "EXEC", ""); - - if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { - efree(cmd); - RETURN_FALSE; - } - efree(cmd); - - if (redis_sock_read_multibulk_multi_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock) < 0) { - zval_dtor(return_value); - free_reply_callbacks(object, redis_sock); - redis_sock->mode = ATOMIC; - redis_sock->watching = 0; - RETURN_FALSE; - } - free_reply_callbacks(object, redis_sock); - redis_sock->mode = ATOMIC; - redis_sock->watching = 0; - } - - IF_PIPELINE() { - - char *request = NULL; - int total = 0; - int offset = 0; - - /* compute the total request size */ - for(ri = redis_sock->pipeline_head; ri; ri = ri->next) { - total += ri->request_size; - } - if(total) { - request = malloc(total); - } - - /* concatenate individual elements one by one in the target buffer */ - for(ri = redis_sock->pipeline_head; ri; ri = ri->next) { - memcpy(request + offset, ri->request_str, ri->request_size); - offset += ri->request_size; - } - - if(request != NULL) { - if (redis_sock_write(redis_sock, request, total TSRMLS_CC) < 0) { - free(request); - free_reply_callbacks(object, redis_sock); - redis_sock->mode = ATOMIC; - RETURN_FALSE; - } - free(request); - } else { - redis_sock->mode = ATOMIC; - free_reply_callbacks(object, redis_sock); - array_init(return_value); /* empty array when no command was run. */ - return; - } - - if (redis_sock_read_multibulk_pipeline_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock) < 0) { - redis_sock->mode = ATOMIC; - free_reply_callbacks(object, redis_sock); - RETURN_FALSE; - } - redis_sock->mode = ATOMIC; - free_reply_callbacks(object, redis_sock); - } -} - -PHPAPI void fold_this_item(INTERNAL_FUNCTION_PARAMETERS, fold_item *item, RedisSock *redis_sock, zval *z_tab) { - item->fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, item->ctx TSRMLS_CC); -} - -PHPAPI int redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAMETERS, - RedisSock *redis_sock, zval *z_tab, int numElems) -{ - - fold_item *head = redis_sock->head; - fold_item *current = redis_sock->current; - for(current = head; current; current = current->next) { - fold_this_item(INTERNAL_FUNCTION_PARAM_PASSTHRU, current, redis_sock, z_tab); - } - redis_sock->current = current; - 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) { - RETURN_FALSE; - } - - /* if the flag is activated, send the command, the reply will be "QUEUED" or -ERR */ - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - redis_sock->mode = PIPELINE; - - /* - 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(getThis(), redis_sock); - - RETURN_ZVAL(getThis(), 1, 0); -} - -/* - publish channel message - @return the number of subscribers -*/ -PHP_METHOD(Redis, publish) -{ - zval *object; - RedisSock *redis_sock; - char *cmd, *key, *val; - int cmd_len, key_len, val_len, key_free; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", - &object, redis_ce, - &key, &key_len, &val, &val_len) == FAILURE) { - RETURN_NULL(); - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format_static(&cmd, "PUBLISH", "ss", key, key_len, val, val_len); - if(key_free) efree(key); - - 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); -} - -PHPAPI void generic_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sub_cmd) -{ - zval *object, *array, **data; - HashTable *arr_hash; - HashPosition pointer; - RedisSock *redis_sock; - char *cmd = "", *old_cmd = NULL, *key; - int cmd_len, array_count, key_len, key_free; - zval *z_tab, **tmp; - char *type_response; - - // Function call information - zend_fcall_info z_callback; - zend_fcall_info_cache z_callback_cache; - - zval *z_ret, **z_args[4]; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oaf", - &object, redis_ce, &array, &z_callback, &z_callback_cache) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - arr_hash = Z_ARRVAL_P(array); - array_count = zend_hash_num_elements(arr_hash); - - if (array_count == 0) { - RETURN_FALSE; - } - for (zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); - zend_hash_get_current_data_ex(arr_hash, (void**) &data, - &pointer) == SUCCESS; - zend_hash_move_forward_ex(arr_hash, &pointer)) { - - if (Z_TYPE_PP(data) == IS_STRING) { - char *old_cmd = NULL; - if(*cmd) { - old_cmd = cmd; - } - - // Grab our key and len - key = Z_STRVAL_PP(data); - key_len = Z_STRLEN_PP(data); - - // Prefix our key if neccisary - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - - cmd_len = spprintf(&cmd, 0, "%s %s", cmd, key); - - if(old_cmd) { - efree(old_cmd); - } - - // Free our key if it was prefixed - if(key_free) { - efree(key); - } - } - } - - old_cmd = cmd; - cmd_len = spprintf(&cmd, 0, "%s %s\r\n", sub_cmd, cmd); - efree(old_cmd); - if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { - efree(cmd); - RETURN_FALSE; - } - efree(cmd); - - /* read the status of the execution of the command `subscribe` */ - - z_tab = redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); - if(z_tab == NULL) { - RETURN_FALSE; - } - - if (zend_hash_index_find(Z_ARRVAL_P(z_tab), 0, (void**)&tmp) == SUCCESS) { - type_response = Z_STRVAL_PP(tmp); - if(strcmp(type_response, sub_cmd) != 0) { - efree(tmp); - efree(z_tab); - RETURN_FALSE; - } - } else { - efree(z_tab); - RETURN_FALSE; - } - efree(z_tab); - - // Set a pointer to our return value and to our arguments. - z_callback.retval_ptr_ptr = &z_ret; - z_callback.params = z_args; - z_callback.no_separation = 0; - - /* Multibulk Response, format : {message type, originating channel, message payload} */ - while(1) { - /* call the callback with this z_tab in argument */ - zval **type, **channel, **pattern, **data; - z_tab = redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); - int is_pmsg, tab_idx = 1; - - if(z_tab == NULL || Z_TYPE_P(z_tab) != IS_ARRAY) { - //ERROR - break; - } - - if (zend_hash_index_find(Z_ARRVAL_P(z_tab), 0, (void**)&type) == FAILURE || Z_TYPE_PP(type) != IS_STRING) { - break; - } - - // Make sure we have a message or pmessage - if(!strncmp(Z_STRVAL_PP(type), "message", 7) || !strncmp(Z_STRVAL_PP(type), "pmessage", 8)) { - // Is this a pmessage - is_pmsg = *Z_STRVAL_PP(type) == 'p'; - } else { - continue; // It's not a message or pmessage - } - - // If this is a pmessage, we'll want to extract the pattern first - if(is_pmsg) { - // Extract pattern - if(zend_hash_index_find(Z_ARRVAL_P(z_tab), tab_idx++, (void**)&pattern) == FAILURE) { - break; - } - } - - // Extract channel and data - if (zend_hash_index_find(Z_ARRVAL_P(z_tab), tab_idx++, (void**)&channel) == FAILURE) { - break; - } - if (zend_hash_index_find(Z_ARRVAL_P(z_tab), tab_idx++, (void**)&data) == FAILURE) { - break; - } - - // Always pass the Redis object through - z_args[0] = &getThis(); - - // Set up our callback args depending on the message type - if(is_pmsg) { - z_args[1] = pattern; - z_args[2] = channel; - z_args[3] = data; - } else { - z_args[1] = channel; - z_args[2] = data; - } - - // Set our argument information - z_callback.param_count = tab_idx; - - // Break if we can't call the function - if(zend_call_function(&z_callback, &z_callback_cache TSRMLS_CC) != SUCCESS) { - break; - } - - // If we have a return value, free it. Note, we could use the return value to break the subscribe loop - if(z_ret) zval_ptr_dtor(&z_ret); - - /* TODO: provide a way to break out of the loop. */ - zval_dtor(z_tab); - efree(z_tab); - } -} - -/* {{{ proto void Redis::psubscribe(Array(pattern1, pattern2, ... patternN)) - */ -PHP_METHOD(Redis, psubscribe) -{ - generic_subscribe_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "psubscribe"); -} - -/* {{{ proto void Redis::subscribe(Array(channel1, channel2, ... channelN)) - */ -PHP_METHOD(Redis, subscribe) { - generic_subscribe_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "subscribe"); -} - -/** - * [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 - * ); - **/ - -PHPAPI void generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *unsub_cmd) -{ - zval *object, *array, **data; - HashTable *arr_hash; - HashPosition pointer; - 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - arr_hash = Z_ARRVAL_P(array); - array_count = zend_hash_num_elements(arr_hash); - - if (array_count == 0) { - RETURN_FALSE; - } - - for (zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); - zend_hash_get_current_data_ex(arr_hash, (void**) &data, - &pointer) == SUCCESS; - zend_hash_move_forward_ex(arr_hash, &pointer)) { - - if (Z_TYPE_PP(data) == IS_STRING) { - char *old_cmd = NULL; - if(*cmd) { - old_cmd = cmd; - } - cmd_len = spprintf(&cmd, 0, "%s %s", cmd, Z_STRVAL_PP(data)); - if(old_cmd) { - efree(old_cmd); - } - } - } - - old_cmd = cmd; - cmd_len = spprintf(&cmd, 0, "%s %s\r\n", unsub_cmd, cmd); - efree(old_cmd); - - if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { - efree(cmd); - RETURN_FALSE; - } - efree(cmd); - - i = 1; - array_init(return_value); - - while( i <= array_count) { - z_tab = redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); - - if(Z_TYPE_P(z_tab) == IS_ARRAY) { - if (zend_hash_index_find(Z_ARRVAL_P(z_tab), 1, (void**)&z_channel) == FAILURE) { - RETURN_FALSE; - } - add_assoc_bool(return_value, Z_STRVAL_PP(z_channel), 1); - } else { - //error - efree(z_tab); - RETURN_FALSE; - } - efree(z_tab); - i ++; - } -} - -PHP_METHOD(Redis, unsubscribe) -{ - generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "UNSUBSCRIBE"); -} - -PHP_METHOD(Redis, punsubscribe) -{ - generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PUNSUBSCRIBE"); -} - -/* {{{ proto string Redis::bgrewriteaof() - */ -PHP_METHOD(Redis, bgrewriteaof) -{ - char *cmd; - int cmd_len = redis_cmd_format_static(&cmd, "BGREWRITEAOF", ""); - generic_empty_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, cmd, cmd_len); - -} -/* }}} */ - -/* {{{ proto string Redis::slaveof([host, port]) - */ -PHP_METHOD(Redis, slaveof) -{ - zval *object; - RedisSock *redis_sock; - char *cmd = "", *host = NULL; - int cmd_len, host_len; - long port = 6379; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|sl", - &object, redis_ce, &host, &host_len, &port) == FAILURE) { - RETURN_FALSE; - } - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - if(host && host_len) { - cmd_len = redis_cmd_format_static(&cmd, "SLAVEOF", "sd", host, host_len, (int)port); - } else { - cmd_len = redis_cmd_format_static(&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) -{ - zval *object; - RedisSock *redis_sock; - char *cmd = "", *info = NULL, *key = NULL; - int cmd_len, info_len, key_len; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oss", - &object, redis_ce, &info, &info_len, &key, &key_len) == FAILURE) { - RETURN_FALSE; - } - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - cmd_len = redis_cmd_format_static(&cmd, "OBJECT", "ss", info, info_len, key, key_len); - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - - if(info_len == 8 && (strncasecmp(info, "refcount", 8) == 0 || strncasecmp(info, "idletime", 8) == 0)) { - IF_ATOMIC() { - redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_long_response); - } else if(info_len == 8 && strncasecmp(info, "encoding", 8) == 0) { - IF_ATOMIC() { - redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_string_response); - } else { /* fail */ - IF_ATOMIC() { - redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_boolean_response); - } -} -/* }}} */ - -/* {{{ proto string Redis::getOption($option) - */ -PHP_METHOD(Redis, getOption) { - RedisSock *redis_sock; - zval *object; - long option; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", - &object, redis_ce, &option) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - switch(option) { - - case REDIS_OPT_SERIALIZER: - RETURN_LONG(redis_sock->serializer); - - case REDIS_OPT_PREFIX: - if(redis_sock->prefix) { - RETURN_STRINGL(redis_sock->prefix, redis_sock->prefix_len, 1); - } - RETURN_NULL(); - - case REDIS_OPT_READ_TIMEOUT: - RETURN_DOUBLE(redis_sock->read_timeout); - - default: - RETURN_FALSE; - - } -} -/* }}} */ - -/* {{{ proto string Redis::setOption(string $option, mixed $value) - */ -PHP_METHOD(Redis, setOption) { - RedisSock *redis_sock; - zval *object; - long option, val_long; - char *val_str; - int val_len; - struct timeval read_tv; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ols", - &object, redis_ce, &option, &val_str, &val_len) == FAILURE) { - RETURN_FALSE; - } - - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - switch(option) { - case REDIS_OPT_SERIALIZER: - val_long = atol(val_str); - if(val_long == REDIS_SERIALIZER_NONE -#ifdef HAVE_REDIS_IGBINARY - || val_long == REDIS_SERIALIZER_IGBINARY -#endif - || val_long == REDIS_SERIALIZER_PHP) { - redis_sock->serializer = val_long; - RETURN_TRUE; - } else { - RETURN_FALSE; - } - break; - - case REDIS_OPT_PREFIX: - if(redis_sock->prefix) { - efree(redis_sock->prefix); - } - if(val_len == 0) { - redis_sock->prefix = NULL; - redis_sock->prefix_len = 0; - } else { - redis_sock->prefix_len = val_len; - redis_sock->prefix = ecalloc(1+val_len, 1); - memcpy(redis_sock->prefix, val_str, val_len); - } - 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; - - default: - RETURN_FALSE; - } -} -/* }}} */ - -/* {{{ 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; - int key_len, val_len, cmd_len, op_len; - enum {CFG_GET, CFG_SET} mode; - - 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - if (mode == CFG_GET && val == NULL) { - cmd_len = redis_cmd_format_static(&cmd, "CONFIG", "ss", op, op_len, key, key_len); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len) - IF_ATOMIC() { - redis_sock_read_multibulk_reply_zipped_strings(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_zipped_strings); - - } else if(mode == CFG_SET && val != NULL) { - cmd_len = redis_cmd_format_static(&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 arg_len, cmd_len; - long option; - 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - 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_cmd_format_static(&cmd, "SLOWLOG", "sl", arg, arg_len, option); - } else { - cmd_len = redis_cmd_format_static(&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) < 0) { - RETURN_FALSE; - } - } - REDIS_PROCESS_RESPONSE(redis_read_variant_reply); -} - -// Construct an EVAL or EVALSHA command, with option argument array and number of arguments that are keys parameter -PHPAPI int -redis_build_eval_cmd(RedisSock *redis_sock, char **ret, char *keyword, char *value, int val_len, zval *args, int keys_count TSRMLS_DC) { - zval **elem; - HashTable *args_hash; - HashPosition hash_pos; - int cmd_len, args_count = 0; - int eval_cmd_count = 2; - - // If we've been provided arguments, we'll want to include those in our eval command - if(args != NULL) { - // Init our hash array value, and grab the count - args_hash = Z_ARRVAL_P(args); - args_count = zend_hash_num_elements(args_hash); - - // We only need to process the arguments if the array is non empty - if(args_count > 0) { - // Header for our EVAL command - cmd_len = redis_cmd_format_header(ret, keyword, eval_cmd_count + args_count); - - // Now append the script itself, and the number of arguments to treat as keys - cmd_len = redis_cmd_append_str(ret, cmd_len, value, val_len); - cmd_len = redis_cmd_append_int(ret, cmd_len, keys_count); - - // Iterate the values in our "keys" array - for(zend_hash_internal_pointer_reset_ex(args_hash, &hash_pos); - zend_hash_get_current_data_ex(args_hash, (void **)&elem, &hash_pos) == SUCCESS; - zend_hash_move_forward_ex(args_hash, &hash_pos)) - { - zval *z_tmp = NULL; - char *key, *old_cmd; - int key_len, key_free; - - if(Z_TYPE_PP(elem) == IS_STRING) { - key = Z_STRVAL_PP(elem); - key_len = Z_STRLEN_PP(elem); - } else { - // Convert it to a string - MAKE_STD_ZVAL(z_tmp); - *z_tmp = **elem; - zval_copy_ctor(z_tmp); - convert_to_string(z_tmp); - - key = Z_STRVAL_P(z_tmp); - key_len = Z_STRLEN_P(z_tmp); - } - - // Keep track of the old command pointer - old_cmd = *ret; - - // If this is still a key argument, prefix it if we've been set up to prefix keys - key_free = keys_count-- > 0 ? redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC) : 0; - - // Append this key to our EVAL command, free our old command - cmd_len = redis_cmd_format(ret, "%s$%d" _NL "%s" _NL, *ret, cmd_len, key_len, key, key_len); - efree(old_cmd); - - // Free our key, old command if we need to - if(key_free) efree(key); - - // Free our temporary zval (converted from non string) if we've got one - if(z_tmp) { - zval_dtor(z_tmp); - efree(z_tmp); - } - } - } - } - - // If there weren't any arguments (none passed, or an empty array), construct a standard no args command - if(args_count < 1) { - cmd_len = redis_cmd_format_static(ret, keyword, "sd", value, val_len, 0); - } - - // Return our command length - return cmd_len; -} - -/* {{{ proto variant Redis::evalsha(string script_sha1, [array keys, int num_key_args]) - */ -PHP_METHOD(Redis, evalsha) -{ - zval *object, *args= NULL; - char *cmd, *sha; - int cmd_len, sha_len; - long keys_count = 0; - RedisSock *redis_sock; - - if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|al", - &object, redis_ce, &sha, &sha_len, &args, &keys_count) == FAILURE) { - RETURN_FALSE; - } - - // Attempt to grab socket - if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - // Construct our EVALSHA command - cmd_len = redis_build_eval_cmd(redis_sock, &cmd, "EVALSHA", sha, sha_len, args, keys_count TSRMLS_CC); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL) < 0) { - RETURN_FALSE; - } - } - REDIS_PROCESS_RESPONSE(redis_read_variant_reply); -} - -/* {{{ proto variant Redis::eval(string script, [array keys, int num_key_args]) - */ -PHP_METHOD(Redis, eval) -{ - zval *object, *args = NULL; - RedisSock *redis_sock; - char *script, *cmd = ""; - int script_len, cmd_len; - long keys_count = 0; - - // Attempt to parse parameters - if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|al", - &object, redis_ce, &script, &script_len, &args, &keys_count) == FAILURE) { - RETURN_FALSE; - } - - // Attempt to grab socket - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - // Construct our EVAL command - cmd_len = redis_build_eval_cmd(redis_sock, &cmd, "EVAL", script, script_len, args, keys_count TSRMLS_CC); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL) < 0) { - RETURN_FALSE; - } - } - REDIS_PROCESS_RESPONSE(redis_read_variant_reply); -} - -PHPAPI int -redis_build_script_exists_cmd(char **ret, zval **argv, int argc) { - // Our command length and iterator - int cmd_len = 0, i; - - // Start building our command - cmd_len = redis_cmd_format_header(ret, "SCRIPT", argc + 1); // +1 for "EXISTS" - cmd_len = redis_cmd_append_str(ret, cmd_len, "EXISTS", 6); - - // Iterate our arguments - for(i=0;iprefix != NULL && redis_sock->prefix_len > 0) { - redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - RETURN_STRINGL(key, key_len, 0); - } else { - RETURN_STRINGL(key, key_len, 1); - } -} - -/* - * {{{ proto Redis::_unserialize(value) - */ -PHP_METHOD(Redis, _unserialize) { - zval *object; - RedisSock *redis_sock; - char *value; - int value_len; - - // Parse our arguments - if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, - &value, &value_len) == FAILURE) { - RETURN_FALSE; - } - // Grab socket - if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - // We only need to attempt unserialization if we have a serializer running - if(redis_sock->serializer != REDIS_SERIALIZER_NONE) { - zval *z_ret = NULL; - if(redis_unserialize(redis_sock, value, value_len, &z_ret TSRMLS_CC) == 0) { - // Badly formed input, throw an execption - zend_throw_exception(redis_exception_ce, "Invalid serialized data, or unserialization error", 0 TSRMLS_CC); - RETURN_FALSE; - } - RETURN_ZVAL(z_ret, 0, 1); - } else { - // Just return the value that was passed to us - RETURN_STRINGL(value, value_len, 1); - } -} - -/* - * {{{ 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - // Return our last error or NULL if we don't have one - if(redis_sock->err != NULL && redis_sock->err_len > 0) { - RETURN_STRINGL(redis_sock->err, redis_sock->err_len, 1); - } else { - 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - // Clear error message - if(redis_sock->err) { - efree(redis_sock->err); - } - redis_sock->err = NULL; - - RETURN_TRUE; -} - - -/* - * {{{ proto Redis::time() - */ -PHP_METHOD(Redis, time) { - zval *object; - RedisSock *redis_sock; - char *cmd; - int cmd_len; - - // 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - // Build TIME command - cmd_len = redis_cmd_format_static(&cmd, "TIME", ""); - - // Execute or queue command - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - if(redis_sock_read_multibulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { - RETURN_FALSE; - } - } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_raw); -} - -/* - * 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_STRING(redis_sock->host, 1); - } 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))) { - if(redis_sock->persistent_id != NULL) { - RETURN_STRING(redis_sock->persistent_id, 1); - } else { - RETURN_NULL(); - } - } else { - RETURN_FALSE; - } -} - -/* - * {{{ proto Redis::getAuth - */ -PHP_METHOD(Redis, getAuth) { - RedisSock *redis_sock; - - if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) { - if(redis_sock->auth != NULL) { - RETURN_STRING(redis_sock->auth, 1); - } else { - RETURN_NULL(); - } - } else { - RETURN_FALSE; - } -} - -/* - * $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; - int cmd_len, opt_len, arg_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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; - } - - // Build our CLIENT command - if(ZEND_NUM_ARGS() == 2) { - cmd_len = redis_cmd_format_static(&cmd, "CLIENT", "ss", opt, opt_len, - arg, arg_len); - } else { - cmd_len = redis_cmd_format_static(&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); - } - REDIS_PROCESS_RESPONSE(redis_read_variant_reply); - } -} - -/* vim: set tabstop=4 softtabstops=4 noexpandtab shiftwidth=4: */ diff -Nru php-redis-2.2.4/redis-2.2.4/redis_session.c php-redis-3.0.0/redis-2.2.4/redis_session.c --- php-redis-2.2.4/redis-2.2.4/redis_session.c 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/redis_session.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,458 +0,0 @@ -/* -*- 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 "common.h" -#include "ext/standard/info.h" -#include "php_redis.h" -#include "redis_session.h" -#include - -#include "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) -}; - -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; - -PHPAPI redis_pool* -redis_pool_new(TSRMLS_D) { - return ecalloc(1, sizeof(redis_pool)); -} - -PHPAPI 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; -} - -PHPAPI 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); - efree(rpm->redis_sock); - if(rpm->prefix) efree(rpm->prefix); - if(rpm->auth) efree(rpm->auth); - efree(rpm); - rpm = next; - } - efree(pool); -} - -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; - - if(!rpm->auth || !rpm->auth_len) { /* no password given. */ - return; - } - cmd_len = redis_cmd_format_static(&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_cmd_format_static(&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); -} - -PHPAPI 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, 0 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) { - MAKE_STD_ZVAL(params); - array_init(params); - - sapi_module.treat_data(PARSE_STRING, estrdup(url->query), params TSRMLS_CC); - - if (zend_hash_find(Z_ARRVAL_P(params), "weight", sizeof("weight"), (void **) ¶m) != FAILURE) { - convert_to_long_ex(param); - weight = Z_LVAL_PP(param); - } - if (zend_hash_find(Z_ARRVAL_P(params), "timeout", sizeof("timeout"), (void **) ¶m) != FAILURE) { - timeout = atof(Z_STRVAL_PP(param)); - } - if (zend_hash_find(Z_ARRVAL_P(params), "persistent", sizeof("persistent"), (void **) ¶m) != FAILURE) { - persistent = (atol(Z_STRVAL_PP(param)) == 1 ? 1 : 0); - } - if (zend_hash_find(Z_ARRVAL_P(params), "persistent_id", sizeof("persistent_id"), (void **) ¶m) != FAILURE) { - persistent_id = estrndup(Z_STRVAL_PP(param), Z_STRLEN_PP(param)); - } - if (zend_hash_find(Z_ARRVAL_P(params), "prefix", sizeof("prefix"), (void **) ¶m) != FAILURE) { - prefix = estrndup(Z_STRVAL_PP(param), Z_STRLEN_PP(param)); - } - if (zend_hash_find(Z_ARRVAL_P(params), "auth", sizeof("auth"), (void **) ¶m) != FAILURE) { - auth = estrndup(Z_STRVAL_PP(param), Z_STRLEN_PP(param)); - } - if (zend_hash_find(Z_ARRVAL_P(params), "database", sizeof("database"), (void **) ¶m) != FAILURE) { - convert_to_long_ex(param); - database = Z_LVAL_PP(param); - } - if (zend_hash_find(Z_ARRVAL_P(params), "retry_interval", sizeof("retry_interval"), (void **) ¶m) != FAILURE) { - convert_to_long_ex(param); - retry_interval = Z_LVAL_PP(param); - } - - zval_ptr_dtor(¶ms); - } - - if ((url->path == NULL && url->host == NULL) || weight <= 0 || timeout <= 0) { - php_url_free(url); - redis_pool_free(pool TSRMLS_CC); - PS_SET_MOD_DATA(NULL); - return FAILURE; - } - - RedisSock *redis_sock; - if(url->host) { - redis_sock = redis_sock_create(url->host, strlen(url->host), url->port, timeout, persistent, persistent_id, retry_interval, 0); - } else { /* unix */ - redis_sock = redis_sock_create(url->path, strlen(url->path), 0, timeout, persistent, persistent_id, retry_interval, 0); - } - 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 *session, *cmd; - int session_len, cmd_len; - - redis_pool *pool = PS_GET_MOD_DATA(); - redis_pool_member *rpm = redis_pool_get_sock(pool, key TSRMLS_CC); - RedisSock *redis_sock = rpm?rpm->redis_sock:NULL; - if(!rpm || !redis_sock){ - return FAILURE; - } - - /* send GET command */ - session = redis_session_key(rpm, key, strlen(key), &session_len); - cmd_len = redis_cmd_format_static(&cmd, "GET", "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 ((*val = redis_sock_read(redis_sock, vallen TSRMLS_CC)) == NULL) { - return FAILURE; - } - - 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(); - redis_pool_member *rpm = redis_pool_get_sock(pool, key TSRMLS_CC); - RedisSock *redis_sock = rpm?rpm->redis_sock:NULL; - if(!rpm || !redis_sock){ - return FAILURE; - } - - /* send SET command */ - session = redis_session_key(rpm, key, strlen(key), &session_len); - cmd_len = redis_cmd_format_static(&cmd, "SETEX", "sds", session, session_len, INI_INT("session.gc_maxlifetime"), val, vallen); - 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(); - redis_pool_member *rpm = redis_pool_get_sock(pool, key TSRMLS_CC); - RedisSock *redis_sock = rpm?rpm->redis_sock:NULL; - if(!rpm || !redis_sock){ - return FAILURE; - } - - /* send DEL command */ - session = redis_session_key(rpm, key, strlen(key), &session_len); - cmd_len = redis_cmd_format_static(&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; -} -/* }}} */ - -#endif -/* vim: set tabstop=4 expandtab: */ - diff -Nru php-redis-2.2.4/redis-2.2.4/redis_session.h php-redis-3.0.0/redis-2.2.4/redis_session.h --- php-redis-2.2.4/redis-2.2.4/redis_session.h 2013-09-02 07:57:58.000000000 +0000 +++ php-redis-3.0.0/redis-2.2.4/redis_session.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -#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); - - -#endif -#endif - diff -Nru php-redis-2.2.4/redis-3.0.0/arrays.markdown php-redis-3.0.0/redis-3.0.0/arrays.markdown --- php-redis-2.2.4/redis-3.0.0/arrays.markdown 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/arrays.markdown 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,155 @@ +Redis 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));
+
+ +#### 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
+
+ diff -Nru php-redis-2.2.4/redis-3.0.0/bybits.php php-redis-3.0.0/redis-3.0.0/bybits.php --- php-redis-2.2.4/redis-3.0.0/bybits.php 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/bybits.php 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,25 @@ + $count) { + echo $proto . "\t" . $count . "\n"; +} +?> diff -Nru php-redis-2.2.4/redis-3.0.0/cluster_library.c php-redis-3.0.0/redis-3.0.0/cluster_library.c --- php-redis-2.2.4/redis-3.0.0/cluster_library.c 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/cluster_library.c 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,2406 @@ +#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; + +/* Some debug methods that will go away when we're through with them */ + +/* +static void cluster_dump_nodes(redisCluster *c) { + redisClusterNode *p; + + for(zend_hash_internal_pointer_reset(c->nodes); + zend_hash_has_more_elements(c->nodes)==SUCCESS; + zend_hash_move_forward(c->nodes)) + { + p = zend_hash_get_current_data_ptr(c->nodes); + + 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"); + } +} + +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) +{ + size_t idx = 0, sz; + clusterReply *r; + long len; + char buf[1024]; + + while(elements-- > 0) { + element[idx] = ecalloc(1, sizeof(clusterReply)); + r = element[idx]; + + // 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) < 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,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; + } + + idx++; + } +} + +/* 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 */ +static void cluster_dist_free_ht(zval *p) { + 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, + size_t key_len, clusterKeyVal **kv) +{ + int key_free; + short slot; + clusterDistList *ppdl, *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((ppdl = zend_hash_index_find_ptr(ht, (ulong)slot)) == NULL) { + dl = cluster_dl_create(); + zend_hash_index_update_ptr(ht, (ulong)slot, dl); + } else { + dl = ppdl; + } + + // 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; + int val_free; + size_t val_len; + + // 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) +{ + if(err && err_len>0) { + if(err_len >= sizeof("CLUSTERDOWN")-1 && + !memcmp(err, "CLUSTERDOWN", sizeof("CLUSTERDOWN")-1)) + { + c->clusterdown = 1; + return; + } + + if(c->err == NULL) { + c->err = emalloc(err_len+1); + } else if(err_len > c->err_len) { + c->err = erealloc(c->err, err_len + 1); + } + memcpy(c->err,err,err_len); + c->err[err_len]='\0'; + c->err_len = err_len; + } else { + if(c->err) efree(c->err); + c->err = NULL; + c->err_len = 0; + } +} + +/* Destructor for slaves */ +static void ht_free_slave(zval *data) { + 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 < 3) + { + 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->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+1)) == NULL) { + master = cluster_node_create(c, host, hlen, port, low, 0); + zend_hash_str_update_ptr(c->nodes, key, klen+1, 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+1)) != 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 */ +static void ht_free_seed(zval *data) { + RedisSock *redis_sock = *(RedisSock**)data; + if(redis_sock) redis_free_socket(redis_sock); +} + +/* Free redisClusterNode objects we've stored */ +static void ht_free_node(zval *data) { + 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; + + /* 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) efree(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; + 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 */ + for (zend_hash_internal_pointer_reset(seeds); + zend_hash_has_more_elements(seeds) == SUCCESS; + zend_hash_move_forward(seeds)) + { + z_seeds[map[index++]] = zend_hash_get_current_data(seeds); + } + + 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; + + /* Grab our seeds in a randomized order */ + z_seeds = cluster_shuffle_seeds(ht_seeds, &count); + + /* Iterate over seeds array */ + for (i = 0; i < count; i++) { + z_seed = z_seeds[i]; + + /* Make sure seed is non null and a string */ + if (z_seed == NULL || Z_TYPE_P(z_seed) != IS_STRING) + continue; + + /* Grab 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->persistent, NULL, 0, 0); + + // Index this seed by host/port + key_len = snprintf(key, sizeof(key), "%s:%u", redis_sock->host, + redis_sock->port); + + // Add to our seed HashTable + zend_hash_str_update_ptr(cluster->seeds, key, key_len+1, redis_sock); + } + + /* Free shuffled seeds array */ + 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 + for(zend_hash_internal_pointer_reset(c->seeds); + !mapped && zend_hash_has_more_elements(c->seeds) == SUCCESS; + zend_hash_move_forward(c->seeds)) + { + // Grab the redis_sock for this seed + seed = zend_hash_get_current_data_ptr(c->seeds); + + // Attempt to connect to this seed node + if(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 && slots) { + memset(c->master, 0, sizeof(redisClusterNode*)*REDIS_CLUSTER_SLOTS); + } + redis_sock_disconnect(seed TSRMLS_CC); + } + + // 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)); + 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[1024]; + 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; + + for(zend_hash_internal_pointer_reset(c->nodes); + (node = zend_hash_get_current_data_ptr(c->nodes)) != NULL; + zend_hash_move_forward(c->nodes)) + { + redis_sock_disconnect(node->sock TSRMLS_CC); + node->sock->lazy_connect = 1; + } +} + +/* 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; + + /* Allocate enough memory for the master and all of our slaves */ + if (c->master[c->cmd_slot]->slaves) { + count += zend_hash_num_elements(c->master[c->cmd_slot]->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 = nomaster; i < count; i++) { + /* 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 commadn 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 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; + + /* 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 if (!cluster_dist_write(c, cmd, sz, 0 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 */ + for(zend_hash_internal_pointer_reset(c->nodes); + zend_hash_has_more_elements(c->nodes)==SUCCESS; + zend_hash_move_forward(c->nodes)) + { + /* Grab node */ + seed_node = zend_hash_get_current_data_ptr(c->nodes); + + /* Skip this node if it's the one that failed, or if it's a slave */ + if(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; + } + } + + /* 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) +{ + redisClusterNode *ret = NULL; + int key_len; + char key[1024]; + + key_len = snprintf(key,sizeof(key),"%s:%d", host, port); + + if((ret = zend_hash_str_find_ptr(c->nodes, key, key_len+1)) != NULL) { + return ret; + } + + /* Not found */ + return NULL; +} + +/* 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; + + /* 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); + + /* 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(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); + } + + /* 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 our response raw + CLUSTER_RETURN_STRING(c, resp, c->reply_len); +} + +/* 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) == 0) + { + CLUSTER_RETURN_STRING(c, resp, c->reply_len); + } else { + efree(resp); + } + } else { + zval z; + if (redis_unserialize(c->flags, resp, c->reply_len, &z TSRMLS_CC)) { + efree(resp); + add_next_index_zval(&c->multi_resp, &z); + } else { + add_next_index_stringl(&c->multi_resp, resp, c->reply_len); + } + } +} + +/* 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, z_ret, z_args[4]; + int pull=0; + + // Consume each MULTI BULK response (one per channel/pattern) + while(sctx->argc--) { + cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, pull, mbulk_resp_loop_raw, &z_tab); + + if(Z_TYPE(z_tab) == IS_UNDEF) { + 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 + sctx->cb.retval = &z_ret; + 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, *z_data; + int tab_idx=1, is_pmsg; + + // Get the next subscribe response + cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, 1, mbulk_resp_loop, &z_tab); + + if(Z_TYPE(z_tab) == IS_UNDEF || (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 + ZVAL_DUP(&z_args[0], getThis()); + + // Set up calbacks depending on type + if(is_pmsg) { + ZVAL_DUP(&z_args[1], z_pat); + ZVAL_DUP(&z_args[2], z_chan); + ZVAL_DUP(&z_args[3], z_data); + } else { + ZVAL_DUP(&z_args[1], z_chan); + ZVAL_DUP(&z_args[2], z_data); + } + + // 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 + if(Z_TYPE(z_ret) != IS_UNDEF) zval_ptr_dtor(&z_ret); + + zval_dtor(&z_tab); + } + + // We're no longer subscribing, due to an error + c->subscribed_slot = -1; + + // Cleanup + efree(sctx); + if(Z_TYPE(z_tab) != IS_UNDEF) { + zval_dtor(&z_tab); + } + + // 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--) { + cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, pull, mbulk_resp_loop_raw, &z_tab); + + // Fail if we didn't get an array or can't find index 1 + if(Z_TYPE(z_tab) == IS_UNDEF || (z_chan = zend_hash_index_find(Z_ARRVAL(z_tab), 1)) == NULL) + { + if(Z_TYPE(z_tab) != IS_UNDEF) { + 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 z_sub_ele; + 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); + else + add_next_index_null(z_ret); + break; + case TYPE_MULTIBULK: + 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 z_arr; + 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: + 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); + } + + ZVAL_DUP(return_value, &z_arr); + 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: + add_next_index_stringl(&c->multi_resp, r->str, r->len); + 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 z_result; + + /* Return FALSE if we didn't get a multi-bulk response */ + if (c->reply_type != TYPE_MULTIBULK) { + CLUSTER_RETURN_FALSE(c); + } + + /* Allocate our array */ + 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); + CLUSTER_RETURN_FALSE(c); + } + } + + // Success, make this array our return value + if(CLUSTER_IS_ATOMIC(c)) { + ZVAL_DUP(return_value, &z_result); + } 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 z_result; + 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)) { + ZVAL_DUP(return_value, &z_result); + } 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) +{ + zval z_result; + char *info; + + /* 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); + } + + /* Parse it and free the bulk string */ + redis_parse_client_list_response(info, &z_result); + efree(info); + + if (CLUSTER_IS_ATOMIC(c)) { + ZVAL_DUP(return_value, &z_result); + } else { + add_next_index_zval(&c->multi_resp, &z_result); + } +} + +/* MULTI BULK response loop where we might pull the next one */ +PHP_REDIS_API void cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS, + redisCluster *c, int pull, mbulk_cb cb, zval *z_result) +{ + // Pull our next response if directed + if(pull) { + if(cluster_check_response(c, &c->reply_type TSRMLS_CC)<0) + { + ZVAL_UNDEF(z_result); + return; + } + } + + // Validate reply type and length + if(c->reply_type != TYPE_MULTIBULK || c->reply_len == -1) { + ZVAL_UNDEF(z_result); + return; + } + + array_init(z_result); + + // Call our callback + if(cb(c->cmd_sock, z_result, c->reply_len, NULL TSRMLS_CC)==FAILURE) { + ZVAL_UNDEF(z_result); + return; + } + + return; +} + +/* MULTI MULTI BULK reply (for EXEC) */ +PHP_REDIS_API void cluster_multi_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS, + redisCluster *c, void *ctx) +{ + array_init(&c->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(&c->multi_resp); + RETURN_FALSE; + } + + fi->callback(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, fi->ctx); + } else { + /* Just add false */ + add_next_index_bool(&c->multi_resp, 0); + } + fi = fi->next; + } + + // Set our return array + zval_dtor(return_value); + ZVAL_DUP(return_value, &c->multi_resp); + zval_dtor(&c->multi_resp); +} + +/* 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_FALSE(return_value); + zval_dtor(mctx->z_multi); + efree(mctx->z_multi); + efree(mctx); + return; + } + + // Set our return if it's the last call + if(mctx->last) { + if(CLUSTER_IS_ATOMIC(c)) { + ZVAL_BOOL(return_value, Z_TYPE_P(mctx->z_multi) == IS_TRUE); + } else { + add_next_index_bool(&c->multi_resp, Z_TYPE_P(mctx->z_multi) == IS_TRUE); + } + 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); + } + + // 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 z; + if(redis_unserialize(redis_sock, line, line_len, &z TSRMLS_CC)==1) { + add_next_index_zval(z_result, &z); + efree(line); + } else { + add_next_index_stringl(z_result, line, line_len); + } + } 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; + int line_len, key_len; + 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 z; + if(redis_unserialize(redis_sock, line, line_len, &z TSRMLS_CC)==1) { + add_assoc_zval(z_result, key, &z); + efree(line); + } else { + add_assoc_stringl_ex(z_result, key, key_len, line, line_len); + } + 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; + int line_len, key_len; + 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 z; + if (redis_unserialize(redis_sock,key,key_len, &z TSRMLS_CC)) { + convert_to_string(&z); + add_assoc_double_ex(z_result, Z_STRVAL(z), Z_STRLEN(z), atof(line)); + 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 z; + if(redis_unserialize(redis_sock, line, line_len, &z TSRMLS_CC)==1) { + efree(line); + 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); + } + } 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]); + //efree(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 noexpandtab shiftwidth=4: */ diff -Nru php-redis-2.2.4/redis-3.0.0/cluster_library.h php-redis-3.0.0/redis-3.0.0/cluster_library.h --- php-redis-2.2.4/redis-3.0.0/cluster_library.h 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/cluster_library.h 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,463 @@ +#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 || \ + strlen(SLOT_SOCK(c,c->redir_slot)->host) != c->redir_host_len || \ + memcmp(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, 1 TSRMLS_CC); \ + } + +/* Clear out our "last error" */ +#define CLUSTER_CLEAR_ERROR(c) \ + if(c->err) { \ + efree(c->err); \ + c->err = NULL; \ + c->err_len = 0; \ + } \ + c->clusterdown = 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)) { \ + RETURN_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_rsrc_list_entry *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 { + /* Object reference for Zend */ + zend_object std; + + /* 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 */ + char *err; + int err_len; + + /* 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; +} 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, + size_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 void cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS, + redisCluster *c, int pull, mbulk_cb cb, zval *z_result); + +/* 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 noexpandtab shiftwidth=4: */ diff -Nru php-redis-2.2.4/redis-3.0.0/cluster.markdown php-redis-3.0.0/redis-3.0.0/cluster.markdown --- php-redis-2.2.4/redis-3.0.0/cluster.markdown 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/cluster.markdown 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,178 @@ +Redis 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. diff -Nru php-redis-2.2.4/redis-3.0.0/common.h php-redis-3.0.0/redis-3.0.0/common.h --- php-redis-2.2.4/redis-3.0.0/common.h 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/common.h 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,305 @@ +#include "php.h" +#include "php_ini.h" +#include + +#ifndef REDIS_COMMON_H +#define REDIS_COMMON_H + +/* NULL check so Eclipse doesn't go crazy */ +#ifndef NULL +#define NULL ((void *) 0) +#endif + +#define redis_sock_name "Redis Socket Buffer" +#define REDIS_SOCK_STATUS_FAILED 0 +#define REDIS_SOCK_STATUS_DISCONNECTED 1 +#define REDIS_SOCK_STATUS_UNKNOWN 2 +#define REDIS_SOCK_STATUS_CONNECTED 3 + +#define redis_multi_access_type_name "Redis Multi type access" + +#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 + +/* cluster options */ +#define REDIS_OPT_FAILOVER 5 +#define REDIS_FAILOVER_NONE 0 +#define REDIS_FAILOVER_ERROR 1 +#define REDIS_FAILOVER_DISTRIBUTE 2 + +/* 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 4294967295 + +/* Specific error messages we want to throw against */ +#define REDIS_ERR_LOADING_MSG "LOADING Redis is loading the dataset in memory" +#define REDIS_ERR_LOADING_KW "LOADING" +#define REDIS_ERR_AUTH_MSG "NOAUTH Authentication required." +#define REDIS_ERR_AUTH_KW "NOAUTH" +#define REDIS_ERR_SYNC_MSG "MASTERDOWN Link with MASTER is down and slave-serve-stale-data is set to 'no'" +#define REDIS_ERR_SYNC_KW "MASTERDOWN" + +#define IF_MULTI() if(redis_sock->mode == MULTI) +#define IF_MULTI_OR_ATOMIC() if(redis_sock->mode == MULTI || redis_sock->mode == ATOMIC)\ + +#define IF_MULTI_OR_PIPELINE() if(redis_sock->mode == MULTI || redis_sock->mode == PIPELINE) +#define IF_PIPELINE() if(redis_sock->mode == PIPELINE) +#define IF_NOT_MULTI() if(redis_sock->mode != MULTI) +#define IF_NOT_ATOMIC() if(redis_sock->mode != ATOMIC) +#define IF_ATOMIC() if(redis_sock->mode == ATOMIC) +#define ELSE_IF_MULTI() else if(redis_sock->mode == MULTI) { \ + if(redis_response_enqueued(redis_sock TSRMLS_CC) == 1) { \ + RETURN_ZVAL(getThis(), 1, 0);\ + } else { \ + RETURN_FALSE; \ + } \ +} + +#define ELSE_IF_PIPELINE() else IF_PIPELINE() { \ + RETURN_ZVAL(getThis(), 1, 0);\ +} + +#define MULTI_RESPONSE(callback) IF_MULTI_OR_PIPELINE() { \ + fold_item *f1, *current; \ + f1 = malloc(sizeof(fold_item)); \ + f1->fun = (void *)callback; \ + f1->next = NULL; \ + current = redis_sock->current;\ + if(current) current->next = f1; \ + redis_sock->current = f1; \ + } + +#define PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len) request_item *tmp; \ + struct request_item *current_request;\ + tmp = malloc(sizeof(request_item));\ + tmp->request_str = calloc(cmd_len, 1);\ + memcpy(tmp->request_str, cmd, cmd_len);\ + tmp->request_size = cmd_len;\ + tmp->next = NULL;\ + current_request = redis_sock->pipeline_current; \ + if(current_request) {\ + current_request->next = tmp;\ + } \ + redis_sock->pipeline_current = tmp; \ + if(NULL == redis_sock->pipeline_head) { \ + redis_sock->pipeline_head = redis_sock->pipeline_current;\ + } + +#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) \ + IF_MULTI_OR_PIPELINE() { \ + fold_item *f1, *current; \ + f1 = malloc(sizeof(fold_item)); \ + f1->fun = (void *)callback; \ + f1->ctx = closure_context; \ + f1->next = NULL; \ + current = redis_sock->current;\ + if(current) current->next = f1; \ + redis_sock->current = f1; \ + if(NULL == redis_sock->head) { \ + redis_sock->head = redis_sock->current;\ + }\ +} + +#define REDIS_ELSE_IF_MULTI(function, closure_context) \ + else if(redis_sock->mode == MULTI) { \ + if(redis_response_enqueued(redis_sock TSRMLS_CC) == 1) {\ + REDIS_SAVE_CALLBACK(function, closure_context); \ + RETURN_ZVAL(getThis(), 1, 0);\ + } else {\ + RETURN_FALSE;\ + }\ +} + +#define REDIS_ELSE_IF_PIPELINE(function, closure_context) \ + else IF_PIPELINE() { \ + REDIS_SAVE_CALLBACK(function, closure_context); \ + RETURN_ZVAL(getThis(), 1, 0); \ +} + +#define REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len) \ + IF_MULTI_OR_ATOMIC() { \ + SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len); \ + efree(cmd); \ + }\ + IF_PIPELINE() { \ + PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len); \ + efree(cmd); \ + } + +#define REDIS_PROCESS_RESPONSE_CLOSURE(function, closure_context) \ + REDIS_ELSE_IF_MULTI(function, closure_context) \ + REDIS_ELSE_IF_PIPELINE(function, closure_context); + +#define REDIS_PROCESS_RESPONSE(function) \ + 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_get(getThis(), &redis_sock TSRMLS_CC, 0)<0 || \ + 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); \ + } \ + 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_get(getThis(), &redis_sock TSRMLS_CC, 0)<0 || \ + 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); \ + } \ + REDIS_PROCESS_RESPONSE_CLOSURE(resp_func,ctx); + +/* 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=='-')))) + +typedef enum {ATOMIC, MULTI, PIPELINE} redis_mode; + +typedef struct fold_item { + zval * (*fun)(INTERNAL_FUNCTION_PARAMETERS, void *, ...); + void *ctx; + struct fold_item *next; +} fold_item; + +typedef struct request_item { + char *request_str; + int request_size; /* size_t */ + struct request_item *next; +} request_item; + +/* {{{ struct RedisSock */ +typedef struct { + php_stream *stream; + char *host; + short port; + char *auth; + double timeout; + double read_timeout; + long retry_interval; + int failed; + int status; + int persistent; + int watching; + char *persistent_id; + + int serializer; + long dbNumber; + + char *prefix; + int prefix_len; + + redis_mode mode; + fold_item *head; + fold_item *current; + + request_item *pipeline_head; + request_item *pipeline_current; + + char *err; + int err_len; + zend_bool lazy_connect; + + int scan; + + int readonly; +} RedisSock; +/* }}} */ + +void +free_reply_callbacks(zval *z_this, RedisSock *redis_sock); + +#endif diff -Nru php-redis-2.2.4/redis-3.0.0/config.m4 php-redis-3.0.0/redis-3.0.0/config.m4 --- php-redis-2.2.4/redis-3.0.0/config.m4 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/config.m4 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,103 @@ +dnl $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 diff -Nru php-redis-2.2.4/redis-3.0.0/config.w32 php-redis-3.0.0/redis-3.0.0/config.w32 --- php-redis-2.2.4/redis-3.0.0/config.w32 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/config.w32 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,27 @@ +// 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); + +} + diff -Nru php-redis-2.2.4/redis-3.0.0/COPYING php-redis-3.0.0/redis-3.0.0/COPYING --- php-redis-2.2.4/redis-3.0.0/COPYING 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/COPYING 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,68 @@ +-------------------------------------------------------------------- + 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 +. diff -Nru php-redis-2.2.4/redis-3.0.0/crc16.h php-redis-3.0.0/redis-3.0.0/crc16.h --- php-redis-2.2.4/redis-3.0.0/crc16.h 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/crc16.h 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,87 @@ +/* + * 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; +} diff -Nru php-redis-2.2.4/redis-3.0.0/CREDITS php-redis-3.0.0/redis-3.0.0/CREDITS --- php-redis-2.2.4/redis-3.0.0/CREDITS 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/CREDITS 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,5 @@ +Redis 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) diff -Nru php-redis-2.2.4/redis-3.0.0/library.c php-redis-3.0.0/redis-3.0.0/library.c --- php-redis-2.2.4/redis-3.0.0/library.c 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/library.c 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,2356 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "common.h" +#include "php_network.h" +#include +#ifndef _MSC_VER +#include /* TCP_NODELAY */ +#include +#endif +#include +#include +#include +#ifdef HAVE_REDIS_IGBINARY +#include "igbinary/igbinary.h" +#endif +#include +#include "php_redis.h" +#include "library.h" +#include "redis_commands.h" +#include +#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 + # if PHP_MAJOR_VERSION >= 7 + PHP_REDIS_API int usleep(unsigned int useconds); + # endif +#endif + +extern zend_class_entry *redis_ce; +extern zend_class_entry *redis_exception_ce; +extern zend_class_entry *spl_ce_RuntimeException; + +/* 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_cmd_format_static(&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_cmd_format_static(&cmd, "AUTH", "s", redis_sock->auth, + strlen(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(char *err, size_t err_len TSRMLS_DC) { + /* Handle stale data error (slave syncing with master) */ + if (err_len == sizeof(REDIS_ERR_SYNC_MSG) - 1 && + !memcmp(err,REDIS_ERR_SYNC_KW,sizeof(REDIS_ERR_SYNC_KW)-1)) + { + zend_throw_exception(redis_exception_ce, + "SYNC with master in progress or master down!", 0 TSRMLS_CC); + } else if (err_len == sizeof(REDIS_ERR_LOADING_MSG) - 1 && + !memcmp(err,REDIS_ERR_LOADING_KW,sizeof(REDIS_ERR_LOADING_KW)-1)) + { + zend_throw_exception(redis_exception_ce, + "Redis is LOADING the dataset", 0 TSRMLS_CC); + } else if (err_len == sizeof(REDIS_ERR_AUTH_MSG) -1 && + !memcmp(err,REDIS_ERR_AUTH_KW,sizeof(REDIS_ERR_AUTH_KW)-1)) + { + zend_throw_exception(redis_exception_ce, + "Failed to AUTH connection", 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 eof; + int count = 0; + + if (!redis_sock->stream) { + return -1; + } + + eof = php_stream_eof(redis_sock->stream); + for (; eof; count++) { + if((MULTI == redis_sock->mode) || redis_sock->watching || count == 10) { + /* too many failures */ + if(redis_sock->stream) { /* close stream if still here */ + 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; + } + if(!no_throw) { + zend_throw_exception(redis_exception_ce, "Connection lost", + 0 TSRMLS_CC); + } + return -1; + } + if(redis_sock->stream) { /* close existing stream before reconnecting */ + redis_stream_close(redis_sock TSRMLS_CC); + redis_sock->stream = NULL; + redis_sock->mode = ATOMIC; + redis_sock->watching = 0; + } + // 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); + } + redis_sock_connect(redis_sock TSRMLS_CC); /* reconnect */ + if(redis_sock->stream) { /* check for EOF again. */ + eof = php_stream_eof(redis_sock->stream); + } + } + + /* We've connected if we have a count */ + if (count) { + /* If we're using a password, attempt a reauthorization */ + if (redis_sock->auth && resend_auth(redis_sock TSRMLS_CC) != 0) { + return -1; + } + + /* If we're using a non-zero db, reselect it */ + if (redis_sock->dbNumber && reselect_db(redis_sock TSRMLS_CC) != 0) { + return -1; + } + } + + /* Success */ + return 0; +} + + +PHP_REDIS_API int +redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + REDIS_SCAN_TYPE type, 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_ret, z_resp, z_args[4]; + + /* Initialize our response as undefined */ + ZVAL_UNDEF(&z_resp); + + // Consume response(s) from subscribe, which will vary on argc + while(sctx->argc--) { + redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp); + if(Z_ISUNDEF(z_resp)) { + efree(sctx); + return -1; + } + + // We'll need to find the command response + if ((z_tmp = zend_hash_index_find(Z_ARRVAL_P(&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); + } + + sctx->cb.retval = &z_ret; + 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, *z_data; + HashTable *ht_tab; + int tab_idx=1, is_pmsg; + + /* Start as undefined */ + ZVAL_UNDEF(&z_resp); + + redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp); + if (Z_ISUNDEF(z_resp)) break; + + ht_tab = Z_ARRVAL_P(&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 + 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; + } + + // 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 + if(Z_TYPE(z_ret) != IS_UNDEF) zval_ptr_dtor(&z_ret); + + zval_dtor(&z_resp); + } + + // This is an error state, clean up + if (!Z_ISUNDEF(z_resp)) 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, z_ret, z_resp; + int i=0; + + ZVAL_UNDEF(&z_resp); + array_init(&z_ret); + + while(iargc) { + redis_sock_read_multibulk_reply_zval( INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp); + + if(Z_ISUNDEF(z_resp) || (z_chan = zend_hash_index_find(Z_ARRVAL_P(&z_resp), 1)) == NULL) + { + if (!Z_ISUNDEF(z_resp)) zval_dtor(&z_resp); + zval_dtor(&z_ret); + return -1; + } + + add_assoc_bool(&z_ret, Z_STRVAL_P(z_chan), 1); + + zval_dtor(&z_resp); + i++; + } + + efree(sctx); + + RETVAL_ZVAL(&z_ret, 0, 1); + + // Success + return 0; +} + +PHP_REDIS_API void +redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, + RedisSock *redis_sock, zval *z_tab) { + char inbuf[1024]; + int numElems; + + if(-1 == redis_check_eof(redis_sock, 0 TSRMLS_CC)) { + return; + } + + if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { + redis_stream_close(redis_sock TSRMLS_CC); + redis_sock->stream = NULL; + redis_sock->status = REDIS_SOCK_STATUS_FAILED; + redis_sock->mode = ATOMIC; + redis_sock->watching = 0; + zend_throw_exception(redis_exception_ce, + "read error on connection", 0 TSRMLS_CC); + return; + } + + if(inbuf[0] != '*') { + return; + } + numElems = atoi(inbuf+1); + + array_init(z_tab); + redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, numElems, UNSERIALIZE_ALL); +} + +/** + * redis_sock_read_bulk_reply + */ +PHP_REDIS_API char *redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes TSRMLS_DC) +{ + int offset = 0; + size_t got; + + char * reply; + + if(-1 == redis_check_eof(redis_sock, 0 TSRMLS_CC)) { + return NULL; + } + + if (bytes == -1) { + return NULL; + } else { + char c; + int i; + + reply = emalloc(bytes+1); + + while(offset < bytes) { + got = php_stream_read(redis_sock->stream, reply + offset, + bytes-offset); + if (got <= 0) { + /* Error or EOF */ + zend_throw_exception(redis_exception_ce, + "socket error on read socket", 0 TSRMLS_CC); + break; + } + offset += got; + } + for(i = 0; i < 2; i++) { + php_stream_read(redis_sock->stream, &c, 1); + } + } + + 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[1024]; + char *resp = NULL; + size_t err_len; + + if(-1 == redis_check_eof(redis_sock, 0 TSRMLS_CC)) { + return NULL; + } + + if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { + redis_stream_close(redis_sock TSRMLS_CC); + redis_sock->stream = NULL; + redis_sock->status = REDIS_SOCK_STATUS_FAILED; + redis_sock->mode = ATOMIC; + redis_sock->watching = 0; + zend_throw_exception(redis_exception_ce, "read error on connection", + 0 TSRMLS_CC); + return NULL; + } + + switch(inbuf[0]) { + case '-': + err_len = strlen(inbuf+1) - 2; + redis_sock_set_err(redis_sock, inbuf+1, err_len); + + /* Filter our ERROR through the few that should actually throw */ + redis_error_throw(inbuf + 1, err_len TSRMLS_CC); + + /* Handle stale data error */ + if(memcmp(inbuf + 1, "-ERR SYNC ", 10) == 0) { + zend_throw_exception(redis_exception_ce, + "SYNC with master in progress", 0 TSRMLS_CC); + } + return NULL; + case '$': + *buf_len = atoi(inbuf + 1); + resp = redis_sock_read_bulk_reply(redis_sock, *buf_len TSRMLS_CC); + return resp; + + case '*': + /* For null multi-bulk replies (like timeouts from brpoplpush): */ + if(memcmp(inbuf + 1, "-1", 2) == 0) { + *buf_len = -1; + return NULL; + } + /* fall through */ + + case '+': + case ':': + /* Single Line Reply */ + /* :123\r\n */ + *buf_len = strlen(inbuf) - 2; + if(*buf_len >= 2) { + resp = emalloc(1+*buf_len); + memcpy(resp, inbuf, *buf_len); + resp[*buf_len] = 0; + return resp; + } + default: + zend_throw_exception_ex( + redis_exception_ce, + 0 TSRMLS_CC, + "protocol error, got '%c' as reply type byte\n", + inbuf[0] + ); + } + + return NULL; +} + +void add_constant_long(zend_class_entry *ce, char *name, int value) { + zend_declare_class_constant_long(ce, name, strlen(name), value); +} + +int +integer_length(int i) { + int sz = 0; + int ci = abs(i); + while (ci > 0) { + ci /= 10; + sz++; + } + if (i == 0) { /* log 0 doesn't make sense. */ + sz = 1; + } else if (i < 0) { /* allow for neg sign as well. */ + sz++; + } + return sz; +} + +int +redis_cmd_format_header(char **ret, char *keyword, int arg_count) { + /* Our return buffer */ + smart_string buf = {0}; + + /* Keyword length */ + int l = strlen(keyword); + + smart_string_appendc(&buf, '*'); + smart_string_append_long(&buf, arg_count + 1); + smart_string_appendl(&buf, _NL, sizeof(_NL) -1); + smart_string_appendc(&buf, '$'); + smart_string_append_long(&buf, l); + smart_string_appendl(&buf, _NL, sizeof(_NL) -1); + smart_string_appendl(&buf, keyword, l); + smart_string_appendl(&buf, _NL, sizeof(_NL) - 1); + + /* Set our return pointer */ + *ret = buf.c; + + /* Return the length */ + return buf.len; +} + +int +redis_cmd_format_static(char **ret, char *keyword, char *format, ...) +{ + char *p = format; + va_list ap; + smart_string buf = {0}; + int l = strlen(keyword); + zend_string *dbl_str; + + va_start(ap, format); + + /* add header */ + smart_string_appendc(&buf, '*'); + smart_string_append_long(&buf, strlen(format) + 1); + smart_string_appendl(&buf, _NL, sizeof(_NL) - 1); + smart_string_appendc(&buf, '$'); + smart_string_append_long(&buf, l); + smart_string_appendl(&buf, _NL, sizeof(_NL) - 1); + smart_string_appendl(&buf, keyword, l); + smart_string_appendl(&buf, _NL, sizeof(_NL) - 1); + + while (*p) { + smart_string_appendc(&buf, '$'); + + switch(*p) { + case 's': { + char *val = va_arg(ap, char*); + int val_len = va_arg(ap, int); + smart_string_append_long(&buf, val_len); + smart_string_appendl(&buf, _NL, sizeof(_NL) - 1); + smart_string_appendl(&buf, val, val_len); + } + break; + case 'f': + case 'F': { + double d = va_arg(ap, double); + REDIS_DOUBLE_TO_STRING(dbl_str, d) + smart_string_append_long(&buf, dbl_str->len); + smart_string_appendl(&buf, _NL, sizeof(_NL) - 1); + smart_string_appendl(&buf, dbl_str->val, dbl_str->len); + zend_string_release(dbl_str); + } + break; + + case 'i': + case 'd': { + int i = va_arg(ap, int); + char tmp[32]; + int tmp_len = snprintf(tmp, sizeof(tmp), "%d", i); + smart_string_append_long(&buf, tmp_len); + smart_string_appendl(&buf, _NL, sizeof(_NL) - 1); + smart_string_appendl(&buf, tmp, tmp_len); + } + break; + case 'l': + case 'L': { + long l = va_arg(ap, long); + char tmp[32]; + int tmp_len = snprintf(tmp, sizeof(tmp), "%ld", l); + smart_string_append_long(&buf, tmp_len); + smart_string_appendl(&buf, _NL, sizeof(_NL) -1); + smart_string_appendl(&buf, tmp, tmp_len); + } + break; + } + p++; + smart_string_appendl(&buf, _NL, sizeof(_NL) - 1); + } + smart_string_0(&buf); + + *ret = buf.c; + + return buf.len; +} + +/** + * This command behave somehow like printf, except that strings need 2 + * arguments: + * Their data and their size (strlen). + * Supported formats are: %d, %i, %s, %l + */ +int +redis_cmd_format(char **ret, char *format, ...) { + + smart_string buf = {0}; + va_list ap; + char *p = format; + zend_string *dbl_str; + + va_start(ap, format); + + while (*p) { + if (*p == '%') { + switch (*(++p)) { + case 's': { + char *tmp = va_arg(ap, char*); + int tmp_len = va_arg(ap, int); + smart_string_appendl(&buf, tmp, tmp_len); + } + break; + + case 'F': + case 'f': { + double d = va_arg(ap, double); + REDIS_DOUBLE_TO_STRING(dbl_str, d) + smart_string_append_long(&buf, dbl_str->len); + smart_string_appendl(&buf, _NL, sizeof(_NL) - 1); + smart_string_appendl(&buf, dbl_str->val, dbl_str->len); + efree(dbl_str); + } + break; + + case 'd': + case 'i': { + int i = va_arg(ap, int); + char tmp[32]; + int tmp_len = snprintf(tmp, sizeof(tmp), "%d", i); + smart_string_appendl(&buf, tmp, tmp_len); + } + break; + } + } else { + smart_string_appendc(&buf, *p); + } + + p++; + } + + smart_string_0(&buf); + + *ret = buf.c; + + return buf.len; +} + +/* + * Append a command sequence to a Redis command + */ +int redis_cmd_append_str(char **cmd, int cmd_len, char *append, int append_len) { + /* Smart string buffer */ + smart_string buf = {0}; + + /* Append the current command to our smart_string */ + smart_string_appendl(&buf, *cmd, cmd_len); + + /* Append our new command sequence */ + smart_string_appendc(&buf, '$'); + smart_string_append_long(&buf, append_len); + smart_string_appendl(&buf, _NL, sizeof(_NL) -1); + smart_string_appendl(&buf, append, append_len); + smart_string_appendl(&buf, _NL, sizeof(_NL) -1); + + /* Free our old command */ + efree(*cmd); + + /* Set our return pointer */ + *cmd = buf.c; + + /* Return new command length */ + return buf.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) { + zend_string *dbl_str; + int retval; + + /* Convert to double */ + REDIS_DOUBLE_TO_STRING(dbl_str, value); + + // Append the string + retval = redis_cmd_append_sstr(str, dbl_str->val, dbl_str->len); + + /* Free our double string */ + zend_string_release(dbl_str); + + /* Return new length */ + return retval; +} + +/* + * Append an integer command to a Redis command + */ +int redis_cmd_append_int(char **cmd, int cmd_len, int append) { + char int_buf[32]; + int int_len; + + // Conver to an int, capture length + int_len = snprintf(int_buf, sizeof(int_buf), "%d", append); + + /* Return the new length */ + return redis_cmd_append_str(cmd, cmd_len, int_buf, int_len); +} + +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_MULTI_OR_PIPELINE() { + add_next_index_bool(z_tab, 0); + } else { + RETURN_FALSE; + } + return; + } + + ret = atof(response); + efree(response); + IF_MULTI_OR_PIPELINE() { + 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_MULTI_OR_PIPELINE() { + add_next_index_bool(z_tab, 0); + } 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_MULTI_OR_PIPELINE() { + 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 z_ret; + + /* 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_MULTI_OR_PIPELINE() { + 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 *key, *value, *p, *cur, *pos; + int is_numeric; + + array_init(z_ret); + + cur = response; + while(1) { + /* skip comments and empty lines */ + if(*cur == '#' || *cur == '\r') { + if(!(cur = strchr(cur, '\n'))) + break; + cur++; + continue; + } + + /* key */ + pos = strchr(cur, ':'); + if(pos == NULL) { + break; + } + key = emalloc(pos - cur + 1); + memcpy(key, cur, pos-cur); + key[pos-cur] = 0; + + /* value */ + cur = pos + 1; + pos = strchr(cur, '\r'); + if(pos == NULL) { + break; + } + value = emalloc(pos - cur + 1); + memcpy(value, cur, pos-cur); + value[pos-cur] = 0; + pos += 2; /* \r, \n */ + cur = pos; + + is_numeric = 1; + for(p = value; *p; ++p) { + if(*p < '0' || *p > '9') { + is_numeric = 0; + break; + } + } + + if(is_numeric == 1) { + add_assoc_long(z_ret, key, atol(value)); + } else { + add_assoc_string(z_ret, key, value); + } + efree(value); + + efree(key); + } +} + +/* + * 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; + zval z_ret; + + /* Make sure we can read the bulk response from Redis */ + if ((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC)) == NULL) { + RETURN_FALSE; + } + + /* 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_MULTI_OR_PIPELINE() { + 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_result) { + zval z_sub_result; + 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_result); + + /* Allocate memory for one user (there should be at least one, namely us!) */ + 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 = emalloc(klen + 1); + strncpy(key, kpos, klen); + key[klen] = 0; + + /* Allocate, copy in our value */ + value = emalloc(p-lpos+1); + strncpy(value,lpos,p-lpos+1); + value[p-lpos]=0; + + /* 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)); + efree(value); + } else { + add_assoc_string(&z_sub_result, key, 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_result, &z_sub_result); + + /* If we have another user, make another one */ + if(*(p+1) != '\0') { + array_init(&z_sub_result); + } + } + + // Free our key + efree(key); + } else { + // Something is wrong + zval_dtor(z_result); + ZVAL_BOOL(z_result, 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++; + } + + /* Return our parsed response */ + return; +} + +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; + char ret; + + if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { + IF_MULTI_OR_PIPELINE() { + add_next_index_bool(z_tab, 0); + return; + } + RETURN_FALSE; + } + ret = response[0]; + efree(response); + + IF_MULTI_OR_PIPELINE() { + if (ret == '+') { + if (success_callback != NULL) { + success_callback(redis_sock); + } + add_next_index_bool(z_tab, 1); + } else { + add_next_index_bool(z_tab, 0); + } + } else { + if (ret == '+') { + if (success_callback != NULL) { + success_callback(redis_sock); + } + RETURN_TRUE; + } else { + RETURN_FALSE; + } + } +} + +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_MULTI_OR_PIPELINE() { + 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_MULTI_OR_PIPELINE() { + if(ret > LONG_MAX) { /* overflow */ + add_next_index_stringl(z_tab, response+1, response_len-1); + } else { + efree(response); + add_next_index_long(z_tab, (long)ret); + } + } else { + if(ret > LONG_MAX) { /* overflow */ + RETURN_STRINGL(response+1, response_len-1); + } else { + efree(response); + RETURN_LONG((long)ret); + } + } + } else { + efree(response); + IF_MULTI_OR_PIPELINE() { + add_next_index_null(z_tab); + } else { + RETURN_FALSE; + } + } +} + +/* 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 z_ret; + 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)) { + + char *hkey, *hval; + zend_string *tablekey; + int hkey_len; + zend_ulong idx; + zval *z_key_p, *z_value_p; + + zend_hash_get_current_key(keytable, &tablekey, &idx); + 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 */ + convert_to_string(z_key_p); + hkey = Z_STRVAL_P(z_key_p); + hkey_len = Z_STRLEN_P(z_key_p); + + /* move forward */ + zend_hash_move_forward(keytable); + + /* fetch again */ + zend_hash_get_current_key(keytable, &tablekey, &idx); + if((z_value_p = zend_hash_get_current_data(keytable)) == NULL) { + continue; /* this should never happen, according to the PHP people. */ + } + + /* get current value, a hash value now. */ + 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, hkey, hkey_len, atoi(hval+1)); + } else if (decode == SCORE_DECODE_DOUBLE) { + add_assoc_double_ex(&z_ret, hkey, hkey_len, atof(hval)); + } else { + zval z; + ZVAL_DUP(&z, z_value_p); + add_assoc_zval_ex(&z_ret, hkey, hkey_len, &z); + } + } + + /* replace */ + zval_dtor(z_tab); + ZVAL_DUP(z_tab, &z_ret); + 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[1024]; + int numElems; + zval z_multi_result; + + if(-1 == redis_check_eof(redis_sock, 0 TSRMLS_CC)) { + return -1; + } + if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { + redis_stream_close(redis_sock TSRMLS_CC); + redis_sock->stream = NULL; + redis_sock->status = REDIS_SOCK_STATUS_FAILED; + redis_sock->mode = ATOMIC; + redis_sock->watching = 0; + zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); + return -1; + } + + if(inbuf[0] != '*') { + IF_MULTI_OR_PIPELINE() { + add_next_index_bool(z_tab, 0); + } else { + RETVAL_FALSE; + } + return -1; + } + numElems = atoi(inbuf+1); + 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_MULTI_OR_PIPELINE() { + add_next_index_zval(z_tab, &z_multi_result); + } else { + ZVAL_DUP(return_value, &z_multi_result); + zval_dtor(&z_multi_result); + } + + 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; + char ret; + + if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) + == NULL) + { + IF_MULTI_OR_PIPELINE() { + add_next_index_bool(z_tab, 0); + return; + } else { + RETURN_FALSE; + } + } + ret = response[1]; + efree(response); + + IF_MULTI_OR_PIPELINE() { + if(ret == '1') { + add_next_index_bool(z_tab, 1); + } else { + add_next_index_bool(z_tab, 0); + } + } else { + if (ret == '1') { + RETURN_TRUE; + } else { + RETURN_FALSE; + } + } +} + +PHP_REDIS_API void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { + + char *response; + int response_len; + zval z; + + /* Handle null response */ + if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { + IF_MULTI_OR_PIPELINE() { + add_next_index_bool(z_tab, 0); + return; + } + RETURN_FALSE; + } + + /* Add to multi/exec tabulation or directly */ + IF_MULTI_OR_PIPELINE() { + if(redis_unserialize(redis_sock, response, response_len, &z) == 1) { + 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) == 0) + { + 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_MULTI_OR_PIPELINE() { + add_next_index_bool(z_tab, 0); + return; + } + RETURN_FALSE; + } + + IF_MULTI_OR_PIPELINE() { + 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; + zval z_result; + + /* 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_MULTI_OR_PIPELINE() { + add_next_index_bool(z_tab, 0); + return; + } + RETURN_FALSE; + } + + 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_MULTI_OR_PIPELINE() { + 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, + int persistent, char *persistent_id, long retry_interval, + zend_bool lazy_connect) +{ + RedisSock *redis_sock; + + redis_sock = ecalloc(1, sizeof(RedisSock)); + redis_sock->host = estrndup(host, host_len); + 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; + + if(persistent_id) { + size_t persistent_id_len = strlen(persistent_id); + redis_sock->persistent_id = ecalloc(persistent_id_len + 1, 1); + memcpy(redis_sock->persistent_id, persistent_id, persistent_id_len); + } else { + redis_sock->persistent_id = NULL; + } + + memcpy(redis_sock->host, host, host_len); + redis_sock->host[host_len] = '\0'; + + redis_sock->port = port; + redis_sock->timeout = timeout; + redis_sock->read_timeout = timeout; + + redis_sock->serializer = REDIS_SERIALIZER_NONE; + redis_sock->mode = ATOMIC; + redis_sock->head = NULL; + redis_sock->current = NULL; + redis_sock->pipeline_head = NULL; + redis_sock->pipeline_current = NULL; + + redis_sock->err = NULL; + redis_sock->err_len = 0; + + 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 = NULL, *persistent_id = NULL; + zend_string *errstr; + const char *fmtstr = "%s:%d"; + int host_len, 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(redis_sock->host[0] == '/' && redis_sock->port < 1) { + host_len = spprintf(&host, 0, "unix://%s", redis_sock->host); + } 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(redis_sock->host, ':') != NULL) { + fmtstr = "[%s]:%d"; + } +#endif + host_len = spprintf(&host, 0, fmtstr, redis_sock->host, redis_sock->port); + } + + if (redis_sock->persistent) { + if (redis_sock->persistent_id) { + spprintf(&persistent_id, 0, "phpredis:%s:%s", host, + 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, &errstr, &err); + + if (persistent_id) { + efree(persistent_id); + } + + efree(host); + + if (!redis_sock->stream) { + if (errstr) efree(errstr); + return -1; + } + + /* set TCP_NODELAY */ + sock = (php_netstream_data_t*)redis_sock->stream->abstract; + setsockopt(sock->socket, IPPROTO_TCP, TCP_NODELAY, (char *) &tcp_flag, + sizeof(int)); + + php_stream_auto_cleanup(redis_sock->stream); + + if(tv.tv_sec != 0 || 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, int force_connect 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; + case REDIS_SOCK_STATUS_UNKNOWN: + if (force_connect > 0 && redis_sock_connect(redis_sock TSRMLS_CC) < 0) { + res = -1; + } else { + res = 0; + + redis_sock->status = REDIS_SOCK_STATUS_CONNECTED; + } + 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->stream && !redis_sock->persistent) { + php_stream_close(redis_sock->stream); + } + redis_sock->stream = NULL; + + return 1; + } + + return 0; +} + +PHP_REDIS_API void redis_send_discard(INTERNAL_FUNCTION_PARAMETERS, + RedisSock *redis_sock) +{ + char *cmd; + int response_len, cmd_len; + char * response; + + cmd_len = redis_cmd_format_static(&cmd, "DISCARD", ""); + + if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { + efree(cmd); + RETURN_FALSE; + } + efree(cmd); + + if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) + == NULL) + { + RETURN_FALSE; + } + + RETVAL_BOOL(response_len == 3 && strncmp(response, "+OK", 3) == 0); + + efree(response); +} + +/** + * redis_sock_set_err + */ +PHP_REDIS_API int redis_sock_set_err(RedisSock *redis_sock, const char *msg, + int msg_len) +{ + // Allocate/Reallocate our last error member + if(msg != NULL && msg_len > 0) { + if(redis_sock->err == NULL) { + redis_sock->err = emalloc(msg_len + 1); + } else if(msg_len > redis_sock->err_len) { + redis_sock->err = erealloc(redis_sock->err, msg_len +1); + } + + // Copy in our new error message, set new length, and null terminate + memcpy(redis_sock->err, msg, msg_len); + redis_sock->err[msg_len] = '\0'; + redis_sock->err_len = msg_len; + } else { + // Free our last error + if(redis_sock->err != NULL) { + efree(redis_sock->err); + } + + // Set to null, with zero length + redis_sock->err = NULL; + redis_sock->err_len = 0; + } + + // Success + return 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[1024]; + int numElems, err_len; + zval z_multi_result; + + if(-1 == redis_check_eof(redis_sock, 0 TSRMLS_CC)) { + return -1; + } + if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { + redis_stream_close(redis_sock TSRMLS_CC); + redis_sock->stream = NULL; + redis_sock->status = REDIS_SOCK_STATUS_FAILED; + redis_sock->mode = ATOMIC; + redis_sock->watching = 0; + zend_throw_exception(redis_exception_ce, "read error on connection", 0 + TSRMLS_CC); + return -1; + } + + if(inbuf[0] != '*') { + IF_MULTI_OR_PIPELINE() { + add_next_index_bool(z_tab, 0); + } else { + if (inbuf[0] == '-') { + err_len = strlen(inbuf+1) - 2; + redis_sock_set_err(redis_sock, inbuf+1, err_len); + } + RETVAL_FALSE; + } + return -1; + } + numElems = atoi(inbuf+1); + 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_MULTI_OR_PIPELINE() { + add_next_index_zval(z_tab, &z_multi_result); + } else { + RETVAL_ZVAL(&z_multi_result, 0, 1); + } + + 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[1024]; + int numElems, err_len; + zval z_multi_result; + + if(-1 == redis_check_eof(redis_sock, 0 TSRMLS_CC)) { + return -1; + } + if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { + redis_stream_close(redis_sock TSRMLS_CC); + redis_sock->stream = NULL; + redis_sock->status = REDIS_SOCK_STATUS_FAILED; + redis_sock->mode = ATOMIC; + redis_sock->watching = 0; + zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); + return -1; + } + + if(inbuf[0] != '*') { + IF_MULTI_OR_PIPELINE() { + add_next_index_bool(z_tab, 0); + } else { + if (inbuf[0] == '-') { + err_len = strlen(inbuf+1) - 2; + redis_sock_set_err(redis_sock, inbuf+1, err_len); + } + RETVAL_FALSE; + } + return -1; + } + numElems = atoi(inbuf+1); + 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_MULTI_OR_PIPELINE() { + add_next_index_zval(z_tab, &z_multi_result); + } else { + ZVAL_COPY_VALUE(return_value, &z_multi_result); + } + 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 len; + + while(count > 0) { + line = redis_sock_read(redis_sock, &len TSRMLS_CC); + if (line != NULL) { + zval z; + int unwrap; + + /* 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! */ + unwrap = unserialize == UNSERIALIZE_ALL || + (unserialize == UNSERIALIZE_KEYS && count % 2 == 0) || + (unserialize == UNSERIALIZE_VALS && count % 2 != 0); + + if (unwrap && redis_unserialize(redis_sock, line, len, &z TSRMLS_CC)) { + add_next_index_zval(z_tab, &z); + } else { + add_next_index_stringl(z_tab, line, len); + } + efree(line); + } else { + add_next_index_bool(z_tab, 0); + } + + count--; + } +} + +/* 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[1024], *response; + int response_len; + int i, numElems; + zval z_multi_result; + + zval *z_keys = ctx; + + if(-1 == redis_check_eof(redis_sock, 0 TSRMLS_CC)) { + return -1; + } + if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { + redis_stream_close(redis_sock TSRMLS_CC); + redis_sock->stream = NULL; + redis_sock->status = REDIS_SOCK_STATUS_FAILED; + redis_sock->mode = ATOMIC; + redis_sock->watching = 0; + zend_throw_exception(redis_exception_ce, "read error on connection", 0 TSRMLS_CC); + return -1; + } + + if(inbuf[0] != '*') { + IF_MULTI_OR_PIPELINE() { + add_next_index_bool(z_tab, 0); + } else { + RETVAL_FALSE; + } + return -1; + } + numElems = atoi(inbuf+1); + 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 z; + if(redis_unserialize(redis_sock, response, response_len, &z) == 1) { + 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_MULTI_OR_PIPELINE() { + add_next_index_zval(z_tab, &z_multi_result); + } else { + ZVAL_DUP(return_value, &z_multi_result); + zval_dtor(&z_multi_result); + } + 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); + return -1; + } + if(-1 == redis_check_eof(redis_sock, 0 TSRMLS_CC)) { + return -1; + } + return php_stream_write(redis_sock->stream, cmd, sz); +} + +/** + * redis_free_socket + */ +PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock) +{ + if(redis_sock->prefix) { + efree(redis_sock->prefix); + } + if(redis_sock->err) { + efree(redis_sock->err); + } + if(redis_sock->auth) { + efree(redis_sock->auth); + } + if(redis_sock->persistent_id) { + efree(redis_sock->persistent_id); + } + efree(redis_sock->host); + efree(redis_sock); +} + +PHP_REDIS_API int +redis_serialize(RedisSock *redis_sock, zval *z, char **val, size_t *val_len + TSRMLS_DC) +{ +#if ZEND_MODULE_API_NO >= 20100000 + php_serialize_data_t ht; +#else + HashTable ht; +#endif + smart_str sstr = {0}; + zval z_copy; +#ifdef HAVE_REDIS_IGBINARY + size_t sz; + uint8_t *val8; +#endif + + 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); + return 0; + + case IS_OBJECT: + ZVAL_STRINGL(&z_copy, "Object", 6); + break; + + case IS_ARRAY: + ZVAL_STRINGL(&z_copy, "Array", 5); + break; + + default: /* copy */ + ZVAL_DUP(&z_copy, z); + break; + } + + /* return string */ + convert_to_string(&z_copy); + *val = estrndup(Z_STRVAL_P(&z_copy), Z_STRLEN_P(&z_copy)); + *val_len = Z_STRLEN_P(&z_copy); + zval_ptr_dtor(&z_copy); + return 1; + + case REDIS_SERIALIZER_PHP: + PHP_VAR_SERIALIZE_INIT(ht); + php_var_serialize(&sstr, z, &ht TSRMLS_CC); + *val = estrndup(sstr.s->val, sstr.s->len); + *val_len = sstr.s->len; + smart_str_free(&sstr); + PHP_VAR_SERIALIZE_DESTROY(ht); + + return 1; + + case REDIS_SERIALIZER_IGBINARY: +#ifdef HAVE_REDIS_IGBINARY + if(igbinary_serialize(&val8, &sz, z TSRMLS_CC) == 0) { + *val = (char*)val8; + *val_len = (int)sz; + return 0; + } +#endif + return 0; + } + return 0; +} + +PHP_REDIS_API int +redis_unserialize(RedisSock* redis_sock, const char *val, int val_len, + zval *return_value TSRMLS_DC) +{ + + php_unserialize_data_t var_hash; + int ret; + + switch(redis_sock->serializer) { + case REDIS_SERIALIZER_NONE: + return 0; + + case REDIS_SERIALIZER_PHP: + PHP_VAR_UNSERIALIZE_INIT(var_hash); + if(!php_var_unserialize(return_value, (const unsigned char**)&val, + (const unsigned char*)val + val_len, &var_hash TSRMLS_CC)) { + ret = 0; + } else { + ret = 1; + } + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + return ret; + + case REDIS_SERIALIZER_IGBINARY: +#ifdef HAVE_REDIS_IGBINARY + if(igbinary_unserialize((const uint8_t *)val, (size_t)val_len, + return_value TSRMLS_CC) == 0) + { + return 1; + } +#endif + return 0; + break; + } + return 0; +} + +PHP_REDIS_API int +redis_key_prefix(RedisSock *redis_sock, char **key, size_t *key_len) { + int ret_len; + char *ret; + + if(redis_sock->prefix == NULL || redis_sock->prefix_len == 0) { + return 0; + } + + ret_len = redis_sock->prefix_len + *key_len; + ret = ecalloc(1 + ret_len, 1); + memcpy(ret, redis_sock->prefix, redis_sock->prefix_len); + memcpy(ret + redis_sock->prefix_len, *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(redis_sock TSRMLS_CC); + redis_sock->stream = NULL; + redis_sock->status = REDIS_SOCK_STATUS_FAILED; + redis_sock->mode = ATOMIC; + redis_sock->watching = 0; + + // Throw a read error exception + zend_throw_exception(redis_exception_ce, "read error on connection", + 0 TSRMLS_CC); + } + + /* 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 + 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); + } + + // 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[1024]; + 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) { + /* Handle throwable errors */ + redis_error_throw(inbuf, line_size TSRMLS_CC); + + /* Set our last error */ + redis_sock_set_err(redis_sock, inbuf, line_size); + + /* 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; + } else { + 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 z_subelem, *z_subelem_p; + z_subelem_p = &z_subelem; + + // 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: + redis_read_variant_line(redis_sock, reply_type, &z_subelem_p + TSRMLS_CC); + add_next_index_zval(*z_ret, z_subelem_p); + 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 + redis_read_variant_bulk(redis_sock, reply_info, &z_subelem_p + TSRMLS_CC); + add_next_index_zval(*z_ret, z_subelem_p); + break; + case TYPE_MULTIBULK: + // Construct an array for our sub element, and add it, + // and recurse + array_init(z_subelem_p); + add_next_index_zval(*z_ret, z_subelem_p); + redis_read_multibulk_recursive(redis_sock, reply_info, + &z_subelem_p 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; + zval z_ret, *z_ret_p; + z_ret_p = &z_ret; + + // Attempt to read our header + if(redis_read_reply_type(redis_sock,&reply_type,&reply_info TSRMLS_CC) < 0) + { + return -1; + } + + /* 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_p TSRMLS_CC); + break; + case TYPE_INT: + ZVAL_LONG(z_ret_p, reply_info); + break; + case TYPE_BULK: + redis_read_variant_bulk(redis_sock, reply_info, &z_ret_p TSRMLS_CC); + break; + case TYPE_MULTIBULK: + /* Initialize an array for our multi-bulk response */ + array_init(z_ret_p); + + // 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_p TSRMLS_CC); + } + break; + default: + // 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_MULTI_OR_PIPELINE() { + add_next_index_zval(z_tab, z_ret_p); + } else { + /* Set our return value */ + ZVAL_DUP(return_value, z_ret_p); + zval_dtor(z_ret_p); + } + + /* Success */ + return 0; +} + +/* vim: set tabstop=4 softtabstop=4 noexpandtab shiftwidth=4: */ diff -Nru php-redis-2.2.4/redis-3.0.0/library.h php-redis-3.0.0/redis-3.0.0/library.h --- php-redis-2.2.4/redis-3.0.0/library.h 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/library.h 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,92 @@ +#ifndef REDIS_LIBRARY_H +#define REDIS_LIBRARY_H + +void add_constant_long(zend_class_entry *ce, char *name, int value); +int integer_length(int i); +int redis_cmd_format(char **ret, char *format, ...); +int redis_cmd_format_static(char **ret, char *keyword, char *format, ...); +int redis_cmd_format_header(char **ret, char *keyword, int arg_count); +int redis_cmd_append_str(char **cmd, int cmd_len, char *append, int append_len); +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_int(char **cmd, int cmd_len, int append); +int redis_cmd_append_sstr_dbl(smart_string *str, double value); + +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 *resp, zval *z_ret); +PHP_REDIS_API void redis_parse_client_list_response(char *resp, zval *z_result); +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, 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, int force_connect TSRMLS_DC); +PHP_REDIS_API int redis_sock_disconnect(RedisSock *redis_sock TSRMLS_DC); +PHP_REDIS_API void 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, 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 int redis_sock_get(zval *id, RedisSock **redis_sock TSRMLS_DC, int nothrow); +PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock); +PHP_REDIS_API void redis_send_discard(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); +PHP_REDIS_API int 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, size_t *val_len TSRMLS_DC); +PHP_REDIS_API int +redis_key_prefix(RedisSock *redis_sock, char **key, size_t *key_len); + +PHP_REDIS_API int +redis_unserialize(RedisSock *redis_sock, const char *val, int val_len, zval *return_value TSRMLS_DC); + +PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock); +PHP_REDIS_API void redis_send_discard(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); +PHP_REDIS_API int redis_sock_set_err(RedisSock *redis_sock, const char *msg, int msg_len); + + +/* +* 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); + +#define REDIS_DOUBLE_TO_STRING(dbl_str, dbl) do { \ + char dbl_decsep = '.'; \ + dbl_str = _php_math_number_format_ex(dbl, 16, &dbl_decsep, 1, NULL, 0); \ + } while (0); + +#endif diff -Nru php-redis-2.2.4/redis-3.0.0/mkdeb-apache2.sh php-redis-3.0.0/redis-3.0.0/mkdeb-apache2.sh --- php-redis-2.2.4/redis-3.0.0/mkdeb-apache2.sh 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/mkdeb-apache2.sh 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,29 @@ +#!/bin/sh +phpize +./configure CFLAGS="-O3" +make clean all +DIR=`php-config --extension-dir | cut -c 2-` + +rm -rf debian + +mkdir -p debian +mkdir -p debian/DEBIAN +mkdir -p debian/$DIR + +cp debian.control debian/DEBIAN/control + +UBUNTU=`uname -v | grep -ci ubuntu` +mkdir -p debian/etc/php5/apache2/conf.d/ +if [ $UBUNTU = "0" ]; then + mkdir -p debian/etc/php5/cli/conf.d/ +fi + +echo "extension=redis.so" >> debian/etc/php5/apache2/conf.d/redis.ini + +if [ $UBUNTU = "0" ]; then + cp debian/etc/php5/apache2/conf.d/redis.ini debian/etc/php5/cli/conf.d/redis.ini +fi + +cp modules/redis.so debian/$DIR +dpkg -b debian phpredis-`uname -m`.deb +rm -rf debian/ diff -Nru php-redis-2.2.4/redis-3.0.0/mkdeb.sh php-redis-3.0.0/redis-3.0.0/mkdeb.sh --- php-redis-2.2.4/redis-3.0.0/mkdeb.sh 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/mkdeb.sh 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,21 @@ +#!/bin/sh +phpize +./configure CFLAGS="-O3" +make clean all +DIR=`php-config --extension-dir | cut -c 2-` + +rm -rf debian + +mkdir -p debian +mkdir -p debian/DEBIAN +mkdir -p debian/$DIR + +cp debian.control debian/DEBIAN/control + +UBUNTU=`uname -v | grep -ci ubuntu` +mkdir -p debian/etc/php5/conf.d/ +echo "extension=redis.so" >> debian/etc/php5/conf.d/redis.ini + +cp modules/redis.so debian/$DIR +dpkg -b debian phpredis-`git describe --abbrev=0 --tags`_`uname -m`.deb +rm -rf debian/ diff -Nru php-redis-2.2.4/redis-3.0.0/php_redis.h php-redis-3.0.0/redis-3.0.0/php_redis.h --- php-redis-2.2.4/redis-3.0.0/php_redis.h 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/php_redis.h 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,295 @@ +/* + +----------------------------------------------------------------------+ + | 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.0.0" + +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, zRangeByLex); +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, 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); + +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_RINIT_FUNCTION(redis); +PHP_RSHUTDOWN_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_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sort, + int use_alpha); + +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); + +/* pipeline */ +PHP_REDIS_API request_item* get_pipeline_head(zval *object); +PHP_REDIS_API void set_pipeline_head(zval *object, request_item *head); +PHP_REDIS_API request_item* get_pipeline_current(zval *object); +PHP_REDIS_API void set_pipeline_current(zval *object, request_item *current); + +#ifndef _MSC_VER +ZEND_BEGIN_MODULE_GLOBALS(redis) +ZEND_END_MODULE_GLOBALS(redis) +#endif + +struct redis_queued_item { + /* reading function */ + zval * (*fun)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, ...); + + char *cmd; + int cmd_len; + + struct redis_queued_item *next; +}; + +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 + */ diff -Nru php-redis-2.2.4/redis-3.0.0/README.markdown php-redis-3.0.0/redis-3.0.0/README.markdown --- php-redis-2.2.4/redis-3.0.0/README.markdown 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/README.markdown 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,3340 @@ +# 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](http://twitter.com/yowgi)), or to michael.grunder@gmail.com ([@grumi78](http://twitter.com/grumi78)). + + +# 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) + * [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 install it using Homebrew: + +- [Get homebrew-php](https://github.com/josegonzalez/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" +~~~ + +`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/nicolasff/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/feature/redis_cluster/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) + +##### *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 to 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) + +##### *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 the 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`, 'value2', `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 there are no more keys + +##### *Example* +~~~ +$it = NULL; /* Initialize our iterator to NULL */ +$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); /* retry when we get no keys back */ +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. +##### *Parameters* +*host* string. The destination host +*port* integer. The TCP port to connect to. +*key* string. The key to migrate. +*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 */ +~~~ + + + +## 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 + +### 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* +*hashKey* + +##### *Return value* +*BOOL* `TRUE` in case of success, `FALSE` in case of failure + + +### 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 */ + } +} +~~~ + +## 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 a random member from a set +* [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* +##### *Return value* +*String* "popped" value +*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'}*/ +$redis->sPop('key1'); /* 'member1', 'key1' => {'member3', 'member2'} */ +$redis->sPop('key1'); /* 'member3', 'key1' => {'member2'} */ +~~~ + +### 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'*/ +$redsi->pubsub("numpat"); /* Get the number of pattern subscribers */ +``` + +## 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 diff -Nru php-redis-2.2.4/redis-3.0.0/redis_array.c php-redis-3.0.0/redis-3.0.0/redis_array.c --- php-redis-2.2.4/redis-3.0.0/redis_array.c 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/redis_array.c 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,1336 @@ +/* + +----------------------------------------------------------------------+ + | 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 "redis_array.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) \ + (Z_TYPE_P(rv) == IS_FALSE || \ + (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(__redis_array_call_args, 0, 0, 2) + ZEND_ARG_INFO(0, function_name) + ZEND_ARG_INFO(0, arguments) +ZEND_END_ARG_INFO() + +zend_function_entry redis_array_functions[] = { + PHP_ME(RedisArray, __construct, NULL, ZEND_ACC_PUBLIC) + PHP_ME(RedisArray, __call, __redis_array_call_args, 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) + {NULL, NULL, NULL} +}; + +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 */ + if(Z_TYPE(ra->z_fun) != IS_UNDEF) { + zval_dtor(&ra->z_fun); + } + + /* Distributor */ + if(Z_TYPE(ra->z_dist) != IS_UNDEF) { + zval_dtor(&ra->z_dist); + } + + /* Delete pure commands */ + zval_dtor(&ra->z_pure_cmds); + + /* Free structure itself */ + efree(ra); +} + +int le_redis_array; +void redis_destructor_redis_array(zend_resource * rsrc TSRMLS_DC) +{ + RedisArray *ra = (RedisArray*)rsrc->ptr; + + /* Free previous ring if it's set */ + if(ra->prev) redis_array_free(ra->prev); + + /* Free parent array */ + redis_array_free(ra); +} + + +/** + * redis_array_get + */ +PHP_REDIS_API int redis_array_get(zval *id, RedisArray **ra TSRMLS_DC) +{ + + zval *socket; + + if (Z_TYPE_P(id) != IS_OBJECT || (socket =zend_hash_str_find(Z_OBJPROP_P(id), "socket", + sizeof("socket") - 1)) == NULL) { + return -1; + } + + *ra = (RedisArray *) Z_RES_P(socket)->ptr; + + if (!*ra || Z_RES_P(socket)->type != le_redis_array) { + return -1; + } + + return Z_RES_P(socket)->handle; +} + +uint32_t rcrc32(const char *s, size_t sz) { + static const uint32_t table[256] = { + 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535, + 0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD, + 0xE7B82D07,0x90BF1D91,0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D, + 0x6DDDE4EB,0xF4D4B551,0x83D385C7,0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC, + 0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,0x3B6E20C8,0x4C69105E,0xD56041E4, + 0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C, + 0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,0x26D930AC, + 0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F, + 0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB, + 0xB6662D3D,0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F, + 0x9FBFE4A5,0xE8B8D433,0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB, + 0x086D3D2D,0x91646C97,0xE6635C01,0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E, + 0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,0x65B0D9C6,0x12B7E950,0x8BBEB8EA, + 0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,0x4DB26158,0x3AB551CE, + 0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,0x4369E96A, + 0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9, + 0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409, + 0xCE61E49F,0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81, + 0xB7BD5C3B,0xC0BA6CAD,0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739, + 0x9DD277AF,0x04DB2615,0x73DC1683,0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8, + 0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,0xF00F9344,0x8708A3D2,0x1E01F268, + 0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,0xFED41B76,0x89D32BE0, + 0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,0xD6D6A3E8, + 0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B, + 0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF, + 0x4669BE79,0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703, + 0x220216B9,0x5505262F,0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7, + 0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A, + 0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,0x95BF4A82,0xE2B87A14,0x7BB12BAE, + 0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,0x86D3D2D4,0xF1D4E242, + 0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6, + 0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45, + 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D, + 0x3E6E77DB,0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5, + 0x47B2CF7F,0x30B5FFE9,0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605, + 0xCDD70693,0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94, + 0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D}; + + unsigned long ret = 0xffffffff; + size_t i; + + for (i = 0; i < sz; i++) { + ret = (ret >> 8) ^ table[ (ret ^ ((unsigned char)s[i])) & 0xFF ]; + } + return (ret ^ 0xFFFFFFFF); + +} + +/* {{{ proto RedisArray RedisArray::__construct() + Public constructor */ +PHP_METHOD(RedisArray, __construct) +{ + zval *z0, z_fun, z_dist, *zpData, *z_opts = NULL; + zval *id; + 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; + + /* Initialize custom functions to 'undefined' */ + ZVAL_UNDEF(&z_fun); + ZVAL_UNDEF(&z_dist); + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|a", &z0, &z_opts) == FAILURE) { + RETURN_FALSE; + } + + /* extract options */ + if(z_opts) { + zval *z_retry_interval_p, *z_connect_timeout_p; + + 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_DUP(&z_fun, zpData); + } + + /* extract function name. */ + if((zpData = zend_hash_str_find(hOpts, "distributor", sizeof("distributor") - 1)) != NULL) { + ZVAL_DUP(&z_dist, zpData); + } + + /* extract index option. */ + if((zpData = zend_hash_str_find(hOpts, "index", sizeof("index") - 1)) != NULL && (Z_TYPE_P(zpData) == IS_TRUE || Z_TYPE_P(zpData) == IS_FALSE)) { + b_index = Z_TYPE_P(zpData) == IS_TRUE; + } + + /* extract autorehash option. */ + if((zpData = zend_hash_str_find(hOpts, "autorehash", sizeof("autorehash") - 1)) != NULL && (Z_TYPE_P(zpData) == IS_TRUE || Z_TYPE_P(zpData) == IS_FALSE)) { + b_autorehash = Z_TYPE_P(zpData) == IS_TRUE; + } + + /* pconnect */ + if((zpData = zend_hash_str_find(hOpts, "pconnect", sizeof("pconnect") - 1)) != NULL && (Z_TYPE_P(zpData) == IS_TRUE || Z_TYPE_P(zpData) == IS_FALSE)) { + b_pconnect = Z_TYPE_P(zpData) == IS_TRUE; + } + + /* extract retry_interval option. */ + if ((z_retry_interval_p = zend_hash_str_find(hOpts, "retry_interval", sizeof("retry_interval") - 1)) != NULL ) { + if (Z_TYPE_P(z_retry_interval_p) == IS_LONG || Z_TYPE_P(z_retry_interval_p) == IS_STRING) { + if (Z_TYPE_P(z_retry_interval_p) == IS_LONG) { + l_retry_interval = Z_LVAL_P(z_retry_interval_p); + } + else { + l_retry_interval = atol(Z_STRVAL_P(z_retry_interval_p)); + } + } + } + + /* extract lazy connect option. */ + if((zpData = zend_hash_str_find(hOpts, "lazy_connect", sizeof("lazy_connect") - 1)) != NULL && (Z_TYPE_P(zpData) == IS_TRUE || Z_TYPE_P(zpData) == IS_FALSE)) { + b_lazy_connect = Z_TYPE_P(zpData) == IS_TRUE; + } + + /* extract connect_timeout option */ + if ((z_connect_timeout_p = zend_hash_str_find(hOpts, "connect_timeout", sizeof("connect_timeout") - 1)) != NULL) { + if (Z_TYPE_P(z_connect_timeout_p) == IS_DOUBLE || + Z_TYPE_P(z_connect_timeout_p) == IS_STRING || + Z_TYPE_P(z_connect_timeout_p) == IS_LONG) + { + if (Z_TYPE_P(z_connect_timeout_p) == IS_DOUBLE) { + d_connect_timeout = Z_DVAL_P(z_connect_timeout_p); + } else if (Z_TYPE_P(z_connect_timeout_p) == IS_LONG) { + d_connect_timeout = Z_LVAL_P(z_connect_timeout_p); + } else { + d_connect_timeout = atof(Z_STRVAL_P(z_connect_timeout_p)); + } + } + } + } + + /* 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 TSRMLS_CC); + break; + + default: + WRONG_PARAM_COUNT; + break; + } + + /* Cleanup function and distribution if they were set */ + if (!Z_ISUNDEF(z_fun)) zval_dtor(&z_fun); + if (!Z_ISUNDEF(z_dist)) zval_dtor(&z_dist); + + if(ra) { + ra->auto_rehash = b_autorehash; + ra->connect_timeout = d_connect_timeout; + if(ra->prev) ra->prev->auto_rehash = b_autorehash; + id = zend_list_insert(ra, le_redis_array TSRMLS_CC); + add_property_resource(getThis(), "socket", Z_RES_P(id)); + } +} + +/* Helper function to destroy an allocated z_val array */ +static void free_zval_array(zval *array, size_t len) { + int i; + + /* Call value destructor for each individual z_val */ + for (i = 0; i < len; i++) { + zval_dtor(&array[i]); + } + + /* Destroy the array itself */ + efree(array); +} + +static void +ra_forward_call(INTERNAL_FUNCTION_PARAMETERS, RedisArray *ra, const char *cmd, int cmd_len, zval *z_args, zval *z_new_target) { + + zval *zp_tmp, z_tmp; + char *key = NULL; /* set to avoid "unused-but-set-variable" */ + int key_len; + int i; + zval *redis_inst; + zval z_fun, *z_callargs; + HashPosition pointer; + HashTable *h_args; + + int argc; + zend_bool b_write_cmd = 0; + + h_args = Z_ARRVAL_P(z_args); + argc = zend_hash_num_elements(h_args); + + if(ra->z_multi_exec) { + redis_inst = ra->z_multi_exec; /* we already have the instance */ + } else { + /* extract key and hash it. */ + if(!(key = ra_find_key(ra, z_args, cmd, &key_len))) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not find key"); + RETURN_FALSE; + } + + /* 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; + } + } + + /* check if write cmd */ + b_write_cmd = ra_is_write_cmd(ra, cmd, cmd_len); + + if(ra->index && b_write_cmd && !ra->z_multi_exec) { /* add MULTI + SADD */ + ra_index_multi(redis_inst, MULTI TSRMLS_CC); + } + + /* pass call through */ + ZVAL_STRING(&z_fun, cmd); /* method name */ + z_callargs = emalloc(argc * sizeof(zval)); + + /* copy args to array */ + for (i = 0, zend_hash_internal_pointer_reset_ex(h_args, &pointer); + (zp_tmp = zend_hash_get_current_data_ex(h_args, &pointer)) != NULL; + ++i, zend_hash_move_forward_ex(h_args, &pointer)) + { + ZVAL_DUP(&z_callargs[i], zp_tmp); + } + + /* multi/exec */ + if(ra->z_multi_exec) { + call_user_function(&redis_ce->function_table, ra->z_multi_exec, &z_fun, &z_tmp, argc, z_callargs TSRMLS_CC); + free_zval_array(z_callargs, i); + zval_dtor(&z_fun); + zval_dtor(&z_tmp); + RETURN_ZVAL(getThis(), 1, 0); + } + + /* CALL! */ + if(ra->index && b_write_cmd) { + /* call using discarded temp value and extract exec results after. */ + call_user_function(&redis_ce->function_table, redis_inst, &z_fun, &z_tmp, argc, z_callargs TSRMLS_CC); + zval_dtor(&z_tmp); + + /* 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 TSRMLS_CC); + + /* If we detect a failure on a read command and have a previous ring, fall back. */ + if(RA_CALL_FAILED(return_value,cmd) && ra->prev && !b_write_cmd) { + zval_dtor(return_value); + 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_CALL_FAILED(return_value,cmd) && !b_write_cmd && z_new_target && ra->auto_rehash) { + ra_move_key(key, key_len, redis_inst, z_new_target TSRMLS_CC); + } + } + + /* We duplicated argument zvals so free them */ + free_zval_array(z_callargs, i); + + /* cleanup function name zval */ + zval_dtor(&z_fun); +} + +PHP_METHOD(RedisArray, __call) +{ + zval *object; + RedisArray *ra; + zval *z_args; + + char *cmd; + size_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 (redis_array_get(object, &ra TSRMLS_CC) < 0) { + 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 (redis_array_get(object, &ra TSRMLS_CC) < 0) { + 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; + size_t key_len; + int i; + zval *redis_inst; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", + &object, redis_array_ce, &key, &key_len) == FAILURE) { + RETURN_FALSE; + } + + if (redis_array_get(object, &ra TSRMLS_CC) < 0) { + RETURN_FALSE; + } + + redis_inst = ra_find_node(ra, key, key_len, &i TSRMLS_CC); + if(redis_inst) { + ZVAL_STRING(return_value, ra->hosts[i]); + } else { + RETURN_NULL(); + } +} + +PHP_METHOD(RedisArray, _instance) +{ + zval *object; + RedisArray *ra; + char *target; + size_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 (redis_array_get(object, &ra TSRMLS_CC) < 0) { + 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; + RedisArray *ra; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", + &object, redis_array_ce) == FAILURE) { + RETURN_FALSE; + } + + if (redis_array_get(object, &ra TSRMLS_CC) < 0) { + RETURN_FALSE; + } + + if(Z_TYPE(ra->z_fun) != IS_UNDEF) { + ZVAL_DUP(return_value, &ra->z_fun); + } else { + RETURN_NULL(); + } +} + +PHP_METHOD(RedisArray, _distributor) +{ + zval *object; + RedisArray *ra; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", + &object, redis_array_ce) == FAILURE) { + RETURN_FALSE; + } + + if (redis_array_get(object, &ra TSRMLS_CC) < 0) { + RETURN_FALSE; + } + + if(Z_TYPE(ra->z_fun) != IS_UNDEF) { + ZVAL_DUP(return_value, &ra->z_fun); + } else { + RETURN_NULL(); + } +} + +PHP_METHOD(RedisArray, _rehash) +{ + zval *object; + RedisArray *ra; + zend_fcall_info z_cb; + zend_fcall_info_cache z_cb_cache; + + 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 (redis_array_get(object, &ra TSRMLS_CC) < 0) { + 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, z_tmp; + int i; + RedisArray *ra; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", + &object, redis_array_ce) == FAILURE) { + RETURN_FALSE; + } + + if (redis_array_get(object, &ra TSRMLS_CC) < 0) { + RETURN_FALSE; + } + + /* prepare call */ + ZVAL_STRING(&z_fun, method_name); + + array_init(return_value); + for(i = 0; i < ra->count; ++i) { + ZVAL_UNDEF(&z_tmp); + + /* Call each node in turn */ + call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, &z_tmp, 0, NULL TSRMLS_CC); + + /* Add our reply */ + add_assoc_zval(return_value, ra->hosts[i], &z_tmp); + } + + /* Cleanup function name */ + 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_tmp, z_fun; + RedisArray *ra; + char *pattern; + size_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(redis_array_get(object, &ra TSRMLS_CC) < 0) { + RETURN_FALSE; + } + + /* Set up our function call (KEYS) */ + ZVAL_STRING(&z_fun, "KEYS"); + + /* We will be passing with one string argument (the pattern) */ + ZVAL_STRINGL(&z_args[0], pattern, pattern_len); + + /* Init our array return */ + array_init(return_value); + + /* Iterate our RedisArray nodes */ + for(i=0; icount; ++i) { + /* Return for this node */ + ZVAL_UNDEF(&z_tmp); + + /* Call KEYS on each node */ + call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, &z_tmp, 1, z_args TSRMLS_CC); + + /* Add the result for this host */ + add_assoc_zval(return_value, ra->hosts[i], &z_tmp); + } +} + +PHP_METHOD(RedisArray, getOption) +{ + zval *object, z_fun, z_tmp, z_args[1]; + int i; + RedisArray *ra; + long opt; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", + &object, redis_array_ce, &opt) == FAILURE) { + RETURN_FALSE; + } + + if (redis_array_get(object, &ra TSRMLS_CC) < 0) { + RETURN_FALSE; + } + + /* prepare call */ + ZVAL_STRING(&z_fun, "getOption"); + + /* copy arg */ + ZVAL_LONG(&z_args[0], opt); + + array_init(return_value); + for(i = 0; i < ra->count; ++i) { + + ZVAL_UNDEF(&z_tmp); + + /* Call each node in turn */ + call_user_function(&redis_ce->function_table, &ra->redis[i], + &z_fun, &z_tmp, 1, z_args TSRMLS_CC); + + add_assoc_zval(return_value, ra->hosts[i], &z_tmp); + } +} + +PHP_METHOD(RedisArray, setOption) +{ + zval *object, z_fun, z_tmp, z_args[2]; + int i; + RedisArray *ra; + long opt; + char *val_str; + size_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 (redis_array_get(object, &ra TSRMLS_CC) < 0) { + RETURN_FALSE; + } + + /* prepare call */ + ZVAL_STRING(&z_fun, "setOption"); + + /* 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_UNDEF(&z_tmp); + + /* Call each node in turn */ + call_user_function(&redis_ce->function_table, &ra->redis[i], + &z_fun, &z_tmp, 2, z_args TSRMLS_CC); + + add_assoc_zval(return_value, ra->hosts[i], &z_tmp); + } +} + +PHP_METHOD(RedisArray, select) +{ + zval *object, z_fun, z_tmp, z_args[2]; + int i; + RedisArray *ra; + long opt; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", + &object, redis_array_ce, &opt) == FAILURE) { + RETURN_FALSE; + } + + if (redis_array_get(object, &ra TSRMLS_CC) < 0) { + RETURN_FALSE; + } + + /* prepare call */ + ZVAL_STRING(&z_fun, "select"); + + /* copy args */ + ZVAL_LONG(&z_args[0], opt); + + array_init(return_value); + for(i = 0; i < ra->count; ++i) { + + ZVAL_UNDEF(&z_tmp); + + /* Call each node in turn */ + call_user_function(&redis_ce->function_table, &ra->redis[i], + &z_fun, &z_tmp, 1, z_args TSRMLS_CC); + + add_assoc_zval(return_value, ra->hosts[i], &z_tmp); + } +} + +#define HANDLE_MULTI_EXEC(cmd) do {\ + if (redis_array_get(getThis(), &ra TSRMLS_CC) >= 0 && ra->z_multi_exec) {\ + int i, num_varargs;\ + zval *varargs = NULL;\ + zval 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_DUP(&z_tmp, &varargs[i]);\ + 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) + +/* MGET will distribute the call to several nodes and regroup the values. */ +PHP_METHOD(RedisArray, mget) +{ + zval *object, *z_keys, z_fun, 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; + HashPosition pointer; + zval **redis_instances, **argv; + + /* Multi/exec support */ + HANDLE_MULTI_EXEC("MGET"); + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", + &object, redis_array_ce, &z_keys) == FAILURE) { + RETURN_FALSE; + } + + if (redis_array_get(object, &ra TSRMLS_CC) < 0) { + RETURN_FALSE; + } + + /* prepare call */ + ZVAL_STRING(&z_fun, "MGET"); + + /* init data structures */ + h_keys = Z_ARRVAL_P(z_keys); + argc = zend_hash_num_elements(h_keys); + pos = emalloc(argc * sizeof(int)); + + redis_instances = ecalloc(argc, sizeof(zval*)); + argv = emalloc(argc * sizeof(zval*)); + + argc_each = emalloc(ra->count * sizeof(int)); + memset(argc_each, 0, ra->count * sizeof(int)); + + /* associate each key to a redis node */ + for (i = 0, zend_hash_internal_pointer_reset_ex(h_keys, &pointer); + (data = zend_hash_get_current_data_ex(h_keys, &pointer)) != NULL; + zend_hash_move_forward_ex(h_keys, &pointer), ++i) + { + /* 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(redis_instances); + 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 */ + redis_instances[i] = ra_find_node(ra, key_lookup, key_len, &pos[i] TSRMLS_CC); + + argc_each[pos[i]]++; /* count number of keys per node */ + argv[i] = data; + } + + /* prepare return value */ + array_init(return_value); + 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; + + ZVAL_DUP(&z_tmp, argv[i]); + add_next_index_zval(&z_argarray, &z_tmp); + } + + /* call MGET on the node */ + call_user_function(&redis_ce->function_table, &ra->redis[n], + &z_fun, &z_ret, 1, &z_argarray TSRMLS_CC); + + /* cleanup args array */ + zval_ptr_dtor(&z_argarray); + + for(i = 0, j = 0; i < argc; ++i) { + /* 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_fun); + zval_ptr_dtor(&z_tmp_array); + efree(pos); + efree(redis_instances); + efree(argc_each); + + /* failure */ + RETURN_FALSE; + } + + if(pos[i] != n) continue; + + z_cur = zend_hash_index_find(Z_ARRVAL(z_ret), j++); + Z_TRY_ADDREF_P(z_cur); + add_index_zval(&z_tmp_array, i, z_cur); + + //z_cur = zend_hash_index_find(Z_ARRVAL(z_ret), j); + //j++; + //ZVAL_DUP(&z_tmp, z_cur); + //add_index_zval(&z_tmp_array, i, &z_tmp); + } + zval_dtor(&z_ret); + } + + /* copy temp array in the right order to return_value */ + for(i = 0; i < argc; ++i) { + z_cur = zend_hash_index_find(Z_ARRVAL(z_tmp_array), i); + Z_TRY_ADDREF_P(z_cur); + add_next_index_zval(return_value, z_cur); + } + + /* cleanup */ + zval_ptr_dtor(&z_tmp_array); + zval_dtor(&z_fun); + efree(argv); + efree(pos); + efree(redis_instances); + efree(argc_each); +} + +/* MSET will distribute the call to several nodes and regroup the values. */ +PHP_METHOD(RedisArray, mset) +{ + zval *object, *z_keys, z_fun, z_argarray, *data, z_ret; + int i, n; + RedisArray *ra; + int *pos, argc, *argc_each; + HashTable *h_keys; + zval *redis_inst, **redis_instances, **argv; + char *key, **keys, **key_free, kbuf[40]; + zend_string *key_zstr; + unsigned int key_len; + int free_idx = 0; + int type, *key_lens; + zend_ulong idx; + + /* Multi/exec support */ + HANDLE_MULTI_EXEC("MSET"); + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", + &object, redis_array_ce, &z_keys) == FAILURE) { + RETURN_FALSE; + } + + if (redis_array_get(object, &ra TSRMLS_CC) < 0) { + RETURN_FALSE; + } + + /* init data structures */ + h_keys = Z_ARRVAL_P(z_keys); + argc = zend_hash_num_elements(h_keys); + pos = emalloc(argc * sizeof(int)); + keys = emalloc(argc * sizeof(char*)); + key_lens = emalloc(argc * sizeof(int)); + + argv = emalloc(argc * sizeof(zval*)); + redis_instances = ecalloc(argc, sizeof(zval*)); + + /* Allocate an array holding the indexes of any keys that need freeing */ + key_free = emalloc(argc * sizeof(char*)); + + argc_each = emalloc(ra->count * sizeof(int)); + memset(argc_each, 0, ra->count * sizeof(int)); + + /* associate each key to a redis node */ + for(i = 0, zend_hash_internal_pointer_reset(h_keys); + zend_hash_has_more_elements(h_keys) == SUCCESS; + zend_hash_move_forward(h_keys), i++) + { + /* We have to skip the element if we can't get the array value */ + if((data = zend_hash_get_current_data(h_keys)) == NULL) { + continue; + } + + /* Grab our key */ + type = zend_hash_get_current_key(h_keys, &key_zstr, &idx); + + /* If the key isn't a string, make a string representation of it */ + if(type != HASH_KEY_IS_STRING) { + key_len = snprintf(kbuf, sizeof(kbuf), "%ld", (long)idx); + key = estrndup(kbuf, key_len); + key_free[free_idx++] = key; + } else { + key_len = key_zstr->len; + key = key_zstr->val; + } + + redis_instances[i] = ra_find_node(ra, key, (int)key_len, &pos[i] TSRMLS_CC); + argc_each[pos[i]]++; /* count number of keys per node */ + argv[i] = data; + keys[i] = key; + key_lens[i] = (int)key_len; + } + + + /* Set up our function call */ + ZVAL_STRING(&z_fun, "MSET"); + + /* calls */ + for(n = 0; n < ra->count; ++n) { /* for each node */ + int found = 0; + + /* prepare call */ + redis_inst = &ra->redis[n]; + + /* copy args */ + array_init(&z_argarray); + for(i = 0; i < argc; ++i) { + zval z_tmp; + + if(pos[i] != n) continue; + + ZVAL_DUP(&z_tmp, argv[i]); + + 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(redis_inst, MULTI TSRMLS_CC); + } + + /* call */ + call_user_function(&redis_ce->function_table, &ra->redis[n], + &z_fun, &z_ret, 1, &z_argarray TSRMLS_CC); + + if(ra->index) { + ra_index_keys(&z_argarray, redis_inst TSRMLS_CC); /* use SADD to add keys to node index */ + ra_index_exec(redis_inst, NULL, 0 TSRMLS_CC); /* run EXEC */ + } + + zval_dtor(&z_ret); + zval_ptr_dtor(&z_argarray); + } + + /* Free any keys that we needed to allocate memory for, because they weren't strings */ + for(i=0; i < free_idx; i++) { + efree(key_free[i]); + } + + /* cleanup */ + zval_dtor(&z_fun); + efree(keys); + efree(key_free); + efree(key_lens); + efree(argv); + efree(pos); + efree(redis_instances); + 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, z_argarray, *data, z_ret, z_tmp, *z_args; + int i, n; + RedisArray *ra; + int *pos, argc, *argc_each; + HashTable *h_keys; + HashPosition pointer; + zval *redis_inst, **redis_instances, **argv;; + long total = 0; + int free_zkeys = 0; + + /* Multi/exec support */ + HANDLE_MULTI_EXEC("DEL"); + + /* get all args in z_args */ + z_args = (zval *) safe_emalloc(sizeof(zval), ZEND_NUM_ARGS(), 0); + if(zend_get_parameters_array(ht, ZEND_NUM_ARGS(), z_args) == FAILURE) { + efree(z_args); + RETURN_FALSE; + } + + /* if single array arg, point z_keys to it. */ + if(ZEND_NUM_ARGS() == 1 && Z_TYPE(z_args[0]) == IS_ARRAY) { + z_keys = z_args[0]; + } else { + /* copy all elements to z_keys */ + array_init(&z_keys); + free_zkeys = 1; + for(i = 0; i < (int)ZEND_NUM_ARGS(); ++i) { + ZVAL_DUP(&z_tmp, &z_args[i]); + + /* add copy to z_keys */ + add_next_index_zval(&z_keys, &z_tmp); + } + } + + + if (redis_array_get(getThis(), &ra TSRMLS_CC) < 0) { + RETURN_FALSE; + } + + /* prepare call */ + ZVAL_STRING(&z_fun, "DEL"); + + /* init data structures */ + h_keys = Z_ARRVAL(z_keys); + argc = zend_hash_num_elements(h_keys); + pos = emalloc(argc * sizeof(int)); + + argv = emalloc(argc * sizeof(zval*)); + redis_instances = ecalloc(argc, sizeof(zval*)); + + argc_each = emalloc(ra->count * sizeof(int)); + memset(argc_each, 0, ra->count * sizeof(int)); + + /* associate each key to a redis node */ + for (i = 0, zend_hash_internal_pointer_reset_ex(h_keys, &pointer); + (data = zend_hash_get_current_data_ex(h_keys, &pointer)) != NULL; + zend_hash_move_forward_ex(h_keys, &pointer), ++i) { + + if (Z_TYPE_P(data) != IS_STRING) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "DEL: all keys must be string."); + efree(pos); + RETURN_FALSE; + } + + redis_instances[i] = ra_find_node(ra, Z_STRVAL_P(data), Z_STRLEN_P(data), &pos[i] TSRMLS_CC); + argc_each[pos[i]]++; /* count number of keys per node */ + argv[i] = data; + } + + /* calls */ + for(n = 0; n < ra->count; ++n) { /* for each node */ + + int found = 0; + redis_inst = &ra->redis[n]; + + /* copy args */ + array_init(&z_argarray); + for(i = 0; i < argc; ++i) { + if(pos[i] != n) continue; + + ZVAL_DUP(&z_tmp, argv[i]); + + 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(redis_inst, MULTI TSRMLS_CC); + } + + /* call */ + call_user_function(&redis_ce->function_table, redis_inst, + &z_fun, &z_ret, 1, &z_argarray TSRMLS_CC); + + if(ra->index) { + ra_index_del(&z_argarray, redis_inst TSRMLS_CC); /* use SREM to remove keys from node index */ + ra_index_exec(redis_inst, &z_tmp, 0 TSRMLS_CC); /* run EXEC */ + total += Z_LVAL(z_tmp); /* increment total from multi/exec block */ + } else { + total += Z_LVAL(z_ret); /* increment total from single command */ + } + + zval_dtor(&z_ret); + + zval_dtor(&z_argarray); + } + + /* cleanup */ + efree(argv); + efree(pos); + efree(redis_instances); + 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; + size_t host_len; + 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 (redis_array_get(object, &ra TSRMLS_CC) < 0) { + 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 (redis_array_get(object, &ra TSRMLS_CC) < 0 || !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 (redis_array_get(object, &ra TSRMLS_CC) < 0 || !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 (redis_array_get(object, &ra TSRMLS_CC) < 0 || !ra->z_multi_exec) { + RETURN_FALSE; + } + + /* unwatch keys, stay in multi/exec mode. */ + ra_index_unwatch(ra->z_multi_exec, return_value TSRMLS_CC); +} + +/* vim: set tabstop=4 softtabstop=4 noexpandtab shiftwidth=4: */ diff -Nru php-redis-2.2.4/redis-3.0.0/redis_array.h php-redis-3.0.0/redis-3.0.0/redis_array.h --- php-redis-2.2.4/redis-3.0.0/redis_array.h 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/redis_array.h 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,62 @@ +#ifndef REDIS_ARRAY_H +#define REDIS_ARRAY_H + +#ifdef PHP_WIN32 +#include "win32/php_stdint.h" +#else +#include +#endif +#include "common.h" + +void redis_destructor_redis_array(zend_resource *rsrc TSRMLS_DC); + +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 */ + zval z_pure_cmds; /* hash table */ + double connect_timeout; /* socket connect timeout */ + + struct RedisArray_ *prev; +} RedisArray; + +uint32_t rcrc32(const char *s, size_t sz); + + +#endif diff -Nru php-redis-2.2.4/redis-3.0.0/redis_array_impl.c php-redis-3.0.0/redis-3.0.0/redis_array_impl.c --- php-redis-2.2.4/redis-3.0.0/redis_array_impl.c 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/redis_array_impl.c 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,1234 @@ +/* + +----------------------------------------------------------------------+ + | 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" + +#define PHPREDIS_INDEX_NAME "__phpredis_array_index__" + +extern int le_redis_sock; +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; + zval *id; + char *host, *p; + short port; + zval *zpData, z_cons, z_ret; + RedisSock *redis_sock = NULL; + + /* function calls on the Redis object */ + ZVAL_STRING(&z_cons, "__construct"); + + /* init connections */ + for (zend_hash_internal_pointer_reset(hosts); + zend_hash_has_more_elements(hosts) == SUCCESS; + zend_hash_move_forward(hosts)) + { + if ((zpData = zend_hash_get_current_data(hosts)) == NULL || (Z_TYPE_P(zpData) != IS_STRING)) + { + zval_dtor(&z_cons); + efree(ra); + return NULL; + } + + ra->hosts[i] = estrdup(Z_STRVAL_P(zpData)); + + /* default values */ + host = Z_STRVAL_P(zpData); + host_len = Z_STRLEN_P(zpData); + 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 */ + object_init_ex(&ra->redis[i], redis_ce); + call_user_function(&redis_ce->function_table, &ra->redis[i], &z_cons, &z_ret, 0, NULL TSRMLS_CC); + + /* create socket */ + redis_sock = redis_sock_create(host, host_len, port, ra->connect_timeout, ra->pconnect, NULL, retry_interval, b_lazy_connect); + + if (!b_lazy_connect) { + /* connect */ + redis_sock_server_open(redis_sock, 1 TSRMLS_CC); + } + + /* attach */ + id = zend_list_insert(redis_sock, le_redis_sock TSRMLS_CC); + add_property_resource(&ra->redis[i], "socket", Z_RES_P(id)); + i++; + } + + /* Cleanup constructor zval */ + zval_dtor(&z_cons); + + return ra; +} + +/* List pure functions */ +void ra_init_function_table(RedisArray *ra) { + array_init(&ra->z_pure_cmds); + + add_assoc_bool(&ra->z_pure_cmds, "HGET", 1); + add_assoc_bool(&ra->z_pure_cmds, "HGETALL", 1); + add_assoc_bool(&ra->z_pure_cmds, "HKEYS", 1); + add_assoc_bool(&ra->z_pure_cmds, "HLEN", 1); + add_assoc_bool(&ra->z_pure_cmds, "SRANDMEMBER", 1); + add_assoc_bool(&ra->z_pure_cmds, "HMGET", 1); + add_assoc_bool(&ra->z_pure_cmds, "STRLEN", 1); + add_assoc_bool(&ra->z_pure_cmds, "SUNION", 1); + add_assoc_bool(&ra->z_pure_cmds, "HVALS", 1); + add_assoc_bool(&ra->z_pure_cmds, "TYPE", 1); + add_assoc_bool(&ra->z_pure_cmds, "EXISTS", 1); + add_assoc_bool(&ra->z_pure_cmds, "LINDEX", 1); + add_assoc_bool(&ra->z_pure_cmds, "SCARD", 1); + add_assoc_bool(&ra->z_pure_cmds, "LLEN", 1); + add_assoc_bool(&ra->z_pure_cmds, "SDIFF", 1); + add_assoc_bool(&ra->z_pure_cmds, "ZCARD", 1); + add_assoc_bool(&ra->z_pure_cmds, "ZCOUNT", 1); + add_assoc_bool(&ra->z_pure_cmds, "LRANGE", 1); + add_assoc_bool(&ra->z_pure_cmds, "ZRANGE", 1); + add_assoc_bool(&ra->z_pure_cmds, "ZRANK", 1); + add_assoc_bool(&ra->z_pure_cmds, "GET", 1); + add_assoc_bool(&ra->z_pure_cmds, "GETBIT", 1); + add_assoc_bool(&ra->z_pure_cmds, "SINTER", 1); + add_assoc_bool(&ra->z_pure_cmds, "GETRANGE", 1); + add_assoc_bool(&ra->z_pure_cmds, "ZREVRANGE", 1); + add_assoc_bool(&ra->z_pure_cmds, "SISMEMBER", 1); + add_assoc_bool(&ra->z_pure_cmds, "ZREVRANGEBYSCORE", 1); + add_assoc_bool(&ra->z_pure_cmds, "ZREVRANK", 1); + add_assoc_bool(&ra->z_pure_cmds, "HEXISTS", 1); + add_assoc_bool(&ra->z_pure_cmds, "ZSCORE", 1); + add_assoc_bool(&ra->z_pure_cmds, "HGET", 1); + add_assoc_bool(&ra->z_pure_cmds, "OBJECT", 1); + add_assoc_bool(&ra->z_pure_cmds, "SMEMBERS", 1); +} + +static int +ra_find_name(const char *name) { + + const char *ini_names, *p, *next; + + 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_params_hosts, *z_hosts; + zval z_params_prev, *z_prev; + zval z_params_funs, *z_data_p, z_fun, z_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_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; + HashTable *hHosts = NULL, *hPrev = NULL; + + /* Initialize callbacks to undefined */ + ZVAL_UNDEF(&z_fun); + ZVAL_UNDEF(&z_dist); + + /* find entry */ + if(!ra_find_name(name)) + return ra; + + /* find hosts */ + array_init(&z_params_hosts); + sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.hosts")), &z_params_hosts TSRMLS_CC); + if ((z_hosts = zend_hash_str_find(Z_ARRVAL(z_params_hosts), name, strlen(name))) != NULL) { + hHosts = Z_ARRVAL_P(z_hosts); + } + + /* find previous hosts */ + array_init(&z_params_prev); + sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.previous")), &z_params_prev TSRMLS_CC); + if ((z_prev = zend_hash_str_find(Z_ARRVAL(z_params_prev), name, strlen(name))) != NULL) { + hPrev = Z_ARRVAL_P(z_prev); + } + + /* find function */ + array_init(&z_params_funs); + sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.functions")), &z_params_funs TSRMLS_CC); + if ((z_data_p = zend_hash_str_find(Z_ARRVAL(z_params_funs), name, strlen(name))) != NULL) { + ZVAL_DUP(&z_fun, z_data_p); + } + + /* find distributor */ + array_init(&z_params_funs); + sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.distributor")), &z_params_funs TSRMLS_CC); + if ((z_data_p = zend_hash_str_find(Z_ARRVAL(z_params_funs), name, strlen(name))) != NULL) { + ZVAL_DUP(&z_dist, z_data_p); + } + + /* find index option */ + array_init(&z_params_index); + sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.index")), &z_params_index TSRMLS_CC); + if ((z_data_p = zend_hash_str_find(Z_ARRVAL(z_params_index), name, strlen(name))) != NULL) { + if(Z_TYPE_P(z_data_p) == IS_STRING && strncmp(Z_STRVAL_P(z_data_p), "1", 1) == 0) { + b_index = 1; + } + } + + /* find autorehash option */ + array_init(&z_params_autorehash); + sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.autorehash")), &z_params_autorehash TSRMLS_CC); + if ((z_data_p = zend_hash_str_find(Z_ARRVAL(z_params_autorehash), name, strlen(name))) != NULL) { + if(Z_TYPE_P(z_data_p) == IS_STRING && strncmp(Z_STRVAL_P(z_data_p), "1", 1) == 0) { + b_autorehash = 1; + } + } + + /* find retry interval option */ + array_init(&z_params_retry_interval); + sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.retryinterval")), &z_params_retry_interval TSRMLS_CC); + if ((z_data_p = zend_hash_str_find(Z_ARRVAL(z_params_retry_interval), name, strlen(name))) != NULL) { + if (Z_TYPE_P(z_data_p) == IS_LONG || Z_TYPE_P(z_data_p) == IS_STRING) { + if (Z_TYPE_P(z_data_p) == IS_LONG) { + l_retry_interval = Z_LVAL_P(z_data_p); + } + else { + l_retry_interval = atol(Z_STRVAL_P(z_data_p)); + } + } + } + + /* find pconnect option */ + array_init(&z_params_pconnect); + sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.pconnect")), &z_params_pconnect TSRMLS_CC); + if ((z_data_p = zend_hash_str_find(Z_ARRVAL(z_params_pconnect), name, strlen(name))) != NULL) { + if(Z_TYPE_P(z_data_p) == IS_STRING && strncmp(Z_STRVAL_P(z_data_p), "1", 1) == 0) { + b_pconnect = 1; + } + } + + /* find lazy connect option */ + array_init(&z_params_lazy_connect); + sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.lazyconnect")), &z_params_lazy_connect TSRMLS_CC); + if ((z_data_p = zend_hash_str_find(Z_ARRVAL(z_params_lazy_connect), name, strlen(name))) != NULL) { + if(Z_TYPE_P(z_data_p) == IS_STRING && strncmp(Z_STRVAL_P(z_data_p), "1", 1) == 0) { + b_lazy_connect = 1; + } + } + + /* find connect timeout option */ + array_init(&z_params_connect_timeout); + sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.connecttimeout")), &z_params_connect_timeout TSRMLS_CC); + if ((z_data_p = zend_hash_str_find(Z_ARRVAL(z_params_connect_timeout), name, strlen(name))) != NULL) { + if (Z_TYPE_P(z_data_p) == IS_DOUBLE || + Z_TYPE_P(z_data_p) == IS_STRING || + Z_TYPE_P(z_data_p) == IS_LONG) + { + if (Z_TYPE_P(z_data_p) == IS_DOUBLE) { + d_connect_timeout = Z_DVAL_P(z_data_p); + } else if (Z_TYPE_P(z_data_p) == IS_LONG) { + d_connect_timeout = Z_LVAL_P(z_data_p); + } else { + d_connect_timeout = atof(Z_STRVAL_P(z_data_p)); + } + } + } + + /* 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 TSRMLS_CC); + 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_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_lazy_connect); + + 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 TSRMLS_DC) +{ + int count = zend_hash_num_elements(hosts); + + /* create object */ + RedisArray *ra = emalloc(sizeof(RedisArray)); + ra->hosts = emalloc(count * sizeof(char*)); + ra->redis = ecalloc(count, sizeof(zval)); + ra->count = count; + ra->index = b_index; + ra->auto_rehash = 0; + ra->pconnect = b_pconnect; + ra->connect_timeout = connect_timeout; + ra->z_multi_exec = NULL; + ra->prev = NULL; + + /* init array data structures */ + ra_init_function_table(ra); + + if(NULL == ra_load_hosts(ra, hosts, retry_interval, b_lazy_connect TSRMLS_CC)) { + return NULL; + } + + if (hosts_prev) { + ra->prev = ra_make_array(hosts_prev, z_fun, z_dist, NULL, b_index, + b_pconnect, retry_interval, b_lazy_connect, + connect_timeout TSRMLS_CC); + } + + /* Set hash function and distribtor if provided */ + if (z_fun != NULL) { + ZVAL_DUP(&ra->z_fun, z_fun); + } + if (z_dist != NULL) { + ZVAL_DUP(&ra->z_dist, z_dist); + } + + 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; + zval z_ret; + zval z_argv0; + + /* check that we can call the extractor function */ + if(!zend_is_callable_ex(&ra->z_fun, NULL, 0, NULL, NULL, NULL)) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not call extractor function"); + return NULL; + } + /* convert_to_string(ra->z_fun); */ + + /* call extraction function */ + ZVAL_STRINGL(&z_argv0, key, key_len); + call_user_function(EG(function_table), NULL, &ra->z_fun, &z_ret, 1, &z_argv0 TSRMLS_CC); + + if(Z_TYPE(z_ret) != IS_STRING) { + zval_dtor(&z_ret); + return NULL; + } + + *out_len = Z_STRLEN(z_ret); + out = emalloc(*out_len + 1); + out[*out_len] = 0; + memcpy(out, Z_STRVAL(z_ret), *out_len); + + zval_dtor(&z_argv0); + 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; + *out_len = key_len; + + if(Z_TYPE(ra->z_fun) != IS_UNDEF) + return ra_call_extractor(ra, key, key_len, out_len TSRMLS_CC); + + /* look for '{' */ + start = strchr(key, '{'); + if(!start) return estrndup(key, key_len); + + /* look for '}' */ + end = strchr(start + 1, '}'); + if(!end) return estrndup(key, key_len); + + /* found substring */ + *out_len = end - start - 1; + out = emalloc(*out_len + 1); + out[*out_len] = 0; + memcpy(out, start+1, *out_len); + + return out; +} + +/* call userland key distributor function */ +zend_bool +ra_call_distributor(RedisArray *ra, const char *key, int key_len, int *pos TSRMLS_DC) { + + zval z_ret; + zval z_argv0; + + /* check that we can call the extractor function */ + if(!zend_is_callable_ex(&ra->z_dist, NULL, 0, NULL, NULL, NULL)) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not call distributor function"); + return 0; + } + /* convert_to_string(ra->z_fun); */ + + /* call extraction function */ + ZVAL_STRINGL(&z_argv0, key, key_len); + call_user_function(EG(function_table), NULL, &ra->z_dist, &z_ret, 1, &z_argv0 TSRMLS_CC); + + if(Z_TYPE(z_ret) != IS_LONG) { + zval_dtor(&z_ret); + return 0; + } + + zval_dtor(&z_argv0); + + *pos = Z_LVAL(z_ret); + zval_dtor(&z_ret); + return 1; +} + +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_UNDEF) { + if (!ra_call_distributor(ra, key, key_len, &pos TSRMLS_CC)) { + efree(out); + return NULL; + } + efree(out); + } else { + uint64_t h64; + + /* hash */ + hash = rcrc32(out, out_len); + efree(out); + + /* get position on ring */ + h64 = hash; + h64 *= ra->count; + h64 /= 0xffffffff; + pos = (int)h64; + } + 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; +} + + +char * +ra_find_key(RedisArray *ra, zval *z_args, const char *cmd, int *key_len) { + + zval *zp_tmp; + int key_pos = 0; /* TODO: change this depending on the command */ + + if( zend_hash_num_elements(Z_ARRVAL_P(z_args)) == 0 || + (zp_tmp = zend_hash_index_find(Z_ARRVAL_P(z_args), key_pos)) == NULL || + Z_TYPE_P(zp_tmp) != IS_STRING) { + + return NULL; + } + + *key_len = Z_STRLEN_P(zp_tmp); + return Z_STRVAL_P(zp_tmp); +} + +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_STRING(&z_fun_multi, "MULTI"); + ZVAL_LONG(&z_args[0], multi_value); + + call_user_function(&redis_ce->function_table, z_redis, &z_fun_multi, &z_ret, 1, z_args TSRMLS_CC); + + /* Clean up allocated string and return value */ + 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 = emalloc(argc * sizeof(zval)); + + /* prepare first parameters */ + ZVAL_STRING(&z_fun, cmd); + ZVAL_STRING(&z_args[0], PHPREDIS_INDEX_NAME); + + /* prepare keys */ + for(i = 0; i < argc - 1; ++i) { + zval *zp; + zp = zend_hash_index_find(Z_ARRVAL_P(z_keys), i); + ZVAL_DUP(&z_args[i+1], zp); + } + + /* run cmd */ + call_user_function(&redis_ce->function_table, z_redis, &z_fun, &z_ret, argc, z_args TSRMLS_CC); + + /* cleanup */ + for (i = 0; i < argc; i++) { + zval_dtor(&z_args[i]); + } + efree(z_args); + zval_dtor(&z_ret); + zval_dtor(&z_fun); +} + +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) { + /* Initialize key array */ + zval z_keys, *z_entry_p; + HashPosition pos; + array_init_size(&z_keys, zend_hash_num_elements(Z_ARRVAL_P(z_pairs))); + + /* Go through input array and add values to the key array */ + zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(z_pairs), &pos); + while ((z_entry_p = zend_hash_get_current_data_ex(Z_ARRVAL_P(z_pairs), &pos)) != NULL) { + zend_string *key; + zend_ulong num_key; + zval z_new; + + switch (zend_hash_get_current_key_ex(Z_ARRVAL_P(z_pairs), &key, &num_key, &pos)) { + case HASH_KEY_IS_STRING: + ZVAL_STRINGL(&z_new, key->val, key->len); + zend_hash_next_index_insert(Z_ARRVAL(z_keys), &z_new); + break; + + case HASH_KEY_IS_LONG: + ZVAL_LONG(&z_new, num_key); + zend_hash_next_index_insert(Z_ARRVAL(z_keys), &z_new); + break; + } + zend_hash_move_forward_ex(Z_ARRVAL_P(z_pairs), &pos); + } + + /* 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_STRING(&z_args[0], PHPREDIS_INDEX_NAME); + 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 TSRMLS_CC); + + zval_dtor(&z_ret); + zval_dtor(&z_fun_sadd); + zval_dtor(&z_args[0]); + zval_dtor(&z_args[1]); +} + +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_STRING(&z_fun_exec, "EXEC"); + call_user_function(&redis_ce->function_table, z_redis, &z_fun_exec, &z_ret, 0, NULL TSRMLS_CC); + 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) { + ZVAL_DUP(return_value, &z_ret); + } else if((zp_tmp = zend_hash_index_find(Z_ARRVAL(z_ret), 0)) != NULL) { + ZVAL_DUP(return_value, zp_tmp); + } + } + zval_dtor(&z_ret); + } +} + +void +ra_index_discard(zval *z_redis, zval *return_value TSRMLS_DC) { + zval z_fun_discard, z_ret; + + /* run DISCARD */ + ZVAL_STRING(&z_fun_discard, "DISCARD"); + + call_user_function(&redis_ce->function_table, z_redis, &z_fun_discard, &z_ret, 0, NULL TSRMLS_CC); + + 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_STRING(&z_fun_unwatch, "UNWATCH"); + call_user_function(&redis_ce->function_table, z_redis, &z_fun_unwatch, &z_ret, 0, NULL TSRMLS_CC); + + 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(Z_ARRVAL(ra->z_pure_cmds), cmd_up, cmd_len); + + efree(cmd_up); + return !ret; +} + +/* list keys from array index */ +static long +ra_rehash_scan(zval *z_redis, char ***keys, int **key_lens, const char *cmd, const char *arg TSRMLS_DC) { + + long count, i; + zval z_fun_smembers, z_ret, z_arg, *z_data_p; + HashTable *h_keys; + HashPosition pointer; + char *key; + int key_len; + + /* Function and argument */ + ZVAL_STRING(&z_fun_smembers, cmd); + ZVAL_STRING(&z_arg, arg); + + /* run SMEMBERS */ + call_user_function(&redis_ce->function_table, z_redis, &z_fun_smembers, &z_ret, 1, &z_arg TSRMLS_CC); + if(Z_TYPE(z_ret) != IS_ARRAY) { /* failure */ + return -1; /* TODO: log error. */ + } + h_keys = Z_ARRVAL(z_ret); + + /* allocate key array */ + count = zend_hash_num_elements(h_keys); + *keys = emalloc(count * sizeof(char*)); + *key_lens = emalloc(count * sizeof(int)); + + for (i = 0, zend_hash_internal_pointer_reset_ex(h_keys, &pointer); + (z_data_p = zend_hash_get_current_data_ex(h_keys, &pointer)) != NULL; + zend_hash_move_forward_ex(h_keys, &pointer), ++i) { + + key = Z_STRVAL_P(z_data_p); + key_len = Z_STRLEN_P(z_data_p); + + /* copy key and length */ + (*keys)[i] = emalloc(1 + key_len); + memcpy((*keys)[i], key, key_len); + (*key_lens)[i] = key_len; + (*keys)[i][key_len] = 0; /* null-terminate string */ + } + + /* cleanup */ + zval_dtor(&z_fun_smembers); + zval_dtor(&z_arg); + zval_dtor(&z_ret); + + return count; +} + +static long +ra_rehash_scan_index(zval *z_redis, char ***keys, int **key_lens TSRMLS_DC) { + return ra_rehash_scan(z_redis, keys, key_lens, "SMEMBERS", PHPREDIS_INDEX_NAME TSRMLS_CC); +} + +/* list keys using KEYS command */ +static long +ra_rehash_scan_keys(zval *z_redis, char ***keys, int **key_lens TSRMLS_DC) { + return ra_rehash_scan(z_redis, keys, key_lens, "KEYS", "*" TSRMLS_CC); +} + +/* 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; + zval z_fun_type, z_ret, z_arg; + zval *z_data; + long success = 1; + + /* Pipelined */ + ra_index_multi(z_from, PIPELINE TSRMLS_CC); + + /* Run TYPE on our key */ + ZVAL_STRINGL(&z_fun_type, "TYPE", 4); + ZVAL_STRINGL(&z_arg, key, key_len); + call_user_function(&redis_ce->function_table, z_redis, &z_fun_type, &z_ret, 1, &z_arg TSRMLS_CC); + + /* Cleanup type args */ + zval_dtor(&z_fun_type); + zval_dtor(&z_arg); + zval_dtor(&z_ret); + + /* Run TTL on our key */ + ZVAL_STRINGL(&z_fun_type, "TTL", 3); + ZVAL_STRINGL(&z_arg, key, key_len); + call_user_function(&redis_ce->function_table, z_redis, &z_fun_type, &z_ret, 1, &z_arg TSRMLS_CC); + + /* Cleanup TTL args */ + zval_dtor(&z_fun_type); + zval_dtor(&z_arg); + 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) { + HashTable *retHash = Z_ARRVAL(z_ret); + for(i = 0, zend_hash_internal_pointer_reset(retHash); + zend_hash_has_more_elements(retHash) == SUCCESS; + zend_hash_move_forward(retHash)) { + + if((z_data = zend_hash_get_current_data(retHash)) == NULL) { + success = 0; + break; + } + if(Z_TYPE_P(z_data) != IS_LONG) { + success = 0; + break; + + } + res[i] = Z_LVAL_P(z_data); + i++; + } + } + 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_STRING(&z_args[0], PHPREDIS_INDEX_NAME); + 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 TSRMLS_CC); + + zval_dtor(&z_fun_srem); + zval_dtor(&z_args[0]); + zval_dtor(&z_args[1]); + 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; + + /* 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, key, key_len); + call_user_function(&redis_ce->function_table, z_from, &z_fun_del, &z_ret, 1, &z_args TSRMLS_CC); + + /* Cleanup DEL and key */ + zval_dtor(&z_fun_del); + zval_dtor(&z_args); + 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 TSRMLS_CC); + } + + 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_score_p, *z_zadd_args; + int count; + HashTable *h_zset_vals; + zend_string *val; + int i; + zend_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 TSRMLS_CC); + + /* cleanup zrange args */ + zval_dtor(&z_fun_zrange); + for(i = 0; i < 4; ++i) { + zval_dtor(&z_args[i]); /* FIXME */ + } + + if(Z_TYPE(z_ret) != IS_ARRAY) { /* key not found or replaced */ + /* TODO: report? */ + 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 = emalloc((1 + 2*count) * sizeof(*z_zadd_args)); + + for(i = 1, zend_hash_internal_pointer_reset(h_zset_vals); + zend_hash_has_more_elements(h_zset_vals) == SUCCESS; + zend_hash_move_forward(h_zset_vals)) { + + if((z_score_p = zend_hash_get_current_data(h_zset_vals)) == NULL) { + continue; + } + + /* add score */ + convert_to_double(z_score_p); + ZVAL_DOUBLE(&z_zadd_args[i], Z_DVAL_P(z_score_p)); + + /* add value */ + switch (zend_hash_get_current_key(h_zset_vals, &val, &idx)) { + case HASH_KEY_IS_STRING: + ZVAL_STRINGL(&z_zadd_args[i+1], val->val, val->len); + break; + case HASH_KEY_IS_LONG: + ZVAL_LONG(&z_zadd_args[i+1], (long)idx); + break; + default: + return -1; /* Todo: log error */ + break; + } + i += 2; + } + + /* run ZADD on target */ + ZVAL_STRINGL(&z_fun_zadd, "ZADD", 4); + ZVAL_STRINGL(&z_zadd_args[0], key, key_len); + call_user_function(&redis_ce->function_table, z_to, &z_fun_zadd, &z_ret_dest, + 1 + 2 * count, z_zadd_args TSRMLS_CC); + + /* Expire if needed */ + ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC); + + /* cleanup */ + for(i = 0; i < 1 + 2 * count; ++i) { + zval_dtor(&z_zadd_args[i]); + } + + zval_dtor(&z_fun_zadd); + zval_dtor(&z_ret); + zval_dtor(&z_ret_dest); + 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]; + int i, argc = 0; + + /* 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 TSRMLS_CC); + + zval_dtor(&z_fun_get); + zval_dtor(&z_args[0]); + + if(Z_TYPE(z_ret) != IS_STRING) { /* key not found or replaced */ + /* TODO: report? */ + return 0; + } + + /* run SET on target */ + if (ttl > 0) { + ZVAL_STRINGL(&z_fun_set, "SETEX", 5); + ZVAL_STRINGL(&z_args[argc++], key, key_len); + ZVAL_LONG(&z_args[argc++], ttl); + ZVAL_STRINGL(&z_args[argc++], Z_STRVAL(z_ret), Z_STRLEN(z_ret)); /* copy z_ret to arg 1 */ + } else { + ZVAL_STRINGL(&z_fun_set, "SET", 3); + ZVAL_STRINGL(&z_args[argc++], key, key_len); + ZVAL_STRINGL(&z_args[argc++], Z_STRVAL(z_ret), Z_STRLEN(z_ret)); /* copy z_ret to arg 1 */ + } + + /* Cleanup get return */ + zval_dtor(&z_ret); + + /* Forward the call */ + call_user_function(&redis_ce->function_table, z_to, &z_fun_set, &z_ret, argc, z_args TSRMLS_CC); + + /* Cleanup */ + zval_dtor(&z_fun_set); + zval_dtor(&z_ret); + + for (i = 0; i < argc; i++) { + zval_dtor(&z_args[i]); + } + + 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, z_ret_dest, z_args[2]; + + /* run HGETALL on source */ + ZVAL_STRINGL(&z_fun_hgetall, "HGETALL", 7); + ZVAL_STRINGL(&z_args[0], key, key_len); + call_user_function(&redis_ce->function_table, z_from, &z_fun_hgetall, &z_ret, 1, z_args TSRMLS_CC); + + /* Cleanup call args */ + zval_dtor(&z_fun_hgetall); + zval_dtor(&z_args[0]); + + if(Z_TYPE(z_ret) != IS_ARRAY) { /* key not found or replaced */ + /* TODO: report? */ + return 0; + } + + /* run HMSET on target */ + ZVAL_STRINGL(&z_fun_hmset, "HMSET", 5); + ZVAL_STRINGL(&z_args[0], key, key_len); + ZVAL_DUP(&z_args[1], &z_ret); + call_user_function(&redis_ce->function_table, z_to, &z_fun_hmset, &z_ret_dest, 2, z_args TSRMLS_CC); + + /* Cleanup call args */ + zval_dtor(&z_fun_hmset); + zval_dtor(&z_args[0]); + zval_dtor(&z_args[1]); + + /* Expire if needed */ + ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC); + + /* cleanup */ + zval_dtor(&z_ret); + zval_dtor(&z_ret_dest); + + 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_data_p, *z_retrieve_args, *z_sadd_args; + int count, i; + HashTable *h_set_vals; + + /* run retrieval command on source */ + z_retrieve_args = emalloc((1+list_count) * sizeof(*z_retrieve_args)); + ZVAL_STRING(&z_fun_retrieve, cmd_list[0]); /* set the command */ + + /* 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 TSRMLS_CC); + + if(Z_TYPE(z_ret) != IS_ARRAY) { /* key not found or replaced */ + /* TODO: report? */ + return 0; + } + + /* run SADD/RPUSH on target */ + h_set_vals = Z_ARRVAL(z_ret); + count = zend_hash_num_elements(h_set_vals); + z_sadd_args = emalloc((1 + count) * sizeof(*z_sadd_args)); + ZVAL_STRING(&z_fun_sadd, cmd_add[0]); + ZVAL_STRINGL(&z_sadd_args[0], key, key_len); + + for(i = 0, zend_hash_internal_pointer_reset(h_set_vals); + zend_hash_has_more_elements(h_set_vals) == SUCCESS; + zend_hash_move_forward(h_set_vals), i++) { + + if((z_data_p = zend_hash_get_current_data(h_set_vals)) == NULL) { + continue; + } + + /* add set elements */ + ZVAL_DUP(&z_sadd_args[i+1], z_data_p); + } + + /* 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+1, z_sadd_args TSRMLS_CC); + + /* Expire if needed */ + ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC); + + /* cleanup */ + for(i = 0; i <= count; ++i) { + zval_dtor(&z_sadd_args[i]); + } + efree(z_sadd_args); + + for (i = 0; i < list_count; i++) { + zval_dtor(&z_retrieve_args[i]); + } + efree(z_retrieve_args); + + /* Clean up our output return value */ + zval_dtor(&z_fun_retrieve); + zval_dtor(&z_fun_sadd); + zval_dtor(&z_ret); + + 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], 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 z_ret, z_args[2]; + + z_cb->retval = &z_ret; + z_cb->params = z_args; + z_cb->param_count = 2; + z_cb->no_separation = 0; + + /* run cb(hostname, count) */ + ZVAL_STRING(&z_args[0], hostname); + ZVAL_LONG(&z_args[1], count); + + zend_call_function(z_cb, z_cb_cache TSRMLS_CC); + + zval_dtor(&z_args[0]); +} + +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) { + char **keys; + int *key_lens; + long count, i; + int target_pos; + zval *z_target; + + /* list all keys */ + if(b_index) { + count = ra_rehash_scan_index(z_redis, &keys, &key_lens TSRMLS_CC); + } else { + count = ra_rehash_scan_keys(z_redis, &keys, &key_lens TSRMLS_CC); + } + + /* callback */ + if(z_cb && z_cb_cache) { + zval_rehash_callback(z_cb, z_cb_cache, hostname, count TSRMLS_CC); + } + + /* for each key, redistribute */ + for(i = 0; i < count; ++i) { + + /* check that we're not moving to the same node. */ + z_target = ra_find_node(ra, keys[i], key_lens[i], &target_pos TSRMLS_CC); + + if(strcmp(hostname, ra->hosts[target_pos])) { /* different host */ + ra_move_key(keys[i], key_lens[i], z_redis, z_target TSRMLS_CC); + } + } + + /* cleanup */ + for(i = 0; i < count; ++i) { + efree(keys[i]); + } + efree(keys); + efree(key_lens); +} + +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); + } +} + +/* vim: set tabstop=4 softtabstop=4 noexpandtab shiftwidth=4: */ diff -Nru php-redis-2.2.4/redis-3.0.0/redis_array_impl.h php-redis-3.0.0/redis-3.0.0/redis_array_impl.h --- php-redis-2.2.4/redis-3.0.0/redis_array_impl.h 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/redis_array_impl.h 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,34 @@ +#ifndef REDIS_ARRAY_IMPL_H +#define REDIS_ARRAY_IMPL_H + +#ifdef PHP_WIN32 +#include +#else +#include +#endif + +#include "common.h" +#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 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); +char * ra_find_key(RedisArray *ra, zval *z_args, const char *cmd, int *key_len); +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 diff -Nru php-redis-2.2.4/redis-3.0.0/redis.c php-redis-3.0.0/redis-3.0.0/redis.c --- php-redis-2.2.4/redis-3.0.0/redis.c 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/redis.c 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,3924 @@ +/* -*- 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_ini.h" +#include "php_redis.h" +#include "redis_commands.h" +#include "redis_array.h" +#include "redis_cluster.h" +#include "zend_exceptions.h" + +#ifdef PHP_SESSION +#include "ext/session/php_session.h" +#endif + +#include +#include +#include + +#include "library.h" + +#define R_SUB_CALLBACK_CLASS_TYPE 1 +#define R_SUB_CALLBACK_FT_TYPE 2 +#define R_SUB_CLOSURE_TYPE 3 + +int le_redis_sock; +extern int le_redis_array; + +#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; +zend_class_entry *redis_ce; +zend_class_entry *redis_exception_ce; +extern zend_class_entry *redis_cluster_exception_ce; +static zend_class_entry *spl_ce_RuntimeException; + +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 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, 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) + + /* 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) + 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) + {NULL, NULL, NULL} +}; + +zend_module_entry redis_module_entry = { +#if ZEND_MODULE_API_NO >= 20010901 + STANDARD_MODULE_HEADER, +#endif + "redis", + NULL, + PHP_MINIT(redis), + PHP_MSHUTDOWN(redis), + PHP_RINIT(redis), + PHP_RSHUTDOWN(redis), + 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 + +PHP_REDIS_API zend_class_entry *redis_get_exception_base(int root) { +#if defined(HAVE_SPL) + if (!root) { + if (!spl_ce_RuntimeException) { + zend_class_entry *pce; + + if ((pce = zend_hash_str_find_ptr(CG(class_table), "runtimeexception", sizeof("RuntimeException") - 1))) { + spl_ce_RuntimeException = pce; + return pce; + } + } else { + return spl_ce_RuntimeException; + } + } +#endif + return zend_ce_exception; +} + +/* Send a static DISCARD in case we're in MULTI mode. */ +static int send_discard_static(RedisSock *redis_sock TSRMLS_DC) { + + int result = FAILURE; + char *cmd, *resp; + int resp_len, cmd_len; + + /* format our discard command */ + cmd_len = redis_cmd_format_static(&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; +} + +/** + * redis_destructor_redis_sock + */ +static void redis_destructor_redis_sock(zend_resource * rsrc TSRMLS_DC) +{ + RedisSock *redis_sock = (RedisSock *) rsrc->ptr; + redis_sock_disconnect(redis_sock TSRMLS_CC); + redis_free_socket(redis_sock); +} + +/** + * redis_sock_get + */ +PHP_REDIS_API int redis_sock_get(zval *id, RedisSock **redis_sock TSRMLS_DC, + int no_throw) +{ + + zval *socket; + + if (Z_TYPE_P(id) != IS_OBJECT || (socket = zend_hash_str_find(Z_OBJPROP_P(id), "socket", + sizeof("socket") - 1)) == NULL || Z_RES_P(socket) == NULL) { + // 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 -1; + } + + *redis_sock = (RedisSock *)Z_RES_P(socket)->ptr; + + if (!*redis_sock || Z_RES_P(socket)->type != le_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 -1; + } + if ((*redis_sock)->lazy_connect) + { + (*redis_sock)->lazy_connect = 0; + if (redis_sock_server_open(*redis_sock, 1 TSRMLS_CC) < 0) { + return -1; + } + } + + return 0; +} + +/** + * 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_get(object, &redis_sock TSRMLS_CC, 1) < 0) || + 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); + + /* 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); + } +#ifdef HAVE_REDIS_IGBINARY + zend_declare_class_constant_long(ce, ZEND_STRL("SERIALIZER_IGBINARY"), REDIS_SERIALIZER_IGBINARY TSRMLS_CC); +#endif + + zend_declare_class_constant_stringl(ce, "AFTER", 5, "after", 5 TSRMLS_CC); + zend_declare_class_constant_stringl(ce, "BEFORE", 6, "before", 6 TSRMLS_CC); + +#ifdef PHP_SESSION + php_session_register_module(&ps_mod_redis); + php_session_register_module(&ps_mod_redis_cluster); +#endif +} + +/** + * 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; + + /* 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); + + /* 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); + + /* 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; + + le_redis_array = zend_register_list_destructors_ex( + redis_destructor_redis_array, + NULL, + "Redis Array", module_number + ); + + /* RedisException class */ + INIT_CLASS_ENTRY(redis_exception_class_entry, "RedisException", NULL); + redis_exception_ce = zend_register_internal_class_ex( + &redis_exception_class_entry, + redis_get_exception_base(0) + ); + + /* 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, + rediscluster_get_exception_base(0) + ); + + le_redis_sock = zend_register_list_destructors_ex( + redis_destructor_redis_sock, + NULL, + redis_sock_name, module_number + ); + + /* 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); + + return SUCCESS; +} + +/** + * PHP_MSHUTDOWN_FUNCTION + */ +PHP_MSHUTDOWN_FUNCTION(redis) +{ + return SUCCESS; +} + +/** + * PHP_RINIT_FUNCTION + */ +PHP_RINIT_FUNCTION(redis) +{ + return SUCCESS; +} + +/** + * PHP_RSHUTDOWN_FUNCTION + */ +PHP_RSHUTDOWN_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); + 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_get(getThis(), &redis_sock TSRMLS_CC, 1) < 0) { + RETURN_FALSE; + } + + // If we think we're in MULTI mode, send a discard + if(redis_sock->mode == MULTI) { + // Discard any multi commands, and free any callbacks that have been + // queued + send_discard_static(redis_sock TSRMLS_CC); + free_reply_callbacks(getThis(), 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 { + /* reset multi/exec state if there is one. */ + RedisSock *redis_sock; + if (redis_sock_get(getThis(), &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + RETURN_TRUE; + } +} +/* }}} */ + +PHP_REDIS_API int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) { + zval *object, *socket, *id; + size_t host_len; + char *host = NULL; + + zend_long port = -1, retry_interval = 0; + + char *persistent_id = NULL; + size_t persistent_id_len = -1; + + double timeout = 0.0; + RedisSock *redis_sock = NULL; + +#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(), getThis(), + "Os|ldsl", &object, redis_ce, &host, + &host_len, &port, &timeout, &persistent_id, + &persistent_id_len, &retry_interval) + == FAILURE) + { + return FAILURE; + } + + if (timeout < 0L || timeout > INT_MAX) { + zend_throw_exception(redis_exception_ce, "Invalid 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 there is a redis sock already we have to remove it from the list */ + if (redis_sock_get(object, &redis_sock TSRMLS_CC, 1) > 0) { + if ((socket = zend_hash_str_find(Z_OBJPROP_P(object), "socket", sizeof("socket") -1)) == NULL) + { + /* maybe there is a socket but the id isn't known.. what to do? */ + } else { + /* The refcount should be decreased and destructor invoked */ + zend_list_delete(Z_RES_P(socket)); + } + } + + redis_sock = redis_sock_create(host, host_len, port, timeout, persistent, + persistent_id, retry_interval, 0); + + if (redis_sock_server_open(redis_sock, 1 TSRMLS_CC) < 0) { + redis_free_socket(redis_sock); + return FAILURE; + } + + id = zend_list_insert(redis_sock, le_redis_sock TSRMLS_CC); + + add_property_resource(object, "socket", Z_RES_P(id)); + + 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) +{ + zval *object; + RedisSock *redis_sock = NULL; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", + &object, redis_ce) == FAILURE) { + RETURN_FALSE; + } + + if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + if (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_string_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; + HashPosition ptr; + 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + 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 */ + for(zend_hash_internal_pointer_reset_ex(hash, &ptr); + (z_ele = zend_hash_get_current_data_ex(hash, &ptr)) != NULL; + zend_hash_move_forward_ex(hash, &ptr)) + { + char *key; + int key_free; + size_t key_len; + zval z_tmp; + ZVAL_UNDEF(&z_tmp); + + /* If the key isn't a string, turn it into one */ + if(Z_TYPE_P(z_ele) == IS_STRING) { + key = Z_STRVAL_P(z_ele); + key_len = Z_STRLEN_P(z_ele); + } else { + ZVAL_DUP(&z_tmp, z_ele); + + convert_to_string(&z_tmp); + key = Z_STRVAL(z_tmp); + key_len = Z_STRLEN(z_tmp); + } + + /* Apply key prefix if necissary */ + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + /* Append this key to our command */ + redis_cmd_append_sstr(&cmd, key, key_len); + + /* Free our key if it was prefixed */ + if(key_free) efree(key); + + /* Free oour temporary ZVAL if we converted from a non-string */ + if(Z_TYPE(z_tmp) != IS_UNDEF) { + zval_dtor(&z_tmp); + } + } + + /* 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); +} +/* }}} */ + +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); +} +/* }}} */ + +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) +{ + REDIS_PROCESS_KW_CMD("SPOP", redis_key_cmd, redis_string_response); +} +/* }}} */ + +/* {{{ 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_get(getThis(), &redis_sock TSRMLS_CC, 0)<0 || + 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_get(getThis(), &redis_sock TSRMLS_CC, 0)<0 || + 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(have_store) { + IF_ATOMIC() { + redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, + NULL, NULL); + } + REDIS_PROCESS_RESPONSE(redis_long_response); + } 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); + } +} + +PHP_REDIS_API void generic_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sort, + int use_alpha) +{ + + zval *object; + RedisSock *redis_sock; + char *key = NULL, *pattern = NULL, *get = NULL, *store = NULL, *cmd; + size_t key_len, pattern_len=-1, get_len=-1, store_len=-1, cmd_len; + int key_free; + zend_long sort_start = -1, sort_count = -1; + + int cmd_elements; + + char *cmd_lines[30]; + int cmd_sizes[30]; + + int sort_len; + int i, pos; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), + "Os|sslls", &object, redis_ce, &key, + &key_len, &pattern, &pattern_len, &get, + &get_len, &sort_start, &sort_count, &store, + &store_len) == FAILURE) + { + RETURN_FALSE; + } + + if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + if(key_len == 0) { + RETURN_FALSE; + } + + /* first line, sort. */ + cmd_lines[1] = estrdup("$4"); + cmd_sizes[1] = 2; + cmd_lines[2] = estrdup("SORT"); + cmd_sizes[2] = 4; + + /* Prefix our key if we need to */ + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + /* second line, key */ + cmd_sizes[3] = redis_cmd_format(&cmd_lines[3], "$%d", key_len); + cmd_lines[4] = emalloc(key_len + 1); + memcpy(cmd_lines[4], key, key_len); + cmd_lines[4][key_len] = 0; + cmd_sizes[4] = key_len; + + /* If we prefixed our key, free it */ + if(key_free) efree(key); + + cmd_elements = 5; + if(pattern && pattern_len) { + /* BY */ + cmd_lines[cmd_elements] = estrdup("$2"); + cmd_sizes[cmd_elements] = 2; + cmd_elements++; + cmd_lines[cmd_elements] = estrdup("BY"); + cmd_sizes[cmd_elements] = 2; + cmd_elements++; + + /* pattern */ + cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], "$%d", pattern_len); + cmd_elements++; + cmd_lines[cmd_elements] = emalloc(pattern_len + 1); + memcpy(cmd_lines[cmd_elements], pattern, pattern_len); + cmd_lines[cmd_elements][pattern_len] = 0; + cmd_sizes[cmd_elements] = pattern_len; + cmd_elements++; + } + if(sort_start >= 0 && sort_count >= 0) { + /* LIMIT */ + cmd_lines[cmd_elements] = estrdup("$5"); + cmd_sizes[cmd_elements] = 2; + cmd_elements++; + cmd_lines[cmd_elements] = estrdup("LIMIT"); + cmd_sizes[cmd_elements] = 5; + cmd_elements++; + + /* start */ + cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], + "$%d", integer_length(sort_start)); + cmd_elements++; + cmd_sizes[cmd_elements] = spprintf(&cmd_lines[cmd_elements], 0, + "%d", (int)sort_start); + cmd_elements++; + + /* count */ + cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], + "$%d", integer_length(sort_count)); + cmd_elements++; + cmd_sizes[cmd_elements] = spprintf(&cmd_lines[cmd_elements], 0, + "%d", (int)sort_count); + cmd_elements++; + } + if(get && get_len) { + /* GET */ + cmd_lines[cmd_elements] = estrdup("$3"); + cmd_sizes[cmd_elements] = 2; + cmd_elements++; + cmd_lines[cmd_elements] = estrdup("GET"); + cmd_sizes[cmd_elements] = 3; + cmd_elements++; + + /* pattern */ + cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], + "$%d", get_len); + cmd_elements++; + cmd_lines[cmd_elements] = emalloc(get_len + 1); + memcpy(cmd_lines[cmd_elements], get, get_len); + cmd_lines[cmd_elements][get_len] = 0; + cmd_sizes[cmd_elements] = get_len; + cmd_elements++; + } + + /* add ASC or DESC */ + sort_len = strlen(sort); + cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], "$%d", + sort_len); + cmd_elements++; + cmd_lines[cmd_elements] = emalloc(sort_len + 1); + memcpy(cmd_lines[cmd_elements], sort, sort_len); + cmd_lines[cmd_elements][sort_len] = 0; + cmd_sizes[cmd_elements] = sort_len; + cmd_elements++; + + if(use_alpha) { + /* ALPHA */ + cmd_lines[cmd_elements] = estrdup("$5"); + cmd_sizes[cmd_elements] = 2; + cmd_elements++; + cmd_lines[cmd_elements] = estrdup("ALPHA"); + cmd_sizes[cmd_elements] = 5; + cmd_elements++; + } + if(store && store_len) { + /* STORE */ + cmd_lines[cmd_elements] = estrdup("$5"); + cmd_sizes[cmd_elements] = 2; + cmd_elements++; + cmd_lines[cmd_elements] = estrdup("STORE"); + cmd_sizes[cmd_elements] = 5; + cmd_elements++; + + /* store key */ + cmd_sizes[cmd_elements] = redis_cmd_format(&cmd_lines[cmd_elements], + "$%d", store_len); + cmd_elements++; + cmd_lines[cmd_elements] = emalloc(store_len + 1); + memcpy(cmd_lines[cmd_elements], store, store_len); + cmd_lines[cmd_elements][store_len] = 0; + cmd_sizes[cmd_elements] = store_len; + cmd_elements++; + } + + /* first line has the star */ + cmd_sizes[0] = spprintf(&cmd_lines[0], 0, "*%d", (cmd_elements-1)/2); + + /* compute the command size */ + cmd_len = 0; + for(i = 0; i < cmd_elements; ++i) { + /* Each line followed by a _NL (\r\n) */ + cmd_len += cmd_sizes[i] + sizeof(_NL) - 1; + } + + /* copy all lines into the final command. */ + cmd = emalloc(1 + cmd_len); + pos = 0; + for(i = 0; i < cmd_elements; ++i) { + memcpy(cmd + pos, cmd_lines[i], cmd_sizes[i]); + pos += cmd_sizes[i]; + memcpy(cmd + pos, _NL, sizeof(_NL) - 1); + pos += sizeof(_NL) - 1; + + /* free every line */ + efree(cmd_lines[i]); + } + + REDIS_PROCESS_REQUEST(redis_sock, cmd, 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 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, "ASC", 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, "ASC", 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, "DESC", 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, "DESC", 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; + int cmd_len; + size_t opt_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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + /* Build a standalone INFO command or one with an option */ + if(opt != NULL) { + cmd_len = redis_cmd_format_static(&cmd, "INFO", "s", opt, + opt_len); + } else { + cmd_len = redis_cmd_format_static(&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 (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + redis_sock->dbNumber = dbNumber; + + cmd_len = redis_cmd_format_static(&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); +} +/* }}} */ + +PHP_REDIS_API void +generic_mset(INTERNAL_FUNCTION_PARAMETERS, char *kw, ResultCallback fun) { + zval *object; + RedisSock *redis_sock; + + char *cmd = NULL, *p = NULL; + int cmd_len = 0, argc = 0, kw_len = strlen(kw); + int step = 0; // 0: compute size; 1: copy strings. + zval *z_array; + + HashTable *keytable; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", + &object, redis_ce, &z_array) == FAILURE) + { + RETURN_FALSE; + } + + if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + if(zend_hash_num_elements(Z_ARRVAL_P(z_array)) == 0) { + RETURN_FALSE; + } + + for(step = 0; step < 2; ++step) { + if(step == 1) { + /* '*' + arg count + NL */ + cmd_len += 1 + integer_length(1 + 2 * argc) + 2; + /* '$' + strlen(kw) + NL */ + cmd_len += 1 + integer_length(kw_len) + 2; + /* kw + NL */ + cmd_len += kw_len + 2; + + p = cmd = emalloc(cmd_len + 1); + p += sprintf(cmd, "*%d" _NL "$%d" _NL "%s" _NL, 1 + 2 * argc, + kw_len, kw); + } + + keytable = Z_ARRVAL_P(z_array); + for(zend_hash_internal_pointer_reset(keytable); + zend_hash_has_more_elements(keytable) == SUCCESS; + zend_hash_move_forward(keytable)) { + + zend_string *key_zstr; + char *val, *key; + size_t val_len, key_len; + zend_ulong idx; + int type; + zval *z_value_p; + int val_free, key_free; + char buf[32]; + + type = zend_hash_get_current_key(keytable, &key_zstr, &idx); + if((z_value_p = zend_hash_get_current_data(keytable)) == NULL) + { + /* Should never happen, according to the PHP people */ + continue; + } + + // If the key isn't a string, use the index value returned when + // grabbing it. We typecast to long, because they could actually + // be negative. + if(type != HASH_KEY_IS_STRING) { + // Create string representation of our index + key_len = snprintf(buf, sizeof(buf), "%ld", (long)idx); + key = (char*)buf; + } else { + key_len = key_zstr->len; + key = key_zstr->val; + } + + if(step == 0) + argc++; /* found a valid arg */ + + val_free = redis_serialize(redis_sock, z_value_p, &val, &val_len + TSRMLS_CC); + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + if(step == 0) { /* counting */ + cmd_len += 1 + integer_length(key_len) + 2 + + key_len + 2 + + 1 + integer_length(val_len) + 2 + + val_len + 2; + } else { + p += sprintf(p, "$%d" _NL, key_len); /* key len */ + memcpy(p, key, key_len); p += key_len; /* key */ + memcpy(p, _NL, 2); p += 2; + + p += sprintf(p, "$%d" _NL, val_len); /* val len */ + memcpy(p, val, val_len); p += val_len; /* val */ + memcpy(p, _NL, 2); p += 2; + } + + if(val_free) efree(val); + if(key_free) efree(key); + } + } + + REDIS_PROCESS_REQUEST(redis_sock, cmd, 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_get(getThis(), &redis_sock TSRMLS_CC, 0)<0) { + 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); +} +/* }}} */ + +/* flag : get, set {ATOMIC, MULTI, PIPELINE} */ + +PHP_METHOD(Redis, multi) +{ + + RedisSock *redis_sock; + char *cmd; + int response_len, cmd_len; + char * response; + 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + if(multi_value == MULTI || multi_value == PIPELINE) { + redis_sock->mode = multi_value; + } else { + RETURN_FALSE; + } + + redis_sock->current = NULL; + + IF_MULTI() { + cmd_len = redis_cmd_format_static(&cmd, "MULTI", ""); + + if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { + efree(cmd); + RETURN_FALSE; + } + efree(cmd); + + if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) + { + RETURN_FALSE; + } + + if(strncmp(response, "+OK", 3) == 0) { + efree(response); + RETURN_ZVAL(getThis(), 1, 0); + } + efree(response); + RETURN_FALSE; + } + IF_PIPELINE() { + free_reply_callbacks(getThis(), redis_sock); + 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + redis_sock->mode = ATOMIC; + redis_send_discard(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); +} + +PHP_REDIS_API int +redis_sock_read_multibulk_pipeline_reply(INTERNAL_FUNCTION_PARAMETERS, + RedisSock *redis_sock) +{ + zval z_tab; + array_init(&z_tab); + + redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, + redis_sock, &z_tab, 0); + + ZVAL_DUP(return_value, &z_tab); + zval_dtor(&z_tab); + + /* free allocated function/request memory */ + free_reply_callbacks(getThis(), redis_sock); + + return 0; + +} + +/* redis_sock_read_multibulk_multi_reply */ +PHP_REDIS_API int redis_sock_read_multibulk_multi_reply(INTERNAL_FUNCTION_PARAMETERS, + RedisSock *redis_sock) +{ + + char inbuf[1024]; + int numElems; + zval z_tab; + + redis_check_eof(redis_sock, 0 TSRMLS_CC); + + php_stream_gets(redis_sock->stream, inbuf, 1024); + if(inbuf[0] != '*') { + return -1; + } + + /* number of responses */ + numElems = atoi(inbuf+1); + + if(numElems < 0) { + return -1; + } + + zval_dtor(return_value); + + array_init(&z_tab); + + redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, + redis_sock, &z_tab, numElems); + + ZVAL_DUP(return_value, &z_tab); + zval_dtor(&z_tab); + + return 0; +} + +void +free_reply_callbacks(zval *z_this, RedisSock *redis_sock) { + + fold_item *fi; + fold_item *head = redis_sock->head; + request_item *ri; + + for(fi = head; fi; ) { + fold_item *fi_next = fi->next; + free(fi); + fi = fi_next; + } + redis_sock->head = NULL; + redis_sock->current = NULL; + + for(ri = redis_sock->pipeline_head; ri; ) { + struct request_item *ri_next = ri->next; + free(ri->request_str); + free(ri); + ri = ri_next; + } + redis_sock->pipeline_head = NULL; + redis_sock->pipeline_current = NULL; +} + +/* exec */ +PHP_METHOD(Redis, exec) +{ + RedisSock *redis_sock; + char *cmd; + int cmd_len; + zval *object; + struct request_item *ri; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", + &object, redis_ce) == FAILURE) { + RETURN_FALSE; + } + if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + IF_MULTI() { + cmd_len = redis_cmd_format_static(&cmd, "EXEC", ""); + + if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { + efree(cmd); + RETURN_FALSE; + } + efree(cmd); + + if(redis_sock_read_multibulk_multi_reply( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + redis_sock) < 0) + { + zval_dtor(return_value); + free_reply_callbacks(object, redis_sock); + redis_sock->mode = ATOMIC; + redis_sock->watching = 0; + RETURN_FALSE; + } + free_reply_callbacks(object, redis_sock); + redis_sock->mode = ATOMIC; + redis_sock->watching = 0; + } + + IF_PIPELINE() { + char *request = NULL; + int total = 0; + int offset = 0; + + /* compute the total request size */ + for(ri = redis_sock->pipeline_head; ri; ri = ri->next) { + total += ri->request_size; + } + if(total) { + request = malloc(total); + } + + /* concatenate individual elements one by one in the target buffer */ + for(ri = redis_sock->pipeline_head; ri; ri = ri->next) { + memcpy(request + offset, ri->request_str, ri->request_size); + offset += ri->request_size; + } + + if(request != NULL) { + if (redis_sock_write(redis_sock, request, total TSRMLS_CC) < 0) { + free(request); + free_reply_callbacks(object, redis_sock); + redis_sock->mode = ATOMIC; + RETURN_FALSE; + } + free(request); + } else { + redis_sock->mode = ATOMIC; + free_reply_callbacks(object, redis_sock); + + /* Empty array when no command was run. */ + array_init(return_value); + return; + } + + if (redis_sock_read_multibulk_pipeline_reply( + INTERNAL_FUNCTION_PARAM_PASSTHRU, + redis_sock) < 0) + { + redis_sock->mode = ATOMIC; + free_reply_callbacks(object, redis_sock); + RETURN_FALSE; + } + redis_sock->mode = ATOMIC; + free_reply_callbacks(object, redis_sock); + } +} + +PHP_REDIS_API int redis_response_enqueued(RedisSock *redis_sock TSRMLS_DC) { + char *resp; + int resp_len, ret = 0; + + if ((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC)) == NULL) { + return 0; + } + + if(strncmp(resp, "+QUEUED", 7) == 0) { + ret = 1; + } + efree(resp); + return ret; +} + +PHP_REDIS_API void fold_this_item(INTERNAL_FUNCTION_PARAMETERS, fold_item *item, + RedisSock *redis_sock, zval *z_tab) +{ + item->fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, item->ctx + TSRMLS_CC); +} + +PHP_REDIS_API int +redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAMETERS, + RedisSock *redis_sock, zval *z_tab, + int numElems) +{ + + fold_item *head = redis_sock->head; + fold_item *current = redis_sock->current; + for(current = head; current; current = current->next) { + fold_this_item(INTERNAL_FUNCTION_PARAM_PASSTHRU, current, redis_sock, + z_tab); + } + redis_sock->current = current; + 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) { + RETURN_FALSE; + } + + /* if the flag is activated, send the command, the reply will be "QUEUED" + * or -ERR */ + if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + redis_sock->mode = PIPELINE; + + /* 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(getThis(), redis_sock); + + 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; + HashPosition pointer; + 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + arr_hash = Z_ARRVAL_P(array); + array_count = zend_hash_num_elements(arr_hash); + + if (array_count == 0) { + RETURN_FALSE; + } + + for (zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); + (data = zend_hash_get_current_data_ex(arr_hash, &pointer)) != NULL; + zend_hash_move_forward_ex(arr_hash, &pointer)) { + + if (Z_TYPE_P(data) == IS_STRING) { + char *old_cmd = NULL; + if(*cmd) { + old_cmd = cmd; + } + cmd_len = spprintf(&cmd, 0, "%s %s", cmd, Z_STRVAL_P(data)); + if(old_cmd) { + efree(old_cmd); + } + } + } + + old_cmd = cmd; + cmd_len = spprintf(&cmd, 0, "%s %s\r\n", unsub_cmd, cmd); + efree(old_cmd); + + if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { + efree(cmd); + RETURN_FALSE; + } + efree(cmd); + + i = 1; + array_init(return_value); + + while( i <= array_count) { + 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); + i++; + } +} + +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; + int cmd_len; + size_t host_len; + zend_long port = 6379; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), + "O|sl", &object, redis_ce, &host, + &host_len, &port) == FAILURE) + { + RETURN_FALSE; + } + if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + if(host && host_len) { + cmd_len = redis_cmd_format_static(&cmd, "SLAVEOF", "sd", host, + host_len, (int)port); + } else { + cmd_len = redis_cmd_format_static(&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_get(getThis(), &redis_sock TSRMLS_CC, 0)<0) { + 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_get(getThis(), &redis_sock TSRMLS_CC, 0) < 0) { + 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_get(getThis(), &redis_sock TSRMLS_CC, 0)<0) { + 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; + size_t key_len, val_len, op_len; + int cmd_len; + enum {CFG_GET, CFG_SET} mode; + + 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + if (mode == CFG_GET && val == NULL) { + cmd_len = redis_cmd_format_static(&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_cmd_format_static(&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; + size_t arg_len; + zend_long option; + 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + 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_cmd_format_static(&cmd, "SLOWLOG", "sl", arg, + arg_len, option); + } else { + cmd_len = redis_cmd_format_static(&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_get(object, &redis_sock TSRMLS_CC, 0)<0) { + RETURN_FALSE; + } + + // Construct the command + cmd_len = redis_cmd_format_static(&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; + HashPosition ptr; + zval *z_ele; + char *key; + size_t key_len; + int cmd_len, key_free; + smart_string cmd = {0}; + + if(type == PUBSUB_CHANNELS) { + if(arg) { + /* Get string argument and length. */ + key = Z_STRVAL_P(arg); + key_len = Z_STRLEN_P(arg); + + /* Prefix if necissary */ + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // With a pattern + cmd_len = redis_cmd_format_static(ret, "PUBSUB", "ss", + "CHANNELS", sizeof("CHANNELS")-1, key, key_len); + + /* Free the channel name if we prefixed it */ + if(key_free) efree(key); + + /* Return command length */ + return cmd_len; + } else { + // No pattern + return redis_cmd_format_static(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 */ + for(zend_hash_internal_pointer_reset_ex(ht_chan, &ptr); + (z_ele = zend_hash_get_current_data_ex(ht_chan, &ptr)) != NULL; + zend_hash_move_forward_ex(ht_chan, &ptr)) + { + char *key; + int key_free; + size_t key_len; + zval z_tmp; + ZVAL_UNDEF(&z_tmp); + + if(Z_TYPE_P(z_ele) == IS_STRING) { + key = Z_STRVAL_P(z_ele); + key_len = Z_STRLEN_P(z_ele); + } else { + ZVAL_DUP(&z_tmp, z_ele); + convert_to_string(&z_tmp); + + key = Z_STRVAL(z_tmp); + key_len = Z_STRLEN(z_tmp); + } + + /* Apply prefix if required */ + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + /* Append this channel */ + redis_cmd_append_sstr(&cmd, key, key_len); + + /* Free key if prefixed */ + if(key_free) efree(key); + + // Free our temp var if we used it + if(Z_TYPE(z_tmp) != IS_UNDEF) { + zval_dtor(&z_tmp); + } + } + + /* Set return */ + *ret = cmd.c; + return cmd.len; + } else if(type == PUBSUB_NUMPAT) { + return redis_cmd_format_static(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; + size_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_get(object, &redis_sock TSRMLS_CC, 0)<0) { + 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); + } +} + +// Construct an EVAL or EVALSHA command, with option argument array and number +// of arguments that are keys parameter +PHP_REDIS_API int +redis_build_eval_cmd(RedisSock *redis_sock, char **ret, char *keyword, + char *value, int val_len, zval *args, int keys_count + TSRMLS_DC) +{ + zval *elem; + HashTable *args_hash; + HashPosition hash_pos; + int cmd_len, args_count = 0; + int eval_cmd_count = 2; + + // If we've been provided arguments, we'll want to include those in our eval + // command + if(args != NULL) { + // Init our hash array value, and grab the count + args_hash = Z_ARRVAL_P(args); + args_count = zend_hash_num_elements(args_hash); + + // We only need to process the arguments if the array is non empty + if(args_count > 0) { + // Header for our EVAL command + cmd_len = redis_cmd_format_header(ret, keyword, + eval_cmd_count + args_count); + + // Now append the script itself, and the number of arguments to + // treat as keys + cmd_len = redis_cmd_append_str(ret, cmd_len, value, val_len); + cmd_len = redis_cmd_append_int(ret, cmd_len, keys_count); + + // Iterate the values in our "keys" array + for(zend_hash_internal_pointer_reset_ex(args_hash, &hash_pos); + (elem = zend_hash_get_current_data_ex(args_hash, &hash_pos)) != NULL; + zend_hash_move_forward_ex(args_hash, &hash_pos)) + { + zval z_tmp; + char *key, *old_cmd; + int key_free; + size_t key_len; + ZVAL_UNDEF(&z_tmp); + + if(Z_TYPE_P(elem) == IS_STRING) { + key = Z_STRVAL_P(elem); + key_len = Z_STRLEN_P(elem); + } else { + /* Convert it to a string */ + ZVAL_DUP(&z_tmp, elem); + convert_to_string(&z_tmp); + + key = Z_STRVAL(z_tmp); + key_len = Z_STRLEN(z_tmp); + } + + /* Keep track of the old command pointer */ + old_cmd = *ret; + + // If this is still a key argument, prefix it if we've been set + // up to prefix keys + key_free = keys_count-- > 0 ? redis_key_prefix(redis_sock, + &key, &key_len) : 0; + + // Append this key to our EVAL command, free our old command + cmd_len = redis_cmd_format(ret, "%s$%d" _NL "%s" _NL, *ret, + cmd_len, key_len, key, key_len); + efree(old_cmd); + + /* Free our key, old command if we need to */ + if(key_free) efree(key); + + // Free our temporary arg if we created one + if(!Z_ISUNDEF(z_tmp)) { + zval_dtor(&z_tmp); + } + } + } + } + + // If there weren't any arguments (none passed, or an empty array), + // construct a standard no args command + if(args_count < 1) { + cmd_len = redis_cmd_format_static(ret, keyword, "sd", value, + val_len, 0); + } + + /* Return our command length */ + return cmd_len; +} + +/* {{{ proto variant Redis::evalsha(string sha1, [array keys, long num_keys]) */ +PHP_METHOD(Redis, evalsha) +{ + zval *object, *args= NULL; + char *cmd, *sha; + int cmd_len; + size_t sha_len; + zend_long keys_count = 0; + RedisSock *redis_sock; + + if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), + "Os|al", &object, redis_ce, &sha, &sha_len, + &args, &keys_count) == FAILURE) + { + RETURN_FALSE; + } + + /* Attempt to grab socket */ + if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + // Construct our EVALSHA command + cmd_len = redis_build_eval_cmd(redis_sock, &cmd, "EVALSHA", sha, sha_len, + args, keys_count TSRMLS_CC); + + 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 variant Redis::eval(string script, [array keys, long num_keys]) */ +PHP_METHOD(Redis, eval) +{ + zval *object, *args = NULL; + RedisSock *redis_sock; + char *script, *cmd = ""; + int cmd_len; + size_t script_len; + zend_long keys_count = 0; + + // Attempt to parse parameters + if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), + "Os|al", &object, redis_ce, &script, + &script_len, &args, &keys_count) + == FAILURE) + { + RETURN_FALSE; + } + + /* Attempt to grab socket */ + if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + // Construct our EVAL command + cmd_len = redis_build_eval_cmd(redis_sock, &cmd, "EVAL", script, script_len, + args, keys_count TSRMLS_CC); + + 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); +} + +PHP_REDIS_API int +redis_build_script_exists_cmd(char **ret, zval *argv, int argc) { + /* Our command length and iterator */ + int cmd_len = 0, i; + + // Start building our command + cmd_len = redis_cmd_format_header(ret, "SCRIPT", argc + 1); + cmd_len = redis_cmd_append_str(ret, cmd_len, "EXISTS", 6); + + /* Iterate our arguments */ + for(i=0;ierr != NULL && redis_sock->err_len > 0) { + RETURN_STRINGL(redis_sock->err, redis_sock->err_len); + } else { + 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + // Clear error message + if(redis_sock->err) { + efree(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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + RETVAL_LONG(redis_sock->mode); +} + +/* {{{ 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_STRING(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))) { + if(redis_sock->persistent_id != NULL) { + RETURN_STRING(redis_sock->persistent_id); + } else { + RETURN_NULL(); + } + } else { + RETURN_FALSE; + } +} + +/* {{{ proto Redis::getAuth */ +PHP_METHOD(Redis, getAuth) { + RedisSock *redis_sock; + + if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) { + if(redis_sock->auth != NULL) { + RETURN_STRING(redis_sock->auth); + } else { + RETURN_NULL(); + } + } else { + RETURN_FALSE; + } +} + +/* + * $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; + int cmd_len = 0; + size_t opt_len = 0, arg_len = 0; + + // 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + /* Build our CLIENT command */ + if(ZEND_NUM_ARGS() == 2) { + cmd_len = redis_cmd_format_static(&cmd, "CLIENT", "ss", + opt, opt_len, arg, arg_len); + } else { + cmd_len = redis_cmd_format_static(&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 = 0; + char *cmd = NULL; + RedisSock *redis_sock; + zval *z_args; + + /* Sanity check on arguments */ + z_args = (zval *) safe_emalloc(sizeof(zval), argc, 0); + if (argc < 1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Must pass at least one command keyword"); + efree(z_args); + RETURN_FALSE; + } else 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_get(getThis(), &redis_sock TSRMLS_CC, 0) < 0) + { + 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) +{ + char *keyword; + int arg_count = 0, cmd_len; + + /* 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. */ + arg_count = 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 */ + cmd_len = redis_cmd_format_header(cmd, keyword, arg_count); + + /* Add the key in question if we have one */ + if(key_len) { + cmd_len = redis_cmd_append_str(cmd, cmd_len, key, key_len); + } + + /* Add our iterator */ + cmd_len = redis_cmd_append_int(cmd, cmd_len, iter); + + /* Append COUNT if we've got it */ + if(count) { + cmd_len = redis_cmd_append_str(cmd, cmd_len, "COUNT", + sizeof("COUNT")-1); + cmd_len = redis_cmd_append_int(cmd, cmd_len, count); + } + + /* Append MATCH if we've got it */ + if(pattern_len) { + cmd_len = redis_cmd_append_str(cmd, cmd_len, "MATCH", + sizeof("MATCH")-1); + cmd_len = redis_cmd_append_str(cmd, cmd_len, pattern, pattern_len); + } + + /* Return our command length */ + return cmd_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; + size_t key_len = 0, pattern_len = 0; + int cmd_len, num_elements, key_free=0; + zend_long count=0; + long 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_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + 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); +} + +/* vim: set tabstop=4 softtabstop=4 noexpandtab shiftwidth=4: */ diff -Nru php-redis-2.2.4/redis-3.0.0/redis_cluster.c php-redis-3.0.0/redis-3.0.0/redis_cluster.c --- php-redis-2.2.4/redis-3.0.0/redis_cluster.c 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/redis_cluster.c 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,2939 @@ +/* + +----------------------------------------------------------------------+ + | 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 +#include "library.h" +#include +#include + +zend_class_entry *redis_cluster_ce; + +/* Exception handler */ +zend_class_entry *redis_cluster_exception_ce; +zend_class_entry *spl_rte_ce = NULL; + +/* Handlers for RedisCluster */ +zend_object_handlers RedisCluster_handlers; + +/* Argument info for HSCAN, SSCAN, HSCAN */ +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(); + +/* Argument infor for SCAN */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_scan, 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_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, 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, ZEND_ACC_PUBLIC) + PHP_ME(RedisCluster, sscan, arginfo_kscan, ZEND_ACC_PUBLIC) + PHP_ME(RedisCluster, zscan, arginfo_kscan, ZEND_ACC_PUBLIC) + PHP_ME(RedisCluster, hscan, arginfo_kscan, 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) + {NULL, NULL, NULL} +}; + +/* Our context seeds will be a hash table with RedisSock* pointers */ +static void ht_free_seed(zval *data) { + RedisSock *redis_sock = *(RedisSock**)data; + if(redis_sock) redis_free_socket(redis_sock); +} + +/* Free redisClusterNode objects we've stored */ +static void ht_free_node(zval *data) { + redisClusterNode *node = *(redisClusterNode**)data; + cluster_free_node(node); +} + +/* Initialize/Register our RedisCluster exceptions */ +PHP_REDIS_API zend_class_entry *rediscluster_get_exception_base(int root) { +#if defined(HAVE_SPL) + if (!root) { + if (!spl_ce_RuntimeException) { + zend_class_entry *pce; + + if ((pce = zend_hash_str_find_ptr(CG(class_table), "runtimeexception", sizeof("RuntimeException") - 1))) { + spl_ce_RuntimeException = pce; + return pce; + } + } else { + return spl_ce_RuntimeException; + } + } +#endif + return zend_ce_exception; +} + +/* Free redisCluster context */ +void free_cluster_context(zend_object *object) { + redisCluster *cluster; + + // Grab context + cluster = (redisCluster*)object; + + // 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) efree(cluster->err); + + // Finally, free the redisCluster structure itself + //efree(cluster); +} + +/* Create redisCluster context */ +zend_object * +create_cluster_context(zend_class_entry *class_type TSRMLS_DC) { + redisCluster *cluster = ecalloc(1, sizeof(redisCluster) + sizeof(zval) * (class_type->default_properties_count - 1));; + + // We're not currently subscribed anywhere + cluster->subscribed_slot = -1; + + // Assume we're up initially + cluster->clusterdown = 0; + + // 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); + object_properties_init(&cluster->std, class_type); + + memcpy(&RedisCluster_handlers, zend_get_std_object_handlers(), sizeof(RedisCluster_handlers)); + RedisCluster_handlers.free_obj = free_cluster_context; + + cluster->std.handlers = &RedisCluster_handlers; + + return &cluster->std; +} + +/* 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); + iptr = estrdup(INI_STR("redis.clusters.seeds")); + sapi_module.treat_data(PARSE_STRING, 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); + } + + /* Connection timeout */ + array_init(&z_timeout); + iptr = estrdup(INI_STR("redis.clusters.timeout")); + sapi_module.treat_data(PARSE_STRING, 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); + } + } + + /* Read timeout */ + array_init(&z_read_timeout); + iptr = estrdup(INI_STR("redis.clusters.read_timeout")); + sapi_module.treat_data(PARSE_STRING, 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); + } + } + + /* Persistent connections */ + array_init(&z_persistent); + iptr = estrdup(INI_STR("redis.clusters.persistent")); + sapi_module.treat_data(PARSE_STRING, 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); +} + +/* + * PHP Methods + */ + +/* Create a RedisCluster Object */ +PHP_METHOD(RedisCluster, __construct) { + zval *object, *z_seeds=NULL; + char *name; + size_t name_len; + double timeout = 0.0, read_timeout = 0.0; + zend_bool persistent = 0; + redisCluster *context = Z_REDIS_OBJ_P(getThis()); + + // 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(php_redis_fetch_object(Z_OBJ_P(getThis()))); + 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); + 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; + size_t key_len; + int key_free; + short slot; + + char *val; + size_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; + zend_string *key_zstr; + + // Grab the key, convert it to a string using provided kbuf buffer if it's + // a LONG style key + switch(zend_hash_get_current_key_ex(ht, &key_zstr, &idx, ptr)) + { + case HASH_KEY_IS_LONG: + kv->key_len = snprintf(kv->kbuf,sizeof(kv->kbuf),"%ld",(long)idx); + kv->key = kv->kbuf; + break; + case HASH_KEY_IS_STRING: + kv->key_len = key_zstr->len; + kv->key = key_zstr->val; + 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)); + + // 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 = Z_REDIS_OBJ_P(getThis()); + 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 = emalloc(sizeof(*z_args) * argc); + if (zend_get_parameters_array(ht, ZEND_NUM_ARGS(), 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; + } + + /* We no longer need our array args */ + efree(z_args); + + /* 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) { + 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); + } + 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); + } + 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); + } + + // 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 = Z_REDIS_OBJ_P(getThis()); + 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; + + // Initialize a LONG value to zero for our return + z_ret = emalloc(sizeof(*z_ret)); + 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) + { + zval_dtor(z_ret); + efree(z_ret); + RETURN_FALSE; + } +} + +/* {{{ proto array RedisCluster::mget(array keys) */ +PHP_METHOD(RedisCluster, mget) { + zval *z_ret; + + // Array response + z_ret = emalloc(sizeof(*z_ret)); + 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 + z_ret = emalloc(sizeof(*z_ret)); + 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) + { + zval_dtor(z_ret); + efree(z_ret); + RETURN_FALSE; + } +} + +/* {{{ proto array RedisCluster::msetnx(array keyvalues) */ +PHP_METHOD(RedisCluster, msetnx) { + zval *z_ret; + + // Array response + z_ret = emalloc(sizeof(*z_ret)); + 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 = Z_REDIS_OBJ_P(getThis()); + redisClusterNode *node; + int pat_free, cmd_len; + char *pat, *cmd; + size_t pat_len; + clusterReply *resp; + zval z_ret; + int i; + + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &pat, &pat_len) + ==FAILURE) + { + RETURN_FALSE; + } + + /* Prefix and then build our command */ + pat_free = redis_key_prefix(c->flags, &pat, &pat_len); + cmd_len = redis_cmd_format_static(&cmd, "KEYS", "s", pat, pat_len); + if(pat_free) efree(pat); + + array_init(&z_ret); + + /* Treat as readonly */ + c->readonly = CLUSTER_IS_ATOMIC(c); + + /* Iterate over our known nodes */ + for(zend_hash_internal_pointer_reset(c->nodes); + (node = zend_hash_get_current_data_ptr(c->nodes)) != NULL; + zend_hash_move_forward(c->nodes)) + { + 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", + 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", 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); + } + + efree(cmd); + + /* Return our keys */ + RETURN_ZVAL(&z_ret, 0, 1); +} +/* }}} */ + +/* {{{ 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) { + CLUSTER_PROCESS_KW_CMD("SPOP", redis_key_cmd, cluster_bulk_resp, 0); +} +/* }}} */ + +/* {{{ proto string|array RedisCluster::srandmember(string key, [long count]) */ +PHP_METHOD(RedisCluster, srandmember) { + redisCluster *c = Z_REDIS_OBJ_P(getThis()); + 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 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 = Z_REDIS_OBJ_P(getThis()); + 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 = Z_REDIS_OBJ_P(getThis()); + 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 = Z_REDIS_OBJ_P(getThis()); + 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, php_redis_fetch_object(Z_OBJ_P(getThis())), + "UNSUBSCRIBE"); +} +/* }}} */ + +/* {{{ proto array RedisCluster::punsubscribe(array pats) */ +PHP_METHOD(RedisCluster, punsubscribe) { + generic_unsub_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, php_redis_fetch_object(Z_OBJ_P(getThis())), + "PUNSUBSCRIBE"); +} +/* }}} */ + +/* Parse arguments for EVAL or EVALSHA in the context of cluster. If we aren't + * provided any "keys" as arguments, the only choice is to send the command to + * a random node in the cluster. If we are passed key arguments the best we + * can do is make sure they all map to the same "node", as we don't know what + * the user is actually doing in the LUA source itself. */ +/* EVAL/EVALSHA */ +static void cluster_eval_cmd(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, + char *kw, int kw_len) +{ + redisClusterNode *node=NULL; + char *lua, *key; + int key_free, args_count=0; + size_t lua_len, key_len; + zval *z_arr=NULL, *z_ele; + HashTable *ht_arr; + HashPosition ptr; + zend_long num_keys = 0; + short slot; + smart_string cmdstr = {0}; + + /* Parse args */ + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|al", &lua, &lua_len, + &z_arr, &num_keys)==FAILURE) + { + RETURN_FALSE; + } + + /* Grab arg count */ + if(z_arr != NULL) { + ht_arr = Z_ARRVAL_P(z_arr); + args_count = zend_hash_num_elements(ht_arr); + } + + /* Format header, add script or SHA, and the number of args which are keys */ + redis_cmd_init_sstr(&cmdstr, 2 + args_count, kw, kw_len); + 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(args_count > 0) { + for(zend_hash_internal_pointer_reset_ex(ht_arr, &ptr); + (z_ele = zend_hash_get_current_data_ex(ht_arr, &ptr)) != NULL; + zend_hash_move_forward_ex(ht_arr, &ptr)) + { + convert_to_string(z_ele); + key = Z_STRVAL_P(z_ele); + key_len = Z_STRLEN_P(z_ele); + + /* If we're still on a key, prefix it check node */ + if(num_keys-- > 0) { + key_free = redis_key_prefix(c->flags, &key, &key_len); + slot = cluster_hash_key(key, key_len); + + /* validate that this key maps to the same node */ + if(node && c->master[slot] != node) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Keys appear to map to different nodes"); + RETURN_FALSE; + } + + node = c->master[slot]; + } else { + key_free = 0; + } + + /* Append this key/argument */ + redis_cmd_append_sstr(&cmdstr, key, key_len); + + /* Free key if we prefixed */ + if(key_free) efree(key); + } + } else { + /* Pick a slot at random, we're being told there are no keys */ + slot = rand() % REDIS_CLUSTER_MOD; + } + + if(cluster_send_command(c, slot, cmdstr.c, cmdstr.len TSRMLS_CC)<0) { + efree(cmdstr.c); + RETURN_FALSE; + } + + 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); + RETURN_ZVAL(getThis(), 1, 0); + } + + efree(cmdstr.c); +} + +/* {{{ proto mixed RedisCluster::eval(string script, [array args, int numkeys) */ +PHP_METHOD(RedisCluster, eval) { + cluster_eval_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, php_redis_fetch_object(Z_OBJ_P(getThis())), + "EVAL", 4); +} +/* }}} */ + +/* {{{ proto mixed RedisCluster::evalsha(string sha, [array args, int numkeys]) */ +PHP_METHOD(RedisCluster, evalsha) { + cluster_eval_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, php_redis_fetch_object(Z_OBJ_P(getThis())), + "EVALSHA", 7); +} +/* }}} */ + +/* 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 = Z_REDIS_OBJ_P(getThis()); + RETURN_LONG(c->flags->mode); +} +/* }}} */ + +/* {{{ proto string RedisCluster::getlasterror() */ +PHP_METHOD(RedisCluster, getlasterror) { + redisCluster *c = Z_REDIS_OBJ_P(getThis()); + + if(c->err != NULL && c->err_len > 0) { + RETURN_STRINGL(c->err, c->err_len); + } else { + RETURN_NULL(); + } +} +/* }}} */ + +/* {{{ proto bool RedisCluster::clearlasterror() */ +PHP_METHOD(RedisCluster, clearlasterror) { + redisCluster *c = Z_REDIS_OBJ_P(getThis()); + + if (c->err) efree(c->err); + c->err = NULL; + c->err_len = 0; + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto long RedisCluster::getOption(long option */ +PHP_METHOD(RedisCluster, getoption) { + redis_getoption_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, + php_redis_fetch_object(Z_OBJ_P(getThis()))->flags, php_redis_fetch_object(Z_OBJ_P(getThis()))); +} +/* }}} */ + +/* {{{ proto bool RedisCluster::setOption(long option, mixed value) */ +PHP_METHOD(RedisCluster, setoption) { + redis_setoption_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, + php_redis_fetch_object(Z_OBJ_P(getThis()))->flags, php_redis_fetch_object(Z_OBJ_P(getThis()))); +} +/* }}} */ + +/* {{{ proto string RedisCluster::_prefix(string key) */ +PHP_METHOD(RedisCluster, _prefix) { + redis_prefix_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, + php_redis_fetch_object(Z_OBJ_P(getThis()))->flags); +} +/* }}} */ + +/* {{{ proto string RedisCluster::_serialize(mixed val) */ +PHP_METHOD(RedisCluster, _serialize) { + redis_serialize_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, + php_redis_fetch_object(Z_OBJ_P(getThis()))->flags); +} +/* }}} */ + +/* {{{ proto mixed RedisCluster::_unserialize(string val) */ +PHP_METHOD(RedisCluster, _unserialize) { + redis_unserialize_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, + php_redis_fetch_object(Z_OBJ_P(getThis()))->flags, + redis_cluster_exception_ce); +} +/* }}} */ + +/* {{{ proto array RedisCluster::_masters() */ +PHP_METHOD(RedisCluster, _masters) { + redisCluster *c = Z_REDIS_OBJ_P(getThis()); + zval z_ret, z_sub; + redisClusterNode *node; + char *host; + short port; + + array_init(&z_ret); + + for(zend_hash_internal_pointer_reset(c->nodes); + (node = zend_hash_get_current_data_ptr(c->nodes)) != NULL; + zend_hash_move_forward(c->nodes)) + { + host = node->sock->host; + port = node->sock->port; + + array_init(&z_sub); + + add_next_index_stringl(&z_sub, host, strlen(host)); + add_next_index_long(&z_sub, port); + add_next_index_zval(&z_ret, &z_sub); + } + + ZVAL_DUP(return_value, &z_ret); + zval_dtor(&z_ret); +} + +PHP_METHOD(RedisCluster, _redir) { + redisCluster *c = Z_REDIS_OBJ_P(getThis()); + char buf[255]; + size_t len; + + len = snprintf(buf, sizeof(buf), "%s:%d", c->redir_host, c->redir_port); + if(c->redir_host_len) { + RETURN_STRINGL(buf, len); + } else { + RETURN_NULL(); + } +} + +/* + * Transaction handling + */ + +/* {{{ proto bool RedisCluster::multi() */ +PHP_METHOD(RedisCluster, multi) { + redisCluster *c = Z_REDIS_OBJ_P(getThis()); + + 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 = Z_REDIS_OBJ_P(getThis()); + HashTable *ht_dist; + clusterDistList *dl; + smart_string cmd = {0}; + zval *z_args; + int argc = ZEND_NUM_ARGS(), i; + zend_ulong slot; + + // 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(*z_args) * 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;ilen;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; + } + + // Cleanup + cluster_dist_free(ht_dist); + efree(z_args); + efree(cmd.c); + + RETURN_TRUE; +} + +/* {{{ proto bool RedisCluster::unwatch() */ +PHP_METHOD(RedisCluster, unwatch) { + redisCluster *c = Z_REDIS_OBJ_P(getThis()); + 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 = Z_REDIS_OBJ_P(getThis()); + 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 = Z_REDIS_OBJ_P(getThis()); + + 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) +{ + int key_free; + size_t key_len; + zval *z_host, *z_port, z_tmp; + short slot; + char *key; + + /* Start with our temp var undefined */ + ZVAL_UNDEF(&z_tmp); + + /* 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 */ + if (Z_TYPE_P(z_arg) != IS_STRING) { + ZVAL_DUP(&z_tmp, z_arg); + convert_to_string(&z_tmp); + ZVAL_DUP(z_arg, &z_tmp); + } + + key = Z_STRVAL_P(z_arg); + key_len = Z_STRLEN_P(z_arg); + + /* Hash it */ + key_free = redis_key_prefix(c->flags, &key, &key_len); + slot = cluster_hash_key(key, key_len); + if(key_free) efree(key); + + /* Destroy our temp value if we had to convert it */ + if (Z_TYPE(z_tmp) != IS_UNDEF) { + zval_dtor(&z_tmp); + } + } 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 = Z_REDIS_OBJ_P(getThis()); + 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_cmd_format_static(&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 = Z_REDIS_OBJ_P(getThis()); + 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; + } + + /* Grab args */ + z_args = emalloc(sizeof(*z_args) * argc); + 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 = Z_REDIS_OBJ_P(getThis()); + char *cmd, *pat=NULL; + int cmd_len; + size_t pat_len = 0; + 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 = Z_REDIS_OBJ_P(getThis()); + REDIS_REPLY_TYPE rtype; + char *cmd, *opt=NULL; + int cmd_len; + size_t opt_len; + 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_cmd_format_static(&cmd, "INFO", "s", opt, opt_len); + } else { + cmd_len = redis_cmd_format_static(&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 = Z_REDIS_OBJ_P(getThis()); + char *cmd, *opt=NULL, *arg=NULL; + int cmd_len; + size_t opt_len, arg_len; + 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_cmd_format_static(&cmd, "CLIENT", "ss", opt, opt_len, + arg, arg_len); + } else if(ZEND_NUM_ARGS() == 2) { + cmd_len = redis_cmd_format_static(&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 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 = Z_REDIS_OBJ_P(getThis()); + REDIS_REPLY_TYPE rtype; + zval *z_arg; + char *cmd, *msg; + int cmd_len; + size_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_cmd_format_static(&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 = Z_REDIS_OBJ_P(getThis()); + char *cmd = NULL; + zval *z_args; + short slot; + + /* Sanity check on our arguments */ + z_args = (zval *) safe_emalloc(sizeof(zval), argc, 0); + 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."); + efree(z_args); + RETURN_FALSE; + } else 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); + efree(z_args); + 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 noexpandtab shiftwidth=4: */ diff -Nru php-redis-2.2.4/redis-3.0.0/redis_cluster.h php-redis-3.0.0/redis-3.0.0/redis_cluster.h --- php-redis-2.2.4/redis-3.0.0/redis_cluster.h 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/redis_cluster.h 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,282 @@ +#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) + +static inline redisCluster *php_redis_fetch_object(zend_object *obj) { + return (redisCluster *)((char *)(obj) - XtOffsetOf(redisCluster, std)); +} +#define Z_REDIS_OBJ_P(zv) php_redis_fetch_object(Z_OBJ_P(zv)); + + +/* 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; \ + for(zend_hash_internal_pointer_reset(c->nodes); \ + (_node = zend_hash_get_current_data_ptr(c->nodes)); \ + zend_hash_move_forward(c->nodes)) \ + { \ + _node->sock->watching = 0; \ + _node->sock->mode = ATOMIC; \ + } \ + c->flags->watching = 0; \ + c->flags->mode = ATOMIC; \ + +/* Simple 1-1 command -> response macro */ +#define CLUSTER_PROCESS_CMD(cmdname, resp_func, readcmd) \ + redisCluster *c = Z_REDIS_OBJ_P(getThis()); \ + 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 = Z_REDIS_OBJ_P(getThis()); \ + 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); + +/* Create cluster context */ +zend_object *create_cluster_context(zend_class_entry *class_type + TSRMLS_DC); + +/* 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, 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); + +/* 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 diff -Nru php-redis-2.2.4/redis-3.0.0/redis_commands.c php-redis-3.0.0/redis-3.0.0/redis_commands.c --- php-redis-2.2.4/redis-3.0.0/redis_commands.c 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/redis_commands.c 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,3051 @@ +/* -*- 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 + +/* 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_format_static(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; + size_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_format_static(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, *val=NULL; + size_t key_len, val_len; + int val_free, key_free; + 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; + } + + // Serialize value, prefix key + val_free = redis_serialize(redis_sock, z_val, &val, &val_len TSRMLS_CC); + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // Construct our command + *cmd_len = redis_cmd_format_static(cmd, kw, "sls", key, key_len, expire, + val, val_len); + + // Set the slot if directed + CMD_SET_SLOT(slot,key,key_len); + + if(val_free) efree(val); + if(key_free) efree(key); + + 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; + size_t key_len, val_len; + int key_free; + zend_long lval; + + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sls", &key, &key_len, + &lval, &val, &val_len)==FAILURE) + { + return FAILURE; + } + + // Prefix our key if requested + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // Construct command + *cmd_len = redis_cmd_format_static(cmd, kw, "sds", key, key_len, (int)lval, + val, val_len); + + // Set slot + CMD_SET_SLOT(slot,key,key_len); + + // Free our key if we prefixed + if(key_free) efree(key); + + 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, *val; + size_t key_len, val_len; + int key_free, val_free; + zval *z_val; + + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &key, &key_len, + &z_val)==FAILURE) + { + return FAILURE; + } + + val_free = redis_serialize(redis_sock, z_val, &val, &val_len TSRMLS_CC); + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // Construct our command + *cmd_len = redis_cmd_format_static(cmd, kw, "ss", key, key_len, val, + val_len); + + // Set our slot if directed + CMD_SET_SLOT(slot,key,key_len); + + if(val_free) efree(val); + if(key_free) efree(key); + + 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; + size_t key_len, val_len; + + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &key, &key_len, + &val, &val_len)==FAILURE) + { + return FAILURE; + } + + // Prefix key + redis_key_prefix(redis_sock, &key, &key_len); + + // Construct command + *cmd_len = redis_cmd_format_static(cmd, kw, "ss", key, key_len, val, + val_len); + + // Set slot if directed + CMD_SET_SLOT(slot,key,key_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 *key, *val1, *val2; + size_t key_len, val1_len, val2_len; + int key_free; + + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss", &key, &key_len, + &val1, &val1_len, &val2, &val2_len)==FAILURE) + { + return FAILURE; + } + + // Prefix key + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // Construct command + *cmd_len = redis_cmd_format_static(cmd, kw, "sss", key, key_len, val1, + val1_len, val2, val2_len); + + // Set slot + CMD_SET_SLOT(slot,key,key_len); + + // Free key if prefixed + if(key_free) efree(key); + + // 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 *key1, *key2; + size_t key1_len, key2_len; + int key1_free, key2_free; + + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &key1, &key1_len, + &key2, &key2_len)==FAILURE) + { + return FAILURE; + } + + // Prefix both keys + key1_free = redis_key_prefix(redis_sock, &key1, &key1_len); + key2_free = redis_key_prefix(redis_sock, &key2, &key2_len); + + // 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(key1, key1_len); + short slot2 = cluster_hash_key(key2, key2_len); + + // Check if Redis would give us a CROSSLOT error + if(slot1 != slot2) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Keys don't hash to the same slot"); + if(key1_free) efree(key1); + if(key2_free) efree(key2); + return FAILURE; + } + + // They're both the same + *slot = slot1; + } + + // Construct our command + *cmd_len = redis_cmd_format_static(cmd, kw, "ss", key1, key1_len, key2, + key2_len); + + 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; + size_t key_len; + int key_free; + zend_long lval; + + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl", &key, &key_len, + &lval)==FAILURE) + { + return FAILURE; + } + + // Prefix key + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // Disallow zero length keys (for now) + if(key_len == 0) { + if(key_free) efree(key); + return FAILURE; + } + + // Construct our command + *cmd_len = redis_cmd_format_static(cmd, kw, "sl", key, key_len, lval); + + // Set slot if directed + CMD_SET_SLOT(slot, key, key_len); + + // 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; + size_t key_len; + int key_free; + zend_long val1, val2; + + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sll", &key, &key_len, + &val1, &val2)==FAILURE) + { + return FAILURE; + } + + // Prefix our key + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // Construct command + *cmd_len = redis_cmd_format_static(cmd, kw, "sll", key, key_len, val1, + val2); + + // Set slot + CMD_SET_SLOT(slot,key,key_len); + + if(key_free) efree(key); + + 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; + size_t key_len; + int key_free; + + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len) + ==FAILURE) + { + return FAILURE; + } + + // Prefix our key + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // Construct our command + *cmd_len = redis_cmd_format_static(cmd, kw, "s", key, key_len); + + // Set slot if directed + CMD_SET_SLOT(slot,key,key_len); + + if(key_free) efree(key); + + 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; + size_t key_len; + int key_free; + double val; + + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sd", &key, &key_len, + &val)==FAILURE) + { + return FAILURE; + } + + // Prefix our key + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // Construct our command + *cmd_len = redis_cmd_format_static(cmd, kw, "sf", key, key_len, val); + + // Set slot if directed + CMD_SET_SLOT(slot,key,key_len); + + if(key_free) efree(key); + + 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; + size_t key_len; + int key_free; + 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; + } + + key_free = redis_key_prefix(redis_sock, &key, &key_len); + if(ws) { + *cmd_len = redis_cmd_format_static(cmd, kw, "sdds", key, key_len, start, + end, "WITHSCORES", sizeof("WITHSCORES")-1); + } else { + *cmd_len = redis_cmd_format_static(cmd, kw, "sdd", key, key_len, start, + end); + } + + CMD_SET_SLOT(slot, key, key_len); + + // Free key, push out WITHSCORES option + if(key_free) efree(key); + *withscores = ws; + + return SUCCESS; +} + +/* ZRANGEBYSCORE/ZREVRANGEBYSCORE */ +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; + int key_free; + char *start, *end; + size_t key_len, start_len, end_len; + int has_limit=0; + long limit_low, limit_high; + zval *z_opt=NULL, *z_ele; + HashTable *ht_opt; + + 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); + + // Check for WITHSCORES + *withscores = ((z_ele = zend_hash_str_find(ht_opt,"withscores",sizeof("withscores") - 1)) != NULL && Z_TYPE_P(z_ele) == IS_TRUE); + + // LIMIT + if((z_ele = zend_hash_str_find(ht_opt, "limit", sizeof("limit") - 1)) != NULL) + { + HashTable *ht_limit = Z_ARRVAL_P(z_ele); + zval *z_off, *z_cnt; + if((z_off = zend_hash_index_find(ht_limit,0)) != NULL && + (z_cnt = zend_hash_index_find(ht_limit,1)) != NULL && + Z_TYPE_P(z_off) == IS_LONG && Z_TYPE_P(z_cnt) == IS_LONG) + { + has_limit = 1; + limit_low = Z_LVAL_P(z_off); + limit_high = Z_LVAL_P(z_cnt); + } + } + } + + // Prefix our key, set slot + key_free = redis_key_prefix(redis_sock, &key, &key_len); + CMD_SET_SLOT(slot,key,key_len); + + // Construct our command + if(*withscores) { + if(has_limit) { + *cmd_len = redis_cmd_format_static(cmd, kw, "ssssdds", key, key_len, + start, start_len, end, end_len, "LIMIT", 5, limit_low, + limit_high, "WITHSCORES", 10); + } else { + *cmd_len = redis_cmd_format_static(cmd, kw, "ssss", key, key_len, + start, start_len, end, end_len, "WITHSCORES", 10); + } + } else { + if(has_limit) { + *cmd_len = redis_cmd_format_static(cmd, kw, "ssssdd", key, key_len, + start, start_len, end, end_len, "LIMIT", 5, limit_low, + limit_high); + } else { + *cmd_len = redis_cmd_format_static(cmd, kw, "sss", key, key_len, + start, start_len, end, end_len); + } + } + + // Free our key if we prefixed + if(key_free) efree(key); + + 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; + zval *z_keys, *z_weights=NULL, *z_ele; + HashTable *ht_keys, *ht_weights=NULL; + HashPosition ptr; + smart_string cmdstr = {0}; + int argc = 2, keys_count; + size_t agg_op_len = 0, key_len = 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 + for(zend_hash_internal_pointer_reset_ex(ht_keys, &ptr); + (z_ele = zend_hash_get_current_data_ex(ht_keys, &ptr)) != NULL; + zend_hash_move_forward_ex(ht_keys, &ptr)) + { + char *key; + int key_free; + zval z_tmp; + ZVAL_UNDEF(&z_tmp); + + if(Z_TYPE_P(z_ele) == IS_STRING) { + key = Z_STRVAL_P(z_ele); + key_len = Z_STRLEN_P(z_ele); + } else { + ZVAL_DUP(&z_tmp, z_ele); + convert_to_string(&z_tmp); + + key = Z_STRVAL(z_tmp); + key_len = Z_STRLEN(z_tmp); + } + + // Prefix key if necissary + 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); + if(key_free) efree(key); + if(Z_TYPE(z_tmp) != IS_UNDEF) { + zval_dtor(&z_tmp); + } + return FAILURE; + } + + // Append this input set + redis_cmd_append_sstr(&cmdstr, key, key_len); + + // Cleanup + if(key_free) efree(key); + if(Z_TYPE(z_tmp) != IS_UNDEF) { + zval_dtor(&z_tmp); + } + } + + // Weights + if(ht_weights != NULL) { + redis_cmd_append_sstr(&cmdstr, "WEIGHTS", sizeof("WEIGHTS")-1); + + // Process our weights + for(zend_hash_internal_pointer_reset_ex(ht_weights, &ptr); + (z_ele = zend_hash_get_current_data_ex(ht_weights, &ptr)) != NULL; + zend_hash_move_forward_ex(ht_weights, &ptr)) + { + // Ignore non numeric args unless they're inf/-inf + if(Z_TYPE_P(z_ele) != IS_LONG && Z_TYPE_P(z_ele) != IS_DOUBLE && + strncasecmp(Z_STRVAL_P(z_ele),"inf",sizeof("inf"))!=0 && + strncasecmp(Z_STRVAL_P(z_ele),"-inf",sizeof("-inf"))!=0 && + strncasecmp(Z_STRVAL_P(z_ele),"+inf",sizeof("+inf"))!=0) + { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Weights must be numeric or '-inf','inf','+inf'"); + efree(cmdstr.c); + return FAILURE; + } + + 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: + redis_cmd_append_sstr(&cmdstr, Z_STRVAL_P(z_ele), + Z_STRLEN_P(z_ele)); + break; + } + } + } + + // 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; + HashPosition ptr; + smart_string cmdstr = {0}; + subscribeContext *sctx = emalloc(sizeof(subscribeContext)); + int key_free; + size_t key_len; + 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 + for(zend_hash_internal_pointer_reset_ex(ht_chan, &ptr); + (z_chan = zend_hash_get_current_data_ex(ht_chan, &ptr)) != NULL; + zend_hash_move_forward_ex(ht_chan, &ptr)) + { + // We want to deal with strings here + convert_to_string(z_chan); + + // Grab channel name, prefix if required + key = Z_STRVAL_P(z_chan); + key_len = Z_STRLEN_P(z_chan); + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // Add this channel + redis_cmd_append_sstr(&cmdstr, key, key_len); + + // Free our key if it was prefixed + if(key_free) efree(key); + } + + // 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; + HashPosition ptr; + 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)); + + for(zend_hash_internal_pointer_reset_ex(ht_arr, &ptr); + (z_chan = zend_hash_get_current_data_ex(ht_arr, &ptr)) != NULL; + zend_hash_move_forward_ex(ht_arr, &ptr)) + { + char *key = Z_STRVAL_P(z_chan); + size_t key_len = Z_STRLEN_P(z_chan), 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); + } + + // 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; + size_t key_len, min_len, max_len; + int key_free; + zend_long offset, count; + int argc = ZEND_NUM_ARGS(); + + /* 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; + } + + /* Prefix key */ + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + /* Construct command */ + if(argc == 3) { + *cmd_len = redis_cmd_format_static(cmd, kw, "sss", key, key_len, min, + min_len, max, max_len); + } else { + *cmd_len = redis_cmd_format_static(cmd, kw, "ssssll", key, key_len, min, + min_len, max, max_len, "LIMIT", sizeof("LIMIT")-1, offset, count); + } + + /* Pick our slot */ + CMD_SET_SLOT(slot,key,key_len); + + /* Free key if we prefixed */ + if(key_free) efree(key); + + 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; + size_t key_len, min_len, max_len; + int key_free; + + /* 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; + } + + /* Prefix key if we need to */ + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + /* Construct command */ + *cmd_len = redis_cmd_format_static(cmd, kw, "sss", key, key_len, min, + min_len, max, max_len); + + /* set slot */ + CMD_SET_SLOT(slot,key,key_len); + + /* Free key if prefixed */ + if(key_free) efree(key); + + 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}; + char *arg; + int arg_free, i, argc = ZEND_NUM_ARGS(); + size_t arg_len; + + // 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 = (zval *) safe_emalloc(sizeof(zval), argc, 0); + if(zend_get_parameters_array(ht, argc, z_args) == FAILURE) { + efree(z_args); + return FAILURE; + } + + // Grab the first argument (our key) as a string + convert_to_string(&z_args[0]); + arg = Z_STRVAL(z_args[0]); + arg_len = Z_STRLEN(z_args[0]); + + // Prefix if required + arg_free = redis_key_prefix(redis_sock, &arg, &arg_len); + + // Start command construction + redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw)); + redis_cmd_append_sstr(&cmdstr, arg, arg_len); + + // Set our slot, free key prefix if we prefixed it + CMD_SET_SLOT(slot,arg,arg_len); + if(arg_free) efree(arg); + + // Add our members + for(i=1;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; + } + + // Serialize and key prefix if required + val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // Check for an options array + if(z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) { + HashTable *kt = Z_ARRVAL_P(z_opts); + int type; + zend_ulong idx; + zend_string *k; + zval *v; + + /* Iterate our option array */ + for(zend_hash_internal_pointer_reset(kt); + zend_hash_has_more_elements(kt) == SUCCESS; + zend_hash_move_forward(kt)) + { + // Grab key and value + type = zend_hash_get_current_key(kt, &k, &idx); + v = zend_hash_get_current_data(kt); + + /* Detect PX or EX argument and validate timeout */ + if (type == HASH_KEY_IS_STRING && IS_EX_PX_ARG(k->val)) { + /* Set expire type */ + exp_type = k->val; + + /* 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); + } + } + } 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_format_static(cmd, "SET", "ssssl", key, key_len, + val, val_len, set_type, 2, exp_type, + 2, expire); + } else if(exp_type) { + /* SET PX|EX */ + *cmd_len = redis_cmd_format_static(cmd, "SET", "sssl", key, key_len, + val, val_len, exp_type, 2, expire); + } else if(set_type) { + /* SET NX|XX */ + *cmd_len = redis_cmd_format_static(cmd, "SET", "sss", key, key_len, val, + val_len, set_type, 2); + } else if(expire > 0) { + /* Backward compatible SETEX redirection */ + *cmd_len = redis_cmd_format_static(cmd, "SETEX", "sls", key, key_len, + expire, val, val_len); + } else { + /* SET */ + *cmd_len = redis_cmd_format_static(cmd, "SET", "ss", key, key_len, val, + val_len); + } + + // If we've been passed a slot pointer, return the key's slot + CMD_SET_SLOT(slot,key,key_len); + + if(key_free) efree(key); + if(val_free) efree(val); + + 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; + size_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_format_static(cmd, "RPOPLPUSH", "ss", key1, + key1_len, key2, key2_len); + } else { + *cmd_len = redis_cmd_format_static(cmd, "BRPOPLPUSH", "ssd", key1, + key1_len, key2, key2_len, timeout); + } + + 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; + int key_free; + size_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; + } + + /* Prefix the key if required */ + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + /* 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_format_static(cmd,"INCR","s",key,key_len); + } else { + *cmd_len = redis_cmd_format_static(cmd,"INCRBY","sd",key,key_len,val); + } + } else { + if (val == 1) { + *cmd_len = redis_cmd_format_static(cmd,"DECR","s",key,key_len); + } else { + *cmd_len = redis_cmd_format_static(cmd,"DECRBY","sd",key,key_len,val); + } + } + + /* Set our slot */ + CMD_SET_SLOT(slot,key,key_len); + + /* Free our key if we prefixed */ + if (key_free) efree(key); + + /* 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; + size_t key_len, mem_len; + int key_free; + zend_long byval; + + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssl", &key, &key_len, + &mem, &mem_len, &byval)==FAILURE) + { + return FAILURE; + } + + // Prefix our key if necissary + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // Construct command + *cmd_len = redis_cmd_format_static(cmd, "HINCRBY", "ssd", key, key_len, mem, + mem_len, byval); + // Set slot + CMD_SET_SLOT(slot,key,key_len); + + /* Free the key if we prefixed */ + if (key_free) efree(key); + + // 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; + size_t key_len, mem_len; + int key_free; + double byval; + + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssd", &key, &key_len, + &mem, &mem_len, &byval)==FAILURE) + { + return FAILURE; + } + + // Prefix key + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // Construct command + *cmd_len = redis_cmd_format_static(cmd, "HINCRBYFLOAT", "ssf", key, key_len, + mem, mem_len, byval); + + // Set slot + CMD_SET_SLOT(slot,key,key_len); + + /* Free the key if we prefixed */ + if (key_free) efree(key); + + // 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_mem, *z_mems; + int i, count, valid=0, key_free; + size_t key_len; + HashTable *ht_arr; + HashPosition ptr; + 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; + } + + // Prefix our key + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // Allocate memory for mems+1 so we can have a sentinel + z_mems = (zval *) safe_emalloc(sizeof(zval), count + 1, 0); + + // Iterate over our member array + for(zend_hash_internal_pointer_reset_ex(ht_arr, &ptr); + (z_mem = zend_hash_get_current_data_ex(ht_arr, &ptr)) != NULL; + zend_hash_move_forward_ex(ht_arr, &ptr)) + { + // 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_DUP(&z_mems[valid], z_mem); + convert_to_string(&z_mems[valid]); + + // Increment the member count to actually send + valid++; + } + } + + // If nothing was valid, fail + if(valid == 0) { + if(key_free) efree(key); + 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_UNDEF(&z_mems[valid]); + + // Start command construction + redis_cmd_init_sstr(&cmdstr, valid+1, "HMGET", sizeof("HMGET")-1); + redis_cmd_append_sstr(&cmdstr, key, key_len); + + // Iterate over members, appending as arguments + for(i=0;i value array + for(zend_hash_internal_pointer_reset_ex(ht_vals, &pos); + zend_hash_has_more_elements_ex(ht_vals, &pos)==SUCCESS; + zend_hash_move_forward_ex(ht_vals, &pos)) + { + char *mem, *val, kbuf[40]; + zend_string *mem_zstring; + int val_free; + size_t val_len; + unsigned int mem_len; + zval *z_val; + + // Grab our key, and value for this element in our input + ktype = zend_hash_get_current_key_ex(ht_vals, &mem_zstring, &idx, &pos); + z_val = zend_hash_get_current_data_ex(ht_vals, &pos); + + // If the hash key is an integer, convert it to a string + if(ktype != HASH_KEY_IS_STRING) { + mem_len = snprintf(kbuf, sizeof(kbuf), "%ld", (long)idx); + mem = (char*)kbuf; + } else { + // Length returned includes the \0 + mem = mem_zstring->val; + mem_len = mem_zstring->len; + } + + // 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); + } + + // 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; +} + +/* BITPOS */ +int redis_bitpos_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + char **cmd, int *cmd_len, short *slot, void **ctx) +{ + char *key; + int argc; + size_t key_len; + zend_long bit, start, end; + + 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; + } + + // Prefix key + redis_key_prefix(redis_sock, &key, &key_len); + + // Construct command based on arg count + if(argc == 2) { + *cmd_len = redis_cmd_format_static(cmd, "BITPOS", "sd", key, key_len, + bit); + } else if(argc == 3) { + *cmd_len = redis_cmd_format_static(cmd, "BITPOS", "sdd", key, key_len, + bit, start); + } else { + *cmd_len = redis_cmd_format_static(cmd, "BITPOS", "sddd", key, key_len, + bit, start, end); + } + + // Set our slot + CMD_SET_SLOT(slot, key, key_len); + + 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; + int i, key_free, argc = ZEND_NUM_ARGS(); + size_t key_len; + smart_string cmdstr = {0}; + short kslot; + + z_args = (zval *) safe_emalloc(sizeof(zval), argc, 0); + if(zend_get_parameters_array(ht, argc, z_args) == FAILURE || + argc < 3 || Z_TYPE(z_args[0]) != IS_STRING) + { + 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) efree(redis_sock->auth); + redis_sock->auth = estrndup(pw, pw_len); + + // 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; + size_t key_len; + int key_free; + 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; + } + + key_free = redis_key_prefix(redis_sock, &key, &key_len); + *cmd_len = redis_cmd_format_static(cmd, "SETBIT", "sld", key, key_len, + offset, (int)val); + + CMD_SET_SLOT(slot, key, key_len); + + if(key_free) efree(key); + + return SUCCESS; +} + +/* LINSERT */ +int redis_linsert_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + char **cmd, int *cmd_len, short *slot, void **ctx) +{ + char *key, *pivot, *pos, *val; + size_t key_len, pos_len; + size_t pivot_len, val_len; + int key_free, pivot_free, val_free; + 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; + } + + // Prefix key, serialize value and position + key_free = redis_key_prefix(redis_sock, &key, &key_len); + val_free = redis_serialize(redis_sock, z_val, &val, &val_len TSRMLS_CC); + pivot_free = redis_serialize(redis_sock, z_pivot, &pivot, &pivot_len + TSRMLS_CC); + + // Construct command + *cmd_len = redis_cmd_format_static(cmd, "LINSERT", "ssss", key, key_len, + pos, pos_len, pivot, pivot_len, val, val_len); + + // Set slot + CMD_SET_SLOT(slot, key, key_len); + + // Clean up + if(val_free) efree(val); + if(key_free) efree(key); + if(pivot_free) efree(pivot); + + // 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, *val; + size_t key_len; + int key_free, val_free; + size_t val_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; + } + + // Prefix key, serialize value + key_free = redis_key_prefix(redis_sock, &key, &key_len); + val_free = redis_serialize(redis_sock, z_val, &val, &val_len TSRMLS_CC); + + // Construct command + *cmd_len = redis_cmd_format_static(cmd, "LREM", "sds", key, key_len, count, + val, val_len); + + // Set slot + CMD_SET_SLOT(slot, key, key_len); + + // Cleanup + if(val_free) efree(val); + if(key_free) efree(key); + + // 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, *val; + size_t src_len, dst_len, val_len; + int val_free, 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; + } + + val_free = redis_serialize(redis_sock, z_val, &val, &val_len TSRMLS_CC); + 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(val_free) efree(val); + if(src_free) efree(src); + if(dst_free) efree(dst); + return FAILURE; + } + *slot = slot1; + } + + // Construct command + *cmd_len = redis_cmd_format_static(cmd, "SMOVE", "sss", src, src_len, dst, + dst_len, val, val_len); + + // Cleanup + if(val_free) efree(val); + 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, *val; + size_t mem_len, key_len, val_len; + int val_free, key_free; + zval *z_val; + + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssz", &key, &key_len, + &mem, &mem_len, &z_val)==FAILURE) + { + return FAILURE; + } + + // Prefix/serialize + val_free = redis_serialize(redis_sock, z_val, &val, &val_len TSRMLS_CC); + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // Construct command + *cmd_len = redis_cmd_format_static(cmd, kw, "sss", key, key_len, mem, + mem_len, val, val_len); + + // Set slot + CMD_SET_SLOT(slot,key,key_len); + + /* Cleanup our key and value */ + if (val_free) efree(val); + if (key_free) efree(key); + + // 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; + size_t key_len; + int key_free; + zend_long count; + + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &key, &key_len, + &count)==FAILURE) + { + return FAILURE; + } + + // Prefix key if requested + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // 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_format_static(cmd, "SRANDMEMBER", "sl", key, + key_len, count); + } else { + *cmd_len = redis_cmd_format_static(cmd, "SRANDMEMBER", "s", key, + key_len); + } + + // Set slot + CMD_SET_SLOT(slot,key,key_len); + + // Cleanup + if(key_free) efree(key); + + return SUCCESS; +} + +/* ZINCRBY */ +int redis_zincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + char **cmd, int *cmd_len, short *slot, void **ctx) +{ + char *key, *mem; + size_t key_len, mem_len; + int key_free, mem_free; + double incrby; + zval *z_val; + + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sdz", &key, &key_len, + &incrby, &z_val)==FAILURE) + { + return FAILURE; + } + + // Prefix key, serialize + key_free = redis_key_prefix(redis_sock, &key, &key_len); + mem_free = redis_serialize(redis_sock, z_val, &mem, &mem_len TSRMLS_CC); + + *cmd_len = redis_cmd_format_static(cmd, "ZINCRBY", "sfs", key, key_len, + incrby, mem, mem_len); + + CMD_SET_SLOT(slot,key,key_len); + + // Cleanup + if(key_free) efree(key); + if(mem_free) efree(mem); + + 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; + size_t key_len; + HashTable *ht_opts; + smart_string cmdstr = {0}; + 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; + + // Handle key prefixing + key_free = redis_key_prefix(redis_sock, &key, &key_len); + + // 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_format_static(cmd, "SORT", "s", key, key_len); + + // Push out slot, store flag, and clean up + *using_store = 0; + CMD_SET_SLOT(slot,key,key_len); + if(key_free) efree(key); + + return SUCCESS; + } + + // Create our hash table to hold our sort arguments + array_init(&z_argv); + + // SORT + add_next_index_stringl(&z_argv, key, key_len); + + // 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"); + if(key_free) efree(key); + 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!"); + if(key_free) efree(key); + 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"); + if(key_free) efree(key); + 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 { + HashTable *ht_keys = Z_ARRVAL_P(z_ele); + int added=0; + + for(zend_hash_internal_pointer_reset(ht_keys); + zend_hash_has_more_elements(ht_keys)==SUCCESS; + zend_hash_move_forward(ht_keys)) + { + zval *z_key; + + // If we can't get the data, or it's not a string, skip + if((z_key = zend_hash_get_current_data(ht_keys)) == NULL) + continue; + if(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++; + } + + // 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"); + if(key_free) efree(key); + 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) && + Z_TYPE_P(z_ele) == IS_TRUE) + { + 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"); + if(key_free) efree(key); + 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(z_argv); + redis_cmd_init_sstr(&cmdstr, zend_hash_num_elements(ht_argv), "SORT", sizeof("SORT")-1); + + // Iterate through our arguments + for(zend_hash_internal_pointer_reset(ht_argv); + (z_ele = zend_hash_get_current_data(ht_argv)) != NULL; + zend_hash_move_forward(ht_argv)) + { + // 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)); + } + } + + /* 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, argc = ZEND_NUM_ARGS(); + size_t arg_len; + + // We need at least KEY and one member + if(argc < 2) { + return FAILURE; + } + + // Grab arguments as an array + z_args = (zval *) safe_emalloc(sizeof(zval), argc, 0); + if(zend_get_parameters_array(ht, argc, z_args)==FAILURE) { + efree(z_args); + return FAILURE; + } + + // Get first argument (the key) as a string + convert_to_string(&z_args[0]); + arg = Z_STRVAL(z_args[0]); + arg_len = Z_STRLEN(z_args[0]); + + // 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); + if(arg_free) efree(arg); + + // Iterate through the members we're removing + for(i=1;i0) convert_to_string(&z_args[0]); + if(argc<3 || Z_TYPE(z_args[0])!=IS_STRING || (argc-1)%2 != 0) { + efree(z_args); + return FAILURE; + } + + // Prefix our key + key = Z_STRVAL(z_args[0]); + key_len = Z_STRLEN(z_args[0]); + 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); + if(key_free) efree(key); + + // Now the rest of our arguments + for(i=1;i */ + *cmd_len = redis_cmd_format_static(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); + + for(zend_hash_internal_pointer_reset(ht_arr); + (z_ele = zend_hash_get_current_data(ht_arr)) != NULL; + zend_hash_move_forward(ht_arr)) + { + convert_to_string(z_ele); + redis_cmd_append_sstr(&cmdstr, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele)); + } + + *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(redis_sock->prefix, redis_sock->prefix_len); + } + 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) +{ + zend_long option; + long val_long; + char *val_str; + size_t val_len; + struct timeval read_tv; + + 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 +#ifdef HAVE_REDIS_IGBINARY + || val_long == REDIS_SERIALIZER_IGBINARY +#endif + || val_long == REDIS_SERIALIZER_PHP) + { + redis_sock->serializer = val_long; + RETURN_TRUE; + } else { + RETURN_FALSE; + } + break; + case REDIS_OPT_PREFIX: + if(redis_sock->prefix) { + efree(redis_sock->prefix); + } + if(val_len == 0) { + redis_sock->prefix = NULL; + redis_sock->prefix_len = 0; + } else { + redis_sock->prefix_len = val_len; + redis_sock->prefix = ecalloc(1+val_len, 1); + memcpy(redis_sock->prefix, val_str, val_len); + } + 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; + } + RETURN_FALSE; + 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) + { + c->failover = val_long; + RETURN_TRUE; + } else { + RETURN_FALSE; + } + default: + RETURN_FALSE; + } +} + +void redis_prefix_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) { + char *key; + size_t key_len; + + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len) + ==FAILURE) + { + RETURN_FALSE; + } + + if(redis_sock->prefix != NULL && redis_sock->prefix_len>0) { + redis_key_prefix(redis_sock, &key, &key_len); + RETURN_STRINGL(key, key_len); + } else { + RETURN_STRINGL(key, key_len); + } +} + +void redis_serialize_handler(INTERNAL_FUNCTION_PARAMETERS, + RedisSock *redis_sock) +{ + zval *z_val; + char *val; + size_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; + size_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) { + zval z_ret; + if(redis_unserialize(redis_sock, value, value_len, &z_ret TSRMLS_CC) == 0) + { + // 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, 0, 1); + } else { + // Just return the value that was passed to us + RETURN_STRINGL(value, value_len); + } +} + +/* vim: set tabstop=4 softtabstop=4 noexpandtab shiftwidth=4: */ diff -Nru php-redis-2.2.4/redis-3.0.0/redis_commands.h php-redis-3.0.0/redis-3.0.0/redis_commands.h --- php-redis-2.2.4/redis-3.0.0/redis_commands.h 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/redis_commands.h 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,257 @@ +#ifndef REDIS_COMMANDS_H +#define REDIS_COMMANDS_H + +#if defined(_MSC_VER) && _MSC_VER >= 1900 +# if defined(HAVE_GETTIMEOFDAY) +# include + struct timezone + { + int tz_minuteswest; /* minutes W of Greenwich */ + int tz_dsttime; /* type of dst correction */ + }; + int gettimeofday(struct timeval *tv, struct timezone *tz); +# endif +#endif + +#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; + +/* 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); + +/* 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_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); + +/* 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 softtabstops=4 noexpandtab shiftwidth=4: */ diff -Nru php-redis-2.2.4/redis-3.0.0/redis_session.c php-redis-3.0.0/redis-3.0.0/redis_session.c --- php-redis-2.2.4/redis-3.0.0/redis_session.c 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/redis_session.c 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,745 @@ +/* -*- 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 "common.h" +#include "ext/standard/info.h" +#include "php_redis.h" +#include "redis_session.h" +#include +#include "ext/session/php_session.h" + +#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" + + +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); +} + +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; + + if(!rpm->auth || !rpm->auth_len) { /* no password given. */ + return; + } + cmd_len = redis_cmd_format_static(&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_cmd_format_static(&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 < (unsigned int)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, 0 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); + + sapi_module.treat_data(PARSE_STRING, estrdup(url->query), ¶ms TSRMLS_CC); + + if ((param = zend_hash_str_find(Z_ARRVAL(params), "weight", sizeof("weight") - 1)) != NULL) { + convert_to_long_ex(param); + weight = Z_LVAL_P(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), "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) { + convert_to_long_ex(param); + database = Z_LVAL_P(param); + } + if ((param = zend_hash_str_find(Z_ARRVAL(params), "retry_interval", sizeof("retry_interval") - 1)) != NULL) { + convert_to_long_ex(param); + retry_interval = Z_LVAL_P(param); + } + + zval_ptr_dtor(¶ms); + } + + if ((url->path == NULL && url->host == NULL) || weight <= 0 || timeout <= 0) { + php_url_free(url); + redis_pool_free(pool TSRMLS_CC); + PS_SET_MOD_DATA(NULL); + return FAILURE; + } + + RedisSock *redis_sock; + if(url->host) { + redis_sock = redis_sock_create(url->host, strlen(url->host), url->port, timeout, persistent, persistent_id, retry_interval, 0); + } else { /* unix */ + redis_sock = redis_sock_create(url->path, strlen(url->path), 0, timeout, persistent, persistent_id, retry_interval, 0); + } + 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 *session, *cmd; + int session_len, cmd_len; + char *char_val; + int int_val = 0; + + redis_pool *pool = PS_GET_MOD_DATA(); + redis_pool_member *rpm = redis_pool_get_sock(pool, key->val); + RedisSock *redis_sock = rpm?rpm->redis_sock:NULL; + if(!rpm || !redis_sock){ + return FAILURE; + } + + /* send GET command */ + session = redis_session_key(rpm, key->val, key->len, &session_len); + cmd_len = redis_cmd_format_static(&cmd, "GET", "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 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 ((char_val = redis_sock_read(redis_sock, &int_val)) == NULL && int_val != -1) + { + return FAILURE; + } + + *val = zend_string_init(char_val, int_val == -1 ? 0 : int_val, 0); + if (char_val) efree(char_val); + + 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(); + redis_pool_member *rpm = redis_pool_get_sock(pool, key->val TSRMLS_CC); + RedisSock *redis_sock = rpm?rpm->redis_sock:NULL; + if(!rpm || !redis_sock){ + return FAILURE; + } + + /* send SET command */ + session = redis_session_key(rpm, key->val, key->len, &session_len); + cmd_len = redis_cmd_format_static(&cmd, "SETEX", "sds", session, + session_len, + INI_INT("session.gc_maxlifetime"), + val->val, val->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 == 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(); + redis_pool_member *rpm = redis_pool_get_sock(pool, key->val TSRMLS_CC); + RedisSock *redis_sock = rpm?rpm->redis_sock:NULL; + if(!rpm || !redis_sock){ + return FAILURE; + } + + /* send DEL command */ + session = redis_session_key(rpm, key->val, key->len, &session_len); + cmd_len = redis_cmd_format_static(&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)) != 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)) != 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 + c->flags->prefix_len; + skey = emalloc(*skeylen); + memcpy(skey, c->flags->prefix, c->flags->prefix_len); + memcpy(skey + c->flags->prefix_len, 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 = estrndup(prefix, prefix_len); + c->flags->prefix_len = prefix_len; + + 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 */ + skey = cluster_session_key(c, key->val, key->len, &skeylen, &slot); + cmdlen = redis_cmd_format_static(&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 || reply->str == NULL) { + if (reply) cluster_free_reply(reply, 1); + return FAILURE; + } + + /* Push reply value to caller */ + *val = zend_string_init(reply->str, reply->len, 0); + + /* 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 */ + skey = cluster_session_key(c, key->val, key->len, &skeylen, &slot); + cmdlen = redis_cmd_format_static(&cmd, "SETEX", "sds", skey, skeylen, + INI_INT("session.gc_maxlifetime"), + val->val, val->len); + 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 */ + skey = cluster_session_key(c, key->val, key->len, &skeylen, &slot); + cmdlen = redis_cmd_format_static(&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; +} + +ps_module ps_mod_redis = { + PS_MOD(redis) +}; +ps_module ps_mod_redis_cluster = { + PS_MOD(rediscluster) +}; + +#endif + +/* vim: set tabstop=4 expandtab: */ diff -Nru php-redis-2.2.4/redis-3.0.0/redis_session.h php-redis-3.0.0/redis-3.0.0/redis_session.h --- php-redis-2.2.4/redis-3.0.0/redis_session.h 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/redis_session.h 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,22 @@ +#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 + diff -Nru php-redis-2.2.4/redis-3.0.0/rpm/php-redis.spec php-redis-3.0.0/redis-3.0.0/rpm/php-redis.spec --- php-redis-2.2.4/redis-3.0.0/rpm/php-redis.spec 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/rpm/php-redis.spec 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,48 @@ +%global php_apiver %((echo 0; php -i 2>/dev/null | sed -n 's/^PHP API => //p') | tail -1) +%global php_extdir %(php-config --extension-dir 2>/dev/null || echo "undefined") +%global php_version %(php-config --version 2>/dev/null || echo 0) + +Name: php-redis +Version: 2.2.5 +Release: 1%{?dist} +Summary: The phpredis extension provides an API for communicating with the Redis key-value store. + +Group: Development/Languages +License: PHP +URL: https://github.com/nicolasff/phpredis +Source0: https://github.com/nicolasff/phpredis/tarball/master +Source1: redis.ini +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +BuildRequires: php-devel +Requires: php(zend-abi) = %{php_zend_api} +Requires: php(api) = %{php_apiver} + +%description +The phpredis extension provides an API for communicating with the Redis key-value store. + +%prep +%setup -q -n nicolasff-phpredis-43bc590 + +%build +%{_bindir}/phpize +%configure +make %{?_smp_mflags} + +%install +rm -rf $RPM_BUILD_ROOT +make install INSTALL_ROOT=$RPM_BUILD_ROOT + +# install configuration +%{__mkdir} -p $RPM_BUILD_ROOT%{_sysconfdir}/php.d +%{__cp} %{SOURCE1} $RPM_BUILD_ROOT%{_sysconfdir}/php.d/redis.ini + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root,-) +%doc CREDITS +%config(noreplace) %{_sysconfdir}/php.d/redis.ini +%{php_extdir}/redis.so + diff -Nru php-redis-2.2.4/redis-3.0.0/rpm/redis.ini php-redis-3.0.0/redis-3.0.0/rpm/redis.ini --- php-redis-2.2.4/redis-3.0.0/rpm/redis.ini 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/rpm/redis.ini 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1 @@ +extension=redis.so diff -Nru php-redis-2.2.4/redis-3.0.0/serialize.list php-redis-3.0.0/redis-3.0.0/serialize.list --- php-redis-2.2.4/redis-3.0.0/serialize.list 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/serialize.list 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,49 @@ +This file lists which methods support serialization. Only indented methods have been ported. + + get + set + setex + setnx + getSet + getMultiple +append +substr +strlen + lPush + lPushx + rPush + rPushx + lPop + rPop + blPop + brPop + lRemove + lGet + lGetRange + lSet + lInsert + + sAdd + sRemove + sMove + sContains + + zAdd + zDelete + zScore + zRank + zRevRank + zIncrBy + + mset + + hGet + hSet + hGetAll + hExists + hMset + hMget + +publish +subscribe +unsubscribe diff -Nru php-redis-2.2.4/redis-3.0.0/tests/make-cluster.sh php-redis-3.0.0/redis-3.0.0/tests/make-cluster.sh --- php-redis-2.2.4/redis-3.0.0/tests/make-cluster.sh 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/tests/make-cluster.sh 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,142 @@ +#!/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 diff -Nru php-redis-2.2.4/redis-3.0.0/tests/mkring.sh php-redis-3.0.0/redis-3.0.0/tests/mkring.sh --- php-redis-2.2.4/redis-3.0.0/tests/mkring.sh 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/tests/mkring.sh 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,55 @@ +#!/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 diff -Nru php-redis-2.2.4/redis-3.0.0/tests/RedisArrayTest.php php-redis-3.0.0/redis-3.0.0/tests/RedisArrayTest.php --- php-redis-2.2.4/redis-3.0.0/tests/RedisArrayTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/tests/RedisArrayTest.php 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,551 @@ +\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 + TestSuite::run($className, $str_filter); +} + +?> diff -Nru php-redis-2.2.4/redis-3.0.0/tests/RedisClusterTest.php php-redis-3.0.0/redis-3.0.0/tests/RedisClusterTest.php --- php-redis-2.2.4/redis-3.0.0/tests/RedisClusterTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/tests/RedisClusterTest.php 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,498 @@ +markTestSkipped(); } + public function testSortDesc() { return $this->markTestSkipped(); } + public function testWait() { return $this->markTestSkipped(); } + public function testSelect() { return $this->markTestSkipped(); } + public function testReconnectSelect() { return $this->markTestSkipped(); } + + /* Load our seeds on construction */ + public function __construct() { + $str_nodemap_file = dirname(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() { + 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)); + } + + /* 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')); + } +} +?> diff -Nru php-redis-2.2.4/redis-3.0.0/tests/RedisTest.php php-redis-3.0.0/redis-3.0.0/tests/RedisTest.php --- php-redis-2.2.4/redis-3.0.0/tests/RedisTest.php 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/tests/RedisTest.php 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,4770 @@ +redis = $this->newInstance(); + $info = $this->redis->info(); + $this->version = (isset($info['redis_version'])?$info['redis_version']:'0.0.0'); + } + + 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', ((2^32)-1), 1); + $this->assertEquals(1, $this->redis->getBit('key', ((2^32)-1))); + } + + 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 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() { + $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')); + } + + 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', $this->redis->get('key')); + $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 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); + } + + $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('100'); + + $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(1 === $this->redis->zAdd('key', 1, 'val1')); + $this->assertTrue(1 === $this->redis->zAdd('key', 3, 'val3')); + $this->assertTrue(2 === $this->redis->zAdd('key', 4, 'val4', 5, 'val5')); // multiple parameters + + $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)))); + + $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->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->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'))); + + // 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']); + } + + 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, ""); + } + + 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}'); + } + } + } + + /* 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')); + } +} +?> diff -Nru php-redis-2.2.4/redis-3.0.0/tests/TestRedis.php php-redis-3.0.0/redis-3.0.0/tests/TestRedis.php --- php-redis-2.2.4/redis-3.0.0/tests/TestRedis.php 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/tests/TestRedis.php 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,59 @@ + diff -Nru php-redis-2.2.4/redis-3.0.0/tests/TestSuite.php php-redis-3.0.0/redis-3.0.0/tests/TestSuite.php --- php-redis-2.2.4/redis-3.0.0/tests/TestSuite.php 1970-01-01 00:00:00.000000000 +0000 +++ php-redis-3.0.0/redis-3.0.0/tests/TestSuite.php 2016-06-10 20:05:41.000000000 +0000 @@ -0,0 +1,180 @@ +str_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) { + $this->assertTrue(!$bool); + } + + protected function assertTrue($bool) { + if($bool) + return; + + $bt = debug_backtrace(false); + self::$errors []= sprintf("Assertion failed: %s:%d (%s)\n", + $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]); + } + + 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; + } +} + +?>