diff options
author | michael-grunder <michael.grunder@gmail.com> | 2015-01-18 00:26:23 +0300 |
---|---|---|
committer | michael-grunder <michael.grunder@gmail.com> | 2015-01-18 00:26:23 +0300 |
commit | 3e13a1f514dbc479b274a8084920ee86f5b90ecb (patch) | |
tree | 2da47cf913ef218440f55b4afbb299612a9f1f3f | |
parent | 95ca34b71286a4dc887bf7ec075b820eb2a3b42c (diff) | |
parent | c702ae01dce407c3700c18f29d2f0ef83b13bec3 (diff) |
Merge branch 'develop'
Conflicts:
php_redis.h
-rw-r--r-- | README.markdown | 30 | ||||
-rw-r--r-- | common.h | 18 | ||||
-rw-r--r-- | library.c | 414 | ||||
-rw-r--r-- | library.h | 15 | ||||
-rw-r--r-- | php_redis.h | 10 | ||||
-rw-r--r-- | redis.c | 608 | ||||
-rw-r--r-- | redis_array.c | 13 | ||||
-rw-r--r-- | redis_array.h | 2 | ||||
-rw-r--r-- | tests/TestRedis.php | 183 | ||||
-rw-r--r-- | tests/test.php | 26 |
10 files changed, 1026 insertions, 293 deletions
diff --git a/README.markdown b/README.markdown index 634193ac..69783919 100644 --- a/README.markdown +++ b/README.markdown @@ -2488,6 +2488,7 @@ while(($arr_mems = $redis->sscan('set', $it, "*pattern*"))!==FALSE) { * [zInter](#zinter) - Intersect multiple sorted sets and store the resulting sorted set in a new key * [zRange](#zrange) - Return a range of members in a sorted set, by index * [zRangeByScore, zRevRangeByScore](#zrangebyscore-zrevrangebyscore) - Return a range of members in a sorted set, by score +* [zRangeByLex](#zrangebylex) - Return a lexigraphical range from members that share the same score * [zRank, zRevRank](#zrank-zrevrank) - Determine the index of a member in a sorted set * [zRem, zDelete](#zrem-zdelete) - Remove one or more members from a sorted set * [zRemRangeByRank, zDeleteRangeByRank](#zremrangebyrank-zdeleterangebyrank) - Remove all members in a sorted set within the given indexes @@ -2671,6 +2672,30 @@ $redis->zRangeByScore('key', 0, 3, array('limit' => array(1, 1)); /* array('val2 $redis->zRangeByScore('key', 0, 3, array('withscores' => TRUE, 'limit' => array(1, 1)); /* array('val2' => 2) */ ~~~ +### zRangeByLex +----- +_**Description**_: Returns a lexigraphical range of members in a sorted set, assuming the members have the same score. The min and max values are required to start with '(' (exclusive), '[' (inclusive), or be exactly the values '-' (negative inf) or '+' (positive inf). The command must be called with either three *or* five arguments or will return FALSE. + +##### *Parameters* +*key*: The ZSET you wish to run against +*min*: The minimum alphanumeric value you wish to get +*max*: The maximum alphanumeric value you wish to get +*offset*: Optional argument if you wish to start somewhere other than the first element. +*limit*: Optional argument if you wish to limit the number of elements returned. + +##### *Return value* +*Array* containing the values in the specified range. + +##### *Example* +~~~ +foreach(Array('a','b','c','d','e','f','g') as $c) + $redis->zAdd('key',0,$c); + +$redis->zRangeByLex('key','-','[c') /* Array('a','b','c'); */ +$redis->zRangeByLex('key','-','(c') /* Array('a','b') */ +$redis->zRangeByLex('key','-','[c',1,2) /* Array('b','c') */ +~~~ + ### zRank, zRevRank ----- _**Description**_: Returns the rank of a given member in the specified sorted set, starting at 0 for the item with the smallest score. zRevRank starts at 0 for the item with the *largest* score. @@ -2873,7 +2898,7 @@ _**Description**_: Subscribe to channels by pattern ##### *Parameters* *patterns*: An array of patterns to match *callback*: Either a string or an array with an object and method. The callback will get four arguments ($redis, $pattern, $channel, $message) - +*return value*: Mixed. Any non-null return value in the callback will be returned to the caller. ##### *Example* ~~~ function psubscribe($redis, $pattern, $chan, $msg) { @@ -2903,7 +2928,7 @@ _**Description**_: Subscribe to channels. Warning: this function will probably c ##### *Parameters* *channels*: an array of channels to subscribe to *callback*: either a string or an array($instance, 'method_name'). The callback function receives 3 parameters: the redis instance, the channel name, and the message. - +*return value*: Mixed. Any non-null return value in the callback will be returned to the caller. ##### *Example* ~~~ function f($redis, $chan, $msg) { @@ -3182,6 +3207,7 @@ $redis->_serialize(new stdClass()); // Returns "Object" $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); $redis->_serialize("foo"); // Returns 's:3:"foo";' +~~~ ### _unserialize ----- @@ -30,7 +30,8 @@ /* reply types */ typedef enum _REDIS_REPLY_TYPE { - TYPE_LINE = '+', + TYPE_EOF = EOF, + TYPE_LINE = '+', TYPE_INT = ':', TYPE_ERR = '-', TYPE_BULK = '$', @@ -64,7 +65,6 @@ typedef enum _PUBSUB_TYPE { #define REDIS_SERIALIZER_IGBINARY 2 /* SCAN options */ - #define REDIS_SCAN_NORETRY 0 #define REDIS_SCAN_RETRY 1 @@ -72,6 +72,14 @@ typedef enum _PUBSUB_TYPE { #define BITOP_MIN_OFFSET 0 #define BITOP_MAX_OFFSET 4294967295 +/* Specific error messages we want to throw against */ +#define REDIS_ERR_LOADING_MSG "LOADING Redis is loading the dataset in memory" +#define REDIS_ERR_LOADING_KW "LOADING" +#define REDIS_ERR_AUTH_MSG "NOAUTH Authentication required." +#define REDIS_ERR_AUTH_KW "NOAUTH" +#define REDIS_ERR_SYNC_MSG "MASTERDOWN Link with MASTER is down and slave-serve-stale-data is set to 'no'" +#define REDIS_ERR_SYNC_KW "MASTERDOWN" + #define IF_MULTI() if(redis_sock->mode == MULTI) #define IF_MULTI_OR_ATOMIC() if(redis_sock->mode == MULTI || redis_sock->mode == ATOMIC)\ @@ -178,6 +186,12 @@ else if(redis_sock->mode == MULTI) { \ #define IS_EX_PX_ARG(a) (IS_EX_ARG(a) || IS_PX_ARG(a)) #define IS_NX_XX_ARG(a) (IS_NX_ARG(a) || IS_XX_ARG(a)) +/* Given a string and length, validate a zRangeByLex argument. The semantics + * here are that the argument must start with '(' or '[' or be just the char + * '+' or '-' */ +#define IS_LEX_ARG(s,l) \ + (l>0 && (*s=='(' || *s=='[' || (l==1 && (*s=='+' || *s=='-')))) + typedef enum {ATOMIC, MULTI, PIPELINE} redis_mode; typedef struct fold_item { @@ -32,13 +32,102 @@ PHPAPI int usleep(unsigned int useconds); #define usleep Sleep #endif -#define UNSERIALIZE_ONLY_VALUES 0 -#define UNSERIALIZE_ALL 1 +#define UNSERIALIZE_NONE 0 +#define UNSERIALIZE_KEYS 1 +#define UNSERIALIZE_VALS 2 +#define UNSERIALIZE_ALL 3 + +#define SCORE_DECODE_NONE 0 +#define SCORE_DECODE_INT 1 +#define SCORE_DECODE_DOUBLE 2 extern zend_class_entry *redis_ce; extern zend_class_entry *redis_exception_ce; extern zend_class_entry *spl_ce_RuntimeException; +/* Helper to reselect the proper DB number when we reconnect */ +static int reselect_db(RedisSock *redis_sock TSRMLS_DC) { + char *cmd, *response; + int cmd_len, response_len; + + cmd_len = redis_cmd_format_static(&cmd, "SELECT", "d", redis_sock->dbNumber); + + if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { + efree(cmd); + return -1; + } + + efree(cmd); + + if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { + return -1; + } + + if (strncmp(response, "+OK", 3)) { + efree(response); + return -1; + } + + efree(response); + return 0; +} + +/* Helper to resend AUTH <password> in the case of a reconnect */ +static int resend_auth(RedisSock *redis_sock TSRMLS_DC) { + char *cmd, *response; + int cmd_len, response_len; + + cmd_len = redis_cmd_format_static(&cmd, "AUTH", "s", redis_sock->auth, + strlen(redis_sock->auth)); + + if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { + efree(cmd); + return -1; + } + + efree(cmd); + + response = redis_sock_read(redis_sock, &response_len TSRMLS_CC); + if (response == NULL) { + return -1; + } + + if (strncmp(response, "+OK", 3)) { + efree(response); + return -1; + } + + efree(response); + return 0; +} + +/* Helper function that will throw an exception for a small number of ERR codes + * returned by Redis. Typically we just return FALSE to the caller in the event + * of an ERROR reply, but for the following error types: + * 1) MASTERDOWN + * 2) AUTH + * 3) LOADING + */ +static void redis_error_throw(char *err, size_t err_len TSRMLS_DC) { + /* Handle stale data error (slave syncing with master) */ + if (err_len == sizeof(REDIS_ERR_SYNC_MSG) - 1 && + !memcmp(err,REDIS_ERR_SYNC_KW,sizeof(REDIS_ERR_SYNC_KW)-1)) + { + zend_throw_exception(redis_exception_ce, + "SYNC with master in progress or master down!", 0 TSRMLS_CC); + } else if (err_len == sizeof(REDIS_ERR_LOADING_MSG) - 1 && + !memcmp(err,REDIS_ERR_LOADING_KW,sizeof(REDIS_ERR_LOADING_KW)-1)) + { + zend_throw_exception(redis_exception_ce, + "Redis is LOADING the dataset", 0 TSRMLS_CC); + } else if (err_len == sizeof(REDIS_ERR_AUTH_MSG) -1 && + !memcmp(err,REDIS_ERR_AUTH_KW,sizeof(REDIS_ERR_AUTH_KW)-1)) + { + zend_throw_exception(redis_exception_ce, + "Failed to AUTH connection", 0 TSRMLS_CC); + } +} + PHP_REDIS_API void redis_stream_close(RedisSock *redis_sock TSRMLS_DC) { if (!redis_sock->persistent) { php_stream_close(redis_sock->stream); @@ -58,64 +147,60 @@ PHP_REDIS_API int redis_check_eof(RedisSock *redis_sock TSRMLS_DC) eof = php_stream_eof(redis_sock->stream); for (; eof; count++) { - if((MULTI == redis_sock->mode) || redis_sock->watching || count == 10) { /* too many failures */ - if(redis_sock->stream) { /* close stream if still here */ + /* Only try up to a certain point */ + if((MULTI == redis_sock->mode) || redis_sock->watching || count == 10) { + if(redis_sock->stream) { /* close stream if still here */ redis_stream_close(redis_sock TSRMLS_CC); redis_sock->stream = NULL; - redis_sock->mode = ATOMIC; + redis_sock->mode = ATOMIC; redis_sock->status = REDIS_SOCK_STATUS_FAILED; redis_sock->watching = 0; - } + } + zend_throw_exception(redis_exception_ce, "Connection lost", 0 TSRMLS_CC); - return -1; - } - if(redis_sock->stream) { /* close existing stream before reconnecting */ + return -1; + } + + /* Close existing stream before reconnecting */ + if(redis_sock->stream) { redis_stream_close(redis_sock TSRMLS_CC); redis_sock->stream = NULL; - redis_sock->mode = ATOMIC; + redis_sock->mode = ATOMIC; redis_sock->watching = 0; - } - /* Wait for a while before trying to reconnect */ - if (redis_sock->retry_interval) { - // Random factor to avoid having several (or many) concurrent connections trying to reconnect at the same time - long retry_interval = (count ? redis_sock->retry_interval : (php_rand(TSRMLS_C) % redis_sock->retry_interval)); - usleep(retry_interval); - } + } + + /* Wait for a while before trying to reconnect */ + if (redis_sock->retry_interval) { + // Random factor to avoid having several (or many) concurrent connections trying to reconnect at the same time + long retry_interval = (count ? redis_sock->retry_interval : (php_rand(TSRMLS_C) % redis_sock->retry_interval)); + usleep(retry_interval); + } + redis_sock_connect(redis_sock TSRMLS_CC); /* reconnect */ if(redis_sock->stream) { /* check for EOF again. */ eof = php_stream_eof(redis_sock->stream); } } - /* Reselect the DB. */ - if (count && redis_sock->dbNumber) { - char *cmd, *response; - int cmd_len, response_len; - - cmd_len = redis_cmd_format_static(&cmd, "SELECT", "d", redis_sock->dbNumber); - - if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { - efree(cmd); - return -1; - } - efree(cmd); - - if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) { + /* We've reconnected if we have a count */ + if (count) { + /* If we're using a password, attempt a reauthorization */ + if (redis_sock->auth && resend_auth(redis_sock TSRMLS_CC) != 0) { return -1; } - if (strncmp(response, "+OK", 3)) { - efree(response); + /* If we're using a non zero db, reselect it */ + if (redis_sock->dbNumber && reselect_db(redis_sock TSRMLS_CC) != 0) { return -1; } - efree(response); } + /* Success */ return 0; } -PHP_REDIS_API int +PHP_REDIS_API int redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, REDIS_SCAN_TYPE type, long *iter) { @@ -150,13 +235,13 @@ redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, scan command this is. They all come back in slightly different ways */ switch(type) { case TYPE_SCAN: - return redis_sock_read_multibulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); + return redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); case TYPE_SSCAN: return redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); case TYPE_ZSCAN: - return redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); + return redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); case TYPE_HSCAN: - return redis_sock_read_multibulk_reply_zipped_strings(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); + return redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); default: return -1; } @@ -189,9 +274,10 @@ PHP_REDIS_API zval *redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAM MAKE_STD_ZVAL(z_tab); array_init(z_tab); - redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock, z_tab, numElems, 1, UNSERIALIZE_ALL); - return z_tab; + redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, + numElems, UNSERIALIZE_ALL); + + return z_tab; } /** @@ -259,14 +345,14 @@ PHP_REDIS_API char *redis_sock_read(RedisSock *redis_sock, int *buf_len TSRMLS_D switch(inbuf[0]) { case '-': - err_len = strlen(inbuf+1) - 2; + /* Set the last error */ + err_len = strlen(inbuf+1) - 2; redis_sock_set_err(redis_sock, inbuf+1, err_len); - /* stale data */ - if(memcmp(inbuf + 1, "-ERR SYNC ", 10) == 0) { - zend_throw_exception(redis_exception_ce, "SYNC with master in progress", 0 TSRMLS_CC); - } - return NULL; + /* Filter our ERROR through the few that should actually throw */ + redis_error_throw(inbuf + 1, err_len TSRMLS_CC); + + return NULL; case '$': *buf_len = atoi(inbuf + 1); resp = redis_sock_read_bulk_reply(redis_sock, *buf_len TSRMLS_CC); @@ -432,7 +518,7 @@ redis_cmd_format_static(char **ret, char *keyword, char *format, ...) { /** * This command behave somehow like printf, except that strings need 2 arguments: * Their data and their size (strlen). - * Supported formats are: %d, %i, %s, %l + * Supported formats are:d, %i, %s, %l */ int redis_cmd_format(char **ret, char *format, ...) { @@ -944,15 +1030,79 @@ PHP_REDIS_API void redis_long_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock * } } +/* Helper method to convert [key, value, key, value] into [key => value, + * key => value] when returning data to the caller. Depending on our decode + * flag we'll convert the value data types */ +static void array_zip_values_and_scores(RedisSock *redis_sock, zval *z_tab, + int decode TSRMLS_DC) +{ + + zval *z_ret; + HashTable *keytable; + MAKE_STD_ZVAL(z_ret); + array_init(z_ret); + keytable = Z_ARRVAL_P(z_tab); -PHP_REDIS_API int redis_sock_read_multibulk_reply_zipped_with_flag(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int flag) { + for(zend_hash_internal_pointer_reset(keytable); + zend_hash_has_more_elements(keytable) == SUCCESS; + zend_hash_move_forward(keytable)) { - /* - int ret = redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab TSRMLS_CC); - array_zip_values_and_scores(return_value, 0); - */ + char *tablekey, *hkey, *hval; + unsigned int tablekey_len; + int hkey_len; + unsigned long idx; + zval **z_key_pp, **z_value_pp; + zend_hash_get_current_key_ex(keytable, &tablekey, &tablekey_len, &idx, 0, NULL); + if(zend_hash_get_current_data(keytable, (void**)&z_key_pp) == FAILURE) { + continue; /* this should never happen, according to the PHP people. */ + } + + /* get current value, a key */ + convert_to_string(*z_key_pp); + hkey = Z_STRVAL_PP(z_key_pp); + hkey_len = Z_STRLEN_PP(z_key_pp); + + /* move forward */ + zend_hash_move_forward(keytable); + + /* fetch again */ + zend_hash_get_current_key_ex(keytable, &tablekey, &tablekey_len, &idx, 0, NULL); + if(zend_hash_get_current_data(keytable, (void**)&z_value_pp) == FAILURE) { + continue; /* this should never happen, according to the PHP people. */ + } + + /* get current value, a hash value now. */ + hval = Z_STRVAL_PP(z_value_pp); + + /* Decode the score depending on flag */ + if (decode == SCORE_DECODE_INT && Z_STRLEN_PP(z_value_pp) > 0) { + add_assoc_long_ex(z_ret, hkey, 1+hkey_len, atoi(hval+1)); + } else if (decode == SCORE_DECODE_DOUBLE) { + add_assoc_double_ex(z_ret, hkey, 1+hkey_len, atof(hval)); + } else { + zval *z = NULL; + MAKE_STD_ZVAL(z); + *z = **z_value_pp; + zval_copy_ctor(z); + add_assoc_zval_ex(z_ret, hkey, 1+hkey_len, z); + } + } + + /* replace */ + zval_dtor(z_tab); + *z_tab = *z_ret; + zval_copy_ctor(z_tab); + zval_dtor(z_ret); + + efree(z_ret); +} + +static int +redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + zval *z_tab, int unserialize, int decode) +{ char inbuf[1024]; int numElems; zval *z_multi_result; @@ -982,10 +1132,12 @@ PHP_REDIS_API int redis_sock_read_multibulk_reply_zipped_with_flag(INTERNAL_FUNC MAKE_STD_ZVAL(z_multi_result); array_init(z_multi_result); /* pre-allocate array for multi's results. */ - redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock, z_multi_result, numElems, 1, flag ? UNSERIALIZE_ALL : UNSERIALIZE_ONLY_VALUES); + /* Grab our key, value, key, value array */ + redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, + z_multi_result, numElems, unserialize); - array_zip_values_and_scores(redis_sock, z_multi_result, 0 TSRMLS_CC); + /* Zip keys and values */ + array_zip_values_and_scores(redis_sock, z_multi_result, decode TSRMLS_CC); IF_MULTI_OR_PIPELINE() { add_next_index_zval(z_tab, z_multi_result); @@ -999,13 +1151,35 @@ PHP_REDIS_API int redis_sock_read_multibulk_reply_zipped_with_flag(INTERNAL_FUNC return 0; } -PHP_REDIS_API int redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { +/* Zipped key => value reply but we don't touch anything (e.g. CONFIG GET) */ +PHP_REDIS_API int redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) +{ + return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, + z_tab, UNSERIALIZE_NONE, SCORE_DECODE_NONE); +} + +/* Zipped key => value reply unserializing keys and decoding the score as an integer (PUBSUB) */ +PHP_REDIS_API int redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + zval *z_tab, void *ctx) +{ + return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, + z_tab, UNSERIALIZE_KEYS, SCORE_DECODE_INT); +} - return redis_sock_read_multibulk_reply_zipped_with_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, 1); +/* Zipped key => value reply unserializing keys and decoding the score as a double (ZSET commands) */ +PHP_REDIS_API int redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + zval *z_tab, void *ctx) +{ + return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, + z_tab, UNSERIALIZE_KEYS, SCORE_DECODE_DOUBLE); } -PHP_REDIS_API int redis_sock_read_multibulk_reply_zipped_strings(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { - return redis_sock_read_multibulk_reply_zipped_with_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, 0); +/* Zipped key => value reply where only the values are unserialized (e.g. HMGET) */ +PHP_REDIS_API int redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + zval *z_tab, void *ctx) +{ + return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, + z_tab, UNSERIALIZE_VALS, SCORE_DECODE_NONE); } PHP_REDIS_API void redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { @@ -1090,7 +1264,7 @@ PHP_REDIS_API void redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock * } /* Response for DEBUG object which is a formatted single line reply */ -PHP_REDIS_API void redis_debug_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, +PHP_REDIS_API void redis_debug_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *resp, *p, *p2, *p3, *p4; @@ -1116,7 +1290,7 @@ PHP_REDIS_API void redis_debug_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock while((p2 = strchr(p, ':'))!=NULL) { /* Null terminate at the ':' */ *p2++ = '\0'; - + /* Null terminate at the space if we have one */ if((p3 = strchr(p2, ' '))!=NULL) { *p3++ = '\0'; @@ -1138,7 +1312,7 @@ PHP_REDIS_API void redis_debug_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock } else { add_assoc_string(z_result, p, p2, 1); } - + p = p3; } @@ -1393,7 +1567,7 @@ PHP_REDIS_API int redis_sock_set_err(RedisSock *redis_sock, const char *msg, int PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char inbuf[1024]; - int numElems; + int numElems, err_len; zval *z_multi_result; if(-1 == redis_check_eof(redis_sock TSRMLS_CC)) { @@ -1413,6 +1587,12 @@ PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, IF_MULTI_OR_PIPELINE() { add_next_index_bool(z_tab, 0); } else { + /* Capture our error if redis has given us one */ + if (inbuf[0] == '-') { + err_len = strlen(inbuf+1) - 2; + redis_sock_set_err(redis_sock, inbuf+1, err_len); + } + RETVAL_FALSE; } return -1; @@ -1421,8 +1601,8 @@ PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, MAKE_STD_ZVAL(z_multi_result); array_init(z_multi_result); /* pre-allocate array for multi's results. */ - redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock, z_multi_result, numElems, 1, UNSERIALIZE_ALL); + redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, + z_multi_result, numElems, UNSERIALIZE_ALL); IF_MULTI_OR_PIPELINE() { add_next_index_zval(z_tab, z_multi_result); @@ -1437,7 +1617,7 @@ PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, /** * Like multibulk reply, but don't touch the values, they won't be compressed. (this is used by HKEYS). */ -PHP_REDIS_API int redis_sock_read_multibulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) +PHP_REDIS_API int redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char inbuf[1024]; int numElems; @@ -1468,8 +1648,8 @@ PHP_REDIS_API int redis_sock_read_multibulk_reply_raw(INTERNAL_FUNCTION_PARAMETE MAKE_STD_ZVAL(z_multi_result); array_init(z_multi_result); /* pre-allocate array for multi's results. */ - redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock, z_multi_result, numElems, 0, UNSERIALIZE_ALL); + redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, + z_multi_result, numElems, UNSERIALIZE_NONE); IF_MULTI_OR_PIPELINE() { add_next_index_zval(z_tab, z_multi_result); @@ -1481,6 +1661,42 @@ PHP_REDIS_API int redis_sock_read_multibulk_reply_raw(INTERNAL_FUNCTION_PARAMETE return 0; } +PHP_REDIS_API void +redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + zval *z_tab, int count, int unserialize) +{ + char *line; + int len; + + while(count > 0) { + line = redis_sock_read(redis_sock, &len TSRMLS_CC); + if (line != NULL) { + zval *z = NULL; + int unwrap; + + /* We will attempt unserialization, if we're unserializing everything, + * or if we're unserializing keys and we're on a key, or we're + * unserializing values and we're on a value! */ + unwrap = unserialize == UNSERIALIZE_ALL || + (unserialize == UNSERIALIZE_KEYS && count % 2 == 0) || + (unserialize == UNSERIALIZE_VALS && count % 2 != 0); + + if (unwrap && redis_unserialize(redis_sock, line, len, &z TSRMLS_CC)) { + efree(line); + add_next_index_zval(z_tab, z); + } else { + add_next_index_stringl(z_tab, line, len, 0); + } + } else { + add_next_index_bool(z_tab, 0); + } + + count--; + } +} + + +/* PHP_REDIS_API int redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int numElems, int unwrap_key, int unserialize_even_only) @@ -1509,16 +1725,16 @@ redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *re } return 0; } +*/ -/** - * redis_sock_read_multibulk_reply_assoc - */ -PHP_REDIS_API int redis_sock_read_multibulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) +/* Specialized multibulk processing for HMGET where we need to pair requested + * keys with their returned values */ +PHP_REDIS_API int redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char inbuf[1024], *response; int response_len; - int i, numElems; - zval *z_multi_result; + int i, numElems; + zval *z_multi_result; zval **z_keys = ctx; @@ -1526,7 +1742,7 @@ PHP_REDIS_API int redis_sock_read_multibulk_reply_assoc(INTERNAL_FUNCTION_PARAME return -1; } if(php_stream_gets(redis_sock->stream, inbuf, 1024) == NULL) { - redis_stream_close(redis_sock TSRMLS_CC); + redis_stream_close(redis_sock TSRMLS_CC); redis_sock->stream = NULL; redis_sock->status = REDIS_SOCK_STATUS_FAILED; redis_sock->mode = ATOMIC; @@ -1550,30 +1766,30 @@ PHP_REDIS_API int redis_sock_read_multibulk_reply_assoc(INTERNAL_FUNCTION_PARAME for(i = 0; i < numElems; ++i) { response = redis_sock_read(redis_sock, &response_len TSRMLS_CC); if(response != NULL) { - zval *z = NULL; - if(redis_unserialize(redis_sock, response, response_len, &z TSRMLS_CC) == 1) { - efree(response); - add_assoc_zval_ex(z_multi_result, Z_STRVAL_P(z_keys[i]), 1+Z_STRLEN_P(z_keys[i]), z); - } else { - add_assoc_stringl_ex(z_multi_result, Z_STRVAL_P(z_keys[i]), 1+Z_STRLEN_P(z_keys[i]), response, response_len, 0); - } - } else { - add_assoc_bool_ex(z_multi_result, Z_STRVAL_P(z_keys[i]), 1+Z_STRLEN_P(z_keys[i]), 0); - } - zval_dtor(z_keys[i]); - efree(z_keys[i]); + zval *z = NULL; + if(redis_unserialize(redis_sock, response, response_len, &z TSRMLS_CC) == 1) { + efree(response); + add_assoc_zval_ex(z_multi_result, Z_STRVAL_P(z_keys[i]), 1+Z_STRLEN_P(z_keys[i]), z); + } else { + add_assoc_stringl_ex(z_multi_result, Z_STRVAL_P(z_keys[i]), 1+Z_STRLEN_P(z_keys[i]), response, response_len, 0); + } + } else { + add_assoc_bool_ex(z_multi_result, Z_STRVAL_P(z_keys[i]), 1+Z_STRLEN_P(z_keys[i]), 0); + } + zval_dtor(z_keys[i]); + efree(z_keys[i]); } efree(z_keys); IF_MULTI_OR_PIPELINE() { add_next_index_zval(z_tab, z_multi_result); } else { - *return_value = *z_multi_result; - zval_copy_ctor(return_value); - INIT_PZVAL(return_value); - zval_dtor(z_multi_result); - efree(z_multi_result); - } + *return_value = *z_multi_result; + zval_copy_ctor(return_value); + INIT_PZVAL(return_value); + zval_dtor(z_multi_result); + efree(z_multi_result); + } return 0; } @@ -1789,7 +2005,6 @@ redis_sock_gets(RedisSock *redis_sock, char *buf, int buf_size, size_t *line_siz *line_size-=2; buf[*line_size]='\0'; - /* Success! */ return 0; } @@ -1839,11 +2054,11 @@ redis_read_variant_line(RedisSock *redis_sock, REDIS_REPLY_TYPE reply_type, zval return -1; } - /* If this is an error response, check if it is a SYNC error, and throw in that case */ + /* If this is an error response, filter specific errors that should throw + * an exception, and set our error field in our RedisSock object. */ if(reply_type == TYPE_ERR) { - if(memcmp(inbuf, "ERR SYNC", 9) == 0) { - zend_throw_exception(redis_exception_ce, "SYNC with master in progress", 0 TSRMLS_CC); - } + /* Handle throwable errors */ + redis_error_throw(inbuf, line_size TSRMLS_CC); /* Set our last error */ redis_sock_set_err(redis_sock, inbuf, line_size); @@ -1912,6 +2127,7 @@ redis_read_multibulk_recursive(RedisSock *redis_sock, int elements, zval **z_ret add_next_index_zval(*z_ret, z_subelem); redis_read_multibulk_recursive(redis_sock, reply_info, &z_subelem TSRMLS_CC); break; + default: break; /* We know it's not < 0 from previous check */ } /* Decrement our element counter */ @@ -31,13 +31,16 @@ PHP_REDIS_API int redis_sock_disconnect(RedisSock *redis_sock TSRMLS_DC); PHP_REDIS_API zval *redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); PHP_REDIS_API char *redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes TSRMLS_DC); PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *_z_tab, void *ctx); -PHP_REDIS_API int redis_sock_read_multibulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHP_REDIS_API int redis_sock_read_multibulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int numElems, int unwrap_key, int unserialize_even_only); -PHP_REDIS_API int redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHP_REDIS_API int redis_sock_read_multibulk_reply_zipped_strings(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHP_REDIS_API int redis_sock_read_multibulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHP_REDIS_API int redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, REDIS_SCAN_TYPE type, long *iter); +PHP_REDIS_API void redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int count, int unserialize); +PHP_REDIS_API int redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +PHP_REDIS_API int redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +PHP_REDIS_API int redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +PHP_REDIS_API int redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +PHP_REDIS_API int redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +PHP_REDIS_API int redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); + +PHP_REDIS_API int redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, REDIS_SCAN_TYPE type, long *iter); PHP_REDIS_API int redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz TSRMLS_DC); PHP_REDIS_API void redis_stream_close(RedisSock *redis_sock TSRMLS_DC); PHP_REDIS_API int redis_check_eof(RedisSock *redis_sock TSRMLS_DC); diff --git a/php_redis.h b/php_redis.h index dd315a69..ba02eb00 100644 --- a/php_redis.h +++ b/php_redis.h @@ -111,6 +111,7 @@ PHP_METHOD(Redis, zDelete); PHP_METHOD(Redis, zRange); PHP_METHOD(Redis, zReverseRange); PHP_METHOD(Redis, zRangeByScore); +PHP_METHOD(Redis, zRangeByLex); PHP_METHOD(Redis, zRevRangeByScore); PHP_METHOD(Redis, zCount); PHP_METHOD(Redis, zDeleteRangeByScore); @@ -196,6 +197,11 @@ PHP_METHOD(Redis, hscan); PHP_METHOD(Redis, sscan); PHP_METHOD(Redis, zscan); +/* HyperLogLog commands */ +PHP_METHOD(Redis, pfadd); +PHP_METHOD(Redis, pfcount); +PHP_METHOD(Redis, pfmerge); + /* Reflection */ PHP_METHOD(Redis, getHost); PHP_METHOD(Redis, getPort); @@ -205,6 +211,7 @@ PHP_METHOD(Redis, getReadTimeout); PHP_METHOD(Redis, isConnected); PHP_METHOD(Redis, getPersistentID); PHP_METHOD(Redis, getAuth); +PHP_METHOD(Redis, getMode); PHP_METHOD(Redis, rawCommand); #ifdef PHP_WIN32 @@ -236,7 +243,6 @@ PHP_REDIS_API void generic_empty_long_cmd(INTERNAL_FUNCTION_PARAMETERS, char *cm PHP_REDIS_API void generic_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sub_cmd); PHP_REDIS_API void generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *unsub_cmd); -PHP_REDIS_API void array_zip_values_and_scores(RedisSock *redis_sock, zval *z_tab, int use_atof TSRMLS_DC); PHP_REDIS_API int redis_response_enqueued(RedisSock *redis_sock TSRMLS_DC); PHP_REDIS_API int get_flag(zval *object TSRMLS_DC); @@ -271,7 +277,7 @@ extern zend_module_entry redis_module_entry; #define phpext_redis_ptr redis_module_ptr -#define PHP_REDIS_VERSION "2.2.5" +#define PHP_REDIS_VERSION "2.2.6" #endif @@ -192,6 +192,7 @@ static zend_function_entry redis_functions[] = { PHP_ME(Redis, zReverseRange, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRangeByScore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zRevRangeByScore, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Redis, zRangeByLex, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zCount, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zDeleteRangeByScore, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, zDeleteRangeByRank, NULL, ZEND_ACC_PUBLIC) @@ -260,6 +261,11 @@ static zend_function_entry redis_functions[] = { PHP_ME(Redis, zscan, arginfo_kscan, ZEND_ACC_PUBLIC) PHP_ME(Redis, sscan, arginfo_kscan, ZEND_ACC_PUBLIC) + /* HyperLogLog commands */ + PHP_ME(Redis, pfadd, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Redis, pfcount, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Redis, pfmerge, NULL, ZEND_ACC_PUBLIC) + /* options */ PHP_ME(Redis, getOption, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, setOption, NULL, ZEND_ACC_PUBLIC) @@ -282,7 +288,7 @@ static zend_function_entry redis_functions[] = { PHP_ME(Redis, getPersistentID, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, getAuth, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, isConnected, NULL, ZEND_ACC_PUBLIC) - + PHP_ME(Redis, getMode, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, wait, NULL, ZEND_ACC_PUBLIC) PHP_ME(Redis, pubsub, NULL, ZEND_ACC_PUBLIC) @@ -314,8 +320,8 @@ static zend_function_entry redis_functions[] = { PHP_MALIAS(Redis, srem, sRemove, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, sismember, sContains, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, zrevrange, zReverseRange, NULL, ZEND_ACC_PUBLIC) - - PHP_MALIAS(Redis, sendEcho, echo, NULL, ZEND_ACC_PUBLIC) + + PHP_MALIAS(Redis, sendEcho, echo, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, evaluate, eval, NULL, ZEND_ACC_PUBLIC) PHP_MALIAS(Redis, evaluateSha, evalsha, NULL, ZEND_ACC_PUBLIC) @@ -474,7 +480,7 @@ PHP_MINIT_FUNCTION(redis) zend_class_entry redis_exception_class_entry; REGISTER_INI_ENTRIES(); - + /* Redis class */ INIT_CLASS_ENTRY(redis_class_entry, "Redis", redis_functions); redis_ce = zend_register_internal_class(&redis_class_entry TSRMLS_CC); @@ -650,7 +656,7 @@ PHP_REDIS_API int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) { char *persistent_id = NULL; int persistent_id_len = -1; - + double timeout = 0.0; RedisSock *redis_sock = NULL; @@ -830,7 +836,7 @@ PHP_METHOD(Redis, bitcount) /* }}} */ /* {{{ proto integer Redis::bitpos(string key, int bit, [int start], [int end]) */ -PHP_METHOD(Redis, bitpos) +PHP_METHOD(Redis, bitpos) { zval *object; RedisSock *redis_sock; @@ -932,7 +938,7 @@ PHP_METHOD(Redis, set) { /* 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 - && Z_TYPE_P(z_opts) != IS_NULL) + && Z_TYPE_P(z_opts) != IS_NULL) { RETURN_FALSE; } @@ -1428,7 +1434,7 @@ PHP_METHOD(Redis, incrByFloat) { /* Prefix key, format command, free old key if necissary */ key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); cmd_len = redis_cmd_format_static(&cmd, "INCRBYFLOAT", "sf", key, key_len, val); - if(key_free) efree(key); + if(key_free) efree(key); REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { @@ -1691,12 +1697,11 @@ PHP_METHOD(Redis, getKeys) REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { - if (redis_sock_read_multibulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock, NULL, NULL) < 0) { + if (redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_raw); + REDIS_PROCESS_RESPONSE(redis_mbulk_reply_raw); } /* }}} */ @@ -1990,11 +1995,11 @@ PHP_METHOD(Redis, lInsert) int pivot_len, position_len, key_len, val_len, cmd_len; int val_free, pivot_free, key_free; zval *z_value, *z_pivot; - + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osszz", &object, redis_ce, - &key, &key_len, + &key, &key_len, &position, &position_len, &z_pivot, &z_value) == FAILURE) { @@ -2004,7 +2009,7 @@ PHP_METHOD(Redis, lInsert) if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; } - + if(strncasecmp(position, "after", 5) == 0 || strncasecmp(position, "before", 6) == 0) { key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); @@ -2015,15 +2020,15 @@ PHP_METHOD(Redis, lInsert) if(key_free) efree(key); if(pivot_free) STR_FREE(pivot); - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - IF_ATOMIC() { - redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_long_response); + REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); + IF_ATOMIC() { + redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); + } + REDIS_PROCESS_RESPONSE(redis_long_response); } else { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Error on position"); } - + } PHP_METHOD(Redis, lPushx) @@ -2667,7 +2672,7 @@ PHP_REDIS_API int generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAMETERS, char * if(has_timeout && Z_TYPE_P(z_args[argc - 1]) != IS_LONG) { php_error_docref(NULL TSRMLS_CC, E_ERROR, "Syntax error on timeout"); } - + for(i = 0, j = 0; i < argc; ++i) { /* store each key */ if(!all_keys && j != 0) { /* not just operating on keys */ @@ -2681,7 +2686,7 @@ PHP_REDIS_API int generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAMETERS, char * } } else { - + if(Z_TYPE_P(z_args[i]) != IS_STRING) { convert_to_string(z_args[i]); } @@ -2705,7 +2710,7 @@ PHP_REDIS_API int generic_multiple_args_cmd(INTERNAL_FUNCTION_PARAMETERS, char * cmd = emalloc(cmd_len+1); sprintf(cmd, "*%d" _NL "$%d" _NL "%s" _NL, 1+real_argc, keyword_len, keyword); - + pos = 1 +integer_length(real_argc + 1) + 2 + 1 + integer_length(keyword_len) + 2 + keyword_len + 2; @@ -4022,9 +4027,9 @@ PHP_METHOD(Redis, zRange) REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); if(withscores) { IF_ATOMIC() { - redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); + redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_zipped); + REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_keys_dbl); } else { IF_ATOMIC() { if (redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, @@ -4152,9 +4157,9 @@ PHP_METHOD(Redis, zReverseRange) REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); if(withscores) { IF_ATOMIC() { - redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); + redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_zipped); + REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_keys_dbl); } else { IF_ATOMIC() { if (redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, @@ -4246,11 +4251,11 @@ redis_generic_zrange_by_score(INTERNAL_FUNCTION_PARAMETERS, char *keyword) { * we want [elt0 => val0, elt1 => val1], etc. */ IF_ATOMIC() { - if(redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { + if(redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_zipped); + REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_keys_dbl); } else { IF_ATOMIC() { if(redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, @@ -4312,6 +4317,65 @@ PHP_METHOD(Redis, zCount) } /* }}} */ +/* {{{ proto array Redis::zRangeByLex(string $key, string $min, string $max, + * [long $offset, long $count]) */ +PHP_METHOD(Redis, zRangeByLex) { + zval *object; + RedisSock *redis_sock; + char *cmd, *key, *min, *max; + long offset, count; + int argc, cmd_len, key_len; + int key_free, min_len, max_len; + + /* We need either three or five arguments for this to be a valid call */ + argc = ZEND_NUM_ARGS(); + if (argc != 3 && argc != 5) { + RETURN_FALSE; + } + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), + "Osss|ll", &object, redis_ce, &key, &key_len, + &min, &min_len, &max, &max_len, &offset, + &count) == FAILURE) + { + RETURN_FALSE; + } + + /* We can do some simple validation for the user, as we know how min/max are + * required to start */ + if (!IS_LEX_ARG(min,min_len) || !IS_LEX_ARG(max,max_len)) { + RETURN_FALSE; + } + + if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); + + /* Construct our command depending on argc */ + if (argc == 3) { + cmd_len = redis_cmd_format_static(&cmd, "ZRANGEBYLEX", "sss", key, + key_len, min, min_len, max, max_len); + } else { + cmd_len = redis_cmd_format_static(&cmd, "ZRANGEBYLEX", "ssssll", key, + key_len, min, min_len, max, max_len, "LIMIT", sizeof("LIMIT")-1, + offset, count); + } + + if(key_free) efree(key); + + /* Kick it off */ + REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); + IF_ATOMIC() { + if (redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, + redis_sock, NULL, NULL) < 0) { + RETURN_FALSE; + } + } + REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply); +} + /* {{{ proto long Redis::zCard(string key) */ PHP_METHOD(Redis, zCard) @@ -4858,12 +4922,11 @@ PHP_METHOD(Redis, hKeys) RETURN_FALSE; IF_ATOMIC() { - if (redis_sock_read_multibulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock, NULL, NULL) < 0) { + if (redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_raw); + REDIS_PROCESS_RESPONSE(redis_mbulk_reply_raw); } @@ -4892,72 +4955,12 @@ PHP_METHOD(Redis, hGetAll) { RETURN_FALSE; IF_ATOMIC() { - if (redis_sock_read_multibulk_reply_zipped_strings(INTERNAL_FUNCTION_PARAM_PASSTHRU, + if (redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_zipped_strings); -} - -PHP_REDIS_API void array_zip_values_and_scores(RedisSock *redis_sock, zval *z_tab, int use_atof TSRMLS_DC) { - - zval *z_ret; - HashTable *keytable; - - MAKE_STD_ZVAL(z_ret); - array_init(z_ret); - keytable = Z_ARRVAL_P(z_tab); - - for(zend_hash_internal_pointer_reset(keytable); - zend_hash_has_more_elements(keytable) == SUCCESS; - zend_hash_move_forward(keytable)) { - - char *tablekey, *hkey, *hval; - unsigned int tablekey_len; - int hkey_len; - unsigned long idx; - zval **z_key_pp, **z_value_pp; - - zend_hash_get_current_key_ex(keytable, &tablekey, &tablekey_len, &idx, 0, NULL); - if(zend_hash_get_current_data(keytable, (void**)&z_key_pp) == FAILURE) { - continue; /* this should never happen, according to the PHP people. */ - } - - /* get current value, a key */ - convert_to_string(*z_key_pp); - hkey = Z_STRVAL_PP(z_key_pp); - hkey_len = Z_STRLEN_PP(z_key_pp); - - /* move forward */ - zend_hash_move_forward(keytable); - - /* fetch again */ - zend_hash_get_current_key_ex(keytable, &tablekey, &tablekey_len, &idx, 0, NULL); - if(zend_hash_get_current_data(keytable, (void**)&z_value_pp) == FAILURE) { - continue; /* this should never happen, according to the PHP people. */ - } - - /* get current value, a hash value now. */ - hval = Z_STRVAL_PP(z_value_pp); - - if(use_atof) { /* zipping a score */ - add_assoc_double_ex(z_ret, hkey, 1+hkey_len, atof(hval)); - } else { /* add raw copy */ - zval *z = NULL; - MAKE_STD_ZVAL(z); - *z = **z_value_pp; - zval_copy_ctor(z); - add_assoc_zval_ex(z_ret, hkey, 1+hkey_len, z); - } - } - /* replace */ - zval_dtor(z_tab); - *z_tab = *z_ret; - zval_copy_ctor(z_tab); - zval_dtor(z_ret); - - efree(z_ret); + REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_vals); } PHP_METHOD(Redis, hIncrByFloat) @@ -5079,7 +5082,7 @@ PHP_METHOD(Redis, hMget) { /* Make sure the data is a long or string, and if it's a string that */ /* it isn't empty. There is no reason to send empty length members. */ if((Z_TYPE_PP(data) == IS_STRING && Z_STRLEN_PP(data)>0) || - Z_TYPE_PP(data) == IS_LONG) + Z_TYPE_PP(data) == IS_LONG) { /* This is a key we can ask for, copy it and set it in our array */ MAKE_STD_ZVAL(z_keys[valid]); @@ -5116,9 +5119,9 @@ PHP_METHOD(Redis, hMget) { /* Kick off our request */ REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len); IF_ATOMIC() { - redis_sock_read_multibulk_reply_assoc(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, z_keys); + redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, z_keys); } - REDIS_PROCESS_RESPONSE_CLOSURE(redis_sock_read_multibulk_reply_assoc, z_keys); + REDIS_PROCESS_RESPONSE_CLOSURE(redis_mbulk_reply_assoc, z_keys); } PHP_METHOD(Redis, hMset) @@ -5164,7 +5167,7 @@ PHP_METHOD(Redis, hMset) unsigned long idx; int type; zval **z_value_p; - + char *hval; int hval_len, hval_free; @@ -5359,7 +5362,7 @@ free_reply_callbacks(zval *z_this, RedisSock *redis_sock) { fold_item *fi; fold_item *head = redis_sock->head; request_item *ri; - + for(fi = head; fi; ) { fold_item *fi_next = fi->next; free(fi); @@ -5506,9 +5509,9 @@ PHP_METHOD(Redis, pipeline) RETURN_ZVAL(getThis(), 1, 0); } -/* - publish channel message - @return the number of subscribers +/* + publish channel message + @return the number of subscribers */ PHP_METHOD(Redis, publish) { @@ -5554,10 +5557,10 @@ PHP_REDIS_API void generic_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sub zend_fcall_info_cache z_callback_cache; zval *z_ret, **z_args[4]; - + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oaf", &object, redis_ce, &array, &z_callback, &z_callback_cache) == FAILURE) { - RETURN_FALSE; + RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { @@ -5609,9 +5612,9 @@ PHP_REDIS_API void generic_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sub RETURN_FALSE; } efree(cmd); - + /* read the status of the execution of the command `subscribe` */ - + z_tab = redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); if(z_tab == NULL) { RETURN_FALSE; @@ -5620,15 +5623,18 @@ PHP_REDIS_API void generic_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sub if (zend_hash_index_find(Z_ARRVAL_P(z_tab), 0, (void**)&tmp) == SUCCESS) { type_response = Z_STRVAL_PP(tmp); if(strcmp(type_response, sub_cmd) != 0) { - efree(tmp); - efree(z_tab); + efree(tmp); + zval_dtor(z_tab); + efree(z_tab); RETURN_FALSE; - } + } } else { - efree(z_tab); + zval_dtor(z_tab); + efree(z_tab); RETURN_FALSE; } - efree(z_tab); + zval_dtor(z_tab); + efree(z_tab); /* Set a pointer to our return value and to our arguments. */ z_callback.retval_ptr_ptr = &z_ret; @@ -5636,12 +5642,12 @@ PHP_REDIS_API void generic_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sub z_callback.no_separation = 0; /* Multibulk Response, format : {message type, originating channel, message payload} */ - while(1) { + while(1) { /* call the callback with this z_tab in argument */ int is_pmsg, tab_idx = 1; zval **type, **channel, **pattern, **data; z_tab = redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); - + if(z_tab == NULL || Z_TYPE_P(z_tab) != IS_ARRAY) { /*ERROR */ break; @@ -5696,12 +5702,19 @@ PHP_REDIS_API void generic_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sub break; } - /* If we have a return value, free it. Note, we could use the return value to break the subscribe loop */ - if(z_ret) zval_ptr_dtor(&z_ret); + /* Free reply from Redis */ + zval_dtor(z_tab); + efree(z_tab); - /* TODO: provide a way to break out of the loop. */ - zval_dtor(z_tab); - efree(z_tab); + /* Check for a non-null return value. If we have one, return it from + * the subscribe function itself. Otherwise continue our loop. */ + if (z_ret) { + if (Z_TYPE_P(z_ret) != IS_NULL) { + RETVAL_ZVAL(z_ret, 0, 1); + break; + } + zval_ptr_dtor(&z_ret); + } } } @@ -5718,7 +5731,7 @@ PHP_METHOD(Redis, subscribe) { generic_subscribe_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "subscribe"); } -/** +/** * [p]unsubscribe channel_0 channel_1 ... channel_n * [p]unsubscribe(array(channel_0, channel_1, ..., channel_n)) * response format : @@ -5738,13 +5751,13 @@ PHP_REDIS_API void generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *u RedisSock *redis_sock; char *cmd = "", *old_cmd = NULL; int cmd_len, array_count; - + int i; zval *z_tab, **z_channel; - - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa", &object, redis_ce, &array) == FAILURE) { - RETURN_FALSE; + RETURN_FALSE; } if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { RETURN_FALSE; @@ -5790,10 +5803,10 @@ PHP_REDIS_API void generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *u while( i <= array_count) { z_tab = redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); - if(Z_TYPE_P(z_tab) == IS_ARRAY) { + if(Z_TYPE_P(z_tab) == IS_ARRAY) { if (zend_hash_index_find(Z_ARRVAL_P(z_tab), 1, (void**)&z_channel) == FAILURE) { RETURN_FALSE; - } + } add_assoc_bool(return_value, Z_STRVAL_PP(z_channel), 1); } else { /*error */ @@ -6033,9 +6046,9 @@ PHP_METHOD(Redis, config) REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len) IF_ATOMIC() { - redis_sock_read_multibulk_reply_zipped_strings(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); + redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_zipped_strings); + REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_raw); } else if(mode == CFG_SET && val != NULL) { cmd_len = redis_cmd_format_static(&cmd, "CONFIG", "sss", op, op_len, key, key_len, val, val_len); @@ -6156,9 +6169,9 @@ PHP_METHOD(Redis, wait) { int cmd_len; /* Make sure arguments are valid */ - if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oll", + if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oll", &object, redis_ce, &num_slaves, &timeout) - ==FAILURE) + ==FAILURE) { RETURN_FALSE; } @@ -6192,7 +6205,7 @@ redis_build_pubsub_cmd(RedisSock *redis_sock, char **ret, PUBSUB_TYPE type, zval *arg TSRMLS_DC) { HashTable *ht_chan; - HashPosition ptr; + HashPosition ptr; zval **z_ele; char *key; int cmd_len, key_len, key_free; @@ -6222,7 +6235,7 @@ redis_build_pubsub_cmd(RedisSock *redis_sock, char **ret, PUBSUB_TYPE type, } } else if(type == PUBSUB_NUMSUB) { ht_chan = Z_ARRVAL_P(arg); - + /* Add PUBSUB and NUMSUB bits */ redis_cmd_init_sstr(&cmd, zend_hash_num_elements(ht_chan)+1, "PUBSUB", sizeof("PUBSUB")-1); redis_cmd_append_sstr(&cmd, "NUMSUB", sizeof("NUMSUB")-1); @@ -6291,9 +6304,9 @@ PHP_METHOD(Redis, pubsub) { zval *arg=NULL; /* Parse arguments */ - if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|z", + if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|z", &object, redis_ce, &keyword, &kw_len, &arg) - ==FAILURE) + ==FAILURE) { RETURN_FALSE; } @@ -6308,7 +6321,7 @@ PHP_METHOD(Redis, pubsub) { } else if(!strncasecmp(keyword, "numsub", sizeof("numsub"))) { /* One array argument */ if(ZEND_NUM_ARGS() < 2 || Z_TYPE_P(arg) != IS_ARRAY || - zend_hash_num_elements(Z_ARRVAL_P(arg))==0) + zend_hash_num_elements(Z_ARRVAL_P(arg))==0) { RETURN_FALSE; } @@ -6332,11 +6345,11 @@ PHP_METHOD(Redis, pubsub) { if(type == PUBSUB_NUMSUB) { IF_ATOMIC() { - if(redis_sock_read_multibulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL)<0) { + if(redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL)<0) { RETURN_FALSE; } } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_zipped); + REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_keys_int); } else { IF_ATOMIC() { if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL)<0) { @@ -6618,7 +6631,7 @@ PHP_METHOD(Redis, debug) { char *cmd, *key; int cmd_len, key_len, key_free; - if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", + if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &object, redis_ce, &key, &key_len)==FAILURE) { RETURN_FALSE; @@ -6689,7 +6702,7 @@ PHP_METHOD(Redis, migrate) { /* Parse arguments */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oslsll|bb", &object, redis_ce, - &host, &host_len, &port, &key, &key_len, &dest_db, &timeout, + &host, &host_len, &port, &key, &key_len, &dest_db, &timeout, ©, &replace) == FAILURE) { RETURN_FALSE; } @@ -6704,19 +6717,19 @@ PHP_METHOD(Redis, migrate) { /* Construct our command */ if(copy && replace) { - cmd_len = redis_cmd_format_static(&cmd, "MIGRATE", "sdsddss", host, host_len, port, - key, key_len, dest_db, timeout, "COPY", + cmd_len = redis_cmd_format_static(&cmd, "MIGRATE", "sdsddss", host, host_len, port, + key, key_len, dest_db, timeout, "COPY", sizeof("COPY")-1, "REPLACE", sizeof("REPLACE")-1); } else if(copy) { cmd_len = redis_cmd_format_static(&cmd, "MIGRATE", "sdsdds", host, host_len, port, - key, key_len, dest_db, timeout, "COPY", + key, key_len, dest_db, timeout, "COPY", sizeof("COPY")-1); } else if(replace) { cmd_len = redis_cmd_format_static(&cmd, "MIGRATE", "sdsdds", host, host_len, port, key, key_len, dest_db, timeout, "REPLACE", sizeof("REPLACE")-1); } else { - cmd_len = redis_cmd_format_static(&cmd, "MIGRATE", "sdsdd", host, host_len, port, + cmd_len = redis_cmd_format_static(&cmd, "MIGRATE", "sdsdd", host, host_len, port, key, key_len, dest_db, timeout); } @@ -6767,7 +6780,7 @@ PHP_METHOD(Redis, _serialize) { RedisSock *redis_sock; zval *z_val; char *val; - int val_len; + int val_len, val_free; /* Parse arguments */ if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oz", @@ -6782,11 +6795,11 @@ PHP_METHOD(Redis, _serialize) { } /* Serialize, which will return a value even if no serializer is set */ - redis_serialize(redis_sock, z_val, &val, &val_len TSRMLS_CC); + val_free = redis_serialize(redis_sock, z_val, &val, &val_len TSRMLS_CC); /* Return serialized value. Tell PHP to make a copy as some can be interned. */ RETVAL_STRINGL(val, val_len, 1); - STR_FREE(val); + if(val_free) STR_FREE(val); } /* @@ -6872,6 +6885,25 @@ PHP_METHOD(Redis, clearLastError) { RETURN_TRUE; } +/* + * {{{ proto long Redis::getMode() + */ +PHP_METHOD(Redis, getMode) { + zval *object; + RedisSock *redis_sock; + + /* Grab our object */ + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) { + RETURN_FALSE; + } + + /* Grab socket */ + if (redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + RETVAL_LONG(redis_sock->mode); +} /* * {{{ proto Redis::time() @@ -6897,11 +6929,11 @@ PHP_METHOD(Redis, time) { /* Execute or queue command */ REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); IF_ATOMIC() { - if(redis_sock_read_multibulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { + if(redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL) < 0) { RETURN_FALSE; } } - REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply_raw); + REDIS_PROCESS_RESPONSE(redis_mbulk_reply_raw); } /* @@ -7035,8 +7067,8 @@ PHP_METHOD(Redis, client) { int cmd_len, opt_len, arg_len; /* Parse our method parameters */ - if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|s", - &object, redis_ce, &opt, &opt_len, &arg, &arg_len) == FAILURE) + if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|s", + &object, redis_ce, &opt, &opt_len, &arg, &arg_len) == FAILURE) { RETURN_FALSE; } @@ -7049,7 +7081,7 @@ PHP_METHOD(Redis, client) { /* Build our CLIENT command */ if(ZEND_NUM_ARGS() == 2) { cmd_len = redis_cmd_format_static(&cmd, "CLIENT", "ss", opt, opt_len, - arg, arg_len); + arg, arg_len); } else { cmd_len = redis_cmd_format_static(&cmd, "CLIENT", "s", opt, opt_len); } @@ -7243,4 +7275,280 @@ PHP_METHOD(Redis, zscan) { generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_ZSCAN); } +/* + * HyperLogLog based commands + */ + +/* {{{ proto Redis::pfAdd(string key, array elements) }}} */ +PHP_METHOD(Redis, pfadd) { + zval *object; + RedisSock *redis_sock; + char *key; + int key_len, key_free, argc=1; + zval *z_mems, **z_mem; + HashTable *ht_mems; + HashPosition pos; + smart_str cmd = {0}; + + if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa", + &object, redis_ce, &key, &key_len, &z_mems) + ==FAILURE) + { + RETURN_FALSE; + } + + if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + // Grab members as an array + ht_mems = Z_ARRVAL_P(z_mems); + + // Total arguments we'll be sending + argc += zend_hash_num_elements(ht_mems); + + // If the array was empty we can just exit + if(argc < 2) { + RETURN_FALSE; + } + + // Start constructing our command + redis_cmd_init_sstr(&cmd, argc, "PFADD", sizeof("PFADD")-1); + + // Prefix our key if we're prefixing + key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); + redis_cmd_append_sstr(&cmd, key, key_len); + if(key_free) efree(key); + + // Iterate over members we're adding + for(zend_hash_internal_pointer_reset_ex(ht_mems, &pos); + zend_hash_get_current_data_ex(ht_mems, (void**)&z_mem, &pos)==SUCCESS; + zend_hash_move_forward_ex(ht_mems, &pos)) + { + char *mem; + int mem_len, val_free; + zval *z_tmp = NULL; + + // Serialize if requested + val_free = redis_serialize(redis_sock, *z_mem, &mem, &mem_len TSRMLS_CC); + + // Allow for non string members if we're not serializing + if(!val_free) { + if(Z_TYPE_PP(z_mem)==IS_STRING) { + mem = Z_STRVAL_PP(z_mem); + mem_len = Z_STRLEN_PP(z_mem); + } else { + MAKE_STD_ZVAL(z_tmp); + *z_tmp = **z_mem; + convert_to_string(z_tmp); + + mem = Z_STRVAL_P(z_tmp); + mem_len = Z_STRLEN_P(z_tmp); + } + } + + // Append this member + redis_cmd_append_sstr(&cmd, mem, mem_len); + + // Free memory if we serialized or converted types + if(z_tmp) { + zval_dtor(z_tmp); + efree(z_tmp); + z_tmp = NULL; + } else if(val_free) { + efree(mem); + } + } + + REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len); + IF_ATOMIC() { + redis_1_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); + } + REDIS_PROCESS_RESPONSE(redis_1_response); +} + +/* {{{ proto Redis::pfCount(string key) }}} + * proto Redis::pfCount(array keys) }}} */ +PHP_METHOD(Redis, pfcount) { + zval *object, *z_keys, **z_key, *z_tmp = NULL; + HashTable *ht_keys; + HashPosition ptr; + RedisSock *redis_sock; + smart_str cmd = {0}; + int num_keys, key_len, key_free; + char *key; + + if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oz", + &object, redis_ce, &z_keys)==FAILURE) + { + RETURN_FALSE; + } + + if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + /* If we were passed an array of keys, iterate through them prefixing if + * required and capturing lengths and if we need to free them. Otherwise + * attempt to treat the argument as a string and just pass one */ + if (Z_TYPE_P(z_keys) == IS_ARRAY) { + /* Grab key hash table and the number of keys */ + ht_keys = Z_ARRVAL_P(z_keys); + num_keys = zend_hash_num_elements(ht_keys); + + /* There is no reason to send zero keys */ + if (num_keys == 0) { + RETURN_FALSE; + } + + /* Initialize the command with our number of arguments */ + redis_cmd_init_sstr(&cmd, num_keys, "PFCOUNT", sizeof("PFCOUNT")-1); + + /* Append our key(s) */ + for (zend_hash_internal_pointer_reset_ex(ht_keys, &ptr); + zend_hash_get_current_data_ex(ht_keys, (void**)&z_key, &ptr)==SUCCESS; + zend_hash_move_forward_ex(ht_keys, &ptr)) + { + /* Turn our value into a string if it isn't one */ + if (Z_TYPE_PP(z_key) != IS_STRING) { + MAKE_STD_ZVAL(z_tmp); + *z_tmp = **z_key; + zval_copy_ctor(z_tmp); + convert_to_string(z_tmp); + + key = Z_STRVAL_P(z_tmp); + key_len = Z_STRLEN_P(z_tmp); + } else { + key = Z_STRVAL_PP(z_key); + key_len = Z_STRLEN_PP(z_key); + } + + /* Append this key to our command */ + key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); + redis_cmd_append_sstr(&cmd, key, key_len); + + /* Cleanup */ + if (key_free) efree(key); + if (z_tmp) { + zval_dtor(z_tmp); + efree(z_tmp); + z_tmp = NULL; + } + } + } else { + /* Turn our key into a string if it's a different type */ + if (Z_TYPE_P(z_keys) != IS_STRING) { + MAKE_STD_ZVAL(z_tmp); + *z_tmp = *z_keys; + zval_copy_ctor(z_tmp); + convert_to_string(z_tmp); + + key = Z_STRVAL_P(z_tmp); + key_len = Z_STRLEN_P(z_tmp); + } else { + key = Z_STRVAL_P(z_keys); + key_len = Z_STRLEN_P(z_keys); + } + + /* Construct our whole command */ + redis_cmd_init_sstr(&cmd, 1, "PFCOUNT", sizeof("PFCOUNT")-1); + key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); + redis_cmd_append_sstr(&cmd, key, key_len); + + /* Cleanup */ + if (key_free) efree(key); + if (z_tmp) { + zval_dtor(z_tmp); + efree(z_tmp); + } + } + + 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); +} +/* }}} */ + +/* {{{ proto Redis::pfMerge(array keys) }}}*/ +PHP_METHOD(Redis, pfmerge) { + zval *object; + RedisSock *redis_sock; + zval *z_keys, **z_key; + HashTable *ht_keys; + HashPosition pos; + smart_str cmd = {0}; + int key_len, key_free, argc=1; + char *key; + + if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa", + &object, redis_ce, &key, &key_len, &z_keys)==FAILURE) + { + RETURN_FALSE; + } + + if(redis_sock_get(object, &redis_sock TSRMLS_CC, 0) < 0) { + RETURN_FALSE; + } + + // Grab keys as an array + ht_keys = Z_ARRVAL_P(z_keys); + + // Total arguments we'll be sending + argc += zend_hash_num_elements(ht_keys); + + // If no keys were passed we can abort + if(argc<2) { + RETURN_FALSE; + } + + // Initial construction of our command + redis_cmd_init_sstr(&cmd, argc, "PFMERGE", sizeof("PFMERGE")-1); + + // Add our destination key (prefixed if necessary) + key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); + redis_cmd_append_sstr(&cmd, key, key_len); + if(key_free) efree(key); + + // Iterate our keys array + for(zend_hash_internal_pointer_reset_ex(ht_keys, &pos); + zend_hash_get_current_data_ex(ht_keys, (void**)&z_key, &pos)==SUCCESS; + zend_hash_move_forward_ex(ht_keys, &pos)) + { + zval *z_tmp = NULL; + + // Keys could look like a number + if(Z_TYPE_PP(z_key) == IS_STRING) { + key = Z_STRVAL_PP(z_key); + key_len = Z_STRLEN_PP(z_key); + } else { + MAKE_STD_ZVAL(z_tmp); + *z_tmp = **z_key; + convert_to_string(z_tmp); + + key = Z_STRVAL_P(z_tmp); + key_len = Z_STRLEN_P(z_tmp); + } + + // Prefix our key if necessary and append this key + key_free = redis_key_prefix(redis_sock, &key, &key_len TSRMLS_CC); + redis_cmd_append_sstr(&cmd, key, key_len); + if(key_free) efree(key); + + // Free temporary zval if we converted + if(z_tmp) { + zval_dtor(z_tmp); + efree(z_tmp); + z_tmp = NULL; + } + } + + REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len); + IF_ATOMIC() { + redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); + } + REDIS_PROCESS_RESPONSE(redis_boolean_response); +} + /* vim: set tabstop=4 softtabstops=4 noexpandtab shiftwidth=4: */ diff --git a/redis_array.c b/redis_array.c index c271fc1a..a71d938c 100644 --- a/redis_array.c +++ b/redis_array.c @@ -69,6 +69,8 @@ zend_function_entry redis_array_functions[] = { PHP_ME(RedisArray, getOption, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, setOption, NULL, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, keys, NULL, ZEND_ACC_PUBLIC) + PHP_ME(RedisArray, save, NULL, ZEND_ACC_PUBLIC) + PHP_ME(RedisArray, bgsave, NULL, ZEND_ACC_PUBLIC) /* Multi/Exec */ PHP_ME(RedisArray, multi, NULL, ZEND_ACC_PUBLIC) @@ -625,6 +627,17 @@ PHP_METHOD(RedisArray, flushall) multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "FLUSHALL"); } +PHP_METHOD(RedisArray, save) +{ + multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SAVE"); +} + +PHP_METHOD(RedisArray, bgsave) +{ + multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "BGSAVE"); +} + + PHP_METHOD(RedisArray, keys) { zval *object, *z_args[1], *z_tmp, z_fun; diff --git a/redis_array.h b/redis_array.h index 19d0a3a4..2455a375 100644 --- a/redis_array.h +++ b/redis_array.h @@ -30,6 +30,8 @@ PHP_METHOD(RedisArray, del); PHP_METHOD(RedisArray, keys); PHP_METHOD(RedisArray, getOption); PHP_METHOD(RedisArray, setOption); +PHP_METHOD(RedisArray, save); +PHP_METHOD(RedisArray, bgsave); PHP_METHOD(RedisArray, multi); PHP_METHOD(RedisArray, exec); diff --git a/tests/TestRedis.php b/tests/TestRedis.php index 9bbef4da..88d47254 100644 --- a/tests/TestRedis.php +++ b/tests/TestRedis.php @@ -88,7 +88,7 @@ class Redis_Test extends TestSuite $this->assertTrue(is_array($result)); // PUBSUB NUMSUB - + $c1 = uniqid() . '-' . rand(1,100); $c2 = uniqid() . '-' . rand(1,100); @@ -101,7 +101,7 @@ class Redis_Test extends TestSuite // Make sure the elements are correct, and have zero counts foreach(Array($c1,$c2) as $channel) { $this->assertTrue(isset($result[$channel])); - $this->assertEquals($result[$channel], "0"); + $this->assertEquals($result[$channel], 0); } // PUBSUB NUMPAT @@ -218,7 +218,7 @@ class Redis_Test extends TestSuite $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')); @@ -528,7 +528,7 @@ class Redis_Test extends TestSuite } $this->redis->delete('key'); - + $this->redis->set('key', 0); $this->redis->incrbyfloat('key', 1.5); @@ -1227,9 +1227,9 @@ class Redis_Test extends TestSuite } } - // + // // With and without count, while serializing - // + // $this->redis->delete('set0'); $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); @@ -1253,11 +1253,11 @@ class Redis_Test extends TestSuite public function testSRandMemberWithCount() { // Make sure the set is nuked $this->redis->delete('set0'); - + // Run with a count (positive and negative) on an empty set $ret_pos = $this->redis->sRandMember('set0', 10); $ret_neg = $this->redis->sRandMember('set0', -10); - + // Should both be empty arrays $this->assertTrue(is_array($ret_pos) && empty($ret_pos)); $this->assertTrue(is_array($ret_neg) && empty($ret_neg)); @@ -1845,10 +1845,10 @@ class Redis_Test extends TestSuite // We should have found our connection $this->assertFalse(empty($str_addr)); - + /* CLIENT GETNAME */ $this->assertTrue($this->redis->client('getname'), 'phpredis_unit_tests'); - + /* CLIENT KILL -- phpredis will reconnect, so we can do this */ $this->assertTrue($this->redis->client('kill', $str_addr)); } @@ -2037,14 +2037,13 @@ class Redis_Test extends TestSuite } public function testZAddFirstArg() { + $zsetName = 100; // Make sure int keys work + $this->redis->delete($zsetName); - $this->redis->delete('key'); - - $zsetName = 100; // not a string! - $this->assertTrue(1 === $this->redis->zAdd($zsetName, 0, 'val0')); - $this->assertTrue(1 === $this->redis->zAdd($zsetName, 1, 'val1')); + $this->assertEquals(1, $this->redis->zAdd($zsetName, 0, 'val0')); + $this->assertEquals(1, $this->redis->zAdd($zsetName, 1, 'val1')); - $this->assertTrue(array('val0', 'val1') === $this->redis->zRange($zsetName, 0, -1)); + $this->assertTrue(array('val0', 'val1') === $this->redis->zRange($zsetName, 0, -1)); } public function testZX() { @@ -2328,6 +2327,40 @@ class Redis_Test extends TestSuite } + public function testZRangeByLex() { + /* Only out since 2.8.9 */ + if (version_compare($this->version, '2.8.9', 'lt')) { + $this->markTestSkipped(); + return; + } + + $arr_vals = Array('a','b','c','d','e','f','g'); + + $this->redis->del('zlex'); + foreach($arr_vals as $str_val) { + $this->redis->zadd('zlex', 0, $str_val); + } + + /* These tests were taken off of redis.io out of sheer laziness :) */ + $arr_ret = $this->redis->zRangeByLex('zlex', '-', '[c'); + $this->assertTrue($arr_ret === Array('a','b','c')); + + $arr_ret = $this->redis->zRangeByLex('zlex', '-', '(c'); + $this->assertTrue($arr_ret === Array('a','b')); + + $arr_ret = $this->redis->zRangeByLex('zlex', '[aaa', '(g'); + $this->assertTrue($arr_ret === Array('b','c','d','e','f')); + + /* Test with a limit and count */ + $arr_ret = $this->redis->zRangeBylex('zlex', '-', '[c', 1, 2); + $this->assertTrue($arr_ret === Array('b','c')); + + /* Test some invalid calls */ + $this->assertFalse($this->redis->zRangeByLex('zlex','b','[s')); + $this->assertFalse($this->redis->zRangeByLex('zlex','(a', '')); + $this->assertFalse($this->redis->zRangeByLex('zlex','(a','[b',1)); + } + public function testHashes() { $this->redis->delete('h', 'key'); @@ -2466,13 +2499,21 @@ class Redis_Test extends TestSuite } public function testObject() { - $this->redis->del('key'); + /* Version 3.0.0 (represented as >= 2.9.0 in redis info) and moving + * forward uses "embstr" instead of "raw" for small string values */ + if (version_compare($this->version, "2.9.0", "lt")) { + $str_small_encoding = "raw"; + } else { + $str_small_encoding = "embstr"; + } + + $this->redis->del('key'); $this->assertTrue($this->redis->object('encoding', 'key') === FALSE); $this->assertTrue($this->redis->object('refcount', 'key') === FALSE); $this->assertTrue($this->redis->object('idletime', 'key') === FALSE); $this->redis->set('key', 'value'); - $this->assertTrue($this->redis->object('encoding', 'key') === "raw"); + $this->assertTrue($this->redis->object('encoding', 'key') === $str_small_encoding); $this->assertTrue($this->redis->object('refcount', 'key') === 1); $this->assertTrue($this->redis->object('idletime', 'key') === 0); @@ -4496,7 +4537,7 @@ class Redis_Test extends TestSuite public function testSerialize() { $vals = Array(1, 1.5, 'one', Array('here','is','an','array')); - + // Test with no serialization at all $this->assertTrue($this->redis->_serialize('test') === 'test'); $this->assertTrue($this->redis->_serialize(1) === '1'); @@ -4509,7 +4550,7 @@ class Redis_Test extends TestSuite } foreach($arr_serializers as $mode) { - $arr_enc = Array(); + $arr_enc = Array(); $arr_dec = Array(); foreach($vals as $k => $v) { @@ -4679,7 +4720,7 @@ class Redis_Test extends TestSuite $this->markTestSkipped(); return; } - + // Never get empty sets $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); @@ -4688,7 +4729,7 @@ class Redis_Test extends TestSuite for($i=0;$i<100;$i++) { if($i>3) { - $this->redis->hset('hash', "member:$i", "value:$i"); + $this->redis->hset('hash', "member:$i", "value:$i"); } else { $this->redis->hset('hash', "foomember:$i", "value:$i"); $i_foo_mems++; @@ -4766,7 +4807,7 @@ class Redis_Test extends TestSuite } else { $this->redis->zadd('zset', $i, "mem:$i"); } - + $i_tot_score += $i; } @@ -4778,8 +4819,9 @@ class Redis_Test extends TestSuite $i--; } } + $this->assertEquals(0, $i); - $this->assertEquals(0, $i_tot_score); + $this->assertEquals(0.0, $i_tot_score); // Just scan "pmem" members $it = NULL; @@ -4791,7 +4833,7 @@ class Redis_Test extends TestSuite $i_p_count -= 1; } } - $this->assertEquals(0, $i_p_score); + $this->assertEquals(0.0, $i_p_score); $this->assertEquals(0, $i_p_count); // Turn off retrying and we should get some empty results @@ -4809,11 +4851,98 @@ class Redis_Test extends TestSuite } // We should still get all the keys, just with several empty results $this->assertTrue($i_skips > 0); - $this->assertEquals(0, $i_p_score); + $this->assertEquals(0.0, $i_p_score); $this->assertEquals(0, $i_p_count); } + + // + // HyperLogLog (PF) commands + // + + protected function createPFKey($str_key, $i_count) { + $arr_mems = Array(); + for($i=0;$i<$i_count;$i++) { + $arr_mems[] = uniqid() . '-' . $i; + } + + // Estimation by Redis + $this->redis->pfadd($str_key, $i_count); + } + + public function testPFCommands() { + // Isn't available until 2.8.9 + if(version_compare($this->version, "2.8.9", "lt")) { + $this->markTestSkipped(); + return; + } + + $str_uniq = uniqid(); + $arr_mems = Array(); + + for($i=0;$i<1000;$i++) { + if($i%2 == 0) { + $arr_mems[] = $str_uniq . '-' . $i; + } else { + $arr_mems[] = $i; + } + } + + // How many keys to create + $i_keys = 10; + + // Iterate prefixing/serialization options + foreach(Array(Redis::SERIALIZER_NONE, Redis::SERIALIZER_PHP) as $str_ser) { + foreach(Array('', 'hl-key-prefix:') as $str_prefix) { + $arr_keys = Array(); + + // Now add for each key + for($i=0;$i<$i_keys;$i++) { + $str_key = "key:$i"; + $arr_keys[] = $str_key; + + // Clean up this key + $this->redis->del($str_key); + + // Add to our cardinality set, and confirm we got a valid response + $this->assertTrue($this->redis->pfadd($str_key, $arr_mems)); + + // Grab estimated cardinality + $i_card = $this->redis->pfcount($str_key); + $this->assertTrue(is_int($i_card)); + + + // Count should be close + $this->assertLess(abs($i_card-count($arr_mems)), count($arr_mems) * .1); + + // The PFCOUNT on this key should be the same as the above returned response + $this->assertEquals($this->redis->pfcount($str_key), $i_card); + + } + + // Make sure we can pass an array of keys into pfCount + $i_card = $this->redis->pfcount($arr_keys); + $this->assertTrue(is_int($i_card)); + + // Clean up merge key + $this->redis->del('pf-merge-key'); + + // Merge the counters + $this->assertTrue($this->redis->pfmerge('pf-merge-key', $arr_keys)); + + // Validate our merged count + $i_redis_card = $this->redis->pfcount('pf-merge-key'); + + // Merged cardinality should still be roughly 1000 + $this->assertLess(abs($i_redis_card-count($arr_mems)), count($arr_mems) * .1); + + // Clean up merge key + $this->redis->del('pf-merge-key'); + } + } + } } -exit(TestSuite::run("Redis_Test")); +$str_test = isset($argv[1]) ? $argv[1] : NULL; +exit(TestSuite::run("Redis_Test", $str_test)); ?> diff --git a/tests/test.php b/tests/test.php index 8ccba5cf..43d6a445 100644 --- a/tests/test.php +++ b/tests/test.php @@ -2,7 +2,6 @@ // phpunit is such a pain to install, we're going with pure-PHP here. class TestSuite { - public static $errors = array(); public static $warnings = array(); @@ -19,6 +18,16 @@ class TestSuite { $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]); } + protected function assertLess($a, $b) { + if($a < $b) + return; + + $bt = debug_backtrace(false); + self::$errors[] = sprintf("Assertion failed (%s >= %s): %s: %d (%s\n", + print_r($a, true), print_r($b, true), + $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]); + } + protected function assertEquals($a, $b) { if($a === $b) return; @@ -37,17 +46,24 @@ class TestSuite { throw new Exception($msg); } - public static function run($className) { - - $rc = new ReflectionClass($className); + public static function run($className, $str_limit) { + $rc = new ReflectionClass($className); $methods = $rc->GetMethods(ReflectionMethod::IS_PUBLIC); - foreach($methods as $m) { + if ($str_limit) { + echo "Limiting to tests with the substring: '$str_limit'\n"; + } + foreach($methods as $m) { $name = $m->name; if(substr($name, 0, 4) !== 'test') continue; + /* If TestRedis.php was envoked with an argument, do a simple + * match against the routine. Useful to limit to one test */ + if ($str_limit && strpos(strtolower($name),strtolower($str_limit))===false) + continue; + $count = count($className::$errors); $rt = new $className; try { |