├── debian ├── compat ├── control ├── postrm ├── postinst ├── copyright ├── changelog └── rules ├── rpm ├── redis.ini └── php-redis.spec ├── CREDITS ├── redis_session.h ├── debian.control ├── mkdeb.sh ├── serialize.list ├── mkdeb-apache2.sh ├── tests ├── memory.php ├── mkring.sh ├── test.php └── array-tests.php ├── config.w32 ├── redis_array_impl.h ├── redis_array.h ├── COPYING ├── config.m4 ├── library.h ├── package.xml ├── common.h ├── arrays.markdown ├── php_redis.h ├── redis_session.c ├── redis_array_impl.c └── redis_array.c /debian/compat: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /rpm/redis.ini: -------------------------------------------------------------------------------- 1 | extension=redis.so 2 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | Redis client extension for PHP 2 | Alfonso Jimenez (yo@alfonsojimenez.com) 3 | Nasreddine Bouafif (n.bouafif@owlient.eu) 4 | Nicolas Favre-Felix (n.favre-felix@owlient.eu) 5 | Michael Grunder (michael.grunder@gmail.com) 6 | -------------------------------------------------------------------------------- /redis_session.h: -------------------------------------------------------------------------------- 1 | #ifndef REDIS_SESSION_H 2 | #define REDIS_SESSION_H 3 | #ifdef PHP_SESSION 4 | #include "ext/session/php_session.h" 5 | 6 | PS_OPEN_FUNC(redis); 7 | PS_CLOSE_FUNC(redis); 8 | PS_READ_FUNC(redis); 9 | PS_WRITE_FUNC(redis); 10 | PS_DESTROY_FUNC(redis); 11 | PS_GC_FUNC(redis); 12 | 13 | 14 | #endif 15 | #endif 16 | 17 | -------------------------------------------------------------------------------- /debian.control: -------------------------------------------------------------------------------- 1 | Package: phpredis 2 | Version: 2.2.4 3 | Section: web 4 | Priority: optional 5 | Architecture: all 6 | Essential: no 7 | Depends: 8 | Pre-Depends: 9 | Recommends: php5 10 | Suggests: 11 | Installed-Size: 12 | Maintainer: Nicolas Favre-Felix [n.favre-felix@owlient.eu] 13 | Conflicts: 14 | Replaces: 15 | Provides: phpredis 16 | Description: Redis C extension for PHP5. 17 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: php5-redis 2 | Section: web 3 | Priority: optional 4 | Maintainer: Nicolas Favre-Felix 5 | Build-Depends: debhelper (>= 7), php5-dev 6 | Standards-Version: 3.8.0 7 | Homepage: https://github.com/nicolasff/phpredis 8 | 9 | Package: php5-redis 10 | Architecture: any 11 | Depends: ${shlibs:Depends}, ${misc:Depends}, ${php5:Depends} 12 | Recommends: php5 13 | Provides: php5-redis 14 | Conflicts: phpredis 15 | Replaces: phpredis 16 | Description: Redis C extension for PHP5. 17 | -------------------------------------------------------------------------------- /mkdeb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | phpize 3 | ./configure CFLAGS="-O3" 4 | make clean all 5 | DIR=`php-config --extension-dir | cut -c 2-` 6 | 7 | rm -rf debian 8 | 9 | mkdir -p debian 10 | mkdir -p debian/DEBIAN 11 | mkdir -p debian/$DIR 12 | 13 | cp debian.control debian/DEBIAN/control 14 | 15 | UBUNTU=`uname -v | grep -ci ubuntu` 16 | mkdir -p debian/etc/php5/conf.d/ 17 | echo "extension=redis.so" >> debian/etc/php5/conf.d/redis.ini 18 | 19 | cp modules/redis.so debian/$DIR 20 | dpkg -b debian phpredis-`git describe --abbrev=0 --tags`_`uname -m`.deb 21 | rm -rf debian/ 22 | -------------------------------------------------------------------------------- /serialize.list: -------------------------------------------------------------------------------- 1 | This file lists which methods support serialization. Only indented methods have been ported. 2 | 3 | get 4 | set 5 | setex 6 | setnx 7 | getSet 8 | getMultiple 9 | append 10 | substr 11 | strlen 12 | lPush 13 | lPushx 14 | rPush 15 | rPushx 16 | lPop 17 | rPop 18 | blPop 19 | brPop 20 | lRemove 21 | lGet 22 | lGetRange 23 | lSet 24 | lInsert 25 | 26 | sAdd 27 | sRemove 28 | sMove 29 | sContains 30 | 31 | zAdd 32 | zDelete 33 | zScore 34 | zRank 35 | zRevRank 36 | zIncrBy 37 | 38 | mset 39 | 40 | hGet 41 | hSet 42 | hGetAll 43 | hExists 44 | hMset 45 | hMget 46 | 47 | publish 48 | subscribe 49 | unsubscribe 50 | -------------------------------------------------------------------------------- /mkdeb-apache2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | phpize 3 | ./configure CFLAGS="-O3" 4 | make clean all 5 | DIR=`php-config --extension-dir | cut -c 2-` 6 | 7 | rm -rf debian 8 | 9 | mkdir -p debian 10 | mkdir -p debian/DEBIAN 11 | mkdir -p debian/$DIR 12 | 13 | cp debian.control debian/DEBIAN/control 14 | 15 | UBUNTU=`uname -v | grep -ci ubuntu` 16 | mkdir -p debian/etc/php5/apache2/conf.d/ 17 | if [ $UBUNTU = "0" ]; then 18 | mkdir -p debian/etc/php5/cli/conf.d/ 19 | fi 20 | 21 | echo "extension=redis.so" >> debian/etc/php5/apache2/conf.d/redis.ini 22 | 23 | if [ $UBUNTU = "0" ]; then 24 | cp debian/etc/php5/apache2/conf.d/redis.ini debian/etc/php5/cli/conf.d/redis.ini 25 | fi 26 | 27 | cp modules/redis.so debian/$DIR 28 | dpkg -b debian phpredis-`uname -m`.deb 29 | rm -rf debian/ 30 | -------------------------------------------------------------------------------- /tests/memory.php: -------------------------------------------------------------------------------- 1 | mset($data); 26 | foreach($data as $k => $v) { 27 | if($v != $ra->get($k)) { 28 | echo "Expected $v\n"; 29 | die("FAIL"); 30 | } 31 | } 32 | 33 | $ra = ra(); 34 | $data = data(); 35 | if(array_values($data) != $ra->mget(array_keys($data))) { 36 | die("FAIL"); 37 | } 38 | } 39 | ?> 40 | -------------------------------------------------------------------------------- /tests/mkring.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PORTS="6379 6380 6381 6382" 4 | REDIS=redis-server 5 | 6 | function start_node() { 7 | P=$1 8 | echo "starting node on port $P"; 9 | CONFIG_FILE=`tempfile` 10 | cat > $CONFIG_FILE << CONFIG 11 | port $P 12 | CONFIG 13 | $REDIS $CONFIG_FILE > /dev/null 2>/dev/null & 14 | sleep 1 15 | rm -f $CONFIG_FILE 16 | } 17 | 18 | function stop_node() { 19 | 20 | P=$1 21 | PID=$2 22 | redis-cli -h localhost -p $P shutdown 23 | kill -9 $PID 2>/dev/null 24 | } 25 | 26 | function stop() { 27 | for P in $PORTS; do 28 | PID=`lsof -i :$P | tail -1 | cut -f 2 -d " "` 29 | if [ "$PID" != "" ]; then 30 | stop_node $P $PID 31 | fi 32 | done 33 | } 34 | 35 | function start() { 36 | for P in $PORTS; do 37 | start_node $P 38 | done 39 | } 40 | 41 | case "$1" in 42 | start) 43 | start 44 | ;; 45 | stop) 46 | stop 47 | ;; 48 | restart) 49 | stop 50 | start 51 | ;; 52 | *) 53 | echo "Usage: $0 [start|stop|restart]" 54 | ;; 55 | esac 56 | -------------------------------------------------------------------------------- /config.w32: -------------------------------------------------------------------------------- 1 | // vim: ft=javascript: 2 | 3 | ARG_ENABLE("redis", "whether to enable redis support", "yes"); 4 | ARG_ENABLE("redis-session", "whether to enable sessions", "yes"); 5 | ARG_ENABLE("redis-igbinary", "whether to enable igbinary serializer support", "no"); 6 | 7 | if (PHP_REDIS != "no") { 8 | var sources = "redis.c library.c redis_array.c redis_array_impl.c"; 9 | if (PHP_REDIS_SESSION != "no") { 10 | ADD_SOURCES(configure_module_dirname, "redis_session.c", "redis"); 11 | ADD_EXTENSION_DEP("redis", "session"); 12 | ADD_FLAG("CFLAGS_REDIS", ' /D PHP_SESSION=1 '); 13 | AC_DEFINE("HAVE_REDIS_SESSION", 1); 14 | } 15 | 16 | if (PHP_REDIS_IGBINARY != "no") { 17 | if (CHECK_HEADER_ADD_INCLUDE("igbinary.h", "CFLAGS_REDIS", configure_module_dirname + "\\..\\igbinary")) { 18 | 19 | ADD_EXTENSION_DEP("redis", "igbinary"); 20 | AC_DEFINE("HAVE_REDIS_IGBINARY", 1); 21 | } else { 22 | WARNING("redis igbinary support not enabled"); 23 | } 24 | } 25 | 26 | EXTENSION("redis", sources); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /debian/postrm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # postinst script for an Apache virtual host component of plista 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | set -e 7 | 8 | # summary of how this script can be called: 9 | # * `remove' 10 | # * `purge' 11 | # * `upgrade' 12 | # * `failed-upgrade' 13 | # * `abort-install' 14 | # * `abort-install' 15 | # * `abort-upgrade' 16 | # * `disappear' 17 | # 18 | # for details, see http://www.debian.org/doc/debian-policy/ or 19 | # the debian-policy package 20 | 21 | case "$1" in 22 | purge) 23 | if [ -e /etc/php5/conf.d/redis.ini ]; then 24 | rm -f /etc/php5/conf.d/redis.ini 25 | fi 26 | ;; 27 | remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) 28 | ;; 29 | 30 | *) 31 | echo "postrm called with unknown argument \`$1'" >&2 32 | exit 1 33 | ;; 34 | esac 35 | 36 | # dh_installdeb will replace this with shell code automatically 37 | # generated by other debhelper scripts. 38 | 39 | #DEBHELPER# 40 | 41 | exit 0 42 | -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # postinst script for phpredis 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | set -e 7 | 8 | # summary of how this script can be called: 9 | # * `configure' 10 | # * `abort-upgrade' 11 | # * `abort-remove' `in-favour' 12 | # 13 | # * `abort-remove' 14 | # * `abort-deconfigure' `in-favour' 15 | # `removing' 16 | # 17 | # for details, see http://www.debian.org/doc/debian-policy/ or 18 | # the debian-policy package 19 | 20 | case "$1" in 21 | configure) 22 | cat > /etc/php5/conf.d/redis.ini <&2 33 | exit 1 34 | ;; 35 | esac 36 | 37 | # dh_installdeb will replace this with shell code automatically 38 | # generated by other debhelper scripts. 39 | 40 | #DEBHELPER# 41 | 42 | exit 0 43 | -------------------------------------------------------------------------------- /rpm/php-redis.spec: -------------------------------------------------------------------------------- 1 | %global php_apiver %((echo 0; php -i 2>/dev/null | sed -n 's/^PHP API => //p') | tail -1) 2 | %global php_extdir %(php-config --extension-dir 2>/dev/null || echo "undefined") 3 | %global php_version %(php-config --version 2>/dev/null || echo 0) 4 | 5 | Name: php-redis 6 | Version: 2.2.5 7 | Release: 1%{?dist} 8 | Summary: The phpredis extension provides an API for communicating with the Redis key-value store. 9 | 10 | Group: Development/Languages 11 | License: PHP 12 | URL: https://github.com/nicolasff/phpredis 13 | Source0: https://github.com/nicolasff/phpredis/tarball/master 14 | Source1: redis.ini 15 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) 16 | 17 | BuildRequires: php-devel 18 | Requires: php(zend-abi) = %{php_zend_api} 19 | Requires: php(api) = %{php_apiver} 20 | 21 | %description 22 | The phpredis extension provides an API for communicating with the Redis key-value store. 23 | 24 | %prep 25 | %setup -q -n nicolasff-phpredis-43bc590 26 | 27 | %build 28 | %{_bindir}/phpize 29 | %configure 30 | make %{?_smp_mflags} 31 | 32 | %install 33 | rm -rf $RPM_BUILD_ROOT 34 | make install INSTALL_ROOT=$RPM_BUILD_ROOT 35 | 36 | # install configuration 37 | %{__mkdir} -p $RPM_BUILD_ROOT%{_sysconfdir}/php.d 38 | %{__cp} %{SOURCE1} $RPM_BUILD_ROOT%{_sysconfdir}/php.d/redis.ini 39 | 40 | %clean 41 | rm -rf $RPM_BUILD_ROOT 42 | 43 | %files 44 | %defattr(-,root,root,-) 45 | %doc CREDITS 46 | %config(noreplace) %{_sysconfdir}/php.d/redis.ini 47 | %{php_extdir}/redis.so 48 | 49 | -------------------------------------------------------------------------------- /redis_array_impl.h: -------------------------------------------------------------------------------- 1 | #ifndef REDIS_ARRAY_IMPL_H 2 | #define REDIS_ARRAY_IMPL_H 3 | 4 | #ifdef PHP_WIN32 5 | #include "win32/php_stdint.h" 6 | #else 7 | #include 8 | #endif 9 | #include "common.h" 10 | #include "redis_array.h" 11 | 12 | RedisArray *ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC); 13 | RedisArray *ra_load_array(const char *name TSRMLS_DC); 14 | 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); 15 | zval *ra_find_node_by_name(RedisArray *ra, const char *host, int host_len TSRMLS_DC); 16 | zval *ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos TSRMLS_DC); 17 | void ra_init_function_table(RedisArray *ra); 18 | 19 | void ra_move_key(const char *key, int key_len, zval *z_from, zval *z_to TSRMLS_DC); 20 | char * ra_find_key(RedisArray *ra, zval *z_args, const char *cmd, int *key_len); 21 | void ra_index_multi(zval *z_redis, long multi_value TSRMLS_DC); 22 | 23 | void ra_index_key(const char *key, int key_len, zval *z_redis TSRMLS_DC); 24 | void ra_index_keys(zval *z_pairs, zval *z_redis TSRMLS_DC); 25 | void ra_index_del(zval *z_keys, zval *z_redis TSRMLS_DC); 26 | void ra_index_exec(zval *z_redis, zval *return_value, int keep_all TSRMLS_DC); 27 | void ra_index_discard(zval *z_redis, zval *return_value TSRMLS_DC); 28 | void ra_index_unwatch(zval *z_redis, zval *return_value TSRMLS_DC); 29 | zend_bool ra_is_write_cmd(RedisArray *ra, const char *cmd, int cmd_len); 30 | 31 | void ra_rehash(RedisArray *ra, zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache TSRMLS_DC); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | This package was debianized by: 2 | 3 | Simon Effenberg on Mon, 22 Sep 2010 14:24:03 +0100 4 | 5 | It was downloaded from: 6 | 7 | http://github.com/owlient/phpredis 8 | 9 | Upstream Author(s): 10 | 11 | Nicolas Favre-Felix 12 | Nasreddine Bouafif 13 | 14 | Copyright: 15 | 16 | 17 | 18 | 19 | License: 20 | 21 | ### SELECT: ### 22 | This package is free software; you can redistribute it and/or modify 23 | it under the terms of the GNU General Public License as published by 24 | the Free Software Foundation; either version 2 of the License, or 25 | (at your option) any later version. 26 | ### OR ### 27 | This package is free software; you can redistribute it and/or modify 28 | it under the terms of the GNU General Public License version 2 as 29 | published by the Free Software Foundation. 30 | ########## 31 | 32 | This package is distributed in the hope that it will be useful, 33 | but WITHOUT ANY WARRANTY; without even the implied warranty of 34 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 35 | GNU General Public License for more details. 36 | 37 | You should have received a copy of the GNU General Public License 38 | along with this package; if not, write to the Free Software 39 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 40 | 41 | On Debian systems, the complete text of the GNU General 42 | Public License can be found in `/usr/share/common-licenses/GPL'. 43 | 44 | The Debian packaging is: 45 | 46 | Copyright C) 2010, Simon Effenberg 47 | 48 | and is licensed under the GPL, see above. 49 | 50 | 51 | # Please also look if there are files or directories which have a 52 | # different copyright/license attached and list them here. 53 | 54 | -------------------------------------------------------------------------------- /redis_array.h: -------------------------------------------------------------------------------- 1 | #ifndef REDIS_ARRAY_H 2 | #define REDIS_ARRAY_H 3 | 4 | #ifdef PHP_WIN32 5 | #include "win32/php_stdint.h" 6 | #else 7 | #include 8 | #endif 9 | #include "common.h" 10 | 11 | void redis_destructor_redis_array(zend_rsrc_list_entry * rsrc TSRMLS_DC); 12 | 13 | PHP_METHOD(RedisArray, __construct); 14 | PHP_METHOD(RedisArray, __call); 15 | PHP_METHOD(RedisArray, _hosts); 16 | PHP_METHOD(RedisArray, _target); 17 | PHP_METHOD(RedisArray, _instance); 18 | PHP_METHOD(RedisArray, _function); 19 | PHP_METHOD(RedisArray, _distributor); 20 | PHP_METHOD(RedisArray, _rehash); 21 | 22 | PHP_METHOD(RedisArray, select); 23 | PHP_METHOD(RedisArray, info); 24 | PHP_METHOD(RedisArray, ping); 25 | PHP_METHOD(RedisArray, flushdb); 26 | PHP_METHOD(RedisArray, flushall); 27 | PHP_METHOD(RedisArray, mget); 28 | PHP_METHOD(RedisArray, mset); 29 | PHP_METHOD(RedisArray, del); 30 | PHP_METHOD(RedisArray, keys); 31 | PHP_METHOD(RedisArray, getOption); 32 | PHP_METHOD(RedisArray, setOption); 33 | PHP_METHOD(RedisArray, save); 34 | PHP_METHOD(RedisArray, bgsave); 35 | 36 | PHP_METHOD(RedisArray, multi); 37 | PHP_METHOD(RedisArray, exec); 38 | PHP_METHOD(RedisArray, discard); 39 | PHP_METHOD(RedisArray, unwatch); 40 | 41 | 42 | typedef struct RedisArray_ { 43 | 44 | int count; 45 | char **hosts; /* array of host:port strings */ 46 | zval **redis; /* array of Redis instances */ 47 | zval *z_multi_exec; /* Redis instance to be used in multi-exec */ 48 | zend_bool index; /* use per-node index */ 49 | zend_bool auto_rehash; /* migrate keys on read operations */ 50 | zend_bool pconnect; /* should we use pconnect */ 51 | zval *z_fun; /* key extractor, callable */ 52 | zval *z_dist; /* key distributor, callable */ 53 | zval *z_pure_cmds; /* hash table */ 54 | double connect_timeout; /* socket connect timeout */ 55 | 56 | struct RedisArray_ *prev; 57 | } RedisArray; 58 | 59 | uint32_t rcrc32(const char *s, size_t sz); 60 | 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | php5-redis (2.2.2-1) unstable; urgency=low 2 | 3 | * Non-maintainer upload. 4 | * Updating version from upstream 5 | 6 | -- Rémi Paulmier Fri, 02 Nov 2012 10:30:28 +0100 7 | 8 | php5-redis (2.1.3-1) unstable; urgency=low 9 | 10 | * Non-maintainer upload. 11 | * changing package name, updating version from upstream 12 | 13 | -- Roman Ovchinnikov Mon, 02 Apr 2012 20:43:25 +0400 14 | 15 | phpredis (2.0.11) unstable; urgency=low 16 | 17 | * Merged with upstream 18 | 19 | -- Simon Effenberg Tue, 30 Nov 2010 09:20:40 +0100 20 | 21 | phpredis (2.0.10-1) unstable; urgency=low 22 | 23 | * Fixed wrong timeout option when setting timeout to lower then 1s. 24 | 25 | -- Simon Effenberg Mon, 08 Nov 2010 12:30:54 +0100 26 | 27 | phpredis (2.0.10) unstable; urgency=low 28 | 29 | * Merged with upstream 30 | 31 | -- Simon Effenberg Fri, 05 Nov 2010 16:07:09 +0100 32 | 33 | phpredis (2.0.8-2) unstable; urgency=low 34 | 35 | * Fixed problem when doing incrBy or decrBy with 1 like incrBy('key', 1). 36 | 37 | -- Simon Effenberg Wed, 27 Oct 2010 13:33:18 +0200 38 | 39 | phpredis (2.0.8-1) unstable; urgency=low 40 | 41 | * Merged with upstream but there seems to be no new version. 42 | * incrBy/decrBy added 43 | 44 | -- Simon Effenberg Wed, 27 Oct 2010 10:36:35 +0200 45 | 46 | phpredis (2.0.8) unstable; urgency=low 47 | 48 | * Merged with upstream 49 | 50 | -- Simon Effenberg Fri, 15 Oct 2010 13:00:32 +0200 51 | 52 | phpredis (2.0.4) unstable; urgency=low 53 | 54 | * Merged with upstream 55 | 56 | -- Simon Effenberg Tue, 05 Oct 2010 09:47:48 +0200 57 | 58 | phpredis (2.0.2) unstable; urgency=low 59 | 60 | * Fixed version number 61 | 62 | -- Nicolas Favre-Felix Thu, 23 Sep 2010 14:28:00 +0100 63 | 64 | phpredis (1.0.0-2) unstable; urgency=low 65 | 66 | * Fix some little debian/* problems. 67 | 68 | -- Simon Effenberg Thu, 23 Sep 2010 11:50:59 +0200 69 | 70 | phpredis (1.0.0-1) unstable; urgency=low 71 | 72 | * Initial release. 73 | 74 | -- Simon Effenberg Wed, 22 Sep 2010 16:04:53 +0200 75 | -------------------------------------------------------------------------------- /tests/test.php: -------------------------------------------------------------------------------- 1 | assertTrue(!$bool); 10 | } 11 | 12 | protected function assertTrue($bool) { 13 | if($bool) 14 | return; 15 | 16 | $bt = debug_backtrace(false); 17 | self::$errors []= sprintf("Assertion failed: %s:%d (%s)\n", 18 | $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]); 19 | } 20 | 21 | protected function assertLess($a, $b) { 22 | if($a < $b) 23 | return; 24 | 25 | $bt = debug_backtrace(false); 26 | self::$errors[] = sprintf("Assertion failed (%s >= %s): %s: %d (%s\n", 27 | print_r($a, true), print_r($b, true), 28 | $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]); 29 | } 30 | 31 | protected function assertEquals($a, $b) { 32 | if($a === $b) 33 | return; 34 | 35 | $bt = debug_backtrace(false); 36 | self::$errors []= sprintf("Assertion failed (%s !== %s): %s:%d (%s)\n", 37 | print_r($a, true), print_r($b, true), 38 | $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]); 39 | } 40 | 41 | protected function markTestSkipped($msg='') { 42 | $bt = debug_backtrace(false); 43 | self::$warnings []= sprintf("Skipped test: %s:%d (%s) %s\n", 44 | $bt[0]["file"], $bt[0]["line"], $bt[1]["function"], $msg); 45 | 46 | throw new Exception($msg); 47 | } 48 | 49 | public static function run($className, $str_limit) { 50 | $rc = new ReflectionClass($className); 51 | $methods = $rc->GetMethods(ReflectionMethod::IS_PUBLIC); 52 | 53 | if ($str_limit) { 54 | echo "Limiting to tests with the substring: '$str_limit'\n"; 55 | } 56 | 57 | foreach($methods as $m) { 58 | $name = $m->name; 59 | if(substr($name, 0, 4) !== 'test') 60 | continue; 61 | 62 | /* If TestRedis.php was envoked with an argument, do a simple 63 | * match against the routine. Useful to limit to one test */ 64 | if ($str_limit && strpos(strtolower($name),strtolower($str_limit))===false) 65 | continue; 66 | 67 | $count = count($className::$errors); 68 | $rt = new $className; 69 | try { 70 | $rt->setUp(); 71 | $rt->$name(); 72 | echo ($count === count($className::$errors)) ? "." : "F"; 73 | } catch (Exception $e) { 74 | if ($e instanceof RedisException) { 75 | $className::$errors[] = "Uncaught exception '".$e->getMessage()."' ($name)\n"; 76 | echo 'F'; 77 | } else { 78 | echo 'S'; 79 | } 80 | } 81 | } 82 | echo "\n"; 83 | echo implode('', $className::$warnings); 84 | 85 | if(empty($className::$errors)) { 86 | echo "All tests passed.\n"; 87 | return 0; 88 | } 89 | 90 | echo implode('', $className::$errors); 91 | return 1; 92 | } 93 | } 94 | 95 | ?> 96 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # Sample debian/rules that uses debhelper. 3 | # This file is public domain software, originally written by Joey Hess. 4 | # 5 | # This version is for packages that are architecture dependent. 6 | 7 | # Uncomment this to turn on verbose mode. 8 | #export DH_VERBOSE=1 9 | 10 | DEB_SRCDIR=$(shell pwd) 11 | INSTALL_DIR=$(DEB_SRCDIR)/debian/$(shell dh_listpackages) 12 | EXTENSION_DIR=`php-config5 --extension-dir` 13 | CFLAGS = -O3 14 | 15 | 16 | build: build-stamp 17 | build-stamp: 18 | dh_testdir 19 | 20 | # Add here commands to compile the package. 21 | 22 | # Because phpize --clean removes all testfiles 23 | # in tests/*.php the svn-buildpackage will fail 24 | # when tests/TestRedis.php was removed. 25 | # So we backup the file: 26 | cd $(DEB_SRCDIR) && mv tests/TestRedis.php tests/TestRedis.php.bak && \ 27 | phpize --clean && mv tests/TestRedis.php.bak tests/TestRedis.php && \ 28 | phpize && \ 29 | ./configure --with-php-config=/usr/bin/php-config5 30 | $(MAKE) -C $(DEB_SRCDIR) 31 | 32 | touch build-stamp 33 | 34 | clean: 35 | dh_testdir 36 | dh_testroot 37 | rm -f build-stamp 38 | 39 | # Add here commands to clean up after the build process. 40 | # See comment in build-stamp why doing the 'mv' 41 | cd $(DEB_SRCDIR) && mv tests/TestRedis.php tests/TestRedis.php.bak && \ 42 | phpize --clean && mv tests/TestRedis.php.bak tests/TestRedis.php 43 | #$(MAKE) -C $(DEB_SRCDIR) clean 44 | #$(MAKE) distclean 45 | 46 | dh_clean 47 | 48 | install: build 49 | dh_testdir 50 | dh_testroot 51 | dh_prep 52 | dh_installdirs 53 | 54 | # Add here commands to install the package into debian/ 55 | $(MAKE) prefix=$(INSTALL_DIR)/usr EXTENSION_DIR=$(INSTALL_DIR)$(EXTENSION_DIR) install 56 | 57 | # Build architecture-independent files here. 58 | binary-indep: build install 59 | # We have nothing to do by default. 60 | 61 | # Build architecture-dependent files here. 62 | binary-arch: build install 63 | dh_testdir 64 | dh_testroot 65 | dh_installchangelogs 66 | dh_installdocs 67 | dh_installexamples 68 | # dh_install 69 | # dh_installmenu 70 | # dh_installdebconf 71 | # dh_installlogrotate 72 | # dh_installemacsen 73 | # dh_installcatalogs 74 | # dh_installpam 75 | # dh_installmime 76 | # dh_installinit 77 | # dh_installcron 78 | # dh_installinfo 79 | # dh_installwm 80 | # dh_installudev 81 | # dh_lintian 82 | # dh_bugfiles 83 | # dh_undocumented 84 | dh_installman 85 | dh_link 86 | dh_strip 87 | dh_compress 88 | dh_fixperms 89 | # dh_perl 90 | # dh_makeshlibs 91 | dh_installdeb 92 | dh_shlibdeps 93 | cd $(DEB_SRCDIR) && \ 94 | echo "php5:Depends=phpapi-`php-config5 --phpapi`, php5-common" >> debian/phpredis.substvars 95 | dh_gencontrol 96 | dh_md5sums 97 | dh_builddeb 98 | 99 | binary: binary-indep binary-arch 100 | .PHONY: build clean binary-indep binary-arch binary install 101 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------- 2 | The PHP License, version 3.01 3 | Copyright (c) 1999 - 2010 The PHP Group. All rights reserved. 4 | -------------------------------------------------------------------- 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, is permitted provided that the following conditions 8 | are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | 18 | 3. The name "PHP" must not be used to endorse or promote products 19 | derived from this software without prior written permission. For 20 | written permission, please contact group@php.net. 21 | 22 | 4. Products derived from this software may not be called "PHP", nor 23 | may "PHP" appear in their name, without prior written permission 24 | from group@php.net. You may indicate that your software works in 25 | conjunction with PHP by saying "Foo for PHP" instead of calling 26 | it "PHP Foo" or "phpfoo" 27 | 28 | 5. The PHP Group may publish revised and/or new versions of the 29 | license from time to time. Each version will be given a 30 | distinguishing version number. 31 | Once covered code has been published under a particular version 32 | of the license, you may always continue to use it under the terms 33 | of that version. You may also choose to use such covered code 34 | under the terms of any subsequent version of the license 35 | published by the PHP Group. No one other than the PHP Group has 36 | the right to modify the terms applicable to covered code created 37 | under this License. 38 | 39 | 6. Redistributions of any form whatsoever must retain the following 40 | acknowledgment: 41 | "This product includes PHP software, freely available from 42 | ". 43 | 44 | THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND 45 | ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 46 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 47 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP 48 | DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 49 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 50 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 51 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 52 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 53 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 54 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 55 | OF THE POSSIBILITY OF SUCH DAMAGE. 56 | 57 | -------------------------------------------------------------------- 58 | 59 | This software consists of voluntary contributions made by many 60 | individuals on behalf of the PHP Group. 61 | 62 | The PHP Group can be contacted via Email at group@php.net. 63 | 64 | For more information on the PHP Group and the PHP project, 65 | please see . 66 | 67 | PHP includes the Zend Engine, freely available at 68 | . 69 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | dnl $Id$ 2 | dnl config.m4 for extension redis 3 | 4 | PHP_ARG_ENABLE(redis, whether to enable redis support, 5 | dnl Make sure that the comment is aligned: 6 | [ --enable-redis Enable redis support]) 7 | 8 | PHP_ARG_ENABLE(redis-session, whether to enable sessions, 9 | [ --disable-redis-session Disable session support], yes, no) 10 | 11 | PHP_ARG_ENABLE(redis-igbinary, whether to enable igbinary serializer support, 12 | [ --enable-redis-igbinary Enable igbinary serializer support], no, no) 13 | 14 | 15 | if test "$PHP_REDIS" != "no"; then 16 | 17 | if test "$PHP_REDIS_SESSION" != "no"; then 18 | AC_DEFINE(PHP_SESSION,1,[redis sessions]) 19 | fi 20 | 21 | dnl Check for igbinary 22 | if test "$PHP_REDIS_IGBINARY" != "no"; then 23 | AC_MSG_CHECKING([for igbinary includes]) 24 | igbinary_inc_path="" 25 | 26 | if test -f "$abs_srcdir/include/php/ext/igbinary/igbinary.h"; then 27 | igbinary_inc_path="$abs_srcdir/include/php" 28 | elif test -f "$abs_srcdir/ext/igbinary/igbinary.h"; then 29 | igbinary_inc_path="$abs_srcdir" 30 | elif test -f "$phpincludedir/ext/igbinary/igbinary.h"; then 31 | igbinary_inc_path="$phpincludedir" 32 | else 33 | for i in php php4 php5 php6; do 34 | if test -f "$prefix/include/$i/ext/igbinary/igbinary.h"; then 35 | igbinary_inc_path="$prefix/include/$i" 36 | fi 37 | done 38 | fi 39 | 40 | if test "$igbinary_inc_path" = ""; then 41 | AC_MSG_ERROR([Cannot find igbinary.h]) 42 | else 43 | AC_MSG_RESULT([$igbinary_inc_path]) 44 | fi 45 | fi 46 | 47 | AC_MSG_CHECKING([for redis igbinary support]) 48 | if test "$PHP_REDIS_IGBINARY" != "no"; then 49 | AC_MSG_RESULT([enabled]) 50 | AC_DEFINE(HAVE_REDIS_IGBINARY,1,[Whether redis igbinary serializer is enabled]) 51 | IGBINARY_INCLUDES="-I$igbinary_inc_path" 52 | IGBINARY_EXT_DIR="$igbinary_inc_path/ext" 53 | ifdef([PHP_ADD_EXTENSION_DEP], 54 | [ 55 | PHP_ADD_EXTENSION_DEP(redis, igbinary) 56 | ]) 57 | PHP_ADD_INCLUDE($IGBINARY_EXT_DIR) 58 | else 59 | IGBINARY_INCLUDES="" 60 | AC_MSG_RESULT([disabled]) 61 | fi 62 | 63 | dnl # --with-redis -> check with-path 64 | dnl SEARCH_PATH="/usr/local /usr" # you might want to change this 65 | dnl SEARCH_FOR="/include/redis.h" # you most likely want to change this 66 | dnl if test -r $PHP_REDIS/$SEARCH_FOR; then # path given as parameter 67 | dnl REDIS_DIR=$PHP_REDIS 68 | dnl else # search default path list 69 | dnl AC_MSG_CHECKING([for redis files in default path]) 70 | dnl for i in $SEARCH_PATH ; do 71 | dnl if test -r $i/$SEARCH_FOR; then 72 | dnl REDIS_DIR=$i 73 | dnl AC_MSG_RESULT(found in $i) 74 | dnl fi 75 | dnl done 76 | dnl fi 77 | dnl 78 | dnl if test -z "$REDIS_DIR"; then 79 | dnl AC_MSG_RESULT([not found]) 80 | dnl AC_MSG_ERROR([Please reinstall the redis distribution]) 81 | dnl fi 82 | 83 | dnl # --with-redis -> add include path 84 | dnl PHP_ADD_INCLUDE($REDIS_DIR/include) 85 | 86 | dnl # --with-redis -> check for lib and symbol presence 87 | dnl LIBNAME=redis # you may want to change this 88 | dnl LIBSYMBOL=redis # you most likely want to change this 89 | 90 | dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, 91 | dnl [ 92 | dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $REDIS_DIR/lib, REDIS_SHARED_LIBADD) 93 | dnl AC_DEFINE(HAVE_REDISLIB,1,[ ]) 94 | dnl ],[ 95 | dnl AC_MSG_ERROR([wrong redis lib version or lib not found]) 96 | dnl ],[ 97 | dnl -L$REDIS_DIR/lib -lm -ldl 98 | dnl ]) 99 | dnl 100 | dnl PHP_SUBST(REDIS_SHARED_LIBADD) 101 | 102 | PHP_NEW_EXTENSION(redis, redis.c library.c redis_session.c redis_array.c redis_array_impl.c, $ext_shared) 103 | fi 104 | -------------------------------------------------------------------------------- /library.h: -------------------------------------------------------------------------------- 1 | void add_constant_long(zend_class_entry *ce, char *name, int value); 2 | int integer_length(int i); 3 | int redis_cmd_format(char **ret, char *format, ...); 4 | int redis_cmd_format_static(char **ret, char *keyword, char *format, ...); 5 | int redis_cmd_format_header(char **ret, char *keyword, int arg_count); 6 | int redis_cmd_append_str(char **cmd, int cmd_len, char *append, int append_len); 7 | int redis_cmd_init_sstr(smart_str *str, int num_args, char *keyword, int keyword_len); 8 | int redis_cmd_append_sstr(smart_str *str, char *append, int append_len); 9 | int redis_cmd_append_sstr_int(smart_str *str, int append); 10 | int redis_cmd_append_sstr_long(smart_str *str, long append); 11 | int redis_cmd_append_int(char **cmd, int cmd_len, int append); 12 | int redis_cmd_append_sstr_dbl(smart_str *str, double value); 13 | 14 | PHP_REDIS_API char * redis_sock_read(RedisSock *redis_sock, int *buf_len TSRMLS_DC); 15 | 16 | PHP_REDIS_API void redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); 17 | PHP_REDIS_API void redis_long_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval* z_tab, void *ctx); 18 | typedef void (*SuccessCallback)(RedisSock *redis_sock); 19 | PHP_REDIS_API void redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx, SuccessCallback success_callback); 20 | PHP_REDIS_API void redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); 21 | PHP_REDIS_API void redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); 22 | PHP_REDIS_API void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); 23 | PHP_REDIS_API void redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); 24 | PHP_REDIS_API void redis_debug_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); 25 | PHP_REDIS_API void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); 26 | PHP_REDIS_API void redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); 27 | 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); 28 | PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock TSRMLS_DC); 29 | PHP_REDIS_API int redis_sock_server_open(RedisSock *redis_sock, int force_connect TSRMLS_DC); 30 | PHP_REDIS_API int redis_sock_disconnect(RedisSock *redis_sock TSRMLS_DC); 31 | PHP_REDIS_API zval *redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); 32 | PHP_REDIS_API char *redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes TSRMLS_DC); 33 | PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *_z_tab, void *ctx); 34 | 35 | PHP_REDIS_API void redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int count, int unserialize); 36 | PHP_REDIS_API int redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); 37 | PHP_REDIS_API int redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); 38 | PHP_REDIS_API int redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); 39 | PHP_REDIS_API int redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); 40 | PHP_REDIS_API int redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); 41 | PHP_REDIS_API int redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); 42 | 43 | PHP_REDIS_API int redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, REDIS_SCAN_TYPE type, long *iter); 44 | PHP_REDIS_API int redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz TSRMLS_DC); 45 | PHP_REDIS_API void redis_stream_close(RedisSock *redis_sock TSRMLS_DC); 46 | PHP_REDIS_API int redis_check_eof(RedisSock *redis_sock TSRMLS_DC); 47 | /*PHP_REDIS_API int redis_sock_get(zval *id, RedisSock **redis_sock TSRMLS_DC);*/ 48 | PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock); 49 | PHP_REDIS_API void redis_send_discard(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); 50 | PHP_REDIS_API int redis_sock_set_err(RedisSock *redis_sock, const char *msg, int msg_len); 51 | 52 | PHP_REDIS_API int redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz TSRMLS_DC); 53 | PHP_REDIS_API void redis_stream_close(RedisSock *redis_sock TSRMLS_DC); 54 | PHP_REDIS_API int redis_check_eof(RedisSock *redis_sock TSRMLS_DC); 55 | /* PHP_REDIS_API int redis_sock_get(zval *id, RedisSock **redis_sock TSRMLS_DC); */ 56 | PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock); 57 | PHP_REDIS_API void redis_send_discard(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); 58 | PHP_REDIS_API int redis_sock_set_err(RedisSock *redis_sock, const char *msg, int msg_len); 59 | 60 | PHP_REDIS_API int 61 | redis_serialize(RedisSock *redis_sock, zval *z, char **val, int *val_len TSRMLS_DC); 62 | PHP_REDIS_API int 63 | redis_key_prefix(RedisSock *redis_sock, char **key, int *key_len TSRMLS_DC); 64 | 65 | PHP_REDIS_API int 66 | redis_unserialize(RedisSock *redis_sock, const char *val, int val_len, zval **return_value TSRMLS_DC); 67 | 68 | 69 | /* 70 | * Variant Read methods, mostly to implement eval 71 | */ 72 | 73 | PHP_REDIS_API int redis_read_reply_type(RedisSock *redis_sock, REDIS_REPLY_TYPE *reply_type, int *reply_info TSRMLS_DC); 74 | PHP_REDIS_API int redis_read_variant_line(RedisSock *redis_sock, REDIS_REPLY_TYPE reply_type, zval **z_ret TSRMLS_DC); 75 | PHP_REDIS_API int redis_read_variant_bulk(RedisSock *redis_sock, int size, zval **z_ret TSRMLS_DC); 76 | PHP_REDIS_API int redis_read_multibulk_recursive(RedisSock *redis_sock, int elements, zval **z_ret TSRMLS_DC); 77 | PHP_REDIS_API int redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab); 78 | 79 | PHP_REDIS_API void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab); 80 | 81 | #if ZEND_MODULE_API_NO >= 20100000 82 | #define REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, dbl) do { \ 83 | char dbl_decsep; \ 84 | dbl_decsep = '.'; \ 85 | dbl_str = _php_math_number_format_ex(dbl, 16, &dbl_decsep, 1, NULL, 0); \ 86 | dbl_len = strlen(dbl_str); \ 87 | } while (0); 88 | #else 89 | #define REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, dbl) \ 90 | dbl_str = _php_math_number_format(dbl, 16, '.', '\x00'); \ 91 | dbl_len = strlen(dbl_str); 92 | #endif 93 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | redis 7 | pecl.php.net 8 | PHP extension for interfacing with Redis 9 | 10 | This extension provides an API for communicating with Redis servers. 11 | 12 | 13 | Nicolas Favre-Felix 14 | nff 15 | n.favrefelix@gmail.com 16 | yes 17 | 18 | 19 | Michael Grunder 20 | mgrunder 21 | michael.grunder@gmail.com 22 | yes 23 | 24 | 2014-03-15 25 | 26 | 2.2.5 27 | 2.2.5 28 | 29 | 30 | stable 31 | stable 32 | 33 | PHP 34 | 35 | phpredis 2.2.5 36 | 37 | This is a minor release with several bug fixes as well as additions to support 38 | new commands that have been introduced to Redis since our last release. 39 | 40 | A special thanks to everyone who helps the project by commenting on issues and 41 | submitting pull requests! :) 42 | 43 | [NEW] Support for the BITPOS command 44 | [NEW] Connection timeout option for RedisArray (@MikeToString) 45 | [NEW] A _serialize method, to complement our existing _unserialize method 46 | [NEW] Support for the PUBSUB command 47 | [NEW] Support for SCAN, SSCAN, HSCAN, and ZSCAN 48 | [NEW] Support for the WAIT command 49 | 50 | [FIX] Handle the COPY and REPLACE arguments for the MIGRATE command 51 | 52 | [DOC] Fix syntax error in documentation for the SET command (@mithunsatheesh) 53 | [DOC] Homebrew documentation instructions (@mathias) 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 5.2.0 87 | 6.0.0 88 | 6.0.0 89 | 90 | 91 | 1.4.0b1 92 | 93 | 94 | 95 | redis 96 | 97 | 98 | 99 | stablestable 100 | 2.2.52.2.5 101 | 2014-03-15 102 | 103 | phpredis 2.2.5 104 | 105 | This is a minor release with several bug fixes as well as additions to support 106 | new commands that have been introduced to Redis since our last release. 107 | 108 | A special thanks to everyone who helps the project by commenting on issues and 109 | submitting pull requests! :) 110 | 111 | [NEW] Support for the BITPOS command 112 | [NEW] Connection timeout option for RedisArray (@MikeToString) 113 | [NEW] A _serialize method, to complement our existing _unserialize method 114 | [NEW] Support for the PUBSUB command 115 | [NEW] Support for SCAN, SSCAN, HSCAN, and ZSCAN 116 | [NEW] Support for the WAIT command 117 | 118 | [FIX] Handle the COPY and REPLACE arguments for the MIGRATE command 119 | 120 | [DOC] Fix syntax error in documentation for the SET command (@mithunsatheesh) 121 | [DOC] Homebrew documentation instructions (@mathias) 122 | 123 | 124 | 125 | 126 | stablestable 127 | 2.2.42.2.4 128 | 2013-09-01 129 | 130 | ** 131 | ** Features / Improvements 132 | ** 133 | 134 | * Randomized reconnect delay for RedisArray @mobli 135 | This feature adds an optional parameter when constructing a RedisArray object 136 | such that a random delay will be introduced if reconnections are made, 137 | mitigating any 'thundering herd' type problems. 138 | 139 | * Lazy connections to RedisArray servers @mobli 140 | By default, RedisArray will attempt to connect to each server you pass in 141 | the ring on construction. This feature lets you specify that you would 142 | rather have RedisArray only attempt a connection when it needs to get data 143 | from a particular node (throughput/performance improvement). 144 | 145 | * Allow LONG and STRING keys in MGET/MSET 146 | * Extended SET options for Redis >= 2.6.12 147 | * Persistent connections and UNIX SOCKET support for RedisArray 148 | * Allow aggregates for ZUNION/ZINTER without weights @mheijkoop 149 | * Support for SLOWLOG command 150 | * Reworked MGET algorithm to run in linear time regardless of key count. 151 | * Reworked ZINTERSTORE/ZUNIONSTORE algorithm to run in linear time 152 | 153 | ** 154 | ** Bug fixes 155 | ** 156 | 157 | * C99 Compliance (or rather lack thereof) fix @mobli 158 | * Added ZEND_ACC_CTOR and ZEND_ACC_DTOR @euskadi31 159 | * Stop throwing and clearing an exception on connect failure @matmoi 160 | * Fix a false positive unit test failure having to do with TTL returns 161 | 162 | 163 | 164 | stablestable 165 | 2.2.32.2.3 166 | 2013-04-29 167 | 168 | First release to PECL 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | #include "php.h" 2 | #include "php_ini.h" 3 | #include 4 | 5 | #ifndef REDIS_COMMON_H 6 | #define REDIS_COMMON_H 7 | 8 | /* NULL check so Eclipse doesn't go crazy */ 9 | #ifndef NULL 10 | #define NULL ((void *) 0) 11 | #endif 12 | 13 | #define redis_sock_name "Redis Socket Buffer" 14 | #define REDIS_SOCK_STATUS_FAILED 0 15 | #define REDIS_SOCK_STATUS_DISCONNECTED 1 16 | #define REDIS_SOCK_STATUS_UNKNOWN 2 17 | #define REDIS_SOCK_STATUS_CONNECTED 3 18 | 19 | #define redis_multi_access_type_name "Redis Multi type access" 20 | 21 | #define _NL "\r\n" 22 | 23 | /* properties */ 24 | #define REDIS_NOT_FOUND 0 25 | #define REDIS_STRING 1 26 | #define REDIS_SET 2 27 | #define REDIS_LIST 3 28 | #define REDIS_ZSET 4 29 | #define REDIS_HASH 5 30 | 31 | /* reply types */ 32 | typedef enum _REDIS_REPLY_TYPE { 33 | TYPE_EOF = EOF, 34 | TYPE_LINE = '+', 35 | TYPE_INT = ':', 36 | TYPE_ERR = '-', 37 | TYPE_BULK = '$', 38 | TYPE_MULTIBULK = '*' 39 | } REDIS_REPLY_TYPE; 40 | 41 | /* SCAN variants */ 42 | typedef enum _REDIS_SCAN_TYPE { 43 | TYPE_SCAN, 44 | TYPE_SSCAN, 45 | TYPE_HSCAN, 46 | TYPE_ZSCAN 47 | } REDIS_SCAN_TYPE; 48 | 49 | /* PUBSUB subcommands */ 50 | typedef enum _PUBSUB_TYPE { 51 | PUBSUB_CHANNELS, 52 | PUBSUB_NUMSUB, 53 | PUBSUB_NUMPAT 54 | } PUBSUB_TYPE; 55 | 56 | /* options */ 57 | #define REDIS_OPT_SERIALIZER 1 58 | #define REDIS_OPT_PREFIX 2 59 | #define REDIS_OPT_READ_TIMEOUT 3 60 | #define REDIS_OPT_SCAN 4 61 | 62 | /* serializers */ 63 | #define REDIS_SERIALIZER_NONE 0 64 | #define REDIS_SERIALIZER_PHP 1 65 | #define REDIS_SERIALIZER_IGBINARY 2 66 | 67 | /* SCAN options */ 68 | #define REDIS_SCAN_NORETRY 0 69 | #define REDIS_SCAN_RETRY 1 70 | 71 | /* GETBIT/SETBIT offset range limits */ 72 | #define BITOP_MIN_OFFSET 0 73 | #define BITOP_MAX_OFFSET 4294967295 74 | 75 | /* Specific error messages we want to throw against */ 76 | #define REDIS_ERR_LOADING_MSG "LOADING Redis is loading the dataset in memory" 77 | #define REDIS_ERR_LOADING_KW "LOADING" 78 | #define REDIS_ERR_AUTH_MSG "NOAUTH Authentication required." 79 | #define REDIS_ERR_AUTH_KW "NOAUTH" 80 | #define REDIS_ERR_SYNC_MSG "MASTERDOWN Link with MASTER is down and slave-serve-stale-data is set to 'no'" 81 | #define REDIS_ERR_SYNC_KW "MASTERDOWN" 82 | 83 | #define IF_MULTI() if(redis_sock->mode == MULTI) 84 | #define IF_MULTI_OR_ATOMIC() if(redis_sock->mode == MULTI || redis_sock->mode == ATOMIC)\ 85 | 86 | #define IF_MULTI_OR_PIPELINE() if(redis_sock->mode == MULTI || redis_sock->mode == PIPELINE) 87 | #define IF_PIPELINE() if(redis_sock->mode == PIPELINE) 88 | #define IF_NOT_MULTI() if(redis_sock->mode != MULTI) 89 | #define IF_NOT_ATOMIC() if(redis_sock->mode != ATOMIC) 90 | #define IF_ATOMIC() if(redis_sock->mode == ATOMIC) 91 | #define ELSE_IF_MULTI() else if(redis_sock->mode == MULTI) { \ 92 | if(redis_response_enqueued(redis_sock TSRMLS_CC) == 1) {\ 93 | RETURN_ZVAL(getThis(), 1, 0);\ 94 | } else {\ 95 | RETURN_FALSE;\ 96 | } \ 97 | } 98 | 99 | #define ELSE_IF_PIPELINE() else IF_PIPELINE() { \ 100 | RETURN_ZVAL(getThis(), 1, 0);\ 101 | } 102 | 103 | 104 | #define MULTI_RESPONSE(callback) IF_MULTI_OR_PIPELINE() { \ 105 | fold_item *f1, *current; \ 106 | f1 = malloc(sizeof(fold_item)); \ 107 | f1->fun = (void *)callback; \ 108 | f1->next = NULL; \ 109 | current = redis_sock->current;\ 110 | if(current) current->next = f1; \ 111 | redis_sock->current = f1; \ 112 | } 113 | 114 | #define PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len) request_item *tmp; \ 115 | struct request_item *current_request;\ 116 | tmp = malloc(sizeof(request_item));\ 117 | tmp->request_str = calloc(cmd_len, 1);\ 118 | memcpy(tmp->request_str, cmd, cmd_len);\ 119 | tmp->request_size = cmd_len;\ 120 | tmp->next = NULL;\ 121 | current_request = redis_sock->pipeline_current; \ 122 | if(current_request) {\ 123 | current_request->next = tmp;\ 124 | } \ 125 | redis_sock->pipeline_current = tmp; \ 126 | if(NULL == redis_sock->pipeline_head) { \ 127 | redis_sock->pipeline_head = redis_sock->pipeline_current;\ 128 | } 129 | 130 | #define SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len) if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { \ 131 | efree(cmd); \ 132 | RETURN_FALSE; \ 133 | } 134 | 135 | #define REDIS_SAVE_CALLBACK(callback, closure_context) IF_MULTI_OR_PIPELINE() { \ 136 | fold_item *f1, *current; \ 137 | f1 = malloc(sizeof(fold_item)); \ 138 | f1->fun = (void *)callback; \ 139 | f1->ctx = closure_context; \ 140 | f1->next = NULL; \ 141 | current = redis_sock->current;\ 142 | if(current) current->next = f1; \ 143 | redis_sock->current = f1; \ 144 | if(NULL == redis_sock->head) { \ 145 | redis_sock->head = redis_sock->current;\ 146 | }\ 147 | } 148 | 149 | #define REDIS_ELSE_IF_MULTI(function, closure_context) \ 150 | else if(redis_sock->mode == MULTI) { \ 151 | if(redis_response_enqueued(redis_sock TSRMLS_CC) == 1) {\ 152 | REDIS_SAVE_CALLBACK(function, closure_context); \ 153 | RETURN_ZVAL(getThis(), 1, 0);\ 154 | } else {\ 155 | RETURN_FALSE;\ 156 | }\ 157 | } 158 | 159 | #define REDIS_ELSE_IF_PIPELINE(function, closure_context) else IF_PIPELINE() { \ 160 | REDIS_SAVE_CALLBACK(function, closure_context); \ 161 | RETURN_ZVAL(getThis(), 1, 0);\ 162 | } 163 | 164 | #define REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len) \ 165 | IF_MULTI_OR_ATOMIC() { \ 166 | SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len); \ 167 | efree(cmd); \ 168 | }\ 169 | IF_PIPELINE() { \ 170 | PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len); \ 171 | efree(cmd); \ 172 | } 173 | 174 | #define REDIS_PROCESS_RESPONSE_CLOSURE(function, closure_context) \ 175 | REDIS_ELSE_IF_MULTI(function, closure_context) \ 176 | REDIS_ELSE_IF_PIPELINE(function, closure_context); 177 | 178 | #define REDIS_PROCESS_RESPONSE(function) REDIS_PROCESS_RESPONSE_CLOSURE(function, NULL) 179 | 180 | /* Extended SET argument detection */ 181 | #define IS_EX_ARG(a) ((a[0]=='e' || a[0]=='E') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') 182 | #define IS_PX_ARG(a) ((a[0]=='p' || a[0]=='P') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') 183 | #define IS_NX_ARG(a) ((a[0]=='n' || a[0]=='N') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') 184 | #define IS_XX_ARG(a) ((a[0]=='x' || a[0]=='X') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') 185 | 186 | #define IS_EX_PX_ARG(a) (IS_EX_ARG(a) || IS_PX_ARG(a)) 187 | #define IS_NX_XX_ARG(a) (IS_NX_ARG(a) || IS_XX_ARG(a)) 188 | 189 | /* Given a string and length, validate a zRangeByLex argument. The semantics 190 | * here are that the argument must start with '(' or '[' or be just the char 191 | * '+' or '-' */ 192 | #define IS_LEX_ARG(s,l) \ 193 | (l>0 && (*s=='(' || *s=='[' || (l==1 && (*s=='+' || *s=='-')))) 194 | 195 | typedef enum {ATOMIC, MULTI, PIPELINE} redis_mode; 196 | 197 | typedef struct fold_item { 198 | zval * (*fun)(INTERNAL_FUNCTION_PARAMETERS, void *, ...); 199 | void *ctx; 200 | struct fold_item *next; 201 | } fold_item; 202 | 203 | typedef struct request_item { 204 | char *request_str; 205 | int request_size; /* size_t */ 206 | struct request_item *next; 207 | } request_item; 208 | 209 | /* {{{ struct RedisSock */ 210 | typedef struct { 211 | php_stream *stream; 212 | char *host; 213 | short port; 214 | char *auth; 215 | double timeout; 216 | double read_timeout; 217 | long retry_interval; 218 | int failed; 219 | int status; 220 | int persistent; 221 | int watching; 222 | char *persistent_id; 223 | 224 | int serializer; 225 | long dbNumber; 226 | 227 | char *prefix; 228 | int prefix_len; 229 | 230 | redis_mode mode; 231 | fold_item *head; 232 | fold_item *current; 233 | 234 | request_item *pipeline_head; 235 | request_item *pipeline_current; 236 | 237 | char *err; 238 | int err_len; 239 | zend_bool lazy_connect; 240 | 241 | int scan; 242 | } RedisSock; 243 | /* }}} */ 244 | 245 | void 246 | free_reply_callbacks(zval *z_this, RedisSock *redis_sock); 247 | 248 | #endif 249 | -------------------------------------------------------------------------------- /arrays.markdown: -------------------------------------------------------------------------------- 1 | Redis Arrays 2 | ============ 3 | 4 | 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/).** 5 | 6 | An array is composed of the following: 7 | 8 | * A list of Redis hosts. 9 | * 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. 10 | * 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. 11 | * 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. 12 | * An option to rehash the array automatically as nodes are added or removed, set by the "autorehash" option. 13 | 14 | ## Creating an array 15 | 16 | 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);` 17 | 18 | #### Declaring a new array with a list of nodes 19 |
 20 | $ra = new RedisArray(array("host1", "host2:63792", "host2:6380"));
 21 | 
22 | 23 | 24 | #### Declaring a new array with a list of nodes and a function to extract a part of the key 25 |
 26 | function extract_key_part($k) {
 27 |     return substr($k, 0, 3);	// hash only on first 3 characters.
 28 | }
 29 | $ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("function" => "extract_key_part"));
 30 | 
31 | 32 | #### Defining a "previous" array when nodes are added or removed. 33 | 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. 34 | 35 |
 36 | // 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.
 37 | $ra = new RedisArray(array("host1", "host2", "host3"), array("previous" => array("host1", "host2")));
 38 | 
39 | 40 | #### Specifying the "retry_interval" parameter 41 | The retry_interval is used to specify a delay in milliseconds between reconnection attempts in case the client loses connection with a server 42 |
 43 | $ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("retry_timeout" => 100)));
 44 | 
45 | 46 | #### Specifying the "lazy_connect" parameter 47 | This option is useful when a cluster has many shards but not of them are necessarily used at one time. 48 |
 49 | $ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("lazy_connect" => true)));
 50 | 
51 | 52 | #### Defining arrays in Redis.ini 53 | 54 | Because php.ini parameters must be pre-defined, Redis Arrays must all share the same .ini settings. 55 | 56 |
 57 | // list available Redis Arrays
 58 | ini_set('redis.array.names', 'users,friends');
 59 | 
 60 | // set host names for each array.
 61 | ini_set('redis.arrays.hosts', 'users[]=localhost:6379&users[]=localhost:6380&users[]=localhost:6381&users[]=localhost:6382&friends[]=localhost');
 62 | 
 63 | // set functions
 64 | ini_set('redis.arrays.functions', 'users=user_hash');
 65 | 
 66 | // use index only for users
 67 | ini_set('redis.arrays.index', 'users=1,friends=0');
 68 | 
69 | 70 | ## Usage 71 | 72 | Redis arrays can be used just as Redis objects: 73 |
 74 | $ra = new RedisArray("users");
 75 | $ra->set("user1:name", "Joe");
 76 | $ra->set("user2:name", "Mike");
 77 | 
78 | 79 | 80 | ## Key hashing 81 | 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. 82 | 83 | 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. 84 | 85 | 86 | ## Custom key distribution function 87 | 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. 88 | 89 | 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. 90 | 91 | ### Example 92 |
 93 | $ra = new RedisArray(array("host1", "host2", "host3", "host4", "host5", "host6", "host7", "host8"), array("distributor" => array(2, 2)));
 94 | 
95 | 96 | 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. 97 | 98 | ## Migrating keys 99 | 100 | 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){ ... });`. 101 | 102 | 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. 103 | 104 | 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. 105 | 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. 106 | 107 | Adding and/or removing several instances is supported. 108 | 109 | ### Example 110 |
111 | $ra = new RedisArray("users"); // load up a new config from redis.ini, using the “.previous” listing.
112 | $ra->_rehash();
113 | 
114 | 115 | Running this code will: 116 | 117 | * Create a new ring with the updated list of nodes. 118 | * Server by server, look up all the keys in the previous list of nodes. 119 | * Rehash each key and possibly move it to another server. 120 | * Update the array object with the new list of nodes. 121 | 122 | ## Multi/exec 123 | Multi/exec is still available, but must be run on a single node: 124 |
125 | $host = $ra->_target("{users}:user1:name");	// find host first
126 | $ra->multi($host)	// then run transaction on that host.
127 |    ->del("{users}:user1:name")
128 |    ->srem("{users}:index", "user1")
129 |    ->exec();
130 | 
131 | 132 | ## Limitations 133 | 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. 134 | 135 | ## Array info 136 | RedisArray objects provide several methods to help understand the state of the cluster. These methods start with an underscore. 137 | 138 | * `$ra->_hosts()` → returns a list of hosts for the selected array. 139 | * `$ra->_function()` → returns the name of the function used to extract key parts during consistent hashing. 140 | * `$ra->_target($key)` → returns the host to be used for a certain key. 141 | * `$ra->_instance($host)` → returns a redis instance connected to a specific node; use with `_target` to get a single Redis object. 142 | 143 | ## Running the unit tests 144 |
145 | $ cd tests
146 | $ ./mkring.sh start
147 | $ php array-tests.php
148 | 
149 | 150 | -------------------------------------------------------------------------------- /php_redis.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | PHP Version 5 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 1997-2009 The PHP Group | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Original author: Alfonso Jimenez | 16 | | Maintainer: Nicolas Favre-Felix | 17 | | Maintainer: Michael Grunder | 18 | | Maintainer: Nasreddine Bouafif | 19 | +----------------------------------------------------------------------+ 20 | */ 21 | 22 | #include "common.h" 23 | 24 | #ifndef PHP_REDIS_H 25 | #define PHP_REDIS_H 26 | 27 | PHP_METHOD(Redis, __construct); 28 | PHP_METHOD(Redis, __destruct); 29 | PHP_METHOD(Redis, connect); 30 | PHP_METHOD(Redis, pconnect); 31 | PHP_METHOD(Redis, close); 32 | PHP_METHOD(Redis, ping); 33 | PHP_METHOD(Redis, echo); 34 | PHP_METHOD(Redis, get); 35 | PHP_METHOD(Redis, set); 36 | PHP_METHOD(Redis, setex); 37 | PHP_METHOD(Redis, psetex); 38 | PHP_METHOD(Redis, setnx); 39 | PHP_METHOD(Redis, getSet); 40 | PHP_METHOD(Redis, randomKey); 41 | PHP_METHOD(Redis, renameKey); 42 | PHP_METHOD(Redis, renameNx); 43 | PHP_METHOD(Redis, getMultiple); 44 | PHP_METHOD(Redis, exists); 45 | PHP_METHOD(Redis, delete); 46 | PHP_METHOD(Redis, incr); 47 | PHP_METHOD(Redis, incrBy); 48 | PHP_METHOD(Redis, incrByFloat); 49 | PHP_METHOD(Redis, decr); 50 | PHP_METHOD(Redis, decrBy); 51 | PHP_METHOD(Redis, type); 52 | PHP_METHOD(Redis, append); 53 | PHP_METHOD(Redis, getRange); 54 | PHP_METHOD(Redis, setRange); 55 | PHP_METHOD(Redis, getBit); 56 | PHP_METHOD(Redis, setBit); 57 | PHP_METHOD(Redis, strlen); 58 | PHP_METHOD(Redis, getKeys); 59 | PHP_METHOD(Redis, sort); 60 | PHP_METHOD(Redis, sortAsc); 61 | PHP_METHOD(Redis, sortAscAlpha); 62 | PHP_METHOD(Redis, sortDesc); 63 | PHP_METHOD(Redis, sortDescAlpha); 64 | PHP_METHOD(Redis, lPush); 65 | PHP_METHOD(Redis, lPushx); 66 | PHP_METHOD(Redis, rPush); 67 | PHP_METHOD(Redis, rPushx); 68 | PHP_METHOD(Redis, lPop); 69 | PHP_METHOD(Redis, rPop); 70 | PHP_METHOD(Redis, blPop); 71 | PHP_METHOD(Redis, brPop); 72 | PHP_METHOD(Redis, lSize); 73 | PHP_METHOD(Redis, lRemove); 74 | PHP_METHOD(Redis, listTrim); 75 | PHP_METHOD(Redis, lGet); 76 | PHP_METHOD(Redis, lGetRange); 77 | PHP_METHOD(Redis, lSet); 78 | PHP_METHOD(Redis, lInsert); 79 | PHP_METHOD(Redis, sAdd); 80 | PHP_METHOD(Redis, sSize); 81 | PHP_METHOD(Redis, sRemove); 82 | PHP_METHOD(Redis, sMove); 83 | PHP_METHOD(Redis, sPop); 84 | PHP_METHOD(Redis, sRandMember); 85 | PHP_METHOD(Redis, sContains); 86 | PHP_METHOD(Redis, sMembers); 87 | PHP_METHOD(Redis, sInter); 88 | PHP_METHOD(Redis, sInterStore); 89 | PHP_METHOD(Redis, sUnion); 90 | PHP_METHOD(Redis, sUnionStore); 91 | PHP_METHOD(Redis, sDiff); 92 | PHP_METHOD(Redis, sDiffStore); 93 | PHP_METHOD(Redis, setTimeout); 94 | PHP_METHOD(Redis, pexpire); 95 | PHP_METHOD(Redis, save); 96 | PHP_METHOD(Redis, bgSave); 97 | PHP_METHOD(Redis, lastSave); 98 | PHP_METHOD(Redis, flushDB); 99 | PHP_METHOD(Redis, flushAll); 100 | PHP_METHOD(Redis, dbSize); 101 | PHP_METHOD(Redis, auth); 102 | PHP_METHOD(Redis, ttl); 103 | PHP_METHOD(Redis, pttl); 104 | PHP_METHOD(Redis, persist); 105 | PHP_METHOD(Redis, info); 106 | PHP_METHOD(Redis, resetStat); 107 | PHP_METHOD(Redis, select); 108 | PHP_METHOD(Redis, move); 109 | PHP_METHOD(Redis, zAdd); 110 | PHP_METHOD(Redis, zDelete); 111 | PHP_METHOD(Redis, zRange); 112 | PHP_METHOD(Redis, zReverseRange); 113 | PHP_METHOD(Redis, zRangeByScore); 114 | PHP_METHOD(Redis, zRangeByLex); 115 | PHP_METHOD(Redis, zRevRangeByScore); 116 | PHP_METHOD(Redis, zCount); 117 | PHP_METHOD(Redis, zDeleteRangeByScore); 118 | PHP_METHOD(Redis, zDeleteRangeByRank); 119 | PHP_METHOD(Redis, zCard); 120 | PHP_METHOD(Redis, zScore); 121 | PHP_METHOD(Redis, zRank); 122 | PHP_METHOD(Redis, zRevRank); 123 | PHP_METHOD(Redis, zIncrBy); 124 | PHP_METHOD(Redis, zInter); 125 | PHP_METHOD(Redis, zUnion); 126 | PHP_METHOD(Redis, expireAt); 127 | PHP_METHOD(Redis, pexpireAt); 128 | PHP_METHOD(Redis, bgrewriteaof); 129 | PHP_METHOD(Redis, slaveof); 130 | PHP_METHOD(Redis, object); 131 | PHP_METHOD(Redis, bitop); 132 | PHP_METHOD(Redis, bitcount); 133 | PHP_METHOD(Redis, bitpos); 134 | 135 | PHP_METHOD(Redis, eval); 136 | PHP_METHOD(Redis, evalsha); 137 | PHP_METHOD(Redis, script); 138 | PHP_METHOD(Redis, debug); 139 | PHP_METHOD(Redis, dump); 140 | PHP_METHOD(Redis, restore); 141 | PHP_METHOD(Redis, migrate); 142 | 143 | PHP_METHOD(Redis, time); 144 | 145 | PHP_METHOD(Redis, getLastError); 146 | PHP_METHOD(Redis, clearLastError); 147 | PHP_METHOD(Redis, _prefix); 148 | PHP_METHOD(Redis, _serialize); 149 | PHP_METHOD(Redis, _unserialize); 150 | 151 | PHP_METHOD(Redis, mset); 152 | PHP_METHOD(Redis, msetnx); 153 | PHP_METHOD(Redis, rpoplpush); 154 | PHP_METHOD(Redis, brpoplpush); 155 | 156 | PHP_METHOD(Redis, hGet); 157 | PHP_METHOD(Redis, hSet); 158 | PHP_METHOD(Redis, hSetNx); 159 | PHP_METHOD(Redis, hDel); 160 | PHP_METHOD(Redis, hLen); 161 | PHP_METHOD(Redis, hKeys); 162 | PHP_METHOD(Redis, hVals); 163 | PHP_METHOD(Redis, hGetAll); 164 | PHP_METHOD(Redis, hExists); 165 | PHP_METHOD(Redis, hIncrBy); 166 | PHP_METHOD(Redis, hIncrByFloat); 167 | PHP_METHOD(Redis, hMset); 168 | PHP_METHOD(Redis, hMget); 169 | 170 | PHP_METHOD(Redis, multi); 171 | PHP_METHOD(Redis, discard); 172 | PHP_METHOD(Redis, exec); 173 | PHP_METHOD(Redis, watch); 174 | PHP_METHOD(Redis, unwatch); 175 | 176 | PHP_METHOD(Redis, pipeline); 177 | 178 | PHP_METHOD(Redis, publish); 179 | PHP_METHOD(Redis, subscribe); 180 | PHP_METHOD(Redis, psubscribe); 181 | PHP_METHOD(Redis, unsubscribe); 182 | PHP_METHOD(Redis, punsubscribe); 183 | 184 | PHP_METHOD(Redis, getOption); 185 | PHP_METHOD(Redis, setOption); 186 | 187 | PHP_METHOD(Redis, config); 188 | PHP_METHOD(Redis, slowlog); 189 | PHP_METHOD(Redis, wait); 190 | PHP_METHOD(Redis, pubsub); 191 | 192 | PHP_METHOD(Redis, client); 193 | 194 | /* SCAN and friends */ 195 | PHP_METHOD(Redis, scan); 196 | PHP_METHOD(Redis, hscan); 197 | PHP_METHOD(Redis, sscan); 198 | PHP_METHOD(Redis, zscan); 199 | 200 | /* HyperLogLog commands */ 201 | PHP_METHOD(Redis, pfadd); 202 | PHP_METHOD(Redis, pfcount); 203 | PHP_METHOD(Redis, pfmerge); 204 | 205 | /* Reflection */ 206 | PHP_METHOD(Redis, getHost); 207 | PHP_METHOD(Redis, getPort); 208 | PHP_METHOD(Redis, getDBNum); 209 | PHP_METHOD(Redis, getTimeout); 210 | PHP_METHOD(Redis, getReadTimeout); 211 | PHP_METHOD(Redis, isConnected); 212 | PHP_METHOD(Redis, getPersistentID); 213 | PHP_METHOD(Redis, getAuth); 214 | PHP_METHOD(Redis, getMode); 215 | PHP_METHOD(Redis, rawCommand); 216 | 217 | #ifdef PHP_WIN32 218 | #define PHP_REDIS_API __declspec(dllexport) 219 | #else 220 | #define PHP_REDIS_API 221 | #endif 222 | 223 | #ifdef ZTS 224 | #include "TSRM.h" 225 | #endif 226 | 227 | PHP_MINIT_FUNCTION(redis); 228 | PHP_MSHUTDOWN_FUNCTION(redis); 229 | PHP_RINIT_FUNCTION(redis); 230 | PHP_RSHUTDOWN_FUNCTION(redis); 231 | PHP_MINFO_FUNCTION(redis); 232 | 233 | PHP_REDIS_API int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent); 234 | PHP_REDIS_API void redis_atomic_increment(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int count); 235 | PHP_REDIS_API int generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len, 236 | int min_argc, RedisSock **redis_sock, int has_timeout, int all_keys, int can_serialize); 237 | PHP_REDIS_API void generic_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sort, int use_alpha); 238 | typedef void (*ResultCallback)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); 239 | PHP_REDIS_API void generic_empty_cmd_impl(INTERNAL_FUNCTION_PARAMETERS, char *cmd, int cmd_len, ResultCallback result_callback); 240 | PHP_REDIS_API void generic_empty_cmd(INTERNAL_FUNCTION_PARAMETERS, char *cmd, int cmd_len, ...); 241 | PHP_REDIS_API void generic_empty_long_cmd(INTERNAL_FUNCTION_PARAMETERS, char *cmd, int cmd_len, ...); 242 | 243 | PHP_REDIS_API void generic_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sub_cmd); 244 | PHP_REDIS_API void generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *unsub_cmd); 245 | 246 | PHP_REDIS_API int redis_response_enqueued(RedisSock *redis_sock TSRMLS_DC); 247 | 248 | PHP_REDIS_API int get_flag(zval *object TSRMLS_DC); 249 | PHP_REDIS_API void set_flag(zval *object, int new_flag TSRMLS_DC); 250 | 251 | PHP_REDIS_API int redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int numElems); 252 | 253 | /* pipeline */ 254 | PHP_REDIS_API request_item* get_pipeline_head(zval *object); 255 | PHP_REDIS_API void set_pipeline_head(zval *object, request_item *head); 256 | PHP_REDIS_API request_item* get_pipeline_current(zval *object); 257 | PHP_REDIS_API void set_pipeline_current(zval *object, request_item *current); 258 | 259 | #ifndef _MSC_VER 260 | ZEND_BEGIN_MODULE_GLOBALS(redis) 261 | ZEND_END_MODULE_GLOBALS(redis) 262 | #endif 263 | 264 | struct redis_queued_item { 265 | 266 | /* reading function */ 267 | zval * (*fun)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, ...); 268 | 269 | char *cmd; 270 | int cmd_len; 271 | 272 | struct redis_queued_item *next; 273 | }; 274 | 275 | extern zend_module_entry redis_module_entry; 276 | #define redis_module_ptr &redis_module_entry 277 | 278 | #define phpext_redis_ptr redis_module_ptr 279 | 280 | #define PHP_REDIS_VERSION "2.2.7" 281 | 282 | #endif 283 | 284 | /* 285 | * Local variables: 286 | * tab-width: 4 287 | * c-basic-offset: 4 288 | * End: 289 | * vim600: noet sw=4 ts=4 fdm=marker 290 | * vim<600: noet sw=4 ts=4 291 | */ 292 | -------------------------------------------------------------------------------- /redis_session.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; tab-width: 4 -*- */ 2 | /* 3 | +----------------------------------------------------------------------+ 4 | | PHP Version 5 | 5 | +----------------------------------------------------------------------+ 6 | | Copyright (c) 1997-2009 The PHP Group | 7 | +----------------------------------------------------------------------+ 8 | | This source file is subject to version 3.01 of the PHP license, | 9 | | that is bundled with this package in the file LICENSE, and is | 10 | | available through the world-wide-web at the following url: | 11 | | http://www.php.net/license/3_01.txt | 12 | | If you did not receive a copy of the PHP license and are unable to | 13 | | obtain it through the world-wide-web, please send a note to | 14 | | license@php.net so we can mail you a copy immediately. | 15 | +----------------------------------------------------------------------+ 16 | | Original author: Alfonso Jimenez | 17 | | Maintainer: Nicolas Favre-Felix | 18 | | Maintainer: Nasreddine Bouafif | 19 | | Maintainer: Michael Grunder | 20 | +----------------------------------------------------------------------+ 21 | */ 22 | 23 | #include "common.h" 24 | 25 | #ifdef HAVE_CONFIG_H 26 | #include "config.h" 27 | #endif 28 | 29 | #ifdef PHP_SESSION 30 | #include "common.h" 31 | #include "ext/standard/info.h" 32 | #include "php_redis.h" 33 | #include "redis_session.h" 34 | #include 35 | 36 | #include "library.h" 37 | 38 | #include "php.h" 39 | #include "php_ini.h" 40 | #include "php_variables.h" 41 | #include "SAPI.h" 42 | #include "ext/standard/url.h" 43 | 44 | ps_module ps_mod_redis = { 45 | PS_MOD(redis) 46 | }; 47 | 48 | typedef struct redis_pool_member_ { 49 | 50 | RedisSock *redis_sock; 51 | int weight; 52 | int database; 53 | 54 | char *prefix; 55 | size_t prefix_len; 56 | 57 | char *auth; 58 | size_t auth_len; 59 | 60 | struct redis_pool_member_ *next; 61 | 62 | } redis_pool_member; 63 | 64 | typedef struct { 65 | 66 | int totalWeight; 67 | int count; 68 | 69 | redis_pool_member *head; 70 | 71 | } redis_pool; 72 | 73 | PHP_REDIS_API redis_pool* 74 | redis_pool_new(TSRMLS_D) { 75 | return ecalloc(1, sizeof(redis_pool)); 76 | } 77 | 78 | PHP_REDIS_API void 79 | redis_pool_add(redis_pool *pool, RedisSock *redis_sock, int weight, 80 | int database, char *prefix, char *auth TSRMLS_DC) { 81 | 82 | redis_pool_member *rpm = ecalloc(1, sizeof(redis_pool_member)); 83 | rpm->redis_sock = redis_sock; 84 | rpm->weight = weight; 85 | rpm->database = database; 86 | 87 | rpm->prefix = prefix; 88 | rpm->prefix_len = (prefix?strlen(prefix):0); 89 | 90 | rpm->auth = auth; 91 | rpm->auth_len = (auth?strlen(auth):0); 92 | 93 | rpm->next = pool->head; 94 | pool->head = rpm; 95 | 96 | pool->totalWeight += weight; 97 | } 98 | 99 | PHP_REDIS_API void 100 | redis_pool_free(redis_pool *pool TSRMLS_DC) { 101 | 102 | redis_pool_member *rpm, *next; 103 | rpm = pool->head; 104 | while(rpm) { 105 | next = rpm->next; 106 | redis_sock_disconnect(rpm->redis_sock TSRMLS_CC); 107 | efree(rpm->redis_sock); 108 | if(rpm->prefix) efree(rpm->prefix); 109 | if(rpm->auth) efree(rpm->auth); 110 | efree(rpm); 111 | rpm = next; 112 | } 113 | efree(pool); 114 | } 115 | 116 | void 117 | redis_pool_member_auth(redis_pool_member *rpm TSRMLS_DC) { 118 | RedisSock *redis_sock = rpm->redis_sock; 119 | char *response, *cmd; 120 | int response_len, cmd_len; 121 | 122 | if(!rpm->auth || !rpm->auth_len) { /* no password given. */ 123 | return; 124 | } 125 | cmd_len = redis_cmd_format_static(&cmd, "AUTH", "s", rpm->auth, rpm->auth_len); 126 | 127 | if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) >= 0) { 128 | if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC))) { 129 | efree(response); 130 | } 131 | } 132 | efree(cmd); 133 | } 134 | 135 | static void 136 | redis_pool_member_select(redis_pool_member *rpm TSRMLS_DC) { 137 | RedisSock *redis_sock = rpm->redis_sock; 138 | char *response, *cmd; 139 | int response_len, cmd_len; 140 | 141 | cmd_len = redis_cmd_format_static(&cmd, "SELECT", "d", rpm->database); 142 | 143 | if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) >= 0) { 144 | if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC))) { 145 | efree(response); 146 | } 147 | } 148 | efree(cmd); 149 | } 150 | 151 | PHP_REDIS_API redis_pool_member * 152 | redis_pool_get_sock(redis_pool *pool, const char *key TSRMLS_DC) { 153 | redis_pool_member *rpm = pool->head; 154 | unsigned int pos, i; 155 | 156 | memcpy(&pos, key, sizeof(pos)); 157 | pos %= pool->totalWeight; 158 | 159 | for(i = 0; i < pool->totalWeight;) { 160 | if(pos >= i && pos < i + rpm->weight) { 161 | int needs_auth = 0; 162 | if(rpm->auth && rpm->auth_len && rpm->redis_sock->status != REDIS_SOCK_STATUS_CONNECTED) { 163 | needs_auth = 1; 164 | } 165 | redis_sock_server_open(rpm->redis_sock, 0 TSRMLS_CC); 166 | if(needs_auth) { 167 | redis_pool_member_auth(rpm TSRMLS_CC); 168 | } 169 | if(rpm->database >= 0) { /* default is -1 which leaves the choice to redis. */ 170 | redis_pool_member_select(rpm TSRMLS_CC); 171 | } 172 | 173 | return rpm; 174 | } 175 | i += rpm->weight; 176 | rpm = rpm->next; 177 | } 178 | 179 | return NULL; 180 | } 181 | 182 | /* {{{ PS_OPEN_FUNC 183 | */ 184 | PS_OPEN_FUNC(redis) 185 | { 186 | 187 | php_url *url; 188 | zval *params, **param; 189 | int i, j, path_len; 190 | 191 | redis_pool *pool = redis_pool_new(TSRMLS_C); 192 | 193 | for (i=0,j=0,path_len=strlen(save_path); iquery != NULL) { 236 | MAKE_STD_ZVAL(params); 237 | array_init(params); 238 | 239 | sapi_module.treat_data(PARSE_STRING, estrdup(url->query), params TSRMLS_CC); 240 | 241 | if (zend_hash_find(Z_ARRVAL_P(params), "weight", sizeof("weight"), (void **) ¶m) != FAILURE) { 242 | convert_to_long_ex(param); 243 | weight = Z_LVAL_PP(param); 244 | } 245 | if (zend_hash_find(Z_ARRVAL_P(params), "timeout", sizeof("timeout"), (void **) ¶m) != FAILURE) { 246 | timeout = atof(Z_STRVAL_PP(param)); 247 | } 248 | if (zend_hash_find(Z_ARRVAL_P(params), "persistent", sizeof("persistent"), (void **) ¶m) != FAILURE) { 249 | persistent = (atol(Z_STRVAL_PP(param)) == 1 ? 1 : 0); 250 | } 251 | if (zend_hash_find(Z_ARRVAL_P(params), "persistent_id", sizeof("persistent_id"), (void **) ¶m) != FAILURE) { 252 | persistent_id = estrndup(Z_STRVAL_PP(param), Z_STRLEN_PP(param)); 253 | } 254 | if (zend_hash_find(Z_ARRVAL_P(params), "prefix", sizeof("prefix"), (void **) ¶m) != FAILURE) { 255 | prefix = estrndup(Z_STRVAL_PP(param), Z_STRLEN_PP(param)); 256 | } 257 | if (zend_hash_find(Z_ARRVAL_P(params), "auth", sizeof("auth"), (void **) ¶m) != FAILURE) { 258 | auth = estrndup(Z_STRVAL_PP(param), Z_STRLEN_PP(param)); 259 | } 260 | if (zend_hash_find(Z_ARRVAL_P(params), "database", sizeof("database"), (void **) ¶m) != FAILURE) { 261 | convert_to_long_ex(param); 262 | database = Z_LVAL_PP(param); 263 | } 264 | if (zend_hash_find(Z_ARRVAL_P(params), "retry_interval", sizeof("retry_interval"), (void **) ¶m) != FAILURE) { 265 | convert_to_long_ex(param); 266 | retry_interval = Z_LVAL_PP(param); 267 | } 268 | 269 | zval_ptr_dtor(¶ms); 270 | } 271 | 272 | if ((url->path == NULL && url->host == NULL) || weight <= 0 || timeout <= 0) { 273 | php_url_free(url); 274 | redis_pool_free(pool TSRMLS_CC); 275 | PS_SET_MOD_DATA(NULL); 276 | return FAILURE; 277 | } 278 | 279 | if(url->host) { 280 | redis_sock = redis_sock_create(url->host, strlen(url->host), url->port, timeout, persistent, persistent_id, retry_interval, 0); 281 | } else { /* unix */ 282 | redis_sock = redis_sock_create(url->path, strlen(url->path), 0, timeout, persistent, persistent_id, retry_interval, 0); 283 | } 284 | redis_pool_add(pool, redis_sock, weight, database, prefix, auth TSRMLS_CC); 285 | 286 | php_url_free(url); 287 | } 288 | } 289 | 290 | if (pool->head) { 291 | PS_SET_MOD_DATA(pool); 292 | return SUCCESS; 293 | } 294 | 295 | return FAILURE; 296 | } 297 | /* }}} */ 298 | 299 | /* {{{ PS_CLOSE_FUNC 300 | */ 301 | PS_CLOSE_FUNC(redis) 302 | { 303 | redis_pool *pool = PS_GET_MOD_DATA(); 304 | 305 | if(pool){ 306 | redis_pool_free(pool TSRMLS_CC); 307 | PS_SET_MOD_DATA(NULL); 308 | } 309 | return SUCCESS; 310 | } 311 | /* }}} */ 312 | 313 | static char * 314 | redis_session_key(redis_pool_member *rpm, const char *key, int key_len, int *session_len) { 315 | 316 | char *session; 317 | char default_prefix[] = "PHPREDIS_SESSION:"; 318 | char *prefix = default_prefix; 319 | size_t prefix_len = sizeof(default_prefix)-1; 320 | 321 | if(rpm->prefix) { 322 | prefix = rpm->prefix; 323 | prefix_len = rpm->prefix_len; 324 | } 325 | 326 | /* build session key */ 327 | *session_len = key_len + prefix_len; 328 | session = emalloc(*session_len); 329 | memcpy(session, prefix, prefix_len); 330 | memcpy(session + prefix_len, key, key_len); 331 | 332 | return session; 333 | } 334 | 335 | 336 | /* {{{ PS_READ_FUNC 337 | */ 338 | PS_READ_FUNC(redis) 339 | { 340 | char *session, *cmd; 341 | int session_len, cmd_len; 342 | 343 | redis_pool *pool = PS_GET_MOD_DATA(); 344 | redis_pool_member *rpm = redis_pool_get_sock(pool, key TSRMLS_CC); 345 | RedisSock *redis_sock = rpm?rpm->redis_sock:NULL; 346 | if(!rpm || !redis_sock){ 347 | return FAILURE; 348 | } 349 | 350 | /* send GET command */ 351 | session = redis_session_key(rpm, key, strlen(key), &session_len); 352 | cmd_len = redis_cmd_format_static(&cmd, "GET", "s", session, session_len); 353 | efree(session); 354 | if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { 355 | efree(cmd); 356 | return FAILURE; 357 | } 358 | efree(cmd); 359 | 360 | /* read response */ 361 | if ((*val = redis_sock_read(redis_sock, vallen TSRMLS_CC)) == NULL) { 362 | return FAILURE; 363 | } 364 | 365 | return SUCCESS; 366 | } 367 | /* }}} */ 368 | 369 | /* {{{ PS_WRITE_FUNC 370 | */ 371 | PS_WRITE_FUNC(redis) 372 | { 373 | char *cmd, *response, *session; 374 | int cmd_len, response_len, session_len; 375 | 376 | redis_pool *pool = PS_GET_MOD_DATA(); 377 | redis_pool_member *rpm = redis_pool_get_sock(pool, key TSRMLS_CC); 378 | RedisSock *redis_sock = rpm?rpm->redis_sock:NULL; 379 | if(!rpm || !redis_sock){ 380 | return FAILURE; 381 | } 382 | 383 | /* send SET command */ 384 | session = redis_session_key(rpm, key, strlen(key), &session_len); 385 | cmd_len = redis_cmd_format_static(&cmd, "SETEX", "sds", session, session_len, INI_INT("session.gc_maxlifetime"), val, vallen); 386 | efree(session); 387 | if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { 388 | efree(cmd); 389 | return FAILURE; 390 | } 391 | efree(cmd); 392 | 393 | /* read response */ 394 | if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { 395 | return FAILURE; 396 | } 397 | 398 | if(response_len == 3 && strncmp(response, "+OK", 3) == 0) { 399 | efree(response); 400 | return SUCCESS; 401 | } else { 402 | efree(response); 403 | return FAILURE; 404 | } 405 | } 406 | /* }}} */ 407 | 408 | /* {{{ PS_DESTROY_FUNC 409 | */ 410 | PS_DESTROY_FUNC(redis) 411 | { 412 | char *cmd, *response, *session; 413 | int cmd_len, response_len, session_len; 414 | 415 | redis_pool *pool = PS_GET_MOD_DATA(); 416 | redis_pool_member *rpm = redis_pool_get_sock(pool, key TSRMLS_CC); 417 | RedisSock *redis_sock = rpm?rpm->redis_sock:NULL; 418 | if(!rpm || !redis_sock){ 419 | return FAILURE; 420 | } 421 | 422 | /* send DEL command */ 423 | session = redis_session_key(rpm, key, strlen(key), &session_len); 424 | cmd_len = redis_cmd_format_static(&cmd, "DEL", "s", session, session_len); 425 | efree(session); 426 | if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { 427 | efree(cmd); 428 | return FAILURE; 429 | } 430 | efree(cmd); 431 | 432 | /* read response */ 433 | if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { 434 | return FAILURE; 435 | } 436 | 437 | if(response_len == 2 && response[0] == ':' && (response[1] == '0' || response[1] == '1')) { 438 | efree(response); 439 | return SUCCESS; 440 | } else { 441 | efree(response); 442 | return FAILURE; 443 | } 444 | } 445 | /* }}} */ 446 | 447 | /* {{{ PS_GC_FUNC 448 | */ 449 | PS_GC_FUNC(redis) 450 | { 451 | return SUCCESS; 452 | } 453 | /* }}} */ 454 | 455 | #endif 456 | /* vim: set tabstop=4 expandtab: */ 457 | 458 | -------------------------------------------------------------------------------- /tests/array-tests.php: -------------------------------------------------------------------------------- 1 | \d+)_\w+#", $str, $out)) { 10 | return $out['facebook_id']; 11 | } 12 | return $str; 13 | } 14 | 15 | class Redis_Array_Test extends TestSuite 16 | { 17 | private $strings; 18 | public $ra = NULL; 19 | private $data = NULL; 20 | 21 | public function setUp() { 22 | 23 | // initialize strings. 24 | $n = REDIS_ARRAY_DATA_SIZE; 25 | $this->strings = array(); 26 | for($i = 0; $i < $n; $i++) { 27 | $this->strings['key-'.$i] = 'val-'.$i; 28 | } 29 | 30 | global $newRing, $oldRing, $useIndex; 31 | $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex)); 32 | } 33 | 34 | public function testMSet() { 35 | // run mset 36 | $this->assertTrue(TRUE === $this->ra->mset($this->strings)); 37 | 38 | // check each key individually using the array 39 | foreach($this->strings as $k => $v) { 40 | $this->assertTrue($v === $this->ra->get($k)); 41 | } 42 | 43 | // check each key individually using a new connection 44 | foreach($this->strings as $k => $v) { 45 | list($host, $port) = split(':', $this->ra->_target($k)); 46 | 47 | $r = new Redis; 48 | $r->pconnect($host, (int)$port); 49 | $this->assertTrue($v === $r->get($k)); 50 | } 51 | } 52 | 53 | public function testMGet() { 54 | $this->assertTrue(array_values($this->strings) === $this->ra->mget(array_keys($this->strings))); 55 | } 56 | 57 | private function addData($commonString) { 58 | $this->data = array(); 59 | for($i = 0; $i < REDIS_ARRAY_DATA_SIZE; $i++) { 60 | $k = rand().'_'.$commonString.'_'.rand(); 61 | $this->data[$k] = rand(); 62 | } 63 | $this->ra->mset($this->data); 64 | } 65 | 66 | private function checkCommonLocality() { 67 | // check that they're all on the same node. 68 | $lastNode = NULL; 69 | foreach($this->data as $k => $v) { 70 | $node = $this->ra->_target($k); 71 | if($lastNode) { 72 | $this->assertTrue($node === $lastNode); 73 | } 74 | $this->assertTrue($this->ra->get($k) == $v); 75 | $lastNode = $node; 76 | } 77 | } 78 | 79 | public function testKeyLocality() { 80 | 81 | // basic key locality with default hash 82 | $this->addData('{hashed part of the key}'); 83 | $this->checkCommonLocality(); 84 | 85 | // with common hashing function 86 | global $newRing, $oldRing, $useIndex; 87 | $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 88 | 'index' => $useIndex, 89 | 'function' => 'custom_hash')); 90 | 91 | // basic key locality with custom hash 92 | $this->addData('fb'.rand()); 93 | $this->checkCommonLocality(); 94 | } 95 | 96 | public function customDistributor($key) 97 | { 98 | $a = unpack("N*", md5($key, true)); 99 | global $newRing; 100 | $pos = abs($a[1]) % count($newRing); 101 | 102 | return $pos; 103 | } 104 | 105 | public function testKeyDistributor() 106 | { 107 | global $newRing, $useIndex; 108 | $this->ra = new RedisArray($newRing, array( 109 | 'index' => $useIndex, 110 | 'function' => 'custom_hash', 111 | 'distributor' => array($this, "customDistributor"))); 112 | 113 | // custom key distribution function. 114 | $this->addData('fb'.rand()); 115 | 116 | // check that they're all on the expected node. 117 | $lastNode = NULL; 118 | foreach($this->data as $k => $v) { 119 | $node = $this->ra->_target($k); 120 | $pos = $this->customDistributor($k); 121 | $this->assertTrue($node === $newRing[$pos]); 122 | } 123 | } 124 | 125 | } 126 | 127 | class Redis_Rehashing_Test extends TestSuite 128 | { 129 | 130 | public $ra = NULL; 131 | private $useIndex; 132 | 133 | // data 134 | private $strings; 135 | private $sets; 136 | private $lists; 137 | private $hashes; 138 | private $zsets; 139 | 140 | public function setUp() { 141 | 142 | // initialize strings. 143 | $n = REDIS_ARRAY_DATA_SIZE; 144 | $this->strings = array(); 145 | for($i = 0; $i < $n; $i++) { 146 | $this->strings['key-'.$i] = 'val-'.$i; 147 | } 148 | 149 | // initialize sets 150 | for($i = 0; $i < $n; $i++) { 151 | // each set has 20 elements 152 | $this->sets['set-'.$i] = range($i, $i+20); 153 | } 154 | 155 | // initialize lists 156 | for($i = 0; $i < $n; $i++) { 157 | // each list has 20 elements 158 | $this->lists['list-'.$i] = range($i, $i+20); 159 | } 160 | 161 | // initialize hashes 162 | for($i = 0; $i < $n; $i++) { 163 | // each hash has 5 keys 164 | $this->hashes['hash-'.$i] = array('A' => $i, 'B' => $i+1, 'C' => $i+2, 'D' => $i+3, 'E' => $i+4); 165 | } 166 | 167 | // initialize sorted sets 168 | for($i = 0; $i < $n; $i++) { 169 | // each sorted sets has 5 elements 170 | $this->zsets['zset-'.$i] = array($i, 'A', $i+1, 'B', $i+2, 'C', $i+3, 'D', $i+4, 'E'); 171 | } 172 | 173 | global $newRing, $oldRing, $useIndex; 174 | 175 | // create array 176 | $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex)); 177 | } 178 | 179 | public function testFlush() { 180 | 181 | // flush all servers first. 182 | global $serverList; 183 | foreach($serverList as $s) { 184 | list($host, $port) = explode(':', $s); 185 | 186 | $r = new Redis; 187 | $r->pconnect($host, (int)$port); 188 | $r->flushdb(); 189 | } 190 | } 191 | 192 | 193 | private function distributeKeys() { 194 | 195 | // strings 196 | foreach($this->strings as $k => $v) { 197 | $this->ra->set($k, $v); 198 | } 199 | 200 | // sets 201 | foreach($this->sets as $k => $v) { 202 | call_user_func_array(array($this->ra, 'sadd'), array_merge(array($k), $v)); 203 | } 204 | 205 | // lists 206 | foreach($this->lists as $k => $v) { 207 | call_user_func_array(array($this->ra, 'rpush'), array_merge(array($k), $v)); 208 | } 209 | 210 | // hashes 211 | foreach($this->hashes as $k => $v) { 212 | $this->ra->hmset($k, $v); 213 | } 214 | 215 | // sorted sets 216 | foreach($this->zsets as $k => $v) { 217 | call_user_func_array(array($this->ra, 'zadd'), array_merge(array($k), $v)); 218 | } 219 | } 220 | 221 | public function testDistribution() { 222 | 223 | $this->distributeKeys(); 224 | } 225 | 226 | public function testSimpleRead() { 227 | 228 | $this->readAllvalues(); 229 | } 230 | 231 | private function readAllvalues() { 232 | 233 | // strings 234 | foreach($this->strings as $k => $v) { 235 | $this->assertTrue($this->ra->get($k) === $v); 236 | } 237 | 238 | // sets 239 | foreach($this->sets as $k => $v) { 240 | $ret = $this->ra->smembers($k); // get values 241 | 242 | // sort sets 243 | sort($v); 244 | sort($ret); 245 | 246 | $this->assertTrue($ret == $v); 247 | } 248 | 249 | // lists 250 | foreach($this->lists as $k => $v) { 251 | $ret = $this->ra->lrange($k, 0, -1); 252 | $this->assertTrue($ret == $v); 253 | } 254 | 255 | // hashes 256 | foreach($this->hashes as $k => $v) { 257 | $ret = $this->ra->hgetall($k); // get values 258 | $this->assertTrue($ret == $v); 259 | } 260 | 261 | // sorted sets 262 | foreach($this->zsets as $k => $v) { 263 | $ret = $this->ra->zrange($k, 0, -1, TRUE); // get values with scores 264 | 265 | // create assoc array from local dataset 266 | $tmp = array(); 267 | for($i = 0; $i < count($v); $i += 2) { 268 | $tmp[$v[$i+1]] = $v[$i]; 269 | } 270 | 271 | // compare to RA value 272 | $this->assertTrue($ret == $tmp); 273 | } 274 | } 275 | 276 | // add a new node. 277 | public function testCreateSecondRing() { 278 | 279 | global $newRing, $oldRing, $serverList; 280 | $oldRing = $newRing; // back up the original. 281 | $newRing = $serverList; // add a new node to the main ring. 282 | } 283 | 284 | public function testReadUsingFallbackMechanism() { 285 | $this->readAllvalues(); // some of the reads will fail and will go to another target node. 286 | } 287 | 288 | public function testRehash() { 289 | $this->ra->_rehash(); // this will redistribute the keys 290 | } 291 | 292 | public function testRehashWithCallback() { 293 | $total = 0; 294 | $this->ra->_rehash(function ($host, $count) use (&$total) { 295 | $total += $count; 296 | }); 297 | $this->assertTrue($total > 0); 298 | } 299 | 300 | public function testReadRedistributedKeys() { 301 | $this->readAllvalues(); // we shouldn't have any missed reads now. 302 | } 303 | } 304 | 305 | // Test auto-migration of keys 306 | class Redis_Auto_Rehashing_Test extends TestSuite { 307 | 308 | public $ra = NULL; 309 | 310 | // data 311 | private $strings; 312 | 313 | public function setUp() { 314 | 315 | // initialize strings. 316 | $n = REDIS_ARRAY_DATA_SIZE; 317 | $this->strings = array(); 318 | for($i = 0; $i < $n; $i++) { 319 | $this->strings['key-'.$i] = 'val-'.$i; 320 | } 321 | 322 | global $newRing, $oldRing, $useIndex; 323 | 324 | // create array 325 | $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex, 'autorehash' => TRUE)); 326 | } 327 | 328 | public function testDistribute() { 329 | // strings 330 | foreach($this->strings as $k => $v) { 331 | $this->ra->set($k, $v); 332 | } 333 | } 334 | 335 | private function readAllvalues() { 336 | foreach($this->strings as $k => $v) { 337 | $this->assertTrue($this->ra->get($k) === $v); 338 | } 339 | } 340 | 341 | 342 | public function testReadAll() { 343 | $this->readAllvalues(); 344 | } 345 | 346 | // add a new node. 347 | public function testCreateSecondRing() { 348 | 349 | global $newRing, $oldRing, $serverList; 350 | $oldRing = $newRing; // back up the original. 351 | $newRing = $serverList; // add a new node to the main ring. 352 | } 353 | 354 | // Read and migrate keys on fallback, causing the whole ring to be rehashed. 355 | public function testReadAndMigrateAll() { 356 | $this->readAllvalues(); 357 | } 358 | 359 | // Read and migrate keys on fallback, causing the whole ring to be rehashed. 360 | public function testAllKeysHaveBeenMigrated() { 361 | foreach($this->strings as $k => $v) { 362 | // get the target for each key 363 | $target = $this->ra->_target($k); 364 | 365 | // connect to the target host 366 | list($host,$port) = split(':', $target); 367 | $r = new Redis; 368 | $r->pconnect($host, $port); 369 | 370 | $this->assertTrue($v === $r->get($k)); // check that the key has actually been migrated to the new node. 371 | } 372 | } 373 | } 374 | 375 | // Test node-specific multi/exec 376 | class Redis_Multi_Exec_Test extends TestSuite { 377 | 378 | public $ra = NULL; 379 | 380 | public function setUp() { 381 | 382 | global $newRing, $oldRing, $useIndex; 383 | // create array 384 | $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex)); 385 | } 386 | 387 | public function testInit() { 388 | $this->ra->set('{groups}:managers', 2); 389 | $this->ra->set('{groups}:executives', 3); 390 | 391 | $this->ra->set('1_{employee:joe}_name', 'joe'); 392 | $this->ra->set('1_{employee:joe}_group', 2); 393 | $this->ra->set('1_{employee:joe}_salary', 2000); 394 | } 395 | 396 | public function testKeyDistribution() { 397 | // check that all of joe's keys are on the same instance 398 | $lastNode = NULL; 399 | foreach(array('name', 'group', 'salary') as $field) { 400 | $node = $this->ra->_target('1_{employee:joe}_'.$field); 401 | if($lastNode) { 402 | $this->assertTrue($node === $lastNode); 403 | } 404 | $lastNode = $node; 405 | } 406 | } 407 | 408 | public function testMultiExec() { 409 | 410 | // Joe gets a promotion 411 | $newGroup = $this->ra->get('{groups}:executives'); 412 | $newSalary = 4000; 413 | 414 | // change both in a transaction. 415 | $host = $this->ra->_target('{employee:joe}'); // transactions are per-node, so we need a reference to it. 416 | $tr = $this->ra->multi($host) 417 | ->set('1_{employee:joe}_group', $newGroup) 418 | ->set('1_{employee:joe}_salary', $newSalary) 419 | ->exec(); 420 | 421 | // check that the group and salary have been changed 422 | $this->assertTrue($this->ra->get('1_{employee:joe}_group') === $newGroup); 423 | $this->assertTrue($this->ra->get('1_{employee:joe}_salary') == $newSalary); 424 | 425 | } 426 | 427 | public function testMultiExecMSet() { 428 | 429 | global $newGroup, $newSalary; 430 | $newGroup = 1; 431 | $newSalary = 10000; 432 | 433 | // test MSET, making Joe a top-level executive 434 | $out = $this->ra->multi($this->ra->_target('{employee:joe}')) 435 | ->mset(array('1_{employee:joe}_group' => $newGroup, '1_{employee:joe}_salary' => $newSalary)) 436 | ->exec(); 437 | 438 | $this->assertTrue($out[0] === TRUE); 439 | } 440 | 441 | public function testMultiExecMGet() { 442 | 443 | global $newGroup, $newSalary; 444 | 445 | // test MGET 446 | $out = $this->ra->multi($this->ra->_target('{employee:joe}')) 447 | ->mget(array('1_{employee:joe}_group', '1_{employee:joe}_salary')) 448 | ->exec(); 449 | 450 | $this->assertTrue($out[0][0] == $newGroup); 451 | $this->assertTrue($out[0][1] == $newSalary); 452 | } 453 | 454 | public function testMultiExecDel() { 455 | 456 | // test DEL 457 | $out = $this->ra->multi($this->ra->_target('{employee:joe}')) 458 | ->del('1_{employee:joe}_group', '1_{employee:joe}_salary') 459 | ->exec(); 460 | 461 | $this->assertTrue($out[0] === 2); 462 | $this->assertTrue($this->ra->exists('1_{employee:joe}_group') === FALSE); 463 | $this->assertTrue($this->ra->exists('1_{employee:joe}_salary') === FALSE); 464 | } 465 | 466 | public function testDiscard() { 467 | /* phpredis issue #87 */ 468 | $key = 'test_err'; 469 | 470 | $this->assertTrue($this->ra->set($key, 'test')); 471 | $this->assertTrue('test' === $this->ra->get($key)); 472 | 473 | $this->ra->watch($key); 474 | 475 | // After watch, same 476 | $this->assertTrue('test' === $this->ra->get($key)); 477 | 478 | // change in a multi/exec block. 479 | $ret = $this->ra->multi($this->ra->_target($key))->set($key, 'test1')->exec(); 480 | $this->assertTrue($ret === array(true)); 481 | 482 | // Get after exec, 'test1': 483 | $this->assertTrue($this->ra->get($key) === 'test1'); 484 | 485 | $this->ra->watch($key); 486 | 487 | // After second watch, still test1. 488 | $this->assertTrue($this->ra->get($key) === 'test1'); 489 | 490 | $ret = $this->ra->multi($this->ra->_target($key))->set($key, 'test2')->discard(); 491 | // Ret after discard: NULL"; 492 | $this->assertTrue($ret === NULL); 493 | 494 | // Get after discard, unchanged: 495 | $this->assertTrue($this->ra->get($key) === 'test1'); 496 | } 497 | 498 | } 499 | 500 | // Test custom distribution function 501 | class Redis_Distributor_Test extends TestSuite { 502 | 503 | public $ra = NULL; 504 | 505 | public function setUp() { 506 | 507 | global $newRing, $oldRing, $useIndex; 508 | // create array 509 | $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex, 'distributor' => array($this, 'distribute'))); 510 | } 511 | 512 | public function testInit() { 513 | $this->ra->set('{uk}test', 'joe'); 514 | $this->ra->set('{us}test', 'bob'); 515 | } 516 | 517 | public function distribute($key) { 518 | $matches = array(); 519 | if (preg_match('/{([^}]+)}.*/', $key, $matches) == 1) { 520 | $countries = array('uk' => 0, 'us' => 1); 521 | if (array_key_exists($matches[1], $countries)) { 522 | return $countries[$matches[1]]; 523 | } 524 | } 525 | return 2; // default server 526 | } 527 | 528 | public function testDistribution() { 529 | $ukServer = $this->ra->_target('{uk}test'); 530 | $usServer = $this->ra->_target('{us}test'); 531 | $deServer = $this->ra->_target('{de}test'); 532 | $defaultServer = $this->ra->_target('unknown'); 533 | 534 | $nodes = $this->ra->_hosts(); 535 | $this->assertTrue($ukServer === $nodes[0]); 536 | $this->assertTrue($usServer === $nodes[1]); 537 | $this->assertTrue($deServer === $nodes[2]); 538 | $this->assertTrue($defaultServer === $nodes[2]); 539 | } 540 | } 541 | 542 | function run_tests($className) { 543 | // reset rings 544 | global $newRing, $oldRing, $serverList; 545 | $newRing = array('localhost:6379', 'localhost:6380', 'localhost:6381'); 546 | $oldRing = array(); 547 | $serverList = array('localhost:6379', 'localhost:6380', 'localhost:6381', 'localhost:6382'); 548 | 549 | // run 550 | TestSuite::run($className, NULL); 551 | } 552 | 553 | define('REDIS_ARRAY_DATA_SIZE', 1000); 554 | 555 | global $useIndex; 556 | foreach(array(true, false) as $useIndex) { 557 | 558 | echo "\n".($useIndex?"WITH":"WITHOUT"). " per-node index:\n"; 559 | 560 | run_tests('Redis_Array_Test'); 561 | run_tests('Redis_Rehashing_Test'); 562 | run_tests('Redis_Auto_Rehashing_Test'); 563 | run_tests('Redis_Multi_Exec_Test'); 564 | run_tests('Redis_Distributor_Test'); 565 | } 566 | 567 | ?> 568 | -------------------------------------------------------------------------------- /redis_array_impl.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | PHP Version 5 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 1997-2009 The PHP Group | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: Nicolas Favre-Felix | 16 | | Maintainer: Michael Grunder | 17 | +----------------------------------------------------------------------+ 18 | */ 19 | #include "redis_array_impl.h" 20 | #include "php_redis.h" 21 | #include "library.h" 22 | 23 | #include "php_variables.h" 24 | #include "SAPI.h" 25 | #include "ext/standard/url.h" 26 | 27 | #define PHPREDIS_INDEX_NAME "__phpredis_array_index__" 28 | 29 | extern int le_redis_sock; 30 | extern zend_class_entry *redis_ce; 31 | 32 | RedisArray* 33 | ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC) 34 | { 35 | int i = 0, host_len, id; 36 | int count = zend_hash_num_elements(hosts); 37 | char *host, *p; 38 | short port; 39 | zval **zpData, z_cons, z_ret; 40 | RedisSock *redis_sock = NULL; 41 | 42 | /* function calls on the Redis object */ 43 | ZVAL_STRING(&z_cons, "__construct", 0); 44 | 45 | /* init connections */ 46 | for (zend_hash_internal_pointer_reset(hosts); zend_hash_has_more_elements(hosts) == SUCCESS; zend_hash_move_forward(hosts)) 47 | { 48 | if ((zend_hash_get_current_data(hosts, (void **) &zpData) == FAILURE) || (Z_TYPE_PP(zpData) != IS_STRING)) 49 | { 50 | efree(ra); 51 | return NULL; 52 | } 53 | 54 | ra->hosts[i] = estrdup(Z_STRVAL_PP(zpData)); 55 | 56 | /* default values */ 57 | host = Z_STRVAL_PP(zpData); 58 | host_len = Z_STRLEN_PP(zpData); 59 | port = 6379; 60 | 61 | if((p = strchr(host, ':'))) { /* found port */ 62 | host_len = p - host; 63 | port = (short)atoi(p+1); 64 | } else if(strchr(host,'/') != NULL) { /* unix socket */ 65 | port = -1; 66 | } 67 | 68 | /* create Redis object */ 69 | MAKE_STD_ZVAL(ra->redis[i]); 70 | object_init_ex(ra->redis[i], redis_ce); 71 | INIT_PZVAL(ra->redis[i]); 72 | call_user_function(&redis_ce->function_table, &ra->redis[i], &z_cons, &z_ret, 0, NULL TSRMLS_CC); 73 | 74 | /* create socket */ 75 | redis_sock = redis_sock_create(host, host_len, port, ra->connect_timeout, ra->pconnect, NULL, retry_interval, b_lazy_connect); 76 | 77 | if (!b_lazy_connect) 78 | { 79 | /* connect */ 80 | redis_sock_server_open(redis_sock, 1 TSRMLS_CC); 81 | } 82 | 83 | /* attach */ 84 | #if PHP_VERSION_ID >= 50400 85 | id = zend_list_insert(redis_sock, le_redis_sock TSRMLS_CC); 86 | #else 87 | id = zend_list_insert(redis_sock, le_redis_sock); 88 | #endif 89 | add_property_resource(ra->redis[i], "socket", id); 90 | 91 | i++; 92 | } 93 | 94 | return ra; 95 | } 96 | 97 | /* List pure functions */ 98 | void ra_init_function_table(RedisArray *ra) { 99 | 100 | MAKE_STD_ZVAL(ra->z_pure_cmds); 101 | array_init(ra->z_pure_cmds); 102 | 103 | add_assoc_bool(ra->z_pure_cmds, "HGET", 1); 104 | add_assoc_bool(ra->z_pure_cmds, "HGETALL", 1); 105 | add_assoc_bool(ra->z_pure_cmds, "HKEYS", 1); 106 | add_assoc_bool(ra->z_pure_cmds, "HLEN", 1); 107 | add_assoc_bool(ra->z_pure_cmds, "SRANDMEMBER", 1); 108 | add_assoc_bool(ra->z_pure_cmds, "HMGET", 1); 109 | add_assoc_bool(ra->z_pure_cmds, "STRLEN", 1); 110 | add_assoc_bool(ra->z_pure_cmds, "SUNION", 1); 111 | add_assoc_bool(ra->z_pure_cmds, "HVALS", 1); 112 | add_assoc_bool(ra->z_pure_cmds, "TYPE", 1); 113 | add_assoc_bool(ra->z_pure_cmds, "EXISTS", 1); 114 | add_assoc_bool(ra->z_pure_cmds, "LINDEX", 1); 115 | add_assoc_bool(ra->z_pure_cmds, "SCARD", 1); 116 | add_assoc_bool(ra->z_pure_cmds, "LLEN", 1); 117 | add_assoc_bool(ra->z_pure_cmds, "SDIFF", 1); 118 | add_assoc_bool(ra->z_pure_cmds, "ZCARD", 1); 119 | add_assoc_bool(ra->z_pure_cmds, "ZCOUNT", 1); 120 | add_assoc_bool(ra->z_pure_cmds, "LRANGE", 1); 121 | add_assoc_bool(ra->z_pure_cmds, "ZRANGE", 1); 122 | add_assoc_bool(ra->z_pure_cmds, "ZRANK", 1); 123 | add_assoc_bool(ra->z_pure_cmds, "GET", 1); 124 | add_assoc_bool(ra->z_pure_cmds, "GETBIT", 1); 125 | add_assoc_bool(ra->z_pure_cmds, "SINTER", 1); 126 | add_assoc_bool(ra->z_pure_cmds, "GETRANGE", 1); 127 | add_assoc_bool(ra->z_pure_cmds, "ZREVRANGE", 1); 128 | add_assoc_bool(ra->z_pure_cmds, "SISMEMBER", 1); 129 | add_assoc_bool(ra->z_pure_cmds, "ZREVRANGEBYSCORE", 1); 130 | add_assoc_bool(ra->z_pure_cmds, "ZREVRANK", 1); 131 | add_assoc_bool(ra->z_pure_cmds, "HEXISTS", 1); 132 | add_assoc_bool(ra->z_pure_cmds, "ZSCORE", 1); 133 | add_assoc_bool(ra->z_pure_cmds, "HGET", 1); 134 | add_assoc_bool(ra->z_pure_cmds, "OBJECT", 1); 135 | add_assoc_bool(ra->z_pure_cmds, "SMEMBERS", 1); 136 | } 137 | 138 | static int 139 | ra_find_name(const char *name) { 140 | 141 | const char *ini_names, *p, *next; 142 | /* php_printf("Loading redis array with name=[%s]\n", name); */ 143 | 144 | ini_names = INI_STR("redis.arrays.names"); 145 | for(p = ini_names; p;) { 146 | next = strchr(p, ','); 147 | if(next) { 148 | if(strncmp(p, name, next - p) == 0) { 149 | return 1; 150 | } 151 | } else { 152 | if(strcmp(p, name) == 0) { 153 | return 1; 154 | } 155 | break; 156 | } 157 | p = next + 1; 158 | } 159 | 160 | return 0; 161 | } 162 | 163 | /* laod array from INI settings */ 164 | RedisArray *ra_load_array(const char *name TSRMLS_DC) { 165 | 166 | zval *z_params_hosts, **z_hosts; 167 | zval *z_params_prev, **z_prev; 168 | zval *z_params_funs, **z_data_pp, *z_fun = NULL, *z_dist = NULL; 169 | zval *z_params_index; 170 | zval *z_params_autorehash; 171 | zval *z_params_retry_interval; 172 | zval *z_params_pconnect; 173 | zval *z_params_connect_timeout; 174 | zval *z_params_lazy_connect; 175 | RedisArray *ra = NULL; 176 | 177 | zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0; 178 | long l_retry_interval = 0; 179 | zend_bool b_lazy_connect = 0; 180 | double d_connect_timeout = 0; 181 | HashTable *hHosts = NULL, *hPrev = NULL; 182 | 183 | /* find entry */ 184 | if(!ra_find_name(name)) 185 | return ra; 186 | 187 | /* find hosts */ 188 | MAKE_STD_ZVAL(z_params_hosts); 189 | array_init(z_params_hosts); 190 | sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.hosts")), z_params_hosts TSRMLS_CC); 191 | if (zend_hash_find(Z_ARRVAL_P(z_params_hosts), name, strlen(name) + 1, (void **) &z_hosts) != FAILURE) { 192 | hHosts = Z_ARRVAL_PP(z_hosts); 193 | } 194 | 195 | /* find previous hosts */ 196 | MAKE_STD_ZVAL(z_params_prev); 197 | array_init(z_params_prev); 198 | sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.previous")), z_params_prev TSRMLS_CC); 199 | if (zend_hash_find(Z_ARRVAL_P(z_params_prev), name, strlen(name) + 1, (void **) &z_prev) != FAILURE) { 200 | hPrev = Z_ARRVAL_PP(z_prev); 201 | } 202 | 203 | /* find function */ 204 | MAKE_STD_ZVAL(z_params_funs); 205 | array_init(z_params_funs); 206 | sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.functions")), z_params_funs TSRMLS_CC); 207 | if (zend_hash_find(Z_ARRVAL_P(z_params_funs), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { 208 | MAKE_STD_ZVAL(z_fun); 209 | *z_fun = **z_data_pp; 210 | zval_copy_ctor(z_fun); 211 | } 212 | 213 | /* find distributor */ 214 | MAKE_STD_ZVAL(z_params_funs); 215 | array_init(z_params_funs); 216 | sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.distributor")), z_params_funs TSRMLS_CC); 217 | if (zend_hash_find(Z_ARRVAL_P(z_params_funs), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { 218 | MAKE_STD_ZVAL(z_dist); 219 | *z_dist = **z_data_pp; 220 | zval_copy_ctor(z_dist); 221 | } 222 | 223 | /* find index option */ 224 | MAKE_STD_ZVAL(z_params_index); 225 | array_init(z_params_index); 226 | sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.index")), z_params_index TSRMLS_CC); 227 | if (zend_hash_find(Z_ARRVAL_P(z_params_index), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { 228 | if(Z_TYPE_PP(z_data_pp) == IS_STRING && strncmp(Z_STRVAL_PP(z_data_pp), "1", 1) == 0) { 229 | b_index = 1; 230 | } 231 | } 232 | 233 | /* find autorehash option */ 234 | MAKE_STD_ZVAL(z_params_autorehash); 235 | array_init(z_params_autorehash); 236 | sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.autorehash")), z_params_autorehash TSRMLS_CC); 237 | if (zend_hash_find(Z_ARRVAL_P(z_params_autorehash), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { 238 | if(Z_TYPE_PP(z_data_pp) == IS_STRING && strncmp(Z_STRVAL_PP(z_data_pp), "1", 1) == 0) { 239 | b_autorehash = 1; 240 | } 241 | } 242 | 243 | /* find retry interval option */ 244 | MAKE_STD_ZVAL(z_params_retry_interval); 245 | array_init(z_params_retry_interval); 246 | sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.retryinterval")), z_params_retry_interval TSRMLS_CC); 247 | if (zend_hash_find(Z_ARRVAL_P(z_params_retry_interval), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { 248 | if (Z_TYPE_PP(z_data_pp) == IS_LONG || Z_TYPE_PP(z_data_pp) == IS_STRING) { 249 | if (Z_TYPE_PP(z_data_pp) == IS_LONG) { 250 | l_retry_interval = Z_LVAL_PP(z_data_pp); 251 | } 252 | else { 253 | l_retry_interval = atol(Z_STRVAL_PP(z_data_pp)); 254 | } 255 | } 256 | } 257 | 258 | /* find pconnect option */ 259 | MAKE_STD_ZVAL(z_params_pconnect); 260 | array_init(z_params_pconnect); 261 | sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.pconnect")), z_params_pconnect TSRMLS_CC); 262 | if (zend_hash_find(Z_ARRVAL_P(z_params_pconnect), name, strlen(name) + 1, (void**) &z_data_pp) != FAILURE) { 263 | if(Z_TYPE_PP(z_data_pp) == IS_STRING && strncmp(Z_STRVAL_PP(z_data_pp), "1", 1) == 0) { 264 | b_pconnect = 1; 265 | } 266 | } 267 | 268 | /* find lazy connect option */ 269 | MAKE_STD_ZVAL(z_params_lazy_connect); 270 | array_init(z_params_lazy_connect); 271 | sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.lazyconnect")), z_params_lazy_connect TSRMLS_CC); 272 | if (zend_hash_find(Z_ARRVAL_P(z_params_lazy_connect), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { 273 | if(Z_TYPE_PP(z_data_pp) == IS_STRING && strncmp(Z_STRVAL_PP(z_data_pp), "1", 1) == 0) { 274 | b_lazy_connect = 1; 275 | } 276 | } 277 | 278 | /* find connect timeout option */ 279 | MAKE_STD_ZVAL(z_params_connect_timeout); 280 | array_init(z_params_connect_timeout); 281 | sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.connecttimeout")), z_params_connect_timeout TSRMLS_CC); 282 | if (zend_hash_find(Z_ARRVAL_P(z_params_connect_timeout), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { 283 | if (Z_TYPE_PP(z_data_pp) == IS_DOUBLE || 284 | Z_TYPE_PP(z_data_pp) == IS_STRING || 285 | Z_TYPE_PP(z_data_pp) == IS_LONG) 286 | { 287 | if (Z_TYPE_PP(z_data_pp) == IS_DOUBLE) { 288 | d_connect_timeout = Z_DVAL_PP(z_data_pp); 289 | } else if (Z_TYPE_PP(z_data_pp) == IS_LONG) { 290 | d_connect_timeout = Z_LVAL_PP(z_data_pp); 291 | } else { 292 | d_connect_timeout = atof(Z_STRVAL_PP(z_data_pp)); 293 | } 294 | } 295 | } 296 | 297 | /* create RedisArray object */ 298 | 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); 299 | ra->auto_rehash = b_autorehash; 300 | if(ra->prev) ra->prev->auto_rehash = b_autorehash; 301 | 302 | /* cleanup */ 303 | zval_dtor(z_params_hosts); 304 | efree(z_params_hosts); 305 | zval_dtor(z_params_prev); 306 | efree(z_params_prev); 307 | zval_dtor(z_params_funs); 308 | efree(z_params_funs); 309 | zval_dtor(z_params_index); 310 | efree(z_params_index); 311 | zval_dtor(z_params_autorehash); 312 | efree(z_params_autorehash); 313 | zval_dtor(z_params_retry_interval); 314 | efree(z_params_retry_interval); 315 | zval_dtor(z_params_pconnect); 316 | efree(z_params_pconnect); 317 | zval_dtor(z_params_connect_timeout); 318 | efree(z_params_connect_timeout); 319 | zval_dtor(z_params_lazy_connect); 320 | efree(z_params_lazy_connect); 321 | 322 | return ra; 323 | } 324 | 325 | RedisArray * 326 | 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) { 327 | 328 | int count = zend_hash_num_elements(hosts); 329 | 330 | /* create object */ 331 | RedisArray *ra = emalloc(sizeof(RedisArray)); 332 | ra->hosts = emalloc(count * sizeof(char*)); 333 | ra->redis = emalloc(count * sizeof(zval*)); 334 | ra->count = count; 335 | ra->z_fun = NULL; 336 | ra->z_dist = NULL; 337 | ra->z_multi_exec = NULL; 338 | ra->index = b_index; 339 | ra->auto_rehash = 0; 340 | ra->pconnect = b_pconnect; 341 | ra->connect_timeout = connect_timeout; 342 | 343 | /* init array data structures */ 344 | ra_init_function_table(ra); 345 | 346 | if(NULL == ra_load_hosts(ra, hosts, retry_interval, b_lazy_connect TSRMLS_CC)) { 347 | return NULL; 348 | } 349 | ra->prev = hosts_prev ? ra_make_array(hosts_prev, z_fun, z_dist, NULL, b_index, b_pconnect, retry_interval, b_lazy_connect, connect_timeout TSRMLS_CC) : NULL; 350 | 351 | /* copy function if provided */ 352 | if(z_fun) { 353 | MAKE_STD_ZVAL(ra->z_fun); 354 | *ra->z_fun = *z_fun; 355 | zval_copy_ctor(ra->z_fun); 356 | } 357 | 358 | /* copy distributor if provided */ 359 | if(z_dist) { 360 | MAKE_STD_ZVAL(ra->z_dist); 361 | *ra->z_dist = *z_dist; 362 | zval_copy_ctor(ra->z_dist); 363 | } 364 | 365 | return ra; 366 | } 367 | 368 | 369 | /* call userland key extraction function */ 370 | char * 371 | ra_call_extractor(RedisArray *ra, const char *key, int key_len, int *out_len TSRMLS_DC) { 372 | 373 | char *out; 374 | zval z_ret; 375 | zval *z_argv0; 376 | 377 | /* check that we can call the extractor function */ 378 | if(!zend_is_callable_ex(ra->z_fun, NULL, 0, NULL, NULL, NULL, NULL TSRMLS_CC)) { 379 | php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not call extractor function"); 380 | return NULL; 381 | } 382 | /* convert_to_string(ra->z_fun); */ 383 | 384 | /* call extraction function */ 385 | MAKE_STD_ZVAL(z_argv0); 386 | ZVAL_STRINGL(z_argv0, key, key_len, 0); 387 | call_user_function(EG(function_table), NULL, ra->z_fun, &z_ret, 1, &z_argv0 TSRMLS_CC); 388 | efree(z_argv0); 389 | 390 | if(Z_TYPE(z_ret) != IS_STRING) { 391 | zval_dtor(&z_ret); 392 | return NULL; 393 | } 394 | 395 | *out_len = Z_STRLEN(z_ret); 396 | out = emalloc(*out_len + 1); 397 | out[*out_len] = 0; 398 | memcpy(out, Z_STRVAL(z_ret), *out_len); 399 | 400 | zval_dtor(&z_ret); 401 | return out; 402 | } 403 | 404 | static char * 405 | ra_extract_key(RedisArray *ra, const char *key, int key_len, int *out_len TSRMLS_DC) { 406 | 407 | char *start, *end, *out; 408 | *out_len = key_len; 409 | 410 | if(ra->z_fun) 411 | return ra_call_extractor(ra, key, key_len, out_len TSRMLS_CC); 412 | 413 | /* look for '{' */ 414 | start = strchr(key, '{'); 415 | if(!start) return estrndup(key, key_len); 416 | 417 | /* look for '}' */ 418 | end = strchr(start + 1, '}'); 419 | if(!end) return estrndup(key, key_len); 420 | 421 | /* found substring */ 422 | *out_len = end - start - 1; 423 | out = emalloc(*out_len + 1); 424 | out[*out_len] = 0; 425 | memcpy(out, start+1, *out_len); 426 | 427 | return out; 428 | } 429 | 430 | /* call userland key distributor function */ 431 | zend_bool 432 | ra_call_distributor(RedisArray *ra, const char *key, int key_len, int *pos TSRMLS_DC) { 433 | 434 | zval z_ret; 435 | zval *z_argv0; 436 | 437 | /* check that we can call the extractor function */ 438 | if(!zend_is_callable_ex(ra->z_dist, NULL, 0, NULL, NULL, NULL, NULL TSRMLS_CC)) { 439 | php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not call distributor function"); 440 | return 0; 441 | } 442 | /* convert_to_string(ra->z_fun); */ 443 | 444 | /* call extraction function */ 445 | MAKE_STD_ZVAL(z_argv0); 446 | ZVAL_STRINGL(z_argv0, key, key_len, 0); 447 | call_user_function(EG(function_table), NULL, ra->z_dist, &z_ret, 1, &z_argv0 TSRMLS_CC); 448 | efree(z_argv0); 449 | 450 | if(Z_TYPE(z_ret) != IS_LONG) { 451 | zval_dtor(&z_ret); 452 | return 0; 453 | } 454 | 455 | *pos = Z_LVAL(z_ret); 456 | zval_dtor(&z_ret); 457 | return 1; 458 | } 459 | 460 | zval * 461 | ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos TSRMLS_DC) { 462 | 463 | uint32_t hash; 464 | char *out; 465 | int pos, out_len; 466 | 467 | /* extract relevant part of the key */ 468 | out = ra_extract_key(ra, key, key_len, &out_len TSRMLS_CC); 469 | if(!out) 470 | return NULL; 471 | 472 | if(ra->z_dist) { 473 | if (!ra_call_distributor(ra, key, key_len, &pos TSRMLS_CC)) { 474 | return NULL; 475 | } 476 | } 477 | else { 478 | uint64_t h64; 479 | 480 | /* hash */ 481 | hash = rcrc32(out, out_len); 482 | efree(out); 483 | 484 | /* get position on ring */ 485 | h64 = hash; 486 | h64 *= ra->count; 487 | h64 /= 0xffffffff; 488 | pos = (int)h64; 489 | } 490 | if(out_pos) *out_pos = pos; 491 | 492 | return ra->redis[pos]; 493 | } 494 | 495 | zval * 496 | ra_find_node_by_name(RedisArray *ra, const char *host, int host_len TSRMLS_DC) { 497 | 498 | int i; 499 | for(i = 0; i < ra->count; ++i) { 500 | if(strncmp(ra->hosts[i], host, host_len) == 0) { 501 | return ra->redis[i]; 502 | } 503 | } 504 | return NULL; 505 | } 506 | 507 | 508 | char * 509 | ra_find_key(RedisArray *ra, zval *z_args, const char *cmd, int *key_len) { 510 | 511 | zval **zp_tmp; 512 | int key_pos = 0; /* TODO: change this depending on the command */ 513 | 514 | if( zend_hash_num_elements(Z_ARRVAL_P(z_args)) == 0 515 | || zend_hash_index_find(Z_ARRVAL_P(z_args), key_pos, (void**) &zp_tmp) == FAILURE 516 | || Z_TYPE_PP(zp_tmp) != IS_STRING) { 517 | 518 | return NULL; 519 | } 520 | 521 | *key_len = Z_STRLEN_PP(zp_tmp); 522 | return Z_STRVAL_PP(zp_tmp); 523 | } 524 | 525 | void 526 | ra_index_multi(zval *z_redis, long multi_value TSRMLS_DC) { 527 | 528 | zval z_fun_multi, z_ret; 529 | zval *z_args[1]; 530 | 531 | /* run MULTI */ 532 | ZVAL_STRING(&z_fun_multi, "MULTI", 0); 533 | MAKE_STD_ZVAL(z_args[0]); 534 | ZVAL_LONG(z_args[0], multi_value); 535 | call_user_function(&redis_ce->function_table, &z_redis, &z_fun_multi, &z_ret, 1, z_args TSRMLS_CC); 536 | efree(z_args[0]); 537 | /* zval_dtor(&z_ret); */ 538 | } 539 | 540 | static void 541 | ra_index_change_keys(const char *cmd, zval *z_keys, zval *z_redis TSRMLS_DC) { 542 | 543 | int i, argc; 544 | zval z_fun, z_ret, **z_args; 545 | 546 | /* alloc */ 547 | argc = 1 + zend_hash_num_elements(Z_ARRVAL_P(z_keys)); 548 | z_args = emalloc(argc * sizeof(zval*)); 549 | 550 | /* prepare first parameters */ 551 | ZVAL_STRING(&z_fun, cmd, 0); 552 | MAKE_STD_ZVAL(z_args[0]); 553 | ZVAL_STRING(z_args[0], PHPREDIS_INDEX_NAME, 0); 554 | 555 | /* prepare keys */ 556 | for(i = 0; i < argc - 1; ++i) { 557 | zval **zpp; 558 | zend_hash_index_find(Z_ARRVAL_P(z_keys), i, (void**)&zpp); 559 | z_args[i+1] = *zpp; 560 | } 561 | 562 | /* run cmd */ 563 | call_user_function(&redis_ce->function_table, &z_redis, &z_fun, &z_ret, argc, z_args TSRMLS_CC); 564 | 565 | /* don't dtor z_ret, since we're returning z_redis */ 566 | efree(z_args[0]); /* free index name zval */ 567 | efree(z_args); /* free container */ 568 | } 569 | 570 | void 571 | ra_index_del(zval *z_keys, zval *z_redis TSRMLS_DC) { 572 | ra_index_change_keys("SREM", z_keys, z_redis TSRMLS_CC); 573 | } 574 | 575 | void 576 | ra_index_keys(zval *z_pairs, zval *z_redis TSRMLS_DC) { 577 | 578 | /* Initialize key array */ 579 | zval *z_keys, **z_entry_pp; 580 | HashPosition pos; 581 | MAKE_STD_ZVAL(z_keys); 582 | #if PHP_VERSION_ID > 50300 583 | array_init_size(z_keys, zend_hash_num_elements(Z_ARRVAL_P(z_pairs))); 584 | #else 585 | array_init(z_keys); 586 | #endif 587 | 588 | /* Go through input array and add values to the key array */ 589 | zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(z_pairs), &pos); 590 | while (zend_hash_get_current_data_ex(Z_ARRVAL_P(z_pairs), (void **)&z_entry_pp, &pos) == SUCCESS) { 591 | char *key; 592 | unsigned int key_len; 593 | unsigned long num_key; 594 | zval *z_new; 595 | MAKE_STD_ZVAL(z_new); 596 | 597 | switch (zend_hash_get_current_key_ex(Z_ARRVAL_P(z_pairs), &key, &key_len, &num_key, 1, &pos)) { 598 | case HASH_KEY_IS_STRING: 599 | ZVAL_STRINGL(z_new, key, (int)key_len - 1, 0); 600 | zend_hash_next_index_insert(Z_ARRVAL_P(z_keys), &z_new, sizeof(zval *), NULL); 601 | break; 602 | 603 | case HASH_KEY_IS_LONG: 604 | Z_TYPE_P(z_new) = IS_LONG; 605 | Z_LVAL_P(z_new) = (long)num_key; 606 | zend_hash_next_index_insert(Z_ARRVAL_P(z_keys), &z_new, sizeof(zval *), NULL); 607 | break; 608 | } 609 | zend_hash_move_forward_ex(Z_ARRVAL_P(z_pairs), &pos); 610 | } 611 | 612 | /* add keys to index */ 613 | ra_index_change_keys("SADD", z_keys, z_redis TSRMLS_CC); 614 | 615 | /* cleanup */ 616 | zval_dtor(z_keys); 617 | efree(z_keys); 618 | } 619 | 620 | void 621 | ra_index_key(const char *key, int key_len, zval *z_redis TSRMLS_DC) { 622 | 623 | zval z_fun_sadd, z_ret, *z_args[2]; 624 | MAKE_STD_ZVAL(z_args[0]); 625 | MAKE_STD_ZVAL(z_args[1]); 626 | 627 | /* prepare args */ 628 | ZVAL_STRINGL(&z_fun_sadd, "SADD", 4, 0); 629 | 630 | ZVAL_STRING(z_args[0], PHPREDIS_INDEX_NAME, 0); 631 | ZVAL_STRINGL(z_args[1], key, key_len, 1); 632 | 633 | /* run SADD */ 634 | call_user_function(&redis_ce->function_table, &z_redis, &z_fun_sadd, &z_ret, 2, z_args TSRMLS_CC); 635 | 636 | /* don't dtor z_ret, since we're returning z_redis */ 637 | efree(z_args[0]); 638 | zval_dtor(z_args[1]); 639 | efree(z_args[1]); 640 | } 641 | 642 | void 643 | ra_index_exec(zval *z_redis, zval *return_value, int keep_all TSRMLS_DC) { 644 | 645 | zval z_fun_exec, z_ret, **zp_tmp; 646 | 647 | /* run EXEC */ 648 | ZVAL_STRING(&z_fun_exec, "EXEC", 0); 649 | call_user_function(&redis_ce->function_table, &z_redis, &z_fun_exec, &z_ret, 0, NULL TSRMLS_CC); 650 | 651 | 652 | /* extract first element of exec array and put into return_value. */ 653 | if(Z_TYPE(z_ret) == IS_ARRAY) { 654 | if(return_value) { 655 | if(keep_all) { 656 | *return_value = z_ret; 657 | zval_copy_ctor(return_value); 658 | } else if(zend_hash_index_find(Z_ARRVAL(z_ret), 0, (void**)&zp_tmp) != FAILURE) { 659 | *return_value = **zp_tmp; 660 | zval_copy_ctor(return_value); 661 | } 662 | } 663 | zval_dtor(&z_ret); 664 | } 665 | 666 | /* zval *zptr = &z_ret; */ 667 | /* php_var_dump(&zptr, 0 TSRMLS_CC); */ 668 | } 669 | 670 | void 671 | ra_index_discard(zval *z_redis, zval *return_value TSRMLS_DC) { 672 | 673 | zval z_fun_discard, z_ret; 674 | 675 | /* run DISCARD */ 676 | ZVAL_STRING(&z_fun_discard, "DISCARD", 0); 677 | call_user_function(&redis_ce->function_table, &z_redis, &z_fun_discard, &z_ret, 0, NULL TSRMLS_CC); 678 | 679 | zval_dtor(&z_ret); 680 | } 681 | 682 | void 683 | ra_index_unwatch(zval *z_redis, zval *return_value TSRMLS_DC) { 684 | 685 | zval z_fun_unwatch, z_ret; 686 | 687 | /* run UNWATCH */ 688 | ZVAL_STRING(&z_fun_unwatch, "UNWATCH", 0); 689 | call_user_function(&redis_ce->function_table, &z_redis, &z_fun_unwatch, &z_ret, 0, NULL TSRMLS_CC); 690 | 691 | zval_dtor(&z_ret); 692 | } 693 | 694 | zend_bool 695 | ra_is_write_cmd(RedisArray *ra, const char *cmd, int cmd_len) { 696 | 697 | zend_bool ret; 698 | int i; 699 | char *cmd_up = emalloc(1 + cmd_len); 700 | /* convert to uppercase */ 701 | for(i = 0; i < cmd_len; ++i) 702 | cmd_up[i] = toupper(cmd[i]); 703 | cmd_up[cmd_len] = 0; 704 | 705 | ret = zend_hash_exists(Z_ARRVAL_P(ra->z_pure_cmds), cmd_up, cmd_len+1); 706 | 707 | efree(cmd_up); 708 | return !ret; 709 | } 710 | 711 | /* list keys from array index */ 712 | static long 713 | ra_rehash_scan(zval *z_redis, char ***keys, int **key_lens, const char *cmd, const char *arg TSRMLS_DC) { 714 | 715 | long count, i; 716 | zval z_fun_smembers, z_ret, *z_arg, **z_data_pp; 717 | HashTable *h_keys; 718 | HashPosition pointer; 719 | char *key; 720 | int key_len; 721 | 722 | /* arg */ 723 | MAKE_STD_ZVAL(z_arg); 724 | ZVAL_STRING(z_arg, arg, 0); 725 | 726 | /* run SMEMBERS */ 727 | ZVAL_STRING(&z_fun_smembers, cmd, 0); 728 | call_user_function(&redis_ce->function_table, &z_redis, &z_fun_smembers, &z_ret, 1, &z_arg TSRMLS_CC); 729 | efree(z_arg); 730 | if(Z_TYPE(z_ret) != IS_ARRAY) { /* failure */ 731 | return -1; /* TODO: log error. */ 732 | } 733 | h_keys = Z_ARRVAL(z_ret); 734 | 735 | /* allocate key array */ 736 | count = zend_hash_num_elements(h_keys); 737 | *keys = emalloc(count * sizeof(char*)); 738 | *key_lens = emalloc(count * sizeof(int)); 739 | 740 | for (i = 0, zend_hash_internal_pointer_reset_ex(h_keys, &pointer); 741 | zend_hash_get_current_data_ex(h_keys, (void**) &z_data_pp, &pointer) == SUCCESS; 742 | zend_hash_move_forward_ex(h_keys, &pointer), ++i) { 743 | 744 | key = Z_STRVAL_PP(z_data_pp); 745 | key_len = Z_STRLEN_PP(z_data_pp); 746 | 747 | /* copy key and length */ 748 | (*keys)[i] = emalloc(1 + key_len); 749 | memcpy((*keys)[i], key, key_len); 750 | (*key_lens)[i] = key_len; 751 | (*keys)[i][key_len] = 0; /* null-terminate string */ 752 | } 753 | 754 | /* cleanup */ 755 | zval_dtor(&z_ret); 756 | 757 | return count; 758 | } 759 | 760 | static long 761 | ra_rehash_scan_index(zval *z_redis, char ***keys, int **key_lens TSRMLS_DC) { 762 | return ra_rehash_scan(z_redis, keys, key_lens, "SMEMBERS", PHPREDIS_INDEX_NAME TSRMLS_CC); 763 | } 764 | 765 | /* list keys using KEYS command */ 766 | static long 767 | ra_rehash_scan_keys(zval *z_redis, char ***keys, int **key_lens TSRMLS_DC) { 768 | return ra_rehash_scan(z_redis, keys, key_lens, "KEYS", "*" TSRMLS_CC); 769 | } 770 | 771 | /* run TYPE to find the type */ 772 | static zend_bool 773 | ra_get_key_type(zval *z_redis, const char *key, int key_len, zval *z_from, long *res TSRMLS_DC) { 774 | 775 | int i; 776 | zval z_fun_type, z_ret, *z_arg; 777 | zval **z_data; 778 | long success = 1; 779 | 780 | MAKE_STD_ZVAL(z_arg); 781 | /* Pipelined */ 782 | ra_index_multi(z_from, PIPELINE TSRMLS_CC); 783 | 784 | /* prepare args */ 785 | ZVAL_STRINGL(&z_fun_type, "TYPE", 4, 0); 786 | ZVAL_STRINGL(z_arg, key, key_len, 0); 787 | /* run TYPE */ 788 | call_user_function(&redis_ce->function_table, &z_redis, &z_fun_type, &z_ret, 1, &z_arg TSRMLS_CC); 789 | 790 | ZVAL_STRINGL(&z_fun_type, "TTL", 3, 0); 791 | ZVAL_STRINGL(z_arg, key, key_len, 0); 792 | /* run TYPE */ 793 | call_user_function(&redis_ce->function_table, &z_redis, &z_fun_type, &z_ret, 1, &z_arg TSRMLS_CC); 794 | 795 | /* cleanup */ 796 | efree(z_arg); 797 | 798 | /* Get the result from the pipeline. */ 799 | ra_index_exec(z_from, &z_ret, 1 TSRMLS_CC); 800 | if(Z_TYPE(z_ret) == IS_ARRAY) { 801 | HashTable *retHash = Z_ARRVAL(z_ret); 802 | for(i = 0, zend_hash_internal_pointer_reset(retHash); 803 | zend_hash_has_more_elements(retHash) == SUCCESS; 804 | zend_hash_move_forward(retHash)) { 805 | 806 | if(zend_hash_get_current_data(retHash, (void**)&z_data) == FAILURE) { 807 | success = 0; 808 | break; 809 | } 810 | if(Z_TYPE_PP(z_data) != IS_LONG) { 811 | success = 0; 812 | break; 813 | } 814 | /* Get the result - Might change in the future to handle doubles as well */ 815 | res[i] = Z_LVAL_PP(z_data); 816 | i++; 817 | } 818 | } 819 | zval_dtor(&z_ret); 820 | return success; 821 | } 822 | 823 | /* delete key from source server index during rehashing */ 824 | static void 825 | ra_remove_from_index(zval *z_redis, const char *key, int key_len TSRMLS_DC) { 826 | 827 | zval z_fun_srem, z_ret, *z_args[2]; 828 | 829 | /* run SREM on source index */ 830 | ZVAL_STRINGL(&z_fun_srem, "SREM", 4, 0); 831 | MAKE_STD_ZVAL(z_args[0]); 832 | ZVAL_STRING(z_args[0], PHPREDIS_INDEX_NAME, 0); 833 | MAKE_STD_ZVAL(z_args[1]); 834 | ZVAL_STRINGL(z_args[1], key, key_len, 0); 835 | 836 | call_user_function(&redis_ce->function_table, &z_redis, &z_fun_srem, &z_ret, 2, z_args TSRMLS_CC); 837 | 838 | /* cleanup */ 839 | efree(z_args[0]); 840 | efree(z_args[1]); 841 | } 842 | 843 | 844 | /* delete key from source server during rehashing */ 845 | static zend_bool 846 | ra_del_key(const char *key, int key_len, zval *z_from TSRMLS_DC) { 847 | 848 | zval z_fun_del, z_ret, *z_args; 849 | 850 | /* in a transaction */ 851 | ra_index_multi(z_from, MULTI TSRMLS_CC); 852 | 853 | /* run DEL on source */ 854 | MAKE_STD_ZVAL(z_args); 855 | ZVAL_STRINGL(&z_fun_del, "DEL", 3, 0); 856 | ZVAL_STRINGL(z_args, key, key_len, 0); 857 | call_user_function(&redis_ce->function_table, &z_from, &z_fun_del, &z_ret, 1, &z_args TSRMLS_CC); 858 | efree(z_args); 859 | 860 | /* remove key from index */ 861 | ra_remove_from_index(z_from, key, key_len TSRMLS_CC); 862 | 863 | /* close transaction */ 864 | ra_index_exec(z_from, NULL, 0 TSRMLS_CC); 865 | 866 | return 1; 867 | } 868 | 869 | static zend_bool 870 | ra_expire_key(const char *key, int key_len, zval *z_to, long ttl TSRMLS_DC) { 871 | 872 | zval z_fun_expire, z_ret, *z_args[2]; 873 | 874 | if (ttl > 0) 875 | { 876 | /* run EXPIRE on target */ 877 | MAKE_STD_ZVAL(z_args[0]); 878 | MAKE_STD_ZVAL(z_args[1]); 879 | ZVAL_STRINGL(&z_fun_expire, "EXPIRE", 6, 0); 880 | ZVAL_STRINGL(z_args[0], key, key_len, 0); 881 | ZVAL_LONG(z_args[1], ttl); 882 | call_user_function(&redis_ce->function_table, &z_to, &z_fun_expire, &z_ret, 2, z_args TSRMLS_CC); 883 | /* cleanup */ 884 | efree(z_args[0]); 885 | efree(z_args[1]); 886 | } 887 | 888 | return 1; 889 | } 890 | 891 | static zend_bool 892 | ra_move_zset(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) { 893 | 894 | zval z_fun_zrange, z_fun_zadd, z_ret, *z_args[4], **z_zadd_args, **z_score_pp; 895 | int count; 896 | HashTable *h_zset_vals; 897 | char *val; 898 | unsigned int val_len; 899 | int i; 900 | unsigned long idx; 901 | 902 | /* run ZRANGE key 0 -1 WITHSCORES on source */ 903 | ZVAL_STRINGL(&z_fun_zrange, "ZRANGE", 6, 0); 904 | for(i = 0; i < 4; ++i) { 905 | MAKE_STD_ZVAL(z_args[i]); 906 | } 907 | ZVAL_STRINGL(z_args[0], key, key_len, 0); 908 | ZVAL_STRINGL(z_args[1], "0", 1, 0); 909 | ZVAL_STRINGL(z_args[2], "-1", 2, 0); 910 | ZVAL_BOOL(z_args[3], 1); 911 | call_user_function(&redis_ce->function_table, &z_from, &z_fun_zrange, &z_ret, 4, z_args TSRMLS_CC); 912 | 913 | /* cleanup zrange args */ 914 | for(i = 0; i < 4; ++i) { 915 | efree(z_args[i]); /* FIXME */ 916 | } 917 | 918 | if(Z_TYPE(z_ret) != IS_ARRAY) { /* key not found or replaced */ 919 | /* TODO: report? */ 920 | return 0; 921 | } 922 | 923 | /* we now have an array of value → score pairs in z_ret. */ 924 | h_zset_vals = Z_ARRVAL(z_ret); 925 | 926 | /* allocate argument array for ZADD */ 927 | count = zend_hash_num_elements(h_zset_vals); 928 | z_zadd_args = emalloc((1 + 2*count) * sizeof(zval*)); 929 | 930 | for(i = 1, zend_hash_internal_pointer_reset(h_zset_vals); 931 | zend_hash_has_more_elements(h_zset_vals) == SUCCESS; 932 | zend_hash_move_forward(h_zset_vals)) { 933 | 934 | if(zend_hash_get_current_data(h_zset_vals, (void**)&z_score_pp) == FAILURE) { 935 | continue; 936 | } 937 | 938 | /* add score */ 939 | convert_to_double(*z_score_pp); 940 | MAKE_STD_ZVAL(z_zadd_args[i]); 941 | ZVAL_DOUBLE(z_zadd_args[i], Z_DVAL_PP(z_score_pp)); 942 | 943 | /* add value */ 944 | MAKE_STD_ZVAL(z_zadd_args[i+1]); 945 | switch (zend_hash_get_current_key_ex(h_zset_vals, &val, &val_len, &idx, 0, NULL)) { 946 | case HASH_KEY_IS_STRING: 947 | ZVAL_STRINGL(z_zadd_args[i+1], val, (int)val_len-1, 0); /* we have to remove 1 because it is an array key. */ 948 | break; 949 | case HASH_KEY_IS_LONG: 950 | ZVAL_LONG(z_zadd_args[i+1], (long)idx); 951 | break; 952 | default: 953 | return -1; /* Todo: log error */ 954 | break; 955 | } 956 | i += 2; 957 | } 958 | 959 | /* run ZADD on target */ 960 | ZVAL_STRINGL(&z_fun_zadd, "ZADD", 4, 0); 961 | MAKE_STD_ZVAL(z_zadd_args[0]); 962 | ZVAL_STRINGL(z_zadd_args[0], key, key_len, 0); 963 | call_user_function(&redis_ce->function_table, &z_to, &z_fun_zadd, &z_ret, 1 + 2 * count, z_zadd_args TSRMLS_CC); 964 | 965 | /* Expire if needed */ 966 | ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC); 967 | 968 | /* cleanup */ 969 | for(i = 0; i < 1 + 2 * count; ++i) { 970 | efree(z_zadd_args[i]); 971 | } 972 | 973 | return 1; 974 | } 975 | 976 | static zend_bool 977 | ra_move_string(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) { 978 | 979 | zval z_fun_get, z_fun_set, z_ret, *z_args[3]; 980 | 981 | /* run GET on source */ 982 | MAKE_STD_ZVAL(z_args[0]); 983 | ZVAL_STRINGL(&z_fun_get, "GET", 3, 0); 984 | ZVAL_STRINGL(z_args[0], key, key_len, 0); 985 | call_user_function(&redis_ce->function_table, &z_from, &z_fun_get, &z_ret, 1, z_args TSRMLS_CC); 986 | 987 | if(Z_TYPE(z_ret) != IS_STRING) { /* key not found or replaced */ 988 | /* TODO: report? */ 989 | efree(z_args[0]); 990 | return 0; 991 | } 992 | 993 | /* run SET on target */ 994 | MAKE_STD_ZVAL(z_args[1]); 995 | if (ttl > 0) { 996 | MAKE_STD_ZVAL(z_args[2]); 997 | ZVAL_STRINGL(&z_fun_set, "SETEX", 5, 0); 998 | ZVAL_STRINGL(z_args[0], key, key_len, 0); 999 | ZVAL_LONG(z_args[1], ttl); 1000 | ZVAL_STRINGL(z_args[2], Z_STRVAL(z_ret), Z_STRLEN(z_ret), 1); /* copy z_ret to arg 1 */ 1001 | zval_dtor(&z_ret); /* free memory from our previous call */ 1002 | call_user_function(&redis_ce->function_table, &z_to, &z_fun_set, &z_ret, 3, z_args TSRMLS_CC); 1003 | /* cleanup */ 1004 | efree(z_args[1]); 1005 | zval_dtor(z_args[2]); 1006 | efree(z_args[2]); 1007 | } 1008 | else { 1009 | ZVAL_STRINGL(&z_fun_set, "SET", 3, 0); 1010 | ZVAL_STRINGL(z_args[0], key, key_len, 0); 1011 | ZVAL_STRINGL(z_args[1], Z_STRVAL(z_ret), Z_STRLEN(z_ret), 1); /* copy z_ret to arg 1 */ 1012 | zval_dtor(&z_ret); /* free memory from our previous return value */ 1013 | call_user_function(&redis_ce->function_table, &z_to, &z_fun_set, &z_ret, 2, z_args TSRMLS_CC); 1014 | /* cleanup */ 1015 | zval_dtor(z_args[1]); 1016 | efree(z_args[1]); 1017 | } 1018 | 1019 | /* cleanup */ 1020 | efree(z_args[0]); 1021 | 1022 | return 1; 1023 | } 1024 | 1025 | static zend_bool 1026 | ra_move_hash(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) { 1027 | 1028 | zval z_fun_hgetall, z_fun_hmset, z_ret, *z_args[2]; 1029 | 1030 | /* run HGETALL on source */ 1031 | MAKE_STD_ZVAL(z_args[0]); 1032 | ZVAL_STRINGL(&z_fun_hgetall, "HGETALL", 7, 0); 1033 | ZVAL_STRINGL(z_args[0], key, key_len, 0); 1034 | call_user_function(&redis_ce->function_table, &z_from, &z_fun_hgetall, &z_ret, 1, z_args TSRMLS_CC); 1035 | 1036 | if(Z_TYPE(z_ret) != IS_ARRAY) { /* key not found or replaced */ 1037 | /* TODO: report? */ 1038 | efree(z_args[0]); 1039 | return 0; 1040 | } 1041 | 1042 | /* run HMSET on target */ 1043 | ZVAL_STRINGL(&z_fun_hmset, "HMSET", 5, 0); 1044 | ZVAL_STRINGL(z_args[0], key, key_len, 0); 1045 | z_args[1] = &z_ret; /* copy z_ret to arg 1 */ 1046 | call_user_function(&redis_ce->function_table, &z_to, &z_fun_hmset, &z_ret, 2, z_args TSRMLS_CC); 1047 | 1048 | /* Expire if needed */ 1049 | ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC); 1050 | 1051 | /* cleanup */ 1052 | efree(z_args[0]); 1053 | 1054 | return 1; 1055 | } 1056 | 1057 | static zend_bool 1058 | ra_move_collection(const char *key, int key_len, zval *z_from, zval *z_to, 1059 | int list_count, const char **cmd_list, 1060 | int add_count, const char **cmd_add, long ttl TSRMLS_DC) { 1061 | 1062 | zval z_fun_retrieve, z_fun_sadd, z_ret, **z_retrieve_args, **z_sadd_args, **z_data_pp; 1063 | int count, i; 1064 | HashTable *h_set_vals; 1065 | 1066 | /* run retrieval command on source */ 1067 | z_retrieve_args = emalloc((1+list_count) * sizeof(zval*)); 1068 | ZVAL_STRING(&z_fun_retrieve, cmd_list[0], 0); /* set the command */ 1069 | 1070 | /* set the key */ 1071 | MAKE_STD_ZVAL(z_retrieve_args[0]); 1072 | ZVAL_STRINGL(z_retrieve_args[0], key, key_len, 0); 1073 | 1074 | /* possibly add some other args if they were provided. */ 1075 | for(i = 1; i < list_count; ++i) { 1076 | MAKE_STD_ZVAL(z_retrieve_args[i]); 1077 | ZVAL_STRING(z_retrieve_args[i], cmd_list[i], 0); 1078 | } 1079 | 1080 | call_user_function(&redis_ce->function_table, &z_from, &z_fun_retrieve, &z_ret, list_count, z_retrieve_args TSRMLS_CC); 1081 | 1082 | /* cleanup */ 1083 | for(i = 0; i < list_count; ++i) { 1084 | efree(z_retrieve_args[i]); 1085 | } 1086 | efree(z_retrieve_args); 1087 | 1088 | if(Z_TYPE(z_ret) != IS_ARRAY) { /* key not found or replaced */ 1089 | /* TODO: report? */ 1090 | return 0; 1091 | } 1092 | 1093 | /* run SADD/RPUSH on target */ 1094 | h_set_vals = Z_ARRVAL(z_ret); 1095 | count = zend_hash_num_elements(h_set_vals); 1096 | z_sadd_args = emalloc((1 + count) * sizeof(zval*)); 1097 | ZVAL_STRING(&z_fun_sadd, cmd_add[0], 0); 1098 | MAKE_STD_ZVAL(z_sadd_args[0]); /* add key */ 1099 | ZVAL_STRINGL(z_sadd_args[0], key, key_len, 0); 1100 | 1101 | for(i = 0, zend_hash_internal_pointer_reset(h_set_vals); 1102 | zend_hash_has_more_elements(h_set_vals) == SUCCESS; 1103 | zend_hash_move_forward(h_set_vals), i++) { 1104 | 1105 | if(zend_hash_get_current_data(h_set_vals, (void**)&z_data_pp) == FAILURE) { 1106 | continue; 1107 | } 1108 | 1109 | /* add set elements */ 1110 | MAKE_STD_ZVAL(z_sadd_args[i+1]); 1111 | *(z_sadd_args[i+1]) = **z_data_pp; 1112 | zval_copy_ctor(z_sadd_args[i+1]); 1113 | } 1114 | call_user_function(&redis_ce->function_table, &z_to, &z_fun_sadd, &z_ret, count+1, z_sadd_args TSRMLS_CC); 1115 | 1116 | /* Expire if needed */ 1117 | ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC); 1118 | 1119 | /* cleanup */ 1120 | efree(z_sadd_args[0]); /* no dtor at [0] */ 1121 | 1122 | for(i = 0; i < count; ++i) { 1123 | zval_dtor(z_sadd_args[i + 1]); 1124 | efree(z_sadd_args[i + 1]); 1125 | } 1126 | efree(z_sadd_args); 1127 | 1128 | return 1; 1129 | } 1130 | 1131 | static zend_bool 1132 | ra_move_set(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) { 1133 | 1134 | const char *cmd_list[] = {"SMEMBERS"}; 1135 | const char *cmd_add[] = {"SADD"}; 1136 | return ra_move_collection(key, key_len, z_from, z_to, 1, cmd_list, 1, cmd_add, ttl TSRMLS_CC); 1137 | } 1138 | 1139 | static zend_bool 1140 | ra_move_list(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) { 1141 | 1142 | const char *cmd_list[] = {"LRANGE", "0", "-1"}; 1143 | const char *cmd_add[] = {"RPUSH"}; 1144 | return ra_move_collection(key, key_len, z_from, z_to, 3, cmd_list, 1, cmd_add, ttl TSRMLS_CC); 1145 | } 1146 | 1147 | void 1148 | ra_move_key(const char *key, int key_len, zval *z_from, zval *z_to TSRMLS_DC) { 1149 | 1150 | long res[2], type, ttl; 1151 | zend_bool success = 0; 1152 | if (ra_get_key_type(z_from, key, key_len, z_from, res TSRMLS_CC)) { 1153 | type = res[0]; 1154 | ttl = res[1]; 1155 | /* open transaction on target server */ 1156 | ra_index_multi(z_to, MULTI TSRMLS_CC); 1157 | switch(type) { 1158 | case REDIS_STRING: 1159 | success = ra_move_string(key, key_len, z_from, z_to, ttl TSRMLS_CC); 1160 | break; 1161 | 1162 | case REDIS_SET: 1163 | success = ra_move_set(key, key_len, z_from, z_to, ttl TSRMLS_CC); 1164 | break; 1165 | 1166 | case REDIS_LIST: 1167 | success = ra_move_list(key, key_len, z_from, z_to, ttl TSRMLS_CC); 1168 | break; 1169 | 1170 | case REDIS_ZSET: 1171 | success = ra_move_zset(key, key_len, z_from, z_to, ttl TSRMLS_CC); 1172 | break; 1173 | 1174 | case REDIS_HASH: 1175 | success = ra_move_hash(key, key_len, z_from, z_to, ttl TSRMLS_CC); 1176 | break; 1177 | 1178 | default: 1179 | /* TODO: report? */ 1180 | break; 1181 | } 1182 | } 1183 | 1184 | if(success) { 1185 | ra_del_key(key, key_len, z_from TSRMLS_CC); 1186 | ra_index_key(key, key_len, z_to TSRMLS_CC); 1187 | } 1188 | 1189 | /* close transaction */ 1190 | ra_index_exec(z_to, NULL, 0 TSRMLS_CC); 1191 | } 1192 | 1193 | /* callback with the current progress, with hostname and count */ 1194 | static void zval_rehash_callback(zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache, 1195 | const char *hostname, long count TSRMLS_DC) { 1196 | 1197 | zval *z_ret = NULL, **z_args[2]; 1198 | zval *z_host, *z_count; 1199 | 1200 | z_cb->retval_ptr_ptr = &z_ret; 1201 | z_cb->params = (struct _zval_struct ***)&z_args; 1202 | z_cb->param_count = 2; 1203 | z_cb->no_separation = 0; 1204 | 1205 | /* run cb(hostname, count) */ 1206 | MAKE_STD_ZVAL(z_host); 1207 | ZVAL_STRING(z_host, hostname, 0); 1208 | z_args[0] = &z_host; 1209 | MAKE_STD_ZVAL(z_count); 1210 | ZVAL_LONG(z_count, count); 1211 | z_args[1] = &z_count; 1212 | 1213 | zend_call_function(z_cb, z_cb_cache TSRMLS_CC); 1214 | 1215 | /* cleanup */ 1216 | efree(z_host); 1217 | efree(z_count); 1218 | if(z_ret) 1219 | efree(z_ret); 1220 | } 1221 | 1222 | static void 1223 | ra_rehash_server(RedisArray *ra, zval *z_redis, const char *hostname, zend_bool b_index, 1224 | zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache TSRMLS_DC) { 1225 | 1226 | char **keys; 1227 | int *key_lens; 1228 | long count, i; 1229 | int target_pos; 1230 | zval *z_target; 1231 | 1232 | /* list all keys */ 1233 | if(b_index) { 1234 | count = ra_rehash_scan_index(z_redis, &keys, &key_lens TSRMLS_CC); 1235 | } else { 1236 | count = ra_rehash_scan_keys(z_redis, &keys, &key_lens TSRMLS_CC); 1237 | } 1238 | 1239 | /* callback */ 1240 | if(z_cb && z_cb_cache) { 1241 | zval_rehash_callback(z_cb, z_cb_cache, hostname, count TSRMLS_CC); 1242 | } 1243 | 1244 | /* for each key, redistribute */ 1245 | for(i = 0; i < count; ++i) { 1246 | 1247 | /* check that we're not moving to the same node. */ 1248 | z_target = ra_find_node(ra, keys[i], key_lens[i], &target_pos TSRMLS_CC); 1249 | 1250 | if(strcmp(hostname, ra->hosts[target_pos])) { /* different host */ 1251 | /* php_printf("move [%s] from [%s] to [%s]\n", keys[i], hostname, ra->hosts[target_pos]); */ 1252 | ra_move_key(keys[i], key_lens[i], z_redis, z_target TSRMLS_CC); 1253 | } 1254 | } 1255 | 1256 | /* cleanup */ 1257 | for(i = 0; i < count; ++i) { 1258 | efree(keys[i]); 1259 | } 1260 | efree(keys); 1261 | efree(key_lens); 1262 | } 1263 | 1264 | void 1265 | ra_rehash(RedisArray *ra, zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache TSRMLS_DC) { 1266 | 1267 | int i; 1268 | 1269 | /* redistribute the data, server by server. */ 1270 | if(!ra->prev) 1271 | return; /* TODO: compare the two rings for equality */ 1272 | 1273 | for(i = 0; i < ra->prev->count; ++i) { 1274 | ra_rehash_server(ra, ra->prev->redis[i], ra->prev->hosts[i], ra->index, z_cb, z_cb_cache TSRMLS_CC); 1275 | } 1276 | } 1277 | 1278 | -------------------------------------------------------------------------------- /redis_array.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | PHP Version 5 | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 1997-2009 The PHP Group | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: Nicolas Favre-Felix | 16 | | Maintainer: Michael Grunder | 17 | +----------------------------------------------------------------------+ 18 | */ 19 | 20 | #ifdef HAVE_CONFIG_H 21 | #include "config.h" 22 | #endif 23 | 24 | #include "common.h" 25 | #include "ext/standard/info.h" 26 | #include "php_ini.h" 27 | #include "php_redis.h" 28 | #include "redis_array.h" 29 | #include 30 | 31 | #include "library.h" 32 | #include "redis_array.h" 33 | #include "redis_array_impl.h" 34 | 35 | /* Simple macro to detect failure in a RedisArray call */ 36 | #define RA_CALL_FAILED(rv, cmd) \ 37 | ((Z_TYPE_P(rv) == IS_BOOL && Z_BVAL_P(rv) == 0) || \ 38 | (Z_TYPE_P(rv) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(rv)) == 0) || \ 39 | (Z_TYPE_P(rv) == IS_LONG && Z_LVAL_P(rv) == 0 && !strcasecmp(cmd, "TYPE"))) \ 40 | 41 | extern zend_class_entry *redis_ce; 42 | zend_class_entry *redis_array_ce; 43 | 44 | ZEND_BEGIN_ARG_INFO_EX(__redis_array_call_args, 0, 0, 2) 45 | ZEND_ARG_INFO(0, function_name) 46 | ZEND_ARG_INFO(0, arguments) 47 | ZEND_END_ARG_INFO() 48 | 49 | zend_function_entry redis_array_functions[] = { 50 | PHP_ME(RedisArray, __construct, NULL, ZEND_ACC_PUBLIC) 51 | PHP_ME(RedisArray, __call, __redis_array_call_args, ZEND_ACC_PUBLIC) 52 | 53 | PHP_ME(RedisArray, _hosts, NULL, ZEND_ACC_PUBLIC) 54 | PHP_ME(RedisArray, _target, NULL, ZEND_ACC_PUBLIC) 55 | PHP_ME(RedisArray, _instance, NULL, ZEND_ACC_PUBLIC) 56 | PHP_ME(RedisArray, _function, NULL, ZEND_ACC_PUBLIC) 57 | PHP_ME(RedisArray, _distributor, NULL, ZEND_ACC_PUBLIC) 58 | PHP_ME(RedisArray, _rehash, NULL, ZEND_ACC_PUBLIC) 59 | 60 | /* special implementation for a few functions */ 61 | PHP_ME(RedisArray, select, NULL, ZEND_ACC_PUBLIC) 62 | PHP_ME(RedisArray, info, NULL, ZEND_ACC_PUBLIC) 63 | PHP_ME(RedisArray, ping, NULL, ZEND_ACC_PUBLIC) 64 | PHP_ME(RedisArray, flushdb, NULL, ZEND_ACC_PUBLIC) 65 | PHP_ME(RedisArray, flushall, NULL, ZEND_ACC_PUBLIC) 66 | PHP_ME(RedisArray, mget, NULL, ZEND_ACC_PUBLIC) 67 | PHP_ME(RedisArray, mset, NULL, ZEND_ACC_PUBLIC) 68 | PHP_ME(RedisArray, del, NULL, ZEND_ACC_PUBLIC) 69 | PHP_ME(RedisArray, getOption, NULL, ZEND_ACC_PUBLIC) 70 | PHP_ME(RedisArray, setOption, NULL, ZEND_ACC_PUBLIC) 71 | PHP_ME(RedisArray, keys, NULL, ZEND_ACC_PUBLIC) 72 | PHP_ME(RedisArray, save, NULL, ZEND_ACC_PUBLIC) 73 | PHP_ME(RedisArray, bgsave, NULL, ZEND_ACC_PUBLIC) 74 | 75 | /* Multi/Exec */ 76 | PHP_ME(RedisArray, multi, NULL, ZEND_ACC_PUBLIC) 77 | PHP_ME(RedisArray, exec, NULL, ZEND_ACC_PUBLIC) 78 | PHP_ME(RedisArray, discard, NULL, ZEND_ACC_PUBLIC) 79 | PHP_ME(RedisArray, unwatch, NULL, ZEND_ACC_PUBLIC) 80 | 81 | /* Aliases */ 82 | PHP_MALIAS(RedisArray, delete, del, NULL, ZEND_ACC_PUBLIC) 83 | PHP_MALIAS(RedisArray, getMultiple, mget, NULL, ZEND_ACC_PUBLIC) 84 | {NULL, NULL, NULL} 85 | }; 86 | 87 | static void redis_array_free(RedisArray *ra) { 88 | int i; 89 | 90 | /* Redis objects */ 91 | for(i=0;icount;i++) { 92 | zval_dtor(ra->redis[i]); 93 | efree(ra->redis[i]); 94 | efree(ra->hosts[i]); 95 | } 96 | efree(ra->redis); 97 | efree(ra->hosts); 98 | 99 | /* delete hash function */ 100 | if(ra->z_fun) { 101 | zval_dtor(ra->z_fun); 102 | efree(ra->z_fun); 103 | } 104 | 105 | /* Distributor */ 106 | if(ra->z_dist) { 107 | zval_dtor(ra->z_dist); 108 | efree(ra->z_dist); 109 | } 110 | 111 | /* Delete pur commands */ 112 | zval_dtor(ra->z_pure_cmds); 113 | efree(ra->z_pure_cmds); 114 | 115 | /* Free structure itself */ 116 | efree(ra); 117 | } 118 | 119 | int le_redis_array; 120 | void redis_destructor_redis_array(zend_rsrc_list_entry * rsrc TSRMLS_DC) 121 | { 122 | RedisArray *ra = (RedisArray*)rsrc->ptr; 123 | 124 | /* Free previous ring if it's set */ 125 | if(ra->prev) redis_array_free(ra->prev); 126 | 127 | /* Free parent array */ 128 | redis_array_free(ra); 129 | } 130 | 131 | 132 | /** 133 | * redis_array_get 134 | */ 135 | PHP_REDIS_API int redis_array_get(zval *id, RedisArray **ra TSRMLS_DC) 136 | { 137 | 138 | zval **socket; 139 | int resource_type; 140 | 141 | if (Z_TYPE_P(id) != IS_OBJECT || zend_hash_find(Z_OBJPROP_P(id), "socket", 142 | sizeof("socket"), (void **) &socket) == FAILURE) { 143 | return -1; 144 | } 145 | 146 | *ra = (RedisArray *) zend_list_find(Z_LVAL_PP(socket), &resource_type); 147 | 148 | if (!*ra || resource_type != le_redis_array) { 149 | return -1; 150 | } 151 | 152 | return Z_LVAL_PP(socket); 153 | } 154 | 155 | uint32_t rcrc32(const char *s, size_t sz) { 156 | 157 | static const uint32_t table[256] = { 158 | 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535, 159 | 0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD, 160 | 0xE7B82D07,0x90BF1D91,0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D, 161 | 0x6DDDE4EB,0xF4D4B551,0x83D385C7,0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC, 162 | 0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,0x3B6E20C8,0x4C69105E,0xD56041E4, 163 | 0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C, 164 | 0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,0x26D930AC, 165 | 0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F, 166 | 0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB, 167 | 0xB6662D3D,0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F, 168 | 0x9FBFE4A5,0xE8B8D433,0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB, 169 | 0x086D3D2D,0x91646C97,0xE6635C01,0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E, 170 | 0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,0x65B0D9C6,0x12B7E950,0x8BBEB8EA, 171 | 0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,0x4DB26158,0x3AB551CE, 172 | 0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,0x4369E96A, 173 | 0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9, 174 | 0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409, 175 | 0xCE61E49F,0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81, 176 | 0xB7BD5C3B,0xC0BA6CAD,0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739, 177 | 0x9DD277AF,0x04DB2615,0x73DC1683,0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8, 178 | 0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,0xF00F9344,0x8708A3D2,0x1E01F268, 179 | 0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,0xFED41B76,0x89D32BE0, 180 | 0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,0xD6D6A3E8, 181 | 0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B, 182 | 0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF, 183 | 0x4669BE79,0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703, 184 | 0x220216B9,0x5505262F,0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7, 185 | 0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A, 186 | 0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,0x95BF4A82,0xE2B87A14,0x7BB12BAE, 187 | 0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,0x86D3D2D4,0xF1D4E242, 188 | 0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6, 189 | 0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45, 190 | 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D, 191 | 0x3E6E77DB,0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5, 192 | 0x47B2CF7F,0x30B5FFE9,0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605, 193 | 0xCDD70693,0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94, 194 | 0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D}; 195 | 196 | unsigned long ret = 0xffffffff; 197 | size_t i; 198 | 199 | for (i = 0; i < sz; i++) { 200 | ret = (ret >> 8) ^ table[ (ret ^ ((unsigned char)s[i])) & 0xFF ]; 201 | } 202 | return (ret ^ 0xFFFFFFFF); 203 | 204 | } 205 | 206 | /* {{{ proto RedisArray RedisArray::__construct() 207 | Public constructor */ 208 | PHP_METHOD(RedisArray, __construct) 209 | { 210 | zval *z0, *z_fun = NULL, *z_dist = NULL, **zpData, *z_opts = NULL; 211 | int id; 212 | RedisArray *ra = NULL; 213 | zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0; 214 | HashTable *hPrev = NULL, *hOpts = NULL; 215 | long l_retry_interval = 0; 216 | zend_bool b_lazy_connect = 0; 217 | double d_connect_timeout = 0; 218 | 219 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|a", &z0, &z_opts) == FAILURE) { 220 | RETURN_FALSE; 221 | } 222 | 223 | /* extract options */ 224 | if(z_opts) { 225 | zval **z_retry_interval_pp; 226 | zval **z_connect_timeout_pp; 227 | 228 | hOpts = Z_ARRVAL_P(z_opts); 229 | 230 | /* extract previous ring. */ 231 | if(FAILURE != zend_hash_find(hOpts, "previous", sizeof("previous"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_ARRAY 232 | && zend_hash_num_elements(Z_ARRVAL_PP(zpData)) != 0) { 233 | /* consider previous array as non-existent if empty. */ 234 | hPrev = Z_ARRVAL_PP(zpData); 235 | } 236 | 237 | /* extract function name. */ 238 | if(FAILURE != zend_hash_find(hOpts, "function", sizeof("function"), (void**)&zpData)) { 239 | MAKE_STD_ZVAL(z_fun); 240 | *z_fun = **zpData; 241 | zval_copy_ctor(z_fun); 242 | } 243 | 244 | /* extract function name. */ 245 | if(FAILURE != zend_hash_find(hOpts, "distributor", sizeof("distributor"), (void**)&zpData)) { 246 | MAKE_STD_ZVAL(z_dist); 247 | *z_dist = **zpData; 248 | zval_copy_ctor(z_dist); 249 | } 250 | 251 | /* extract index option. */ 252 | if(FAILURE != zend_hash_find(hOpts, "index", sizeof("index"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_BOOL) { 253 | b_index = Z_BVAL_PP(zpData); 254 | } 255 | 256 | /* extract autorehash option. */ 257 | if(FAILURE != zend_hash_find(hOpts, "autorehash", sizeof("autorehash"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_BOOL) { 258 | b_autorehash = Z_BVAL_PP(zpData); 259 | } 260 | 261 | /* pconnect */ 262 | if(FAILURE != zend_hash_find(hOpts, "pconnect", sizeof("pconnect"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_BOOL) { 263 | b_pconnect = Z_BVAL_PP(zpData); 264 | } 265 | 266 | /* extract retry_interval option. */ 267 | if (FAILURE != zend_hash_find(hOpts, "retry_interval", sizeof("retry_interval"), (void**)&z_retry_interval_pp)) { 268 | if (Z_TYPE_PP(z_retry_interval_pp) == IS_LONG || Z_TYPE_PP(z_retry_interval_pp) == IS_STRING) { 269 | if (Z_TYPE_PP(z_retry_interval_pp) == IS_LONG) { 270 | l_retry_interval = Z_LVAL_PP(z_retry_interval_pp); 271 | } 272 | else { 273 | l_retry_interval = atol(Z_STRVAL_PP(z_retry_interval_pp)); 274 | } 275 | } 276 | } 277 | 278 | /* extract lazy connect option. */ 279 | if(FAILURE != zend_hash_find(hOpts, "lazy_connect", sizeof("lazy_connect"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_BOOL) { 280 | b_lazy_connect = Z_BVAL_PP(zpData); 281 | } 282 | 283 | /* extract connect_timeout option */ 284 | if (FAILURE != zend_hash_find(hOpts, "connect_timeout", sizeof("connect_timeout"), (void**)&z_connect_timeout_pp)) { 285 | if (Z_TYPE_PP(z_connect_timeout_pp) == IS_DOUBLE || 286 | Z_TYPE_PP(z_connect_timeout_pp) == IS_STRING || 287 | Z_TYPE_PP(z_connect_timeout_pp) == IS_LONG) 288 | { 289 | if (Z_TYPE_PP(z_connect_timeout_pp) == IS_DOUBLE) { 290 | d_connect_timeout = Z_DVAL_PP(z_connect_timeout_pp); 291 | } else if (Z_TYPE_PP(z_connect_timeout_pp) == IS_LONG) { 292 | d_connect_timeout = Z_LVAL_PP(z_connect_timeout_pp); 293 | } else { 294 | d_connect_timeout = atof(Z_STRVAL_PP(z_connect_timeout_pp)); 295 | } 296 | } 297 | } 298 | } 299 | 300 | /* extract either name of list of hosts from z0 */ 301 | switch(Z_TYPE_P(z0)) { 302 | case IS_STRING: 303 | ra = ra_load_array(Z_STRVAL_P(z0) TSRMLS_CC); 304 | break; 305 | 306 | case IS_ARRAY: 307 | 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); 308 | break; 309 | 310 | default: 311 | WRONG_PARAM_COUNT; 312 | break; 313 | } 314 | 315 | if(ra) { 316 | ra->auto_rehash = b_autorehash; 317 | ra->connect_timeout = d_connect_timeout; 318 | if(ra->prev) ra->prev->auto_rehash = b_autorehash; 319 | #if PHP_VERSION_ID >= 50400 320 | id = zend_list_insert(ra, le_redis_array TSRMLS_CC); 321 | #else 322 | id = zend_list_insert(ra, le_redis_array); 323 | #endif 324 | add_property_resource(getThis(), "socket", id); 325 | } 326 | } 327 | 328 | static void 329 | ra_forward_call(INTERNAL_FUNCTION_PARAMETERS, RedisArray *ra, const char *cmd, int cmd_len, zval *z_args, zval *z_new_target) { 330 | 331 | zval **zp_tmp, z_tmp; 332 | char *key = NULL; /* set to avoid "unused-but-set-variable" */ 333 | int key_len; 334 | int i; 335 | zval *redis_inst; 336 | zval z_fun, **z_callargs; 337 | HashPosition pointer; 338 | HashTable *h_args; 339 | 340 | int argc; 341 | zend_bool b_write_cmd = 0; 342 | 343 | h_args = Z_ARRVAL_P(z_args); 344 | argc = zend_hash_num_elements(h_args); 345 | 346 | if(ra->z_multi_exec) { 347 | redis_inst = ra->z_multi_exec; /* we already have the instance */ 348 | } else { 349 | /* extract key and hash it. */ 350 | if(!(key = ra_find_key(ra, z_args, cmd, &key_len))) { 351 | php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not find key"); 352 | RETURN_FALSE; 353 | } 354 | 355 | /* find node */ 356 | redis_inst = ra_find_node(ra, key, key_len, NULL TSRMLS_CC); 357 | if(!redis_inst) { 358 | php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not find any redis servers for this key."); 359 | RETURN_FALSE; 360 | } 361 | } 362 | 363 | /* check if write cmd */ 364 | b_write_cmd = ra_is_write_cmd(ra, cmd, cmd_len); 365 | 366 | if(ra->index && b_write_cmd && !ra->z_multi_exec) { /* add MULTI + SADD */ 367 | ra_index_multi(redis_inst, MULTI TSRMLS_CC); 368 | } 369 | 370 | /* pass call through */ 371 | ZVAL_STRING(&z_fun, cmd, 0); /* method name */ 372 | z_callargs = emalloc(argc * sizeof(zval*)); 373 | 374 | /* copy args to array */ 375 | for (i = 0, zend_hash_internal_pointer_reset_ex(h_args, &pointer); 376 | zend_hash_get_current_data_ex(h_args, (void**) &zp_tmp, 377 | &pointer) == SUCCESS; 378 | ++i, zend_hash_move_forward_ex(h_args, &pointer)) { 379 | 380 | z_callargs[i] = *zp_tmp; 381 | } 382 | 383 | /* multi/exec */ 384 | if(ra->z_multi_exec) { 385 | call_user_function(&redis_ce->function_table, &ra->z_multi_exec, &z_fun, return_value, argc, z_callargs TSRMLS_CC); 386 | efree(z_callargs); 387 | RETURN_ZVAL(getThis(), 1, 0); 388 | } 389 | 390 | /* CALL! */ 391 | if(ra->index && b_write_cmd) { 392 | /* call using discarded temp value and extract exec results after. */ 393 | call_user_function(&redis_ce->function_table, &redis_inst, &z_fun, &z_tmp, argc, z_callargs TSRMLS_CC); 394 | zval_dtor(&z_tmp); 395 | 396 | /* add keys to index. */ 397 | ra_index_key(key, key_len, redis_inst TSRMLS_CC); 398 | 399 | /* call EXEC */ 400 | ra_index_exec(redis_inst, return_value, 0 TSRMLS_CC); 401 | } else { /* call directly through. */ 402 | call_user_function(&redis_ce->function_table, &redis_inst, &z_fun, return_value, argc, z_callargs TSRMLS_CC); 403 | 404 | /* check if we have an error. */ 405 | if(RA_CALL_FAILED(return_value,cmd) && ra->prev && !b_write_cmd) { /* there was an error reading, try with prev ring. */ 406 | /* ERROR, FALLBACK TO PREVIOUS RING and forward a reference to the first redis instance we were looking at. */ 407 | ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra->prev, cmd, cmd_len, z_args, z_new_target?z_new_target:redis_inst); 408 | } 409 | 410 | /* Autorehash if the key was found on the previous node if this is a read command and auto rehashing is on */ 411 | if(!RA_CALL_FAILED(return_value,cmd) && !b_write_cmd && z_new_target && ra->auto_rehash) { /* move key from old ring to new ring */ 412 | ra_move_key(key, key_len, redis_inst, z_new_target TSRMLS_CC); 413 | } 414 | } 415 | 416 | /* cleanup */ 417 | efree(z_callargs); 418 | } 419 | 420 | PHP_METHOD(RedisArray, __call) 421 | { 422 | zval *object; 423 | RedisArray *ra; 424 | zval *z_args; 425 | 426 | char *cmd; 427 | int cmd_len; 428 | 429 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa", 430 | &object, redis_array_ce, &cmd, &cmd_len, &z_args) == FAILURE) { 431 | RETURN_FALSE; 432 | } 433 | 434 | if (redis_array_get(object, &ra TSRMLS_CC) < 0) { 435 | RETURN_FALSE; 436 | } 437 | 438 | ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra, cmd, cmd_len, z_args, NULL); 439 | } 440 | 441 | PHP_METHOD(RedisArray, _hosts) 442 | { 443 | zval *object; 444 | int i; 445 | RedisArray *ra; 446 | 447 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", 448 | &object, redis_array_ce) == FAILURE) { 449 | RETURN_FALSE; 450 | } 451 | 452 | if (redis_array_get(object, &ra TSRMLS_CC) < 0) { 453 | RETURN_FALSE; 454 | } 455 | 456 | array_init(return_value); 457 | for(i = 0; i < ra->count; ++i) { 458 | add_next_index_string(return_value, ra->hosts[i], 1); 459 | } 460 | } 461 | 462 | PHP_METHOD(RedisArray, _target) 463 | { 464 | zval *object; 465 | RedisArray *ra; 466 | char *key; 467 | int key_len, i; 468 | zval *redis_inst; 469 | 470 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", 471 | &object, redis_array_ce, &key, &key_len) == FAILURE) { 472 | RETURN_FALSE; 473 | } 474 | 475 | if (redis_array_get(object, &ra TSRMLS_CC) < 0) { 476 | RETURN_FALSE; 477 | } 478 | 479 | redis_inst = ra_find_node(ra, key, key_len, &i TSRMLS_CC); 480 | if(redis_inst) { 481 | ZVAL_STRING(return_value, ra->hosts[i], 1); 482 | } else { 483 | RETURN_NULL(); 484 | } 485 | } 486 | 487 | PHP_METHOD(RedisArray, _instance) 488 | { 489 | zval *object; 490 | RedisArray *ra; 491 | char *target; 492 | int target_len; 493 | zval *z_redis; 494 | 495 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", 496 | &object, redis_array_ce, &target, &target_len) == FAILURE) { 497 | RETURN_FALSE; 498 | } 499 | 500 | if (redis_array_get(object, &ra TSRMLS_CC) < 0) { 501 | RETURN_FALSE; 502 | } 503 | 504 | z_redis = ra_find_node_by_name(ra, target, target_len TSRMLS_CC); 505 | if(z_redis) { 506 | RETURN_ZVAL(z_redis, 1, 0); 507 | } else { 508 | RETURN_NULL(); 509 | } 510 | } 511 | 512 | PHP_METHOD(RedisArray, _function) 513 | { 514 | zval *object; 515 | RedisArray *ra; 516 | 517 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", 518 | &object, redis_array_ce) == FAILURE) { 519 | RETURN_FALSE; 520 | } 521 | 522 | if (redis_array_get(object, &ra TSRMLS_CC) < 0) { 523 | RETURN_FALSE; 524 | } 525 | 526 | if(ra->z_fun) { 527 | *return_value = *ra->z_fun; 528 | zval_copy_ctor(return_value); 529 | } else { 530 | RETURN_NULL(); 531 | } 532 | } 533 | 534 | PHP_METHOD(RedisArray, _distributor) 535 | { 536 | zval *object; 537 | RedisArray *ra; 538 | 539 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", 540 | &object, redis_array_ce) == FAILURE) { 541 | RETURN_FALSE; 542 | } 543 | 544 | if (redis_array_get(object, &ra TSRMLS_CC) < 0) { 545 | RETURN_FALSE; 546 | } 547 | 548 | if(ra->z_fun) { 549 | *return_value = *ra->z_fun; 550 | zval_copy_ctor(return_value); 551 | } else { 552 | RETURN_NULL(); 553 | } 554 | } 555 | 556 | PHP_METHOD(RedisArray, _rehash) 557 | { 558 | zval *object; 559 | RedisArray *ra; 560 | zend_fcall_info z_cb; 561 | zend_fcall_info_cache z_cb_cache; 562 | 563 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|f", 564 | &object, redis_array_ce, &z_cb, &z_cb_cache) == FAILURE) { 565 | RETURN_FALSE; 566 | } 567 | 568 | if (redis_array_get(object, &ra TSRMLS_CC) < 0) { 569 | RETURN_FALSE; 570 | } 571 | 572 | if (ZEND_NUM_ARGS() == 0) { 573 | ra_rehash(ra, NULL, NULL TSRMLS_CC); 574 | } else { 575 | ra_rehash(ra, &z_cb, &z_cb_cache TSRMLS_CC); 576 | } 577 | } 578 | 579 | static void multihost_distribute(INTERNAL_FUNCTION_PARAMETERS, const char *method_name) 580 | { 581 | zval *object, z_fun, *z_tmp; 582 | int i; 583 | RedisArray *ra; 584 | 585 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", 586 | &object, redis_array_ce) == FAILURE) { 587 | RETURN_FALSE; 588 | } 589 | 590 | if (redis_array_get(object, &ra TSRMLS_CC) < 0) { 591 | RETURN_FALSE; 592 | } 593 | 594 | /* prepare call */ 595 | ZVAL_STRING(&z_fun, method_name, 0); 596 | 597 | array_init(return_value); 598 | for(i = 0; i < ra->count; ++i) { 599 | 600 | MAKE_STD_ZVAL(z_tmp); 601 | 602 | /* Call each node in turn */ 603 | call_user_function(&redis_ce->function_table, &ra->redis[i], 604 | &z_fun, z_tmp, 0, NULL TSRMLS_CC); 605 | 606 | add_assoc_zval(return_value, ra->hosts[i], z_tmp); 607 | } 608 | } 609 | 610 | PHP_METHOD(RedisArray, info) 611 | { 612 | multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "INFO"); 613 | } 614 | 615 | PHP_METHOD(RedisArray, ping) 616 | { 617 | multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PING"); 618 | } 619 | 620 | PHP_METHOD(RedisArray, flushdb) 621 | { 622 | multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "FLUSHDB"); 623 | } 624 | 625 | PHP_METHOD(RedisArray, flushall) 626 | { 627 | multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "FLUSHALL"); 628 | } 629 | 630 | PHP_METHOD(RedisArray, save) 631 | { 632 | multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SAVE"); 633 | } 634 | 635 | PHP_METHOD(RedisArray, bgsave) 636 | { 637 | multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "BGSAVE"); 638 | } 639 | 640 | 641 | PHP_METHOD(RedisArray, keys) 642 | { 643 | zval *object, *z_args[1], *z_tmp, z_fun; 644 | RedisArray *ra; 645 | char *pattern; 646 | int pattern_len, i; 647 | 648 | /* Make sure the prototype is correct */ 649 | if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", 650 | &object, redis_array_ce, &pattern, &pattern_len) == FAILURE) 651 | { 652 | RETURN_FALSE; 653 | } 654 | 655 | /* Make sure we can grab our RedisArray object */ 656 | if(redis_array_get(object, &ra TSRMLS_CC) < 0) { 657 | RETURN_FALSE; 658 | } 659 | 660 | /* Set up our function call (KEYS) */ 661 | ZVAL_STRING(&z_fun, "KEYS", 0); 662 | 663 | /* We will be passing with one string argument (the pattern) */ 664 | MAKE_STD_ZVAL(z_args[0]); 665 | ZVAL_STRINGL(z_args[0], pattern, pattern_len, 0); 666 | 667 | /* Init our array return */ 668 | array_init(return_value); 669 | 670 | /* Iterate our RedisArray nodes */ 671 | for(i=0; icount; ++i) { 672 | /* Return for this node */ 673 | MAKE_STD_ZVAL(z_tmp); 674 | 675 | /* Call KEYS on each node */ 676 | call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 1, z_args TSRMLS_CC); 677 | 678 | /* Add the result for this host */ 679 | add_assoc_zval(return_value, ra->hosts[i], z_tmp); 680 | } 681 | 682 | /* Free arg array */ 683 | efree(z_args[0]); 684 | } 685 | 686 | PHP_METHOD(RedisArray, getOption) 687 | { 688 | zval *object, z_fun, *z_tmp, *z_args[1]; 689 | int i; 690 | RedisArray *ra; 691 | long opt; 692 | 693 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", 694 | &object, redis_array_ce, &opt) == FAILURE) { 695 | RETURN_FALSE; 696 | } 697 | 698 | if (redis_array_get(object, &ra TSRMLS_CC) < 0) { 699 | RETURN_FALSE; 700 | } 701 | 702 | /* prepare call */ 703 | ZVAL_STRING(&z_fun, "getOption", 0); 704 | 705 | /* copy arg */ 706 | MAKE_STD_ZVAL(z_args[0]); 707 | ZVAL_LONG(z_args[0], opt); 708 | 709 | array_init(return_value); 710 | for(i = 0; i < ra->count; ++i) { 711 | 712 | MAKE_STD_ZVAL(z_tmp); 713 | 714 | /* Call each node in turn */ 715 | call_user_function(&redis_ce->function_table, &ra->redis[i], 716 | &z_fun, z_tmp, 1, z_args TSRMLS_CC); 717 | 718 | add_assoc_zval(return_value, ra->hosts[i], z_tmp); 719 | } 720 | 721 | /* cleanup */ 722 | efree(z_args[0]); 723 | } 724 | 725 | PHP_METHOD(RedisArray, setOption) 726 | { 727 | zval *object, z_fun, *z_tmp, *z_args[2]; 728 | int i; 729 | RedisArray *ra; 730 | long opt; 731 | char *val_str; 732 | int val_len; 733 | 734 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ols", 735 | &object, redis_array_ce, &opt, &val_str, &val_len) == FAILURE) { 736 | RETURN_FALSE; 737 | } 738 | 739 | if (redis_array_get(object, &ra TSRMLS_CC) < 0) { 740 | RETURN_FALSE; 741 | } 742 | 743 | /* prepare call */ 744 | ZVAL_STRING(&z_fun, "setOption", 0); 745 | 746 | /* copy args */ 747 | MAKE_STD_ZVAL(z_args[0]); 748 | ZVAL_LONG(z_args[0], opt); 749 | MAKE_STD_ZVAL(z_args[1]); 750 | ZVAL_STRINGL(z_args[1], val_str, val_len, 0); 751 | 752 | array_init(return_value); 753 | for(i = 0; i < ra->count; ++i) { 754 | 755 | MAKE_STD_ZVAL(z_tmp); 756 | 757 | /* Call each node in turn */ 758 | call_user_function(&redis_ce->function_table, &ra->redis[i], 759 | &z_fun, z_tmp, 2, z_args TSRMLS_CC); 760 | 761 | add_assoc_zval(return_value, ra->hosts[i], z_tmp); 762 | } 763 | 764 | /* cleanup */ 765 | efree(z_args[0]); 766 | efree(z_args[1]); 767 | } 768 | 769 | PHP_METHOD(RedisArray, select) 770 | { 771 | zval *object, z_fun, *z_tmp, *z_args[2]; 772 | int i; 773 | RedisArray *ra; 774 | long opt; 775 | 776 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", 777 | &object, redis_array_ce, &opt) == FAILURE) { 778 | RETURN_FALSE; 779 | } 780 | 781 | if (redis_array_get(object, &ra TSRMLS_CC) < 0) { 782 | RETURN_FALSE; 783 | } 784 | 785 | /* prepare call */ 786 | ZVAL_STRING(&z_fun, "select", 0); 787 | 788 | /* copy args */ 789 | MAKE_STD_ZVAL(z_args[0]); 790 | ZVAL_LONG(z_args[0], opt); 791 | 792 | array_init(return_value); 793 | for(i = 0; i < ra->count; ++i) { 794 | 795 | MAKE_STD_ZVAL(z_tmp); 796 | 797 | /* Call each node in turn */ 798 | call_user_function(&redis_ce->function_table, &ra->redis[i], 799 | &z_fun, z_tmp, 1, z_args TSRMLS_CC); 800 | 801 | add_assoc_zval(return_value, ra->hosts[i], z_tmp); 802 | } 803 | 804 | /* cleanup */ 805 | efree(z_args[0]); 806 | } 807 | 808 | #define HANDLE_MULTI_EXEC(cmd) do {\ 809 | if (redis_array_get(getThis(), &ra TSRMLS_CC) >= 0 && ra->z_multi_exec) {\ 810 | int i, num_varargs;\ 811 | zval ***varargs = NULL;\ 812 | zval z_arg_array;\ 813 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O*",\ 814 | &object, redis_array_ce, &varargs, &num_varargs) == FAILURE) {\ 815 | RETURN_FALSE;\ 816 | }\ 817 | /* copy all args into a zval hash table */\ 818 | array_init(&z_arg_array);\ 819 | for(i = 0; i < num_varargs; ++i) {\ 820 | zval *z_tmp;\ 821 | MAKE_STD_ZVAL(z_tmp);\ 822 | *z_tmp = **varargs[i];\ 823 | zval_copy_ctor(z_tmp);\ 824 | INIT_PZVAL(z_tmp);\ 825 | add_next_index_zval(&z_arg_array, z_tmp);\ 826 | }\ 827 | /* call */\ 828 | ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra, cmd, sizeof(cmd)-1, &z_arg_array, NULL);\ 829 | zval_dtor(&z_arg_array);\ 830 | if(varargs) {\ 831 | efree(varargs);\ 832 | }\ 833 | return;\ 834 | }\ 835 | }while(0) 836 | 837 | /* MGET will distribute the call to several nodes and regroup the values. */ 838 | PHP_METHOD(RedisArray, mget) 839 | { 840 | zval *object, *z_keys, z_fun, *z_argarray, **data, *z_ret, **z_cur, *z_tmp_array, *z_tmp; 841 | int i, j, n; 842 | RedisArray *ra; 843 | int *pos, argc, *argc_each; 844 | HashTable *h_keys; 845 | HashPosition pointer; 846 | zval **redis_instances, **argv; 847 | 848 | /* Multi/exec support */ 849 | HANDLE_MULTI_EXEC("MGET"); 850 | 851 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", 852 | &object, redis_array_ce, &z_keys) == FAILURE) { 853 | RETURN_FALSE; 854 | } 855 | 856 | if (redis_array_get(object, &ra TSRMLS_CC) < 0) { 857 | RETURN_FALSE; 858 | } 859 | 860 | /* prepare call */ 861 | ZVAL_STRING(&z_fun, "MGET", 0); 862 | 863 | /* init data structures */ 864 | h_keys = Z_ARRVAL_P(z_keys); 865 | argc = zend_hash_num_elements(h_keys); 866 | argv = emalloc(argc * sizeof(zval*)); 867 | pos = emalloc(argc * sizeof(int)); 868 | redis_instances = emalloc(argc * sizeof(zval*)); 869 | memset(redis_instances, 0, argc * sizeof(zval*)); 870 | 871 | argc_each = emalloc(ra->count * sizeof(int)); 872 | memset(argc_each, 0, ra->count * sizeof(int)); 873 | 874 | /* associate each key to a redis node */ 875 | for (i = 0, zend_hash_internal_pointer_reset_ex(h_keys, &pointer); 876 | zend_hash_get_current_data_ex(h_keys, (void**) &data, 877 | &pointer) == SUCCESS; 878 | zend_hash_move_forward_ex(h_keys, &pointer), ++i) 879 | { 880 | /* If we need to represent a long key as a string */ 881 | unsigned int key_len; 882 | char kbuf[40], *key_lookup; 883 | 884 | /* phpredis proper can only use string or long keys, so restrict to that here */ 885 | if(Z_TYPE_PP(data) != IS_STRING && Z_TYPE_PP(data) != IS_LONG) { 886 | php_error_docref(NULL TSRMLS_CC, E_ERROR, "MGET: all keys must be strings or longs"); 887 | efree(argv); 888 | efree(pos); 889 | efree(redis_instances); 890 | efree(argc_each); 891 | RETURN_FALSE; 892 | } 893 | 894 | /* Convert to a string for hash lookup if it isn't one */ 895 | if(Z_TYPE_PP(data) == IS_STRING) { 896 | key_len = Z_STRLEN_PP(data); 897 | key_lookup = Z_STRVAL_PP(data); 898 | } else { 899 | key_len = snprintf(kbuf, sizeof(kbuf), "%ld", Z_LVAL_PP(data)); 900 | key_lookup = (char*)kbuf; 901 | } 902 | 903 | /* Find our node */ 904 | redis_instances[i] = ra_find_node(ra, key_lookup, key_len, &pos[i] TSRMLS_CC); 905 | 906 | argc_each[pos[i]]++; /* count number of keys per node */ 907 | argv[i] = *data; 908 | } 909 | 910 | /* prepare return value */ 911 | array_init(return_value); 912 | MAKE_STD_ZVAL(z_tmp_array); 913 | array_init(z_tmp_array); 914 | 915 | /* calls */ 916 | for(n = 0; n < ra->count; ++n) { /* for each node */ 917 | /* We don't even need to make a call to this node if no keys go there */ 918 | if(!argc_each[n]) continue; 919 | 920 | /* copy args for MGET call on node. */ 921 | MAKE_STD_ZVAL(z_argarray); 922 | array_init(z_argarray); 923 | 924 | for(i = 0; i < argc; ++i) { 925 | if(pos[i] != n) continue; 926 | 927 | MAKE_STD_ZVAL(z_tmp); 928 | *z_tmp = *argv[i]; 929 | zval_copy_ctor(z_tmp); 930 | INIT_PZVAL(z_tmp); 931 | add_next_index_zval(z_argarray, z_tmp); 932 | } 933 | 934 | /* call MGET on the node */ 935 | MAKE_STD_ZVAL(z_ret); 936 | call_user_function(&redis_ce->function_table, &ra->redis[n], 937 | &z_fun, z_ret, 1, &z_argarray TSRMLS_CC); 938 | 939 | /* cleanup args array */ 940 | zval_ptr_dtor(&z_argarray); 941 | 942 | for(i = 0, j = 0; i < argc; ++i) { 943 | /* Error out if we didn't get a proper response */ 944 | if(Z_TYPE_P(z_ret) != IS_ARRAY) { 945 | /* cleanup */ 946 | zval_dtor(z_ret); 947 | efree(z_ret); 948 | zval_ptr_dtor(&z_tmp_array); 949 | efree(pos); 950 | efree(redis_instances); 951 | efree(argc_each); 952 | 953 | /* failure */ 954 | RETURN_FALSE; 955 | } 956 | 957 | if(pos[i] != n) continue; 958 | 959 | zend_hash_index_find(Z_ARRVAL_P(z_ret), j, (void**)&z_cur); 960 | j++; 961 | 962 | MAKE_STD_ZVAL(z_tmp); 963 | *z_tmp = **z_cur; 964 | zval_copy_ctor(z_tmp); 965 | INIT_PZVAL(z_tmp); 966 | add_index_zval(z_tmp_array, i, z_tmp); 967 | } 968 | zval_dtor(z_ret); 969 | efree(z_ret); 970 | } 971 | 972 | /* copy temp array in the right order to return_value */ 973 | for(i = 0; i < argc; ++i) { 974 | zend_hash_index_find(Z_ARRVAL_P(z_tmp_array), i, (void**)&z_cur); 975 | 976 | MAKE_STD_ZVAL(z_tmp); 977 | *z_tmp = **z_cur; 978 | zval_copy_ctor(z_tmp); 979 | INIT_PZVAL(z_tmp); 980 | add_next_index_zval(return_value, z_tmp); 981 | } 982 | 983 | /* cleanup */ 984 | zval_ptr_dtor(&z_tmp_array); 985 | efree(argv); 986 | efree(pos); 987 | efree(redis_instances); 988 | efree(argc_each); 989 | } 990 | 991 | 992 | /* MSET will distribute the call to several nodes and regroup the values. */ 993 | PHP_METHOD(RedisArray, mset) 994 | { 995 | zval *object, *z_keys, z_fun, *z_argarray, **data, z_ret; 996 | int i, n; 997 | RedisArray *ra; 998 | int *pos, argc, *argc_each; 999 | HashTable *h_keys; 1000 | zval **redis_instances, *redis_inst, **argv; 1001 | char *key, **keys, **key_free, kbuf[40]; 1002 | unsigned int key_len, free_idx = 0; 1003 | int type, *key_lens; 1004 | unsigned long idx; 1005 | 1006 | /* Multi/exec support */ 1007 | HANDLE_MULTI_EXEC("MSET"); 1008 | 1009 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", 1010 | &object, redis_array_ce, &z_keys) == FAILURE) { 1011 | RETURN_FALSE; 1012 | } 1013 | 1014 | if (redis_array_get(object, &ra TSRMLS_CC) < 0) { 1015 | RETURN_FALSE; 1016 | } 1017 | 1018 | /* init data structures */ 1019 | h_keys = Z_ARRVAL_P(z_keys); 1020 | argc = zend_hash_num_elements(h_keys); 1021 | argv = emalloc(argc * sizeof(zval*)); 1022 | pos = emalloc(argc * sizeof(int)); 1023 | keys = emalloc(argc * sizeof(char*)); 1024 | key_lens = emalloc(argc * sizeof(int)); 1025 | redis_instances = emalloc(argc * sizeof(zval*)); 1026 | memset(redis_instances, 0, argc * sizeof(zval*)); 1027 | 1028 | /* Allocate an array holding the indexes of any keys that need freeing */ 1029 | key_free = emalloc(argc * sizeof(char*)); 1030 | 1031 | argc_each = emalloc(ra->count * sizeof(int)); 1032 | memset(argc_each, 0, ra->count * sizeof(int)); 1033 | 1034 | /* associate each key to a redis node */ 1035 | for(i = 0, zend_hash_internal_pointer_reset(h_keys); 1036 | zend_hash_has_more_elements(h_keys) == SUCCESS; 1037 | zend_hash_move_forward(h_keys), i++) 1038 | { 1039 | /* We have to skip the element if we can't get the array value */ 1040 | if(zend_hash_get_current_data(h_keys, (void**)&data) == FAILURE) { 1041 | continue; 1042 | } 1043 | 1044 | /* Grab our key */ 1045 | type = zend_hash_get_current_key_ex(h_keys, &key, &key_len, &idx, 0, NULL); 1046 | 1047 | /* If the key isn't a string, make a string representation of it */ 1048 | if(type != HASH_KEY_IS_STRING) { 1049 | key_len = snprintf(kbuf, sizeof(kbuf), "%ld", (long)idx); 1050 | key = estrndup(kbuf, key_len); 1051 | key_free[free_idx++]=key; 1052 | } else { 1053 | key_len--; /* We don't want the null terminator */ 1054 | } 1055 | 1056 | redis_instances[i] = ra_find_node(ra, key, (int)key_len, &pos[i] TSRMLS_CC); 1057 | argc_each[pos[i]]++; /* count number of keys per node */ 1058 | argv[i] = *data; 1059 | keys[i] = key; 1060 | key_lens[i] = (int)key_len; 1061 | } 1062 | 1063 | 1064 | /* calls */ 1065 | for(n = 0; n < ra->count; ++n) { /* for each node */ 1066 | int found = 0; 1067 | 1068 | /* prepare call */ 1069 | ZVAL_STRING(&z_fun, "MSET", 0); 1070 | redis_inst = ra->redis[n]; 1071 | 1072 | /* copy args */ 1073 | MAKE_STD_ZVAL(z_argarray); 1074 | array_init(z_argarray); 1075 | for(i = 0; i < argc; ++i) { 1076 | zval *z_tmp; 1077 | 1078 | if(pos[i] != n) continue; 1079 | 1080 | ALLOC_ZVAL(z_tmp); 1081 | *z_tmp = *argv[i]; 1082 | zval_copy_ctor(z_tmp); 1083 | INIT_PZVAL(z_tmp); 1084 | 1085 | add_assoc_zval_ex(z_argarray, keys[i], key_lens[i] + 1, z_tmp); /* +1 to count the \0 here */ 1086 | found++; 1087 | } 1088 | 1089 | if(!found) 1090 | { 1091 | zval_dtor(z_argarray); 1092 | efree(z_argarray); 1093 | continue; /* don't run empty MSETs */ 1094 | } 1095 | 1096 | if(ra->index) { /* add MULTI */ 1097 | ra_index_multi(redis_inst, MULTI TSRMLS_CC); 1098 | } 1099 | 1100 | /* call */ 1101 | call_user_function(&redis_ce->function_table, &ra->redis[n], 1102 | &z_fun, &z_ret, 1, &z_argarray TSRMLS_CC); 1103 | 1104 | if(ra->index) { 1105 | ra_index_keys(z_argarray, redis_inst TSRMLS_CC); /* use SADD to add keys to node index */ 1106 | ra_index_exec(redis_inst, NULL, 0 TSRMLS_CC); /* run EXEC */ 1107 | } 1108 | 1109 | zval_dtor(&z_ret); 1110 | 1111 | zval_ptr_dtor(&z_argarray); 1112 | } 1113 | 1114 | /* Free any keys that we needed to allocate memory for, because they weren't strings */ 1115 | for(i=0; icount * sizeof(int)); 1190 | memset(argc_each, 0, ra->count * sizeof(int)); 1191 | 1192 | /* associate each key to a redis node */ 1193 | for (i = 0, zend_hash_internal_pointer_reset_ex(h_keys, &pointer); 1194 | zend_hash_get_current_data_ex(h_keys, (void**) &data, 1195 | &pointer) == SUCCESS; 1196 | zend_hash_move_forward_ex(h_keys, &pointer), ++i) { 1197 | 1198 | if (Z_TYPE_PP(data) != IS_STRING) { 1199 | php_error_docref(NULL TSRMLS_CC, E_ERROR, "DEL: all keys must be string."); 1200 | efree(pos); 1201 | RETURN_FALSE; 1202 | } 1203 | 1204 | redis_instances[i] = ra_find_node(ra, Z_STRVAL_PP(data), Z_STRLEN_PP(data), &pos[i] TSRMLS_CC); 1205 | argc_each[pos[i]]++; /* count number of keys per node */ 1206 | argv[i] = *data; 1207 | } 1208 | 1209 | /* calls */ 1210 | for(n = 0; n < ra->count; ++n) { /* for each node */ 1211 | 1212 | int found = 0; 1213 | redis_inst = ra->redis[n]; 1214 | 1215 | /* copy args */ 1216 | MAKE_STD_ZVAL(z_argarray); 1217 | array_init(z_argarray); 1218 | for(i = 0; i < argc; ++i) { 1219 | if(pos[i] != n) continue; 1220 | 1221 | MAKE_STD_ZVAL(z_tmp); 1222 | *z_tmp = *argv[i]; 1223 | zval_copy_ctor(z_tmp); 1224 | INIT_PZVAL(z_tmp); 1225 | 1226 | add_next_index_zval(z_argarray, z_tmp); 1227 | found++; 1228 | } 1229 | 1230 | if(!found) { /* don't run empty DELs */ 1231 | zval_dtor(z_argarray); 1232 | efree(z_argarray); 1233 | continue; 1234 | } 1235 | 1236 | if(ra->index) { /* add MULTI */ 1237 | ra_index_multi(redis_inst, MULTI TSRMLS_CC); 1238 | } 1239 | 1240 | /* call */ 1241 | MAKE_STD_ZVAL(z_ret); 1242 | call_user_function(&redis_ce->function_table, &redis_inst, 1243 | &z_fun, z_ret, 1, &z_argarray TSRMLS_CC); 1244 | 1245 | if(ra->index) { 1246 | ra_index_del(z_argarray, redis_inst TSRMLS_CC); /* use SREM to remove keys from node index */ 1247 | ra_index_exec(redis_inst, z_tmp, 0 TSRMLS_CC); /* run EXEC */ 1248 | total += Z_LVAL_P(z_tmp); /* increment total from multi/exec block */ 1249 | } else { 1250 | total += Z_LVAL_P(z_ret); /* increment total from single command */ 1251 | } 1252 | 1253 | zval_dtor(z_ret); 1254 | efree(z_ret); 1255 | 1256 | zval_dtor(z_argarray); 1257 | efree(z_argarray); 1258 | } 1259 | 1260 | /* cleanup */ 1261 | efree(argv); 1262 | efree(pos); 1263 | efree(redis_instances); 1264 | efree(argc_each); 1265 | 1266 | if(free_zkeys) { 1267 | zval_dtor(z_keys); 1268 | efree(z_keys); 1269 | } 1270 | 1271 | efree(z_args); 1272 | RETURN_LONG(total); 1273 | } 1274 | 1275 | PHP_METHOD(RedisArray, multi) 1276 | { 1277 | zval *object; 1278 | RedisArray *ra; 1279 | zval *z_redis; 1280 | char *host; 1281 | int host_len; 1282 | long multi_value = MULTI; 1283 | 1284 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|l", 1285 | &object, redis_array_ce, &host, &host_len, &multi_value) == FAILURE) { 1286 | RETURN_FALSE; 1287 | } 1288 | 1289 | if (redis_array_get(object, &ra TSRMLS_CC) < 0) { 1290 | RETURN_FALSE; 1291 | } 1292 | 1293 | /* find node */ 1294 | z_redis = ra_find_node_by_name(ra, host, host_len TSRMLS_CC); 1295 | if(!z_redis) { 1296 | RETURN_FALSE; 1297 | } 1298 | 1299 | if(multi_value != MULTI && multi_value != PIPELINE) { 1300 | RETURN_FALSE; 1301 | } 1302 | 1303 | /* save multi object */ 1304 | ra->z_multi_exec = z_redis; 1305 | 1306 | /* switch redis instance to multi/exec mode. */ 1307 | ra_index_multi(z_redis, multi_value TSRMLS_CC); 1308 | 1309 | /* return this. */ 1310 | RETURN_ZVAL(object, 1, 0); 1311 | } 1312 | 1313 | PHP_METHOD(RedisArray, exec) 1314 | { 1315 | zval *object; 1316 | RedisArray *ra; 1317 | 1318 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", 1319 | &object, redis_array_ce) == FAILURE) { 1320 | RETURN_FALSE; 1321 | } 1322 | 1323 | if (redis_array_get(object, &ra TSRMLS_CC) < 0 || !ra->z_multi_exec) { 1324 | RETURN_FALSE; 1325 | } 1326 | 1327 | /* switch redis instance out of multi/exec mode. */ 1328 | ra_index_exec(ra->z_multi_exec, return_value, 1 TSRMLS_CC); 1329 | 1330 | /* remove multi object */ 1331 | ra->z_multi_exec = NULL; 1332 | } 1333 | 1334 | PHP_METHOD(RedisArray, discard) 1335 | { 1336 | zval *object; 1337 | RedisArray *ra; 1338 | 1339 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", 1340 | &object, redis_array_ce) == FAILURE) { 1341 | RETURN_FALSE; 1342 | } 1343 | 1344 | if (redis_array_get(object, &ra TSRMLS_CC) < 0 || !ra->z_multi_exec) { 1345 | RETURN_FALSE; 1346 | } 1347 | 1348 | /* switch redis instance out of multi/exec mode. */ 1349 | ra_index_discard(ra->z_multi_exec, return_value TSRMLS_CC); 1350 | 1351 | /* remove multi object */ 1352 | ra->z_multi_exec = NULL; 1353 | } 1354 | 1355 | PHP_METHOD(RedisArray, unwatch) 1356 | { 1357 | zval *object; 1358 | RedisArray *ra; 1359 | 1360 | if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", 1361 | &object, redis_array_ce) == FAILURE) { 1362 | RETURN_FALSE; 1363 | } 1364 | 1365 | if (redis_array_get(object, &ra TSRMLS_CC) < 0 || !ra->z_multi_exec) { 1366 | RETURN_FALSE; 1367 | } 1368 | 1369 | /* unwatch keys, stay in multi/exec mode. */ 1370 | ra_index_unwatch(ra->z_multi_exec, return_value TSRMLS_CC); 1371 | } 1372 | --------------------------------------------------------------------------------