diff options
author | michael-grunder <michael.grunder@gmail.com> | 2013-09-02 05:39:08 +0400 |
---|---|---|
committer | michael-grunder <michael.grunder@gmail.com> | 2013-09-02 05:39:08 +0400 |
commit | 19e6f315f24735a86a1deff67e99003d66df65e1 (patch) | |
tree | e17ffc84b45b82da9a4df343b915e4483abf1297 | |
parent | 4c24dfa5c5320e1bedbb3fe72286ede4e2f7117e (diff) | |
parent | 8ed1a29ad3bcbfbfbd9b7e06b4e78f9010751c92 (diff) |
Merge branch 'release/2.2.4'2.2.4
-rw-r--r-- | README.markdown | 50 | ||||
-rw-r--r-- | arrays.markdown | 25 | ||||
-rw-r--r-- | common.h | 15 | ||||
-rw-r--r-- | config.h | 69 | ||||
-rw-r--r-- | debian.control | 2 | ||||
-rw-r--r-- | library.c | 58 | ||||
-rw-r--r-- | library.h | 7 | ||||
-rw-r--r-- | package.xml | 14 | ||||
-rw-r--r-- | php_redis.h | 3 | ||||
-rw-r--r-- | redis.c | 560 | ||||
-rw-r--r-- | redis_array.c | 105 | ||||
-rw-r--r-- | redis_array.h | 1 | ||||
-rw-r--r-- | redis_array_impl.c | 107 | ||||
-rw-r--r-- | redis_array_impl.h | 4 | ||||
-rw-r--r-- | redis_session.c | 4 | ||||
-rw-r--r-- | rpm/php-redis.spec | 2 | ||||
-rw-r--r-- | tests/TestRedis.php | 200 |
17 files changed, 849 insertions, 377 deletions
diff --git a/README.markdown b/README.markdown index ce978c05..7d5d2dc8 100644 --- a/README.markdown +++ b/README.markdown @@ -165,6 +165,8 @@ _**Description**_: Connects to a Redis instance. *host*: string. can be a host, or the path to a unix domain socket *port*: int, optional *timeout*: float, value in seconds (optional, default is 0 meaning unlimited) +*reserved*: should be NULL if retry_interval is specified +*retry_interval*: int, value in milliseconds (optional) ##### *Return value* @@ -177,6 +179,7 @@ $redis->connect('127.0.0.1', 6379); $redis->connect('127.0.0.1'); // port 6379 by default $redis->connect('127.0.0.1', 6379, 2.5); // 2.5 sec timeout. $redis->connect('/tmp/redis.sock'); // unix domain socket. +$redis->connect('127.0.0.1', 6379, 1, NULL, 100); // 1 sec timeout, 100ms delay between reconnection attempts. ~~~ ### pconnect, popen @@ -199,6 +202,7 @@ persistent equivalents. *port*: int, optional *timeout*: float, value in seconds (optional, default is 0 meaning unlimited) *persistent_id*: string. identity for the requested persistent connection +*retry_interval*: int, value in milliseconds (optional) ##### *Return value* @@ -322,6 +326,7 @@ _**Description**_: Sends a string to Redis, which replies with the same string 1. [save](#save) - Synchronously save the dataset to disk (wait to complete) 1. [slaveof](#slaveof) - Make the server a slave of another instance, or promote it to master 1. [time](#time) - Return the current server time +1. [slowlog](#slowlog) - Access the Redis slowlog entries ### bgrewriteaof ----- @@ -539,6 +544,36 @@ the unix timestamp, and element one being microseconds. $redis->time(); ~~~ +### slowlog +----- +_**Description**_: Access the Redis slowlog + +##### *Parameters* +*Operation* (string): This can be either `GET`, `LEN`, or `RESET` +*Length* (integer), optional: If executing a `SLOWLOG GET` command, you can pass an optional length. +##### + +##### *Return value* +The return value of SLOWLOG will depend on which operation was performed. +SLOWLOG GET: Array of slowlog entries, as provided by Redis +SLOGLOG LEN: Integer, the length of the slowlog +SLOWLOG RESET: Boolean, depending on success +##### + +##### *Examples* +~~~ +// Get ten slowlog entries +$redis->slowlog('get', 10); +// Get the default number of slowlog entries + +$redis->slowlog('get'); +// Reset our slowlog +$redis->slowlog('reset'); + +// Retrieve slowlog length +$redis->slowlog('len'); +~~~ + ## Keys and Strings ### Strings @@ -604,19 +639,30 @@ $redis->get('key'); ### set ----- -_**Description**_: Set the string value in argument as value of the key. +_**Description**_: Set the string value in argument as value of the key. If you're using Redis >= 2.6.12, you can pass extended options as explained below ##### *Parameters* *Key* *Value* -*Timeout* (optional). Calling `SETEX` is preferred if you want a timeout. +*Timeout or Options Array* (optional). If you pass an integer, phpredis will redirect to SETEX, and will try to use Redis >= 2.6.12 extended options if you pass an array with valid values ##### *Return value* *Bool* `TRUE` if the command is successful. ##### *Examples* ~~~ +// Simple key -> value set $redis->set('key', 'value'); + +// Will redirect, and actually make an SETEX call +$redis->set('key','value', 10); + +// Will set the key, if it doesn't exist, with a ttl of 10 seconds +$redis->set('key', 'value', Array('nx', 'ex'=>10); + +// Will set a key, if it does exist, with a ttl of 1000 miliseconds +$redis->set('key', 'value', Array('xx', 'px'=>1000); + ~~~ ### setex, psetex diff --git a/arrays.markdown b/arrays.markdown index 16c9601c..b1220891 100644 --- a/arrays.markdown +++ b/arrays.markdown @@ -17,7 +17,7 @@ There are several ways of creating Redis arrays; they can be pre-defined in red #### Declaring a new array with a list of nodes <pre> -$ra = new RedisArray(array("host1", "host2:63792, "host2:6380")); +$ra = new RedisArray(array("host1", "host2:63792", "host2:6380")); </pre> @@ -26,7 +26,7 @@ $ra = new RedisArray(array("host1", "host2:63792, "host2:6380")); function extract_key_part($k) { return substr($k, 0, 3); // hash only on first 3 characters. } -$ra = new RedisArray(array("host1", "host2:63792, "host2:6380"), array("function" => "extract_key_part")); +$ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("function" => "extract_key_part")); </pre> #### Defining a "previous" array when nodes are added or removed. @@ -34,7 +34,19 @@ When a new node is added to an array, phpredis needs to know about it. The old l <pre> // adding host3 to a ring containing host1 and host2. Read commands will look in the previous ring if the data is not found in the main ring. -$ra = new RedisArray(array('host1', 'host2', 'host3'), array('previous' => array('host1', 'host2'))); +$ra = new RedisArray(array("host1", "host2", "host3"), array("previous" => array("host1", "host2"))); +</pre> + +#### Specifying the "retry_interval" parameter +The retry_interval is used to specify a delay in milliseconds between reconnection attempts in case the client loses connection with a server +<pre> +$ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("retry_timeout" => 100))); +</pre> + +#### Specifying the "lazy_connect" parameter +This option is useful when a cluster has many shards but not of them are necessarily used at one time. +<pre> +$ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("lazy_connect" => true))); </pre> #### Defining arrays in Redis.ini @@ -76,6 +88,13 @@ In order to control the distribution of keys by hand, you can provide a custom f For instance, instanciate a RedisArray object with `new RedisArray(array("us-host", "uk-host", "de-host"), array("distributor" => "dist"));` and write a function called "dist" that will return `2` for all the keys that should end up on the "de-host" server. +### Example +<pre> +$ra = new RedisArray(array("host1", "host2", "host3", "host4", "host5", "host6", "host7", "host8"), array("distributor" => array(2, 2))); +</pre> + +This declares that we started with 2 shards and moved to 4 then 8 shards. The number of initial shards is 2 and the resharding level (or number of iterations) is 2. + ## Migrating keys When a node is added or removed from a ring, RedisArray instances must be instanciated with a “previous” list of nodes. A single call to `$ra->_rehash()` causes all the keys to be redistributed according to the new list of nodes. Passing a callback function to `_rehash()` makes it possible to track the progress of that operation: the function is called with a node name and a number of keys that will be examined, e.g. `_rehash(function ($host, $count){ ... });`. @@ -5,6 +5,11 @@ #ifndef REDIS_COMMON_H #define REDIS_COMMON_H +/* NULL check so Eclipse doesn't go crazy */ +#ifndef NULL +#define NULL ((void *) 0) +#endif + #define redis_sock_name "Redis Socket Buffer" #define REDIS_SOCK_STATUS_FAILED 0 #define REDIS_SOCK_STATUS_DISCONNECTED 1 @@ -138,6 +143,15 @@ else if(redis_sock->mode == MULTI) { \ #define REDIS_PROCESS_RESPONSE(function) REDIS_PROCESS_RESPONSE_CLOSURE(function, NULL) +/* Extended SET argument detection */ +#define IS_EX_ARG(a) ((a[0]=='e' || a[0]=='E') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') +#define IS_PX_ARG(a) ((a[0]=='p' || a[0]=='P') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') +#define IS_NX_ARG(a) ((a[0]=='n' || a[0]=='N') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') +#define IS_XX_ARG(a) ((a[0]=='x' || a[0]=='X') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') + +#define IS_EX_PX_ARG(a) (IS_EX_ARG(a) || IS_PX_ARG(a)) +#define IS_NX_XX_ARG(a) (IS_NX_ARG(a) || IS_XX_ARG(a)) + typedef enum {ATOMIC, MULTI, PIPELINE} redis_mode; typedef struct fold_item { @@ -182,6 +196,7 @@ typedef struct { char *err; int err_len; + zend_bool lazy_connect; } RedisSock; /* }}} */ diff --git a/config.h b/config.h new file mode 100644 index 00000000..00c355bd --- /dev/null +++ b/config.h @@ -0,0 +1,69 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* Whether to build redis as dynamic module */ +#define COMPILE_DL_REDIS 1 + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the <inttypes.h> header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the <memory.h> header file. */ +#define HAVE_MEMORY_H 1 + +/* Whether redis igbinary serializer is enabled */ +/* #undef HAVE_REDIS_IGBINARY */ + +/* Define to 1 if you have the <stdint.h> header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the <string.h> header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the <unistd.h> header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#define LT_OBJDIR ".libs/" + +/* Define to 1 if your C compiler doesn't accept -c and -o together. */ +/* #undef NO_MINUS_C_MINUS_O */ + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "" + +/* redis sessions */ +#define PHP_SESSION 1 + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 diff --git a/debian.control b/debian.control index 5bd408b9..0a5fe738 100644 --- a/debian.control +++ b/debian.control @@ -1,5 +1,5 @@ Package: phpredis -Version: 2.2.2 +Version: 2.2.4 Section: web Priority: optional Architecture: all @@ -455,6 +455,22 @@ int redis_cmd_append_str(char **cmd, int cmd_len, char *append, int append_len) } /* + * Given a smart string, number of arguments, a keyword, and the length of the keyword + * initialize our smart string with the proper Redis header for the command to follow + */ +int redis_cmd_init_sstr(smart_str *str, int num_args, char *keyword, int keyword_len) { + smart_str_appendc(str, '*'); + smart_str_append_long(str, num_args + 1); + smart_str_appendl(str, _NL, sizeof(_NL) -1); + smart_str_appendc(str, '$'); + smart_str_append_long(str, keyword_len); + smart_str_appendl(str, _NL, sizeof(_NL) - 1); + smart_str_appendl(str, keyword, keyword_len); + smart_str_appendl(str, _NL, sizeof(_NL) - 1); + return str->len; +} + +/* * Append a command sequence to a smart_str */ int redis_cmd_append_sstr(smart_str *str, char *append, int append_len) { @@ -469,6 +485,44 @@ int redis_cmd_append_sstr(smart_str *str, char *append, int append_len) { } /* + * Append an integer to a smart string command + */ +int redis_cmd_append_sstr_int(smart_str *str, int append) { + char int_buf[32]; + int int_len = snprintf(int_buf, sizeof(int_buf), "%d", append); + return redis_cmd_append_sstr(str, int_buf, int_len); +} + +/* + * Append a long to a smart string command + */ +int redis_cmd_append_sstr_long(smart_str *str, long append) { + char long_buf[32]; + int long_len = snprintf(long_buf, sizeof(long_buf), "%ld", append); + return redis_cmd_append_sstr(str, long_buf, long_len); +} + +/* + * Append a double to a smart string command + */ +int redis_cmd_append_sstr_dbl(smart_str *str, double value) { + char *dbl_str; + int dbl_len; + + /// Convert to double + REDIS_DOUBLE_TO_STRING(dbl_str, dbl_len, value); + + // Append the string + int retval = redis_cmd_append_sstr(str, dbl_str, dbl_len); + + // Free our double string + efree(dbl_str); + + // Return new length + return retval; +} + +/* * Append an integer command to a Redis command */ int redis_cmd_append_int(char **cmd, int cmd_len, int append) { @@ -970,7 +1024,8 @@ PHPAPI void redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_s */ PHPAPI RedisSock* redis_sock_create(char *host, int host_len, unsigned short port, double timeout, int persistent, char *persistent_id, - long retry_interval) + long retry_interval, + zend_bool lazy_connect) { RedisSock *redis_sock; @@ -982,6 +1037,7 @@ PHPAPI RedisSock* redis_sock_create(char *host, int host_len, unsigned short por redis_sock->dbNumber = 0; redis_sock->retry_interval = retry_interval * 1000; redis_sock->persistent = persistent; + redis_sock->lazy_connect = lazy_connect; if(persistent_id) { size_t persistent_id_len = strlen(persistent_id); @@ -4,9 +4,12 @@ int redis_cmd_format(char **ret, char *format, ...); int redis_cmd_format_static(char **ret, char *keyword, char *format, ...); int redis_cmd_format_header(char **ret, char *keyword, int arg_count); int redis_cmd_append_str(char **cmd, int cmd_len, char *append, int append_len); +int redis_cmd_init_sstr(smart_str *str, int num_args, char *keyword, int keyword_len); int redis_cmd_append_sstr(smart_str *str, char *append, int append_len); +int redis_cmd_append_sstr_int(smart_str *str, int append); +int redis_cmd_append_sstr_long(smart_str *str, long append); int redis_cmd_append_int(char **cmd, int cmd_len, int append); - +int redis_cmd_append_sstr_dbl(smart_str *str, double value); PHPAPI char * redis_sock_read(RedisSock *redis_sock, int *buf_len TSRMLS_DC); @@ -20,7 +23,7 @@ PHPAPI void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis PHPAPI void redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHPAPI void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHPAPI void redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHPAPI RedisSock* redis_sock_create(char *host, int host_len, unsigned short port, double timeout, int persistent, char *persistent_id, long retry_interval); +PHPAPI RedisSock* redis_sock_create(char *host, int host_len, unsigned short port, double timeout, int persistent, char *persistent_id, long retry_interval, zend_bool lazy_connect); PHPAPI int redis_sock_connect(RedisSock *redis_sock TSRMLS_DC); PHPAPI int redis_sock_server_open(RedisSock *redis_sock, int force_connect TSRMLS_DC); PHPAPI int redis_sock_disconnect(RedisSock *redis_sock TSRMLS_DC); diff --git a/package.xml b/package.xml index 1fd2ab3b..dd97ad77 100644 --- a/package.xml +++ b/package.xml @@ -21,10 +21,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> <email>michael.grunder@gmail.com</email> <active>yes</active> </lead> - <date>2013-04-29</date> + <date>2013-09-01</date> <version> - <release>2.2.3</release> - <api>2.2.3</api> + <release>2.2.4</release> + <api>2.2.4</api> </version> <stability> <release>stable</release> @@ -72,6 +72,14 @@ http://pear.php.net/dtd/package-2.0.xsd"> <changelog> <release> <stability><release>stable</release><api>stable</api></stability> + <version><release>2.2.4</release><api>2.2.4</api></version> + <date>2013-09-01</date> + <notes> + See GitHub for release notes + </notes> + </release> + <release> + <stability><release>stable</release><api>stable</api></stability> <version><release>2.2.3</release><api>2.2.3</api></version> <date>2013-04-29</date> <notes> diff --git a/php_redis.h b/php_redis.h index a8f99be0..879503aa 100644 --- a/php_redis.h +++ b/php_redis.h @@ -180,6 +180,7 @@ PHP_METHOD(Redis, getOption); PHP_METHOD(Redis, setOption); PHP_METHOD(Redis, config); +PHP_METHOD(Redis, slowlog); PHP_METHOD(Redis, client); @@ -256,7 +257,7 @@ extern zend_module_entry redis_module_entry; #define phpext_redis_ptr redis_module_ptr -#define PHP_REDIS_VERSION "2.2.3" +#define PHP_REDIS_VERSION "2.2.4" #endif @@ -72,8 +72,8 @@ PHP_INI_END() ZEND_DECLARE_MODULE_GLOBALS(redis) static zend_function_entry redis_functions[] = { - PHP_ME(Redis, __construct, NULL, ZEND_ACC_PUBLIC) - PHP_ME(Redis, __destruct, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Redis, __construct, NULL, ZEND_ACC_CTOR | ZEND_ACC_PUBLIC) + PHP_ME(Redis, __destruct, NULL, ZEND_ACC_DTOR | ZEND_ACC_PUBLIC) PHP_ME(Redis, connect, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pconnect, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, close, NULL, ZEND_ACC_PUBLIC) @@ -237,6 +237,9 @@ static zend_function_entry redis_functions[] = { /* config */ PHP_ME(Redis, config, NULL, ZEND_ACC_PUBLIC) + /* slowlog */ + PHP_ME(Redis, slowlog, NULL, ZEND_ACC_PUBLIC) + /* introspection */ PHP_ME(Redis, getHost, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getPort, NULL, ZEND_ACC_PUBLIC) @@ -394,6 +397,13 @@ PHPAPI int redis_sock_get(zval *id, RedisSock **redis_sock TSRMLS_DC, int no_thr } return -1; } + if ((*redis_sock)->lazy_connect) + { + (*redis_sock)->lazy_connect = 0; + if (redis_sock_server_open(*redis_sock, 1 TSRMLS_CC) < 0) { + return -1; + } + } return Z_LVAL_PP(socket); } @@ -629,18 +639,17 @@ PHPAPI int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) { } /* if there is a redis sock already we have to remove it from the list */ - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) > 0) { + if (redis_sock_get(object, &redis_sock TSRMLS_CC, 1) > 0) { if (zend_hash_find(Z_OBJPROP_P(object), "socket", - sizeof("socket"), (void **) &socket) == FAILURE) { + sizeof("socket"), (void **) &socket) == FAILURE) + { /* maybe there is a socket but the id isn't known.. what to do? */ } else { zend_list_delete(Z_LVAL_PP(socket)); /* the refcount should be decreased and the detructor called */ } - } else { - zend_clear_exception(TSRMLS_C); /* clear exception triggered by non-existent socket during connect(). */ } - redis_sock = redis_sock_create(host, host_len, port, timeout, persistent, persistent_id, retry_interval); + redis_sock = redis_sock_create(host, host_len, port, timeout, persistent, persistent_id, retry_interval, 0); if (redis_sock_server_open(redis_sock, 1 TSRMLS_CC) < 0) { redis_free_socket(redis_sock); @@ -802,43 +811,103 @@ PHP_METHOD(Redis, close) } /* }}} */ -/* {{{ proto boolean Redis::set(string key, mixed value) - */ -PHP_METHOD(Redis, set) -{ +/* {{{ proto boolean Redis::set(string key, mixed value, long timeout | array options) */ +PHP_METHOD(Redis, set) { zval *object; RedisSock *redis_sock; - char *key = NULL, *val = NULL, *cmd; + char *key = NULL, *val = NULL, *cmd, *exp_type = NULL, *set_type = NULL; int key_len, val_len, cmd_len; long expire = -1; int val_free = 0, key_free = 0; - zval *z_value; + zval *z_value, *z_opts = NULL; - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz|l", - &object, redis_ce, &key, &key_len, - &z_value, &expire) == FAILURE) { + // Make sure the arguments are correct + if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osz|z", + &object, redis_ce, &key, &key_len, &z_value, + &z_opts) == FAILURE) + { RETURN_FALSE; } - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + // Ensure we can grab our redis socket + if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + /* Our optional argument can either be a long (to support legacy SETEX */ + /* redirection), or an array with Redis >= 2.6.12 set options */ + if(z_opts && Z_TYPE_P(z_opts) != IS_LONG && Z_TYPE_P(z_opts) != IS_ARRAY) { RETURN_FALSE; } + /* Serialization, key prefixing */ val_free = redis_serialize(redis_sock, z_value, &val, &val_len TSRMLS_CC); - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - if(expire > 0) { - cmd_len = redis_cmd_format_static(&cmd, "SETEX", "sds", key, key_len, expire, val, val_len); + key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); + + if(z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) { + HashTable *kt = Z_ARRVAL_P(z_opts); + int type; + unsigned int ht_key_len; + unsigned long idx; + char *k; + zval **v; + + /* Iterate our option array */ + for(zend_hash_internal_pointer_reset(kt); + zend_hash_has_more_elements(kt) == SUCCESS; + zend_hash_move_forward(kt)) + { + // Grab key and value + type = zend_hash_get_current_key_ex(kt, &k, &ht_key_len, &idx, 0, NULL); + zend_hash_get_current_data(kt, (void**)&v); + + if(type == HASH_KEY_IS_STRING && (Z_TYPE_PP(v) == IS_LONG) && + (Z_LVAL_PP(v) > 0) && IS_EX_PX_ARG(k)) + { + exp_type = k; + expire = Z_LVAL_PP(v); + } else if(Z_TYPE_PP(v) == IS_STRING && IS_NX_XX_ARG(Z_STRVAL_PP(v))) { + set_type = Z_STRVAL_PP(v); + } + } + } else if(z_opts && Z_TYPE_P(z_opts) == IS_LONG) { + expire = Z_LVAL_P(z_opts); + } + + /* Now let's construct the command we want */ + if(exp_type && set_type) { + /* SET <key> <value> NX|XX PX|EX <timeout> */ + cmd_len = redis_cmd_format_static(&cmd, "SET", "ssssl", key, key_len, + val, val_len, set_type, 2, exp_type, + 2, expire); + } else if(exp_type) { + /* SET <key> <value> PX|EX <timeout> */ + cmd_len = redis_cmd_format_static(&cmd, "SET", "sssl", key, key_len, + val, val_len, exp_type, 2, expire); + } else if(set_type) { + /* SET <key> <value> NX|XX */ + cmd_len = redis_cmd_format_static(&cmd, "SET", "sss", key, key_len, + val, val_len, set_type, 2); + } else if(expire > 0) { + /* Backward compatible SETEX redirection */ + cmd_len = redis_cmd_format_static(&cmd, "SETEX", "sds", key, key_len, + expire, val, val_len); } else { - cmd_len = redis_cmd_format_static(&cmd, "SET", "ss", key, key_len, val, val_len); + /* SET <key> <value> */ + cmd_len = redis_cmd_format_static(&cmd, "SET", "ss", key, key_len, + val, val_len); } - if(val_free) efree(val); + + /* Free our key or value if we prefixed/serialized */ if(key_free) efree(key); + if(val_free) efree(val); - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_boolean_response); + /* Kick off the command */ + REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); + IF_ATOMIC() { + redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); + } + REDIS_PROCESS_RESPONSE(redis_boolean_response); } PHPAPI void redis_generic_setex(INTERNAL_FUNCTION_PARAMETERS, char *keyword) { @@ -1323,88 +1392,85 @@ PHP_METHOD(Redis, decrBy){ */ PHP_METHOD(Redis, getMultiple) { - zval *object, *array, **data; - HashTable *arr_hash; - HashPosition pointer; + zval *object, *z_args, **z_ele; + HashTable *hash; + HashPosition ptr; RedisSock *redis_sock; - char *cmd = "", *old_cmd = NULL; - int cmd_len = 0, array_count, elements = 1; + smart_str cmd = {0}; + int arg_count; - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", - &object, redis_ce, &array) == FAILURE) { + // Make sure we have proper arguments + if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", + &object, redis_ce, &z_args) == FAILURE) { RETURN_FALSE; } - if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + // We'll need the socket + if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } - arr_hash = Z_ARRVAL_P(array); - array_count = zend_hash_num_elements(arr_hash); + // Grab our array + hash = Z_ARRVAL_P(z_args); - if (array_count == 0) { + // We don't need to do anything if there aren't any keys + if((arg_count = zend_hash_num_elements(hash)) == 0) { RETURN_FALSE; } - for (zend_hash_internal_pointer_reset_ex(arr_hash, &pointer); - zend_hash_get_current_data_ex(arr_hash, (void**) &data, - &pointer) == SUCCESS; - zend_hash_move_forward_ex(arr_hash, &pointer)) { + // Build our command header + redis_cmd_init_sstr(&cmd, arg_count, "MGET", 4); + // Iterate through and grab our keys + for(zend_hash_internal_pointer_reset_ex(hash, &ptr); + zend_hash_get_current_data_ex(hash, (void**)&z_ele, &ptr) == SUCCESS; + zend_hash_move_forward_ex(hash, &ptr)) + { char *key; - int key_len; + int key_len, key_free; zval *z_tmp = NULL; - char *old_cmd; - int key_free; - if (Z_TYPE_PP(data) == IS_STRING) { - key = Z_STRVAL_PP(data); - key_len = Z_STRLEN_PP(data); - } else { /* not a string, copy and convert. */ + // If the key isn't a string, turn it into one + if(Z_TYPE_PP(z_ele) == IS_STRING) { + key = Z_STRVAL_PP(z_ele); + key_len = Z_STRLEN_PP(z_ele); + } else { MAKE_STD_ZVAL(z_tmp); - *z_tmp = **data; + *z_tmp = **z_ele; zval_copy_ctor(z_tmp); convert_to_string(z_tmp); key = Z_STRVAL_P(z_tmp); key_len = Z_STRLEN_P(z_tmp); } - old_cmd = NULL; - if(*cmd) { - old_cmd = cmd; - } - key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - cmd_len = redis_cmd_format(&cmd, "%s$%d" _NL "%s" _NL - , cmd, cmd_len - , key_len, key, key_len); - if(key_free) efree(key); + // Apply key prefix if necissary + key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - if(old_cmd) { - efree(old_cmd); - } - elements++; + // Append this key to our command + redis_cmd_append_sstr(&cmd, key, key_len); + + // Free our key if it was prefixed + if(key_free) efree(key); + + // Free oour temporary ZVAL if we converted from a non-string if(z_tmp) { zval_dtor(z_tmp); efree(z_tmp); + z_tmp = NULL; } } - old_cmd = cmd; - cmd_len = redis_cmd_format(&cmd, "*%d" _NL "$4" _NL "MGET" _NL "%s", elements, cmd, cmd_len); - efree(old_cmd); - - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); + // Kick off our command + REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len); IF_ATOMIC() { - if (redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock, NULL, NULL) < 0) { - RETURN_FALSE; - } + if(redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, + redis_sock, NULL, NULL) < 0) { + RETURN_FALSE; + } } REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); - } -/* }}} */ /* {{{ proto boolean Redis::exists(string key) */ @@ -2360,7 +2426,6 @@ PHP_METHOD(Redis, sMembers) } /* }}} */ - PHPAPI int generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAMETERS, char *keyword, int keyword_len, int min_argc, RedisSock **out_sock, int has_timeout, int all_keys, int can_serialize) { @@ -4290,187 +4355,167 @@ PHP_METHOD(Redis, zIncrBy) generic_incrby_method(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZINCRBY", sizeof("ZINCRBY")-1); } /* }}} */ -PHPAPI void generic_z_command(INTERNAL_FUNCTION_PARAMETERS, char *command, int command_len) { - - zval *object, *keys_array, *weights_array = NULL, **data; - HashTable *arr_weights_hash = NULL, *arr_keys_hash; - int key_output_len, array_weights_count, array_keys_count, operation_len = 0; - char *key_output, *operation; - RedisSock *redis_sock; - HashPosition pointer; - char *cmd = ""; - char *old_cmd; - int cmd_len, cmd_elements; - int free_key_output; - - if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa|as", - &object, redis_ce, - &key_output, &key_output_len, &keys_array, &weights_array, &operation, &operation_len) == FAILURE) { - RETURN_FALSE; - } +PHPAPI void generic_z_command(INTERNAL_FUNCTION_PARAMETERS, char *command, int command_len) { + zval *object, *z_keys, *z_weights = NULL, **z_data; + HashTable *ht_keys, *ht_weights = NULL; + RedisSock *redis_sock; + smart_str cmd = {0}; + HashPosition ptr; + char *store_key, *agg_op = NULL; + int cmd_arg_count = 2, store_key_len, agg_op_len = 0, keys_count; + + // Grab our parameters + if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa|a!s", + &object, redis_ce, &store_key, &store_key_len, + &z_keys, &z_weights, &agg_op, &agg_op_len) == FAILURE) + { + RETURN_FALSE; + } + // We'll need our socket if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { - RETURN_FALSE; + RETURN_FALSE; } - arr_keys_hash = Z_ARRVAL_P(keys_array); - array_keys_count = zend_hash_num_elements(arr_keys_hash); + // Grab our keys argument as an array + ht_keys = Z_ARRVAL_P(z_keys); - if (array_keys_count == 0) { + // Nothing to do if there aren't any keys + if((keys_count = zend_hash_num_elements(ht_keys)) == 0) { RETURN_FALSE; + } else { + // Increment our overall argument count + cmd_arg_count += keys_count; } - if(weights_array != NULL) { - arr_weights_hash = Z_ARRVAL_P(weights_array); - array_weights_count = zend_hash_num_elements(arr_weights_hash); - if (array_weights_count == 0) { - RETURN_FALSE; - } - if((array_weights_count != 0) && (array_weights_count != array_keys_count)) { - RETURN_FALSE; - } + // Grab and validate our weights array + if(z_weights != NULL) { + ht_weights = Z_ARRVAL_P(z_weights); - } + // This command is invalid if the weights array isn't the same size + // as our keys array. + if(zend_hash_num_elements(ht_weights) != keys_count) { + RETURN_FALSE; + } - free_key_output = redis_key_prefix(redis_sock, &key_output, &key_output_len TSRMLS_CC); - cmd_elements = 3; - cmd_len = redis_cmd_format(&cmd, - "$%d" _NL /* command_len */ - "%s" _NL /* command */ + // Increment our overall argument count by the number of keys + // plus one, for the "WEIGHTS" argument itself + cmd_arg_count += keys_count + 1; + } - "$%d" _NL /* key_output_len */ - "%s" _NL /* key_output */ + // AGGREGATE option + if(agg_op_len != 0) { + // Verify our aggregation option + if(strncasecmp(agg_op, "SUM", sizeof("SUM")) && + strncasecmp(agg_op, "MIN", sizeof("MIN")) && + strncasecmp(agg_op, "MAX", sizeof("MAX"))) + { + RETURN_FALSE; + } - "$%d" _NL - "%d" _NL /* array_keys_count */ + // Two more arguments: "AGGREGATE" and agg_op + cmd_arg_count += 2; + } - , command_len, command, command_len - , key_output_len, key_output, key_output_len - , integer_length(array_keys_count), array_keys_count); - if(free_key_output) efree(key_output); + // Command header + redis_cmd_init_sstr(&cmd, cmd_arg_count, command, command_len); - /* keys */ - for (zend_hash_internal_pointer_reset_ex(arr_keys_hash, &pointer); - zend_hash_get_current_data_ex(arr_keys_hash, (void**) &data, - &pointer) == SUCCESS; - zend_hash_move_forward_ex(arr_keys_hash, &pointer)) { + // Prefix our key if necessary and add the output key + int key_free = redis_key_prefix(redis_sock, &store_key, &store_key_len TSRMLS_CC); + redis_cmd_append_sstr(&cmd, store_key, store_key_len); + if(key_free) efree(store_key); - if (Z_TYPE_PP(data) == IS_STRING) { - char *old_cmd = NULL; - char *data_str; - int data_len; - int free_data; - - if(*cmd) { - old_cmd = cmd; - } - data_str = Z_STRVAL_PP(data); - data_len = Z_STRLEN_PP(data); - - free_data = redis_key_prefix(redis_sock, &data_str, &data_len TSRMLS_CC); - cmd_len = redis_cmd_format(&cmd, - "%s" /* cmd */ - "$%d" _NL - "%s" _NL - , cmd, cmd_len - , data_len, data_str, data_len); - cmd_elements++; - if(free_data) efree(data_str); - if(old_cmd) { - efree(old_cmd); - } + // Number of input keys argument + redis_cmd_append_sstr_int(&cmd, keys_count); + + // Process input keys + for(zend_hash_internal_pointer_reset_ex(ht_keys, &ptr); + zend_hash_get_current_data_ex(ht_keys, (void**)&z_data, &ptr)==SUCCESS; + zend_hash_move_forward_ex(ht_keys, &ptr)) + { + char *key; + int key_free, key_len; + zval *z_tmp = NULL; + + if(Z_TYPE_PP(z_data) == IS_STRING) { + key = Z_STRVAL_PP(z_data); + key_len = Z_STRLEN_PP(z_data); + } else { + MAKE_STD_ZVAL(z_tmp); + *z_tmp = **z_data; + convert_to_string(z_tmp); + + key = Z_STRVAL_P(z_tmp); + key_len = Z_STRLEN_P(z_tmp); } - } - /* weight */ - if(weights_array != NULL) { - cmd_len = redis_cmd_format(&cmd, - "%s" /* cmd */ - "$7" _NL - "WEIGHTS" _NL - , cmd, cmd_len); - cmd_elements++; - - for (zend_hash_internal_pointer_reset_ex(arr_weights_hash, &pointer); - zend_hash_get_current_data_ex(arr_weights_hash, (void**) &data, &pointer) == SUCCESS; - zend_hash_move_forward_ex(arr_weights_hash, &pointer)) { - - // Ignore non numeric arguments, unless they're the special Redis numbers - // "inf" ,"-inf", and "+inf" which can be passed as weights - if (Z_TYPE_PP(data) != IS_LONG && Z_TYPE_PP(data) != IS_DOUBLE && - strncasecmp(Z_STRVAL_PP(data), "inf", sizeof("inf")) != 0 && - strncasecmp(Z_STRVAL_PP(data), "-inf", sizeof("-inf")) != 0 && - strncasecmp(Z_STRVAL_PP(data), "+inf", sizeof("+inf")) != 0) - { - continue; - } + // Apply key prefix if necessary + key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); - old_cmd = NULL; - if(*cmd) { - old_cmd = cmd; - } + // Append this input set + redis_cmd_append_sstr(&cmd, key, key_len); - if(Z_TYPE_PP(data) == IS_LONG) { - cmd_len = redis_cmd_format(&cmd, - "%s" /* cmd */ - "$%d" _NL /* data_len */ - "%d" _NL /* data */ - , cmd, cmd_len - , integer_length(Z_LVAL_PP(data)), Z_LVAL_PP(data)); - - } else if(Z_TYPE_PP(data) == IS_DOUBLE) { - cmd_len = redis_cmd_format(&cmd, - "%s" /* cmd */ - "$%f" _NL /* data, including size */ - , cmd, cmd_len - , Z_DVAL_PP(data)); - } else if(Z_TYPE_PP(data) == IS_STRING) { - cmd_len = redis_cmd_format(&cmd, - "%s" /* cmd */ - "$%d" _NL /* data len */ - "%s" _NL /* data */ - , cmd, cmd_len, Z_STRLEN_PP(data), - Z_STRVAL_PP(data), Z_STRLEN_PP(data)); - } + // Free our key if it was prefixed + if(key_free) efree(key); - // keep track of elements added - cmd_elements++; - if(old_cmd) { - efree(old_cmd); - } - } - } + // Free our temporary z_val if it was converted + if(z_tmp) { + zval_dtor(z_tmp); + efree(z_tmp); + z_tmp = NULL; + } + } - if(operation_len != 0) { - char *old_cmd = NULL; - old_cmd = cmd; - cmd_len = redis_cmd_format(&cmd, - "%s" /* cmd */ - "$9" _NL - "AGGREGATE" _NL - "$%d" _NL - "%s" _NL - , cmd, cmd_len - , operation_len, operation, operation_len); - cmd_elements += 2; - efree(old_cmd); - } + // Weights + if(ht_weights != NULL) { + // Append "WEIGHTS" argument + redis_cmd_append_sstr(&cmd, "WEIGHTS", sizeof("WEIGHTS") - 1); + + // Process weights + for(zend_hash_internal_pointer_reset_ex(ht_weights, &ptr); + zend_hash_get_current_data_ex(ht_weights, (void**)&z_data, &ptr)==SUCCESS; + zend_hash_move_forward_ex(ht_weights, &ptr)) + { + // Ignore non numeric arguments, unless they're special Redis numbers + if (Z_TYPE_PP(z_data) != IS_LONG && Z_TYPE_PP(z_data) != IS_DOUBLE && + strncasecmp(Z_STRVAL_PP(z_data), "inf", sizeof("inf")) != 0 && + strncasecmp(Z_STRVAL_PP(z_data), "-inf", sizeof("-inf")) != 0 && + strncasecmp(Z_STRVAL_PP(z_data), "+inf", sizeof("+inf")) != 0) + { + // We should abort if we have an invalid weight, rather than pass + // a different number of weights than the user is expecting + efree(cmd.c); + RETURN_FALSE; + } - old_cmd = cmd; - cmd_len = redis_cmd_format(&cmd, - "*%d" _NL - "%s" - , cmd_elements - , cmd, cmd_len); - efree(old_cmd); + // Append the weight based on the input type + switch(Z_TYPE_PP(z_data)) { + case IS_LONG: + redis_cmd_append_sstr_long(&cmd, Z_LVAL_PP(z_data)); + break; + case IS_DOUBLE: + redis_cmd_append_sstr_dbl(&cmd, Z_DVAL_PP(z_data)); + break; + case IS_STRING: + redis_cmd_append_sstr(&cmd, Z_STRVAL_PP(z_data), Z_STRLEN_PP(z_data)); + break; + } + } + } - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_long_response); + // Aggregation options, if we have them + if(agg_op_len != 0) { + redis_cmd_append_sstr(&cmd, "AGGREGATE", sizeof("AGGREGATE") - 1); + redis_cmd_append_sstr(&cmd, agg_op, agg_op_len); + } + // Kick off our request + REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len); + IF_ATOMIC() { + redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); + } + REDIS_PROCESS_RESPONSE(redis_long_response); } /* zInter */ @@ -5885,6 +5930,56 @@ PHP_METHOD(Redis, config) /* }}} */ +/* {{{ proto boolean Redis::slowlog(string arg, [int option]) + */ +PHP_METHOD(Redis, slowlog) { + zval *object; + RedisSock *redis_sock; + char *arg, *cmd; + int arg_len, cmd_len; + long option; + enum {SLOWLOG_GET, SLOWLOG_LEN, SLOWLOG_RESET} mode; + + // Make sure we can get parameters + if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|l", + &object, redis_ce, &arg, &arg_len, &option) == FAILURE) + { + RETURN_FALSE; + } + + // Figure out what kind of slowlog command we're executing + if(!strncasecmp(arg, "GET", 3)) { + mode = SLOWLOG_GET; + } else if(!strncasecmp(arg, "LEN", 3)) { + mode = SLOWLOG_LEN; + } else if(!strncasecmp(arg, "RESET", 5)) { + mode = SLOWLOG_RESET; + } else { + // This command is not valid + RETURN_FALSE; + } + + // Make sure we can grab our redis socket + if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + // Create our command. For everything except SLOWLOG GET (with an arg) it's just two parts + if(mode == SLOWLOG_GET && ZEND_NUM_ARGS() == 2) { + cmd_len = redis_cmd_format_static(&cmd, "SLOWLOG", "sl", arg, arg_len, option); + } else { + cmd_len = redis_cmd_format_static(&cmd, "SLOWLOG", "s", arg, arg_len); + } + + // Kick off our command + REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); + IF_ATOMIC() { + if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL) < 0) { + RETURN_FALSE; + } + } + REDIS_PROCESS_RESPONSE(redis_read_variant_reply); +} // Construct an EVAL or EVALSHA command, with option argument array and number of arguments that are keys parameter PHPAPI int @@ -6511,9 +6606,6 @@ PHP_METHOD(Redis, client) { cmd_len = redis_cmd_format_static(&cmd, "CLIENT", "s", opt, opt_len); } - // Handle CLIENT LIST specifically - int is_list = !strncasecmp(opt, "list", 4); - // Execute our queue command REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); diff --git a/redis_array.c b/redis_array.c index 31f77278..ecc158ea 100644 --- a/redis_array.c +++ b/redis_array.c @@ -194,9 +194,10 @@ PHP_METHOD(RedisArray, __construct) zval *z0, *z_fun = NULL, *z_dist = NULL, **zpData, *z_opts = NULL; int id; RedisArray *ra = NULL; - zend_bool b_index = 0, b_autorehash = 0; + zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0; HashTable *hPrev = NULL, *hOpts = NULL; long l_retry_interval = 0; + zend_bool b_lazy_connect = 0; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|a", &z0, &z_opts) == FAILURE) { RETURN_FALSE; @@ -238,9 +239,14 @@ PHP_METHOD(RedisArray, __construct) b_autorehash = Z_BVAL_PP(zpData); } + /* pconnect */ + if(FAILURE != zend_hash_find(hOpts, "pconnect", sizeof("pconnect"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_BOOL) { + b_pconnect = Z_BVAL_PP(zpData); + } + /* extract retry_interval option. */ - zval **z_retry_interval_pp; - if (FAILURE != zend_hash_find(hOpts, "retry_interval", sizeof("retry_interval"), (void**)&z_retry_interval_pp)) { + zval **z_retry_interval_pp; + if (FAILURE != zend_hash_find(hOpts, "retry_interval", sizeof("retry_interval"), (void**)&z_retry_interval_pp)) { if (Z_TYPE_PP(z_retry_interval_pp) == IS_LONG || Z_TYPE_PP(z_retry_interval_pp) == IS_STRING) { if (Z_TYPE_PP(z_retry_interval_pp) == IS_LONG) { l_retry_interval = Z_LVAL_PP(z_retry_interval_pp); @@ -250,6 +256,11 @@ PHP_METHOD(RedisArray, __construct) } } } + + /* extract lazy connect option. */ + if(FAILURE != zend_hash_find(hOpts, "lazy_connect", sizeof("lazy_connect"), (void**)&zpData) && Z_TYPE_PP(zpData) == IS_BOOL) { + b_lazy_connect = Z_BVAL_PP(zpData); + } } /* extract either name of list of hosts from z0 */ @@ -259,7 +270,7 @@ PHP_METHOD(RedisArray, __construct) break; case IS_ARRAY: - ra = ra_make_array(Z_ARRVAL_P(z0), z_fun, z_dist, hPrev, b_index, l_retry_interval TSRMLS_CC); + ra = ra_make_array(Z_ARRVAL_P(z0), z_fun, z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect TSRMLS_CC); break; default: @@ -826,18 +837,34 @@ PHP_METHOD(RedisArray, mget) for (i = 0, zend_hash_internal_pointer_reset_ex(h_keys, &pointer); zend_hash_get_current_data_ex(h_keys, (void**) &data, &pointer) == SUCCESS; - zend_hash_move_forward_ex(h_keys, &pointer), ++i) { - - if (Z_TYPE_PP(data) != IS_STRING) { - php_error_docref(NULL TSRMLS_CC, E_ERROR, "MGET: all keys must be string."); - efree(argv); - efree(pos); - efree(redis_instances); - efree(argc_each); - RETURN_FALSE; - } + zend_hash_move_forward_ex(h_keys, &pointer), ++i) + { + /* If we need to represent a long key as a string */ + unsigned int key_len; + char kbuf[40], *key_lookup; + + /* phpredis proper can only use string or long keys, so restrict to that here */ + if(Z_TYPE_PP(data) != IS_STRING && Z_TYPE_PP(data) != IS_LONG) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "MGET: all keys must be strings or longs"); + efree(argv); + efree(pos); + efree(redis_instances); + efree(argc_each); + RETURN_FALSE; + } + + /* Convert to a string for hash lookup if it isn't one */ + if(Z_TYPE_PP(data) == IS_STRING) { + key_len = Z_STRLEN_PP(data); + key_lookup = Z_STRVAL_PP(data); + } else { + key_len = snprintf(kbuf, sizeof(kbuf), "%ld", Z_LVAL_PP(data)); + key_lookup = (char*)kbuf; + } + + /* Find our node */ + redis_instances[i] = ra_find_node(ra, key_lookup, key_len, &pos[i] TSRMLS_CC); - redis_instances[i] = ra_find_node(ra, Z_STRVAL_PP(data), Z_STRLEN_PP(data), &pos[i] TSRMLS_CC); argc_each[pos[i]]++; /* count number of keys per node */ argv[i] = *data; } @@ -930,8 +957,8 @@ PHP_METHOD(RedisArray, mset) int *pos, argc, *argc_each; HashTable *h_keys; zval **redis_instances, *redis_inst, **argv; - char *key, **keys; - unsigned int key_len; + char *key, **keys, **key_free, kbuf[40]; + unsigned int key_len, free_idx = 0; int type, *key_lens; unsigned long idx; @@ -953,31 +980,43 @@ PHP_METHOD(RedisArray, mset) argv = emalloc(argc * sizeof(zval*)); pos = emalloc(argc * sizeof(int)); keys = emalloc(argc * sizeof(char*)); - key_lens = emalloc(argc * sizeof(int)); + key_lens = emalloc(argc * sizeof(int)); redis_instances = emalloc(argc * sizeof(zval*)); memset(redis_instances, 0, argc * sizeof(zval*)); + /* Allocate an array holding the indexes of any keys that need freeing */ + key_free = emalloc(argc * sizeof(char*)); + argc_each = emalloc(ra->count * sizeof(int)); memset(argc_each, 0, ra->count * sizeof(int)); /* associate each key to a redis node */ for(i = 0, zend_hash_internal_pointer_reset(h_keys); zend_hash_has_more_elements(h_keys) == SUCCESS; - zend_hash_move_forward(h_keys), i++) { - - type = zend_hash_get_current_key_ex(h_keys, &key, &key_len, &idx, 0, NULL); - if(type != HASH_KEY_IS_STRING) { /* ignore non-string keys */ - continue; - } - if(zend_hash_get_current_data(h_keys, (void**)&data) == FAILURE) { - continue; - } - - redis_instances[i] = ra_find_node(ra, key, (int)key_len - 1, &pos[i] TSRMLS_CC); /* -1 because of PHP assoc keys which count \0... */ + zend_hash_move_forward(h_keys), i++) + { + /* We have to skip the element if we can't get the array value */ + if(zend_hash_get_current_data(h_keys, (void**)&data) == FAILURE) { + continue; + } + + /* Grab our key */ + type = zend_hash_get_current_key_ex(h_keys, &key, &key_len, &idx, 0, NULL); + + /* If the key isn't a string, make a string representation of it */ + if(type != HASH_KEY_IS_STRING) { + key_len = snprintf(kbuf, sizeof(kbuf), "%ld", (long)idx); + key = estrndup(kbuf, key_len); + key_free[free_idx++]=key; + } else { + key_len--; /* We don't want the null terminator */ + } + + redis_instances[i] = ra_find_node(ra, key, (int)key_len, &pos[i] TSRMLS_CC); argc_each[pos[i]]++; /* count number of keys per node */ argv[i] = *data; keys[i] = key; - key_lens[i] = (int)key_len - 1; + key_lens[i] = (int)key_len; } @@ -1030,8 +1069,14 @@ PHP_METHOD(RedisArray, mset) zval_ptr_dtor(&z_argarray); } + /* Free any keys that we needed to allocate memory for, because they weren't strings */ + for(i=0; i<free_idx; i++) { + efree(key_free[i]); + } + /* cleanup */ efree(keys); + efree(key_free); efree(key_lens); efree(argv); efree(pos); diff --git a/redis_array.h b/redis_array.h index e61b5977..652b5aef 100644 --- a/redis_array.h +++ b/redis_array.h @@ -41,6 +41,7 @@ typedef struct RedisArray_ { zval *z_multi_exec; /* Redis instance to be used in multi-exec */ zend_bool index; /* use per-node index */ zend_bool auto_rehash; /* migrate keys on read operations */ + zend_bool pconnect; /* should we use pconnect */ zval *z_fun; /* key extractor, callable */ zval *z_dist; /* key distributor, callable */ zval *z_pure_cmds; /* hash table */ diff --git a/redis_array_impl.c b/redis_array_impl.c index bafcbd49..c3a1f365 100644 --- a/redis_array_impl.c +++ b/redis_array_impl.c @@ -30,7 +30,7 @@ extern int le_redis_sock; extern zend_class_entry *redis_ce; RedisArray* -ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval TSRMLS_DC) +ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC) { int i, host_len, id; int count = zend_hash_num_elements(hosts); @@ -59,7 +59,9 @@ ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval TSRMLS_DC) if((p = strchr(host, ':'))) { /* found port */ host_len = p - host; port = (short)atoi(p+1); - } + } else if(strchr(host,'/') != NULL) { /* unix socket */ + port = -1; + } /* create Redis object */ MAKE_STD_ZVAL(ra->redis[i]); @@ -68,10 +70,13 @@ ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval TSRMLS_DC) call_user_function(&redis_ce->function_table, &ra->redis[i], &z_cons, &z_ret, 0, NULL TSRMLS_CC); /* create socket */ - redis_sock = redis_sock_create(host, host_len, port, 0, 0, NULL, retry_interval); /* TODO: persistence? */ + redis_sock = redis_sock_create(host, host_len, port, 0, ra->pconnect, NULL, retry_interval, b_lazy_connect); - /* connect */ - redis_sock_server_open(redis_sock, 1 TSRMLS_CC); + if (!b_lazy_connect) + { + /* connect */ + redis_sock_server_open(redis_sock, 1 TSRMLS_CC); + } /* attach */ #if PHP_VERSION_ID >= 50400 @@ -160,10 +165,13 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) { zval *z_params_index; zval *z_params_autorehash; zval *z_params_retry_interval; + zval *z_params_pconnect; + zval *z_params_lazy_connect; RedisArray *ra = NULL; - zend_bool b_index = 0, b_autorehash = 0; + zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0; long l_retry_interval = 0; + zend_bool b_lazy_connect = 0; HashTable *hHosts = NULL, *hPrev = NULL; /* find entry */ @@ -241,8 +249,27 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) { } } + /* find pconnect option */ + MAKE_STD_ZVAL(z_params_pconnect); + array_init(z_params_pconnect); + sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.pconnect")), z_params_pconnect TSRMLS_CC); + if (zend_hash_find(Z_ARRVAL_P(z_params_pconnect), name, strlen(name) + 1, (void**) &z_data_pp) != FAILURE) { + if(Z_TYPE_PP(z_data_pp) == IS_STRING && strncmp(Z_STRVAL_PP(z_data_pp), "1", 1) == 0) { + b_pconnect = 1; + } + } + /* find retry interval option */ + MAKE_STD_ZVAL(z_params_lazy_connect); + array_init(z_params_lazy_connect); + sapi_module.treat_data(PARSE_STRING, estrdup(INI_STR("redis.arrays.lazyconnect")), z_params_lazy_connect TSRMLS_CC); + if (zend_hash_find(Z_ARRVAL_P(z_params_lazy_connect), name, strlen(name) + 1, (void **) &z_data_pp) != FAILURE) { + if(Z_TYPE_PP(z_data_pp) == IS_STRING && strncmp(Z_STRVAL_PP(z_data_pp), "1", 1) == 0) { + b_lazy_connect = 1; + } + } + /* create RedisArray object */ - ra = ra_make_array(hHosts, z_fun, z_dist, hPrev, b_index, l_retry_interval TSRMLS_CC); + ra = ra_make_array(hHosts, z_fun, z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect TSRMLS_CC); ra->auto_rehash = b_autorehash; /* cleanup */ @@ -258,12 +285,16 @@ RedisArray *ra_load_array(const char *name TSRMLS_DC) { efree(z_params_autorehash); zval_dtor(z_params_retry_interval); efree(z_params_retry_interval); + zval_dtor(z_params_pconnect); + efree(z_params_pconnect); + zval_dtor(z_params_lazy_connect); + efree(z_params_lazy_connect); return ra; } RedisArray * -ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, long retry_interval TSRMLS_DC) { +ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC) { int count = zend_hash_num_elements(hosts); @@ -281,10 +312,10 @@ ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev /* init array data structures */ ra_init_function_table(ra); - if(NULL == ra_load_hosts(ra, hosts, retry_interval TSRMLS_CC)) { + if(NULL == ra_load_hosts(ra, hosts, retry_interval, b_lazy_connect TSRMLS_CC)) { return NULL; } - ra->prev = hosts_prev ? ra_make_array(hosts_prev, z_fun, z_dist, NULL, b_index, retry_interval TSRMLS_CC) : NULL; + ra->prev = hosts_prev ? ra_make_array(hosts_prev, z_fun, z_dist, NULL, b_index, b_pconnect, retry_interval, b_lazy_connect TSRMLS_CC) : NULL; /* copy function if provided */ if(z_fun) { @@ -398,34 +429,34 @@ ra_call_distributor(RedisArray *ra, const char *key, int key_len, int *pos TSRML zval * ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos TSRMLS_DC) { - uint32_t hash; - char *out; - int pos, out_len; - - /* extract relevant part of the key */ - out = ra_extract_key(ra, key, key_len, &out_len TSRMLS_CC); - if(!out) - return NULL; - - if(ra->z_dist) { - if (!ra_call_distributor(ra, key, key_len, &pos TSRMLS_CC)) { - return NULL; - } - } - else { - /* hash */ - hash = rcrc32(out, out_len); - efree(out); - - /* get position on ring */ - uint64_t h64 = hash; - h64 *= ra->count; - h64 /= 0xffffffff; - pos = (int)h64; - } - if(out_pos) *out_pos = pos; - - return ra->redis[pos]; + uint32_t hash; + char *out; + int pos, out_len; + + /* extract relevant part of the key */ + out = ra_extract_key(ra, key, key_len, &out_len TSRMLS_CC); + if(!out) + return NULL; + + if(ra->z_dist) { + if (!ra_call_distributor(ra, key, key_len, &pos TSRMLS_CC)) { + return NULL; + } + } + else { + /* hash */ + hash = rcrc32(out, out_len); + efree(out); + + /* get position on ring */ + uint64_t h64 = hash; + h64 *= ra->count; + h64 /= 0xffffffff; + pos = (int)h64; + } + if(out_pos) *out_pos = pos; + + return ra->redis[pos]; } zval * diff --git a/redis_array_impl.h b/redis_array_impl.h index 8dd5201b..10f8512a 100644 --- a/redis_array_impl.h +++ b/redis_array_impl.h @@ -5,9 +5,9 @@ #include "common.h" #include "redis_array.h" -RedisArray *ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval TSRMLS_DC); +RedisArray *ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC); RedisArray *ra_load_array(const char *name TSRMLS_DC); -RedisArray *ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, long retry_interval TSRMLS_DC); +RedisArray *ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC); zval *ra_find_node_by_name(RedisArray *ra, const char *host, int host_len TSRMLS_DC); zval *ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos TSRMLS_DC); void ra_init_function_table(RedisArray *ra); diff --git a/redis_session.c b/redis_session.c index 4e633fc9..591f07b8 100644 --- a/redis_session.c +++ b/redis_session.c @@ -278,9 +278,9 @@ PS_OPEN_FUNC(redis) RedisSock *redis_sock; if(url->host) { - redis_sock = redis_sock_create(url->host, strlen(url->host), url->port, timeout, persistent, persistent_id, retry_interval); + redis_sock = redis_sock_create(url->host, strlen(url->host), url->port, timeout, persistent, persistent_id, retry_interval, 0); } else { /* unix */ - redis_sock = redis_sock_create(url->path, strlen(url->path), 0, timeout, persistent, persistent_id, retry_interval); + redis_sock = redis_sock_create(url->path, strlen(url->path), 0, timeout, persistent, persistent_id, retry_interval, 0); } redis_pool_add(pool, redis_sock, weight, database, prefix, auth TSRMLS_CC); diff --git a/rpm/php-redis.spec b/rpm/php-redis.spec index 714854bc..4f04fb18 100644 --- a/rpm/php-redis.spec +++ b/rpm/php-redis.spec @@ -3,7 +3,7 @@ %global php_version %(php-config --version 2>/dev/null || echo 0) Name: php-redis -Version: 2.2.3 +Version: 2.2.4 Release: 1%{?dist} Summary: The phpredis extension provides an API for communicating with the Redis key-value store. diff --git a/tests/TestRedis.php b/tests/TestRedis.php index 6091a94d..d19f9e0b 100644 --- a/tests/TestRedis.php +++ b/tests/TestRedis.php @@ -138,84 +138,140 @@ class Redis_Test extends TestSuite public function testSet() { - $this->assertEquals(TRUE, $this->redis->set('key', 'nil')); - $this->assertEquals('nil', $this->redis->get('key')); + $this->assertEquals(TRUE, $this->redis->set('key', 'nil')); + $this->assertEquals('nil', $this->redis->get('key')); - $this->assertEquals(TRUE, $this->redis->set('key', 'val')); + $this->assertEquals(TRUE, $this->redis->set('key', 'val')); - $this->assertEquals('val', $this->redis->get('key')); - $this->assertEquals('val', $this->redis->get('key')); - $this->redis->delete('keyNotExist'); - $this->assertEquals(FALSE, $this->redis->get('keyNotExist')); + $this->assertEquals('val', $this->redis->get('key')); + $this->assertEquals('val', $this->redis->get('key')); + $this->redis->delete('keyNotExist'); + $this->assertEquals(FALSE, $this->redis->get('keyNotExist')); - $this->redis->set('key2', 'val'); - $this->assertEquals('val', $this->redis->get('key2')); + $this->redis->set('key2', 'val'); + $this->assertEquals('val', $this->redis->get('key2')); - $value = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; - $this->redis->set('key2', $value); - $this->assertEquals($value, $this->redis->get('key2')); - $this->assertEquals($value, $this->redis->get('key2')); + $value = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; + + $this->redis->set('key2', $value); + $this->assertEquals($value, $this->redis->get('key2')); + $this->assertEquals($value, $this->redis->get('key2')); - $this->redis->delete('key'); - $this->redis->delete('key2'); + $this->redis->delete('key'); + $this->redis->delete('key2'); - $i = 66000; - $value2 = 'X'; - while($i--) { - $value2 .= 'A'; - } - $value2 .= 'X'; + $i = 66000; + $value2 = 'X'; + while($i--) { + $value2 .= 'A'; + } + $value2 .= 'X'; - $this->redis->set('key', $value2); + $this->redis->set('key', $value2); $this->assertEquals($value2, $this->redis->get('key')); - $this->redis->delete('key'); - $this->assertEquals(False, $this->redis->get('key')); + $this->redis->delete('key'); + $this->assertEquals(False, $this->redis->get('key')); - $data = gzcompress('42'); + $data = gzcompress('42'); $this->assertEquals(True, $this->redis->set('key', $data)); - $this->assertEquals('42', gzuncompress($this->redis->get('key'))); + $this->assertEquals('42', gzuncompress($this->redis->get('key'))); - $this->redis->delete('key'); - $data = gzcompress('value1'); + $this->redis->delete('key'); + $data = gzcompress('value1'); $this->assertEquals(True, $this->redis->set('key', $data)); - $this->assertEquals('value1', gzuncompress($this->redis->get('key'))); - - $this->redis->delete('key'); - $this->assertEquals(TRUE, $this->redis->set('key', 0)); - $this->assertEquals('0', $this->redis->get('key')); - $this->assertEquals(TRUE, $this->redis->set('key', 1)); - $this->assertEquals('1', $this->redis->get('key')); - $this->assertEquals(TRUE, $this->redis->set('key', 0.1)); - $this->assertEquals('0.1', $this->redis->get('key')); - $this->assertEquals(TRUE, $this->redis->set('key', '0.1')); - $this->assertEquals('0.1', $this->redis->get('key')); - $this->assertEquals(TRUE, $this->redis->set('key', TRUE)); - $this->assertEquals('1', $this->redis->get('key')); - - $this->assertEquals(True, $this->redis->set('key', '')); - $this->assertEquals('', $this->redis->get('key')); - $this->assertEquals(True, $this->redis->set('key', NULL)); - $this->assertEquals('', $this->redis->get('key')); + $this->assertEquals('value1', gzuncompress($this->redis->get('key'))); + + $this->redis->delete('key'); + $this->assertEquals(TRUE, $this->redis->set('key', 0)); + $this->assertEquals('0', $this->redis->get('key')); + $this->assertEquals(TRUE, $this->redis->set('key', 1)); + $this->assertEquals('1', $this->redis->get('key')); + $this->assertEquals(TRUE, $this->redis->set('key', 0.1)); + $this->assertEquals('0.1', $this->redis->get('key')); + $this->assertEquals(TRUE, $this->redis->set('key', '0.1')); + $this->assertEquals('0.1', $this->redis->get('key')); + $this->assertEquals(TRUE, $this->redis->set('key', TRUE)); + $this->assertEquals('1', $this->redis->get('key')); + + $this->assertEquals(True, $this->redis->set('key', '')); + $this->assertEquals('', $this->redis->get('key')); + $this->assertEquals(True, $this->redis->set('key', NULL)); + $this->assertEquals('', $this->redis->get('key')); $this->assertEquals(True, $this->redis->set('key', gzcompress('42'))); $this->assertEquals('42', gzuncompress($this->redis->get('key'))); + } + /* Extended SET options for Redis >= 2.6.12 */ + public function testExtendedSet() { + // Skip the test if we don't have a new enough version of Redis + if(version_compare($this->version, '2.6.12', 'lt')) { + $this->markTestSkipped(); + return; + } + + /* Legacy SETEX redirection */ + $this->redis->del('foo'); + $this->assertTrue($this->redis->set('foo','bar', 20)); + $this->assertEquals($this->redis->get('foo'), 'bar'); + $this->assertEquals($this->redis->ttl('foo'), 20); + + /* Invalid third arguments */ + $this->assertFalse($this->redis->set('foo','bar','baz')); + $this->assertFalse($this->redis->set('foo','bar',new StdClass())); + + /* Set if not exist */ + $this->redis->del('foo'); + $this->assertTrue($this->redis->set('foo','bar',Array('nx'))); + $this->assertEquals($this->redis->get('foo'), 'bar'); + $this->assertFalse($this->redis->set('foo','bar',Array('nx'))); + + /* Set if exists */ + $this->assertTrue($this->redis->set('foo','bar',Array('xx'))); + $this->assertEquals($this->redis->get('foo'), 'bar'); + $this->redis->del('foo'); + $this->assertFalse($this->redis->set('foo','bar',Array('xx'))); + + /* Set with a TTL */ + $this->assertTrue($this->redis->set('foo','bar',Array('ex'=>100))); + $this->assertEquals($this->redis->ttl('foo'), 100); + + /* Set with a PTTL */ + $this->assertTrue($this->redis->set('foo','bar',Array('px'=>100000))); + $this->assertTrue(100000 - $this->redis->pttl('foo') < 1000); + + /* Set if exists, with a TTL */ + $this->assertTrue($this->redis->set('foo','bar',Array('xx','ex'=>105))); + $this->assertEquals($this->redis->ttl('foo'), 105); + $this->assertEquals($this->redis->get('foo'), 'bar'); + + /* Set if not exists, with a TTL */ + $this->redis->del('foo'); + $this->assertTrue($this->redis->set('foo','bar', Array('nx', 'ex'=>110))); + $this->assertEquals($this->redis->ttl('foo'), 110); + $this->assertEquals($this->redis->get('foo'), 'bar'); + $this->assertFalse($this->redis->set('foo','bar', Array('nx', 'ex'=>110))); + + /* Throw some nonsense into the array, and check that the TTL came through */ + $this->redis->del('foo'); + $this->assertTrue($this->redis->set('foo','barbaz', Array('not-valid','nx','invalid','ex'=>200))); + $this->assertEquals($this->redis->ttl('foo'), 200); + $this->assertEquals($this->redis->get('foo'), 'barbaz'); } - public function testGetSet() { - $this->redis->delete('key'); - $this->assertTrue($this->redis->getSet('key', '42') === FALSE); - $this->assertTrue($this->redis->getSet('key', '123') === '42'); - $this->assertTrue($this->redis->getSet('key', '123') === '123'); + public function testGetSet() { + $this->redis->delete('key'); + $this->assertTrue($this->redis->getSet('key', '42') === FALSE); + $this->assertTrue($this->redis->getSet('key', '123') === '42'); + $this->assertTrue($this->redis->getSet('key', '123') === '123'); } public function testRandomKey() { - for($i = 0; $i < 1000; $i++) { $k = $this->redis->randomKey(); - $this->assertTrue($this->redis->exists($k)); - } + $this->assertTrue($this->redis->exists($k)); + } } public function testRename() { @@ -1643,9 +1699,11 @@ class Redis_Test extends TestSuite $this->redis->del('x'); $this->redis->set('x', 'bar'); $this->assertEquals($this->redis->ttl('x'), -1); - // A key that doesn't exist - $this->redis->del('x'); - $this->assertEquals($this->redis->ttl('x'), -2); + // A key that doesn't exist (> 2.8 will return -2) + if(version_compare($this->version, "2.8.0", "gte")) { + $this->redis->del('x'); + $this->assertEquals($this->redis->ttl('x'), -2); + } } public function testPersist() { @@ -1684,6 +1742,16 @@ class Redis_Test extends TestSuite $this->assertTrue($this->redis->client('kill', $str_addr)); } + public function testSlowlog() { + // We don't really know what's going to be in the slowlog, but make sure + // the command returns proper types when called in various ways + $this->assertTrue(is_array($this->redis->slowlog('get'))); + $this->assertTrue(is_array($this->redis->slowlog('get', 10))); + $this->assertTrue(is_int($this->redis->slowlog('len'))); + $this->assertTrue($this->redis->slowlog('reset')); + $this->assertFalse($this->redis->slowlog('notvalid')); + } + public function testinfo() { $info = $this->redis->info(); @@ -1971,6 +2039,20 @@ class Redis_Test extends TestSuite $this->redis->delete('key2'); $this->redis->delete('key3'); + //test zUnion with weights and aggegration function + $this->redis->zadd('key1', 1, 'duplicate'); + $this->redis->zadd('key2', 2, 'duplicate'); + $this->redis->zUnion('keyU', array('key1','key2'), array(1,1), 'MIN'); + $this->assertTrue($this->redis->zScore('keyU', 'duplicate')===1.0); + $this->redis->delete('keyU'); + + //now test zUnion *without* weights but with aggregrate function + $this->redis->zUnion('keyU', array('key1','key2'), null, 'MIN'); + $this->assertTrue($this->redis->zScore('keyU', 'duplicate')===1.0); + $this->redis->delete('keyU', 'key1', 'key2'); + + + // test integer and float weights (GitHub issue #109). $this->redis->del('key1', 'key2', 'key3'); @@ -2087,6 +2169,10 @@ class Redis_Test extends TestSuite $this->assertTrue( 2 === $this->redis->zInter('keyI', array('key1', 'key2', 'key3'), array(1, 5, 1), 'max')); $this->assertTrue(array('val3', 'val1') === $this->redis->zRange('keyI', 0, -1)); + $this->redis->delete('keyI'); + $this->assertTrue(2 === $this->redis->zInter('keyI', array('key1', 'key2', 'key3'), null, 'max')); + $this->assertTrue($this->redis->zScore('keyI', 'val1') === floatval(7)); + // zrank, zrevrank $this->redis->delete('z'); $this->redis->zadd('z', 1, 'one'); |