diff options
author | Pavlo Yatsukhnenko <yatsukhnenko@gmail.com> | 2022-09-04 21:31:20 +0300 |
---|---|---|
committer | Pavlo Yatsukhnenko <yatsukhnenko@gmail.com> | 2022-09-07 22:47:02 +0300 |
commit | a8d10291a28497e5288b6b7ef9e443ac8290707a (patch) | |
tree | 6c2334679b3e54ce5d5e3925e1ffa86d9d6ef7ad | |
parent | 8d99181e81c43e7c18c8275e48fc9aef85f5bea5 (diff) |
Redis::client commandissue-1894-client
-rw-r--r-- | library.c | 151 | ||||
-rw-r--r-- | library.h | 1 | ||||
-rw-r--r-- | redis.c | 57 | ||||
-rw-r--r-- | redis.stub.php | 2 | ||||
-rw-r--r-- | redis_arginfo.h | 4 | ||||
-rw-r--r-- | redis_commands.c | 415 | ||||
-rw-r--r-- | redis_commands.h | 3 | ||||
-rw-r--r-- | redis_legacy_arginfo.h | 4 | ||||
-rw-r--r-- | tests/RedisTest.php | 22 |
9 files changed, 565 insertions, 94 deletions
@@ -1196,6 +1196,67 @@ redis_parse_info_response(char *response, zval *z_ret) } } +static void +redis_parse_client_info(char *info, zval *z_ret) +{ + char *p1, *s1 = NULL; + + ZVAL_FALSE(z_ret); + if ((p1 = php_strtok_r(info, " ", &s1)) != NULL) { + array_init(z_ret); + do { + char *p; + zend_uchar type; + zend_long lval; + double dval; + if ((p = strchr(p1, '=')) != NULL) { + type = is_numeric_string(p + 1, strlen(p + 1), &lval, &dval, 0); + switch (type) { + case IS_LONG: + add_assoc_long_ex(z_ret, p1, p - p1, lval); + break; + case IS_DOUBLE: + add_assoc_double_ex(z_ret, p1, p - p1, dval); + break; + default: + add_assoc_string_ex(z_ret, p1, p - p1, p + 1); + } + } else { + add_next_index_string(z_ret, p1); + } + } while ((p1 = php_strtok_r(NULL, " ", &s1)) != NULL); + } +} + +static int +redis_client_info_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) +{ + char *resp; + int resp_len; + zval z_ret; + + /* Make sure we can read the bulk response from Redis */ + if ((resp = redis_sock_read(redis_sock, &resp_len)) == NULL) { + RETVAL_FALSE; + return FAILURE; + } + + /* Parse it out */ + redis_parse_client_info(resp, &z_ret); + + /* Free our response */ + efree(resp); + + /* Return or append depending if we're atomic */ + if (IS_ATOMIC(redis_sock)) { + RETVAL_ZVAL(&z_ret, 0, 1); + } else { + add_next_index_zval(z_tab, &z_ret); + } + + return SUCCESS; +} + /* * Specialized handling of the CLIENT LIST output so it comes out in a simple way for PHP userland code * to handle. @@ -1210,11 +1271,13 @@ redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zva if ((resp = redis_sock_read(redis_sock, &resp_len)) == NULL) { RETVAL_FALSE; return FAILURE; + } else if (resp_len > 0) { + /* Parse it out */ + redis_parse_client_list_response(resp, &z_ret); + } else { + array_init(&z_ret); } - /* Parse it out */ - redis_parse_client_list_response(resp, &z_ret); - /* Free our response */ efree(resp); @@ -1231,42 +1294,16 @@ redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zva PHP_REDIS_API void redis_parse_client_list_response(char *response, zval *z_ret) { - char *p1, *s1 = NULL; + char *p, *s = NULL; ZVAL_FALSE(z_ret); - if ((p1 = php_strtok_r(response, _NL, &s1)) != NULL) { + if ((p = php_strtok_r(response, _NL, &s)) != NULL) { array_init(z_ret); do { - char *p2, *s2 = NULL; zval z_sub; - - ZVAL_FALSE(&z_sub); - if ((p2 = php_strtok_r(p1, " ", &s2)) != NULL) { - array_init(&z_sub); - do { - char *p; - zend_uchar type; - zend_long lval; - double dval; - if ((p = strchr(p2, '=')) != NULL) { - type = is_numeric_string(p + 1, strlen(p + 1), &lval, &dval, 0); - switch (type) { - case IS_LONG: - add_assoc_long_ex(&z_sub, p2, p - p2, lval); - break; - case IS_DOUBLE: - add_assoc_double_ex(&z_sub, p2, p - p2, dval); - break; - default: - add_assoc_string_ex(&z_sub, p2, p - p2, p + 1); - } - } else { - add_next_index_string(&z_sub, p2); - } - } while ((p2 = php_strtok_r(NULL, " ", &s2)) != NULL); - } + redis_parse_client_info(p, &z_sub); add_next_index_zval(z_ret, &z_sub); - } while ((p1 = php_strtok_r(NULL, _NL, &s1)) != NULL); + } while ((p = php_strtok_r(NULL, _NL, &s)) != NULL); } } @@ -1629,6 +1666,54 @@ redis_geosearch_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, return SUCCESS; } +static int +redis_client_trackinginfo_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) +{ + int numElems; + zval z_ret; + + if (read_mbulk_header(redis_sock, &numElems) < 0) { + if (IS_ATOMIC(redis_sock)) { + RETVAL_FALSE; + } else { + add_next_index_bool(z_tab, 0); + } + return FAILURE; + } + + array_init(&z_ret); + redis_read_multibulk_recursive(redis_sock, numElems, 0, &z_ret); + array_zip_values_and_scores(redis_sock, &z_ret, 0); + + if (IS_ATOMIC(redis_sock)) { + RETVAL_ZVAL(&z_ret, 0, 1); + } else { + add_next_index_zval(z_tab, &z_ret); + } + + return SUCCESS; +} + +PHP_REDIS_API int +redis_client_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) +{ + if (ctx == NULL) { + return redis_client_info_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, NULL); + } else if (ctx == PHPREDIS_CTX_PTR) { + return redis_client_list_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, NULL); + } else if (ctx == PHPREDIS_CTX_PTR + 1) { + return redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, NULL); + } else if (ctx == PHPREDIS_CTX_PTR + 2) { + return redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, NULL); + } else if (ctx == PHPREDIS_CTX_PTR + 3) { + return redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, NULL); + } else if (ctx == PHPREDIS_CTX_PTR + 4) { + return redis_client_trackinginfo_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, NULL); + } else { + ZEND_ASSERT(!"memory corruption?"); + } +} + /* Helper function to consume Redis stream message data. This is useful for * multiple stream callers (e.g. XREAD[GROUP], and X[REV]RANGE handlers). */ PHP_REDIS_API int @@ -169,6 +169,7 @@ PHP_REDIS_API int redis_geosearch_response(INTERNAL_FUNCTION_PARAMETERS, RedisSo PHP_REDIS_API int redis_hrandfield_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_pop_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_lpos_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +PHP_REDIS_API int redis_client_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); /* Helper methods to get configuration values from a HashTable. */ @@ -3286,62 +3286,11 @@ PHP_METHOD(Redis, getAuth) { } } -/* - * $redis->client('list'); - * $redis->client('info'); - * $redis->client('kill', <ip:port>); - * $redis->client('setname', <name>); - * $redis->client('getname'); - */ +/* {{{ proto mixed Redis::client(string $command, [ $arg1 ... $argN]) */ PHP_METHOD(Redis, client) { - zval *object; - RedisSock *redis_sock; - char *cmd, *opt = NULL, *arg = NULL; - size_t opt_len, arg_len; - int cmd_len; - - // Parse our method parameters - if(zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), - "Os|s", &object, redis_ce, &opt, &opt_len, - &arg, &arg_len) == FAILURE) - { - RETURN_FALSE; - } - - /* Grab our socket */ - if ((redis_sock = redis_sock_get(object, 0)) == NULL) { - RETURN_FALSE; - } - - /* Build our CLIENT command */ - if (ZEND_NUM_ARGS() == 2) { - cmd_len = REDIS_SPPRINTF(&cmd, "CLIENT", "ss", opt, opt_len, arg, arg_len); - } else { - cmd_len = REDIS_SPPRINTF(&cmd, "CLIENT", "s", opt, opt_len); - } - - /* Execute our queue command */ - REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); - - /* We handle CLIENT LIST with a custom response function */ - if(!strncasecmp(opt, "list", 4)) { - if (IS_ATOMIC(redis_sock)) { - redis_client_list_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_client_list_reply); - } else if (!strncasecmp(opt, "info", 4)) { - if (IS_ATOMIC(redis_sock)) { - redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); - } - REDIS_PROCESS_RESPONSE(redis_string_response); - } else { - if (IS_ATOMIC(redis_sock)) { - redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, - redis_sock,NULL,NULL); - } - REDIS_PROCESS_RESPONSE(redis_read_variant_reply); - } + REDIS_PROCESS_CMD(client, redis_client_response); } +/* }}} */ /* {{{ proto mixed Redis::rawcommand(string $command, [ $arg1 ... $argN]) */ PHP_METHOD(Redis, rawcommand) { diff --git a/redis.stub.php b/redis.stub.php index 16b6164c..4a2751b3 100644 --- a/redis.stub.php +++ b/redis.stub.php @@ -64,7 +64,7 @@ class Redis { public function clearLastError(): bool; - public function client(string $opt, string $arg = null): mixed; + public function client(string $opt, mixed ...$args): mixed; public function close(): bool; diff --git a/redis_arginfo.h b/redis_arginfo.h index b5f67ad0..35ea8d58 100644 --- a/redis_arginfo.h +++ b/redis_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 7fc0b991dc8404945a0081aef8a422c9c670eab9 */ + * Stub hash: 8d3ef188b058066309394ffaaf00489572d7b629 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 0, "null") @@ -91,7 +91,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Redis_client, 0, 1, IS_MIXED, 0) ZEND_ARG_TYPE_INFO(0, opt, IS_STRING, 0) - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, arg, IS_STRING, 0, "null") + ZEND_ARG_VARIADIC_TYPE_INFO(0, args, IS_MIXED, 0) ZEND_END_ARG_INFO() #define arginfo_class_Redis_close arginfo_class_Redis_bgSave diff --git a/redis_commands.c b/redis_commands.c index ea1eedc7..a208119a 100644 --- a/redis_commands.c +++ b/redis_commands.c @@ -4028,6 +4028,421 @@ int redis_sdiffstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, "SDIFFSTORE", sizeof("SDIFFSTORE")-1, 1, 0, cmd, cmd_len, slot); } +static int +redis_build_client_list_command(smart_string *cmdstr, int argc, zval *z_args) +{ + zend_string *zkey; + zval *z_ele, *type = NULL, *id = NULL; + + if (argc > 1) { + if (Z_TYPE(z_args[1]) != IS_ARRAY) { + return FAILURE; + } + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL(z_args[1]), zkey, z_ele) { + if (zkey != NULL) { + ZVAL_DEREF(z_ele); + if (zend_string_equals_literal_ci(zkey, "type")) { + if (Z_TYPE_P(z_ele) != IS_STRING || ( + !ZVAL_STRICMP_STATIC(z_ele, "normal") && + !ZVAL_STRICMP_STATIC(z_ele, "master") && + !ZVAL_STRICMP_STATIC(z_ele, "replica") && + !ZVAL_STRICMP_STATIC(z_ele, "pubsub") + )) { + return FAILURE; + } + type = z_ele; + } else if (zend_string_equals_literal_ci(zkey, "id")) { + if (Z_TYPE_P(z_ele) != IS_STRING && ( + Z_TYPE_P(z_ele) != IS_ARRAY || + !zend_hash_num_elements(Z_ARRVAL_P(z_ele)) + )) { + return FAILURE; + } + id = z_ele; + } + } + } ZEND_HASH_FOREACH_END(); + } + REDIS_CMD_INIT_SSTR_STATIC(cmdstr, 1 + (type ? 2 : 0) + ( + id ? (Z_TYPE_P(id) == IS_ARRAY ? 1 + zend_hash_num_elements(Z_ARRVAL_P(id)) : 2) : 0 + ), "CLIENT"); + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "LIST"); + if (type != NULL) { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "TYPE"); + redis_cmd_append_sstr(cmdstr, Z_STRVAL_P(type), Z_STRLEN_P(type)); + } + if (id != NULL) { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "ID"); + if (Z_TYPE_P(id) == IS_ARRAY) { + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(id), z_ele) { + if (Z_TYPE_P(z_ele) == IS_STRING) { + redis_cmd_append_sstr(cmdstr, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele)); + } else { + zkey = zval_get_string(z_ele); + redis_cmd_append_sstr(cmdstr, ZSTR_VAL(zkey), ZSTR_LEN(zkey)); + zend_string_release(zkey); + } + } ZEND_HASH_FOREACH_END(); + } else { + redis_cmd_append_sstr(cmdstr, Z_STRVAL_P(id), Z_STRLEN_P(id)); + } + } + return SUCCESS; +} + +static int +redis_build_client_kill_command(smart_string *cmdstr, int argc, zval *z_args) +{ + zend_string *zkey; + zval *z_ele, *id = NULL, *type = NULL, *address = NULL, *opts = NULL, + *user = NULL, *addr = NULL, *laddr = NULL, *skipme = NULL; + + if (argc > 1) { + if (argc > 2) { + if (Z_TYPE(z_args[1]) != IS_STRING || Z_TYPE(z_args[2]) != IS_ARRAY) { + return FAILURE; + } + address = &z_args[1]; + opts = &z_args[2]; + } else if (Z_TYPE(z_args[1]) == IS_STRING) { + address = &z_args[1]; + } else if (Z_TYPE(z_args[1]) == IS_ARRAY) { + opts = &z_args[1]; + } else { + return FAILURE; + } + if (opts != NULL) { + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(opts), zkey, z_ele) { + if (zkey != NULL) { + ZVAL_DEREF(z_ele); + if (Z_TYPE_P(z_ele) != IS_STRING) { + return FAILURE; + } + if (zend_string_equals_literal_ci(zkey, "id")) { + id = z_ele; + } else if (zend_string_equals_literal_ci(zkey, "type")) { + if (!ZVAL_STRICMP_STATIC(z_ele, "normal") && + !ZVAL_STRICMP_STATIC(z_ele, "master") && + !ZVAL_STRICMP_STATIC(z_ele, "slave") && + !ZVAL_STRICMP_STATIC(z_ele, "replica") && + !ZVAL_STRICMP_STATIC(z_ele, "pubsub") + ) { + return FAILURE; + } + type = z_ele; + } else if (zend_string_equals_literal_ci(zkey, "user")) { + user = z_ele; + } else if (zend_string_equals_literal_ci(zkey, "addr")) { + addr = z_ele; + } else if (zend_string_equals_literal_ci(zkey, "laddr")) { + laddr = z_ele; + } else if (zend_string_equals_literal_ci(zkey, "skipme")) { + if (!ZVAL_STRICMP_STATIC(z_ele, "yes") && + !ZVAL_STRICMP_STATIC(z_ele, "no") + ) { + return FAILURE; + } + skipme = z_ele; + } + } + } ZEND_HASH_FOREACH_END(); + } + } + REDIS_CMD_INIT_SSTR_STATIC(cmdstr, 1 + (address != 0) + (id ? 2 : 0) + + (type ? 2 : 0) + (user ? 2 : 0) + (addr ? 2 : 0) + (laddr ? 2 : 0) + + (skipme ? 2 : 0), "CLIENT"); + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "KILL"); + if (address != NULL) { + redis_cmd_append_sstr(cmdstr, Z_STRVAL_P(address), Z_STRLEN_P(address)); + } + if (id != NULL) { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "ID"); + redis_cmd_append_sstr(cmdstr, Z_STRVAL_P(id), Z_STRLEN_P(id)); + } + if (type != NULL) { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "TYPE"); + redis_cmd_append_sstr(cmdstr, Z_STRVAL_P(type), Z_STRLEN_P(type)); + } + if (user != NULL) { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "USER"); + redis_cmd_append_sstr(cmdstr, Z_STRVAL_P(user), Z_STRLEN_P(user)); + } + if (addr != NULL) { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "ADDR"); + redis_cmd_append_sstr(cmdstr, Z_STRVAL_P(addr), Z_STRLEN_P(addr)); + } + if (laddr != NULL) { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "LADDR"); + redis_cmd_append_sstr(cmdstr, Z_STRVAL_P(laddr), Z_STRLEN_P(laddr)); + } + if (skipme != NULL) { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "SKIPME"); + redis_cmd_append_sstr(cmdstr, Z_STRVAL_P(skipme), Z_STRLEN_P(skipme)); + } + return SUCCESS; +} + +static int +redis_build_client_tracking_command(smart_string *cmdstr, int argc, zval *z_args) +{ + zend_string *zkey; + zval *z_ele, *redirect = NULL, *prefix = NULL; + zend_bool bcast = 0, optin = 0, optout = 0, noloop = 0; + + if (argc < 2) { + return FAILURE; + } + if (argc > 2) { + if (Z_TYPE(z_args[2]) != IS_ARRAY) { + return FAILURE; + } + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL(z_args[2]), zkey, z_ele) { + if (zkey != NULL) { + ZVAL_DEREF(z_ele); + if (zend_string_equals_literal_ci(zkey, "redirect")) { + if (Z_TYPE_P(z_ele) != IS_STRING) { + return FAILURE; + } + redirect = z_ele; + } else if (zend_string_equals_literal_ci(zkey, "prefix")) { + if (Z_TYPE_P(z_ele) != IS_STRING && Z_TYPE_P(z_ele) != IS_ARRAY) { + return FAILURE; + } + prefix = z_ele; + } else if (zend_string_equals_literal_ci(zkey, "bcast")) { + bcast = zval_is_true(z_ele); + } else if (zend_string_equals_literal_ci(zkey, "optin")) { + optin = zval_is_true(z_ele); + } else if (zend_string_equals_literal_ci(zkey, "optout")) { + optout = zval_is_true(z_ele); + } else if (zend_string_equals_literal_ci(zkey, "noloop")) { + noloop = zval_is_true(z_ele); + } + } + } ZEND_HASH_FOREACH_END(); + } + REDIS_CMD_INIT_SSTR_STATIC(cmdstr, 2 + (redirect ? 2 : 0) + + (prefix ? 2 * zend_hash_num_elements(Z_ARRVAL_P(prefix)) : 0) + + bcast + optin + optout + noloop, "CLIENT"); + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "TRACKING"); + if (Z_TYPE(z_args[1]) == IS_STRING && ( + ZVAL_STRICMP_STATIC(&z_args[1], "on") || + ZVAL_STRICMP_STATIC(&z_args[1], "off") + )) { + redis_cmd_append_sstr(cmdstr, Z_STRVAL(z_args[1]), Z_STRLEN(z_args[1])); + } else if (zval_is_true(&z_args[1])) { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "ON"); + } else { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "OFF"); + } + if (redirect != NULL) { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "REDIRECT"); + redis_cmd_append_sstr(cmdstr, Z_STRVAL_P(redirect), Z_STRLEN_P(redirect)); + } + if (prefix != NULL) { + if (Z_TYPE_P(prefix) == IS_ARRAY) { + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(prefix), z_ele) { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "PREFIX"); + if (Z_TYPE_P(z_ele) == IS_STRING) { + redis_cmd_append_sstr(cmdstr, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele)); + } else { + zkey = zval_get_string(z_ele); + redis_cmd_append_sstr(cmdstr, ZSTR_VAL(zkey), ZSTR_LEN(zkey)); + zend_string_release(zkey); + } + } ZEND_HASH_FOREACH_END(); + } else { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "PREFIX"); + redis_cmd_append_sstr(cmdstr, Z_STRVAL_P(prefix), Z_STRLEN_P(prefix)); + } + } + if (bcast) { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "BCAST"); + } + if (optin) { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "OPTIN"); + } + if (optout) { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "OPTOUT"); + } + if (noloop) { + REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "NOLOOP"); + } + return SUCCESS; +} + +int +redis_client_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + char **cmd, int *cmd_len, short *slot, void **ctx) +{ + int argc; + smart_string cmdstr = {0}; + zval *z_args; + + if ((argc = ZEND_NUM_ARGS()) < 1) { + return FAILURE; + } + + z_args = ecalloc(argc, sizeof(*z_args)); + if (zend_get_parameters_array(ht, argc, z_args) == FAILURE || + Z_TYPE(z_args[0]) != IS_STRING + ) { + efree(z_args); + return FAILURE; + } + + if (ZVAL_STRICMP_STATIC(&z_args[0], "info")) { + REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 1, "CLIENT"); + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "INFO"); + } else if (ZVAL_STRICMP_STATIC(&z_args[0], "list")) { + if (redis_build_client_list_command(&cmdstr, argc, z_args) != 0) { + efree(z_args); + return FAILURE; + } + *ctx = PHPREDIS_CTX_PTR; + } else if (ZVAL_STRICMP_STATIC(&z_args[0], "caching")) { + if (argc < 2) { + efree(z_args); + return FAILURE; + } + REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 2, "CLIENT"); + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "CACHING"); + if (Z_TYPE(z_args[1]) == IS_STRING && ( + ZVAL_STRICMP_STATIC(&z_args[1], "yes") || + ZVAL_STRICMP_STATIC(&z_args[1], "no") + )) { + redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[1]), Z_STRLEN(z_args[1])); + } else if (zval_is_true(&z_args[1])) { + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "YES"); + } else { + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "NO"); + } + *ctx = PHPREDIS_CTX_PTR + 1; + } else if (ZVAL_STRICMP_STATIC(&z_args[0], "getname")) { + REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 1, "CLIENT"); + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "GETNAME"); + *ctx = PHPREDIS_CTX_PTR + 3; + } else if (ZVAL_STRICMP_STATIC(&z_args[0], "getredir") || ZVAL_STRICMP_STATIC(&z_args[0], "id")) { + REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 1, "CLIENT"); + redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[0]), Z_STRLEN(z_args[0])); + *ctx = PHPREDIS_CTX_PTR + 2; + } else if (ZVAL_STRICMP_STATIC(&z_args[0], "kill")) { + if (redis_build_client_kill_command(&cmdstr, argc, z_args) != 0) { + efree(z_args); + return FAILURE; + } + *ctx = PHPREDIS_CTX_PTR + 1; + } else if (ZVAL_STRICMP_STATIC(&z_args[0], "no-evict")) { + if (argc < 2) { + efree(z_args); + return FAILURE; + } + REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 2, "CLIENT"); + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "NO-EVICT"); + if (Z_TYPE(z_args[1]) == IS_STRING && ( + ZVAL_STRICMP_STATIC(&z_args[1], "on") || + ZVAL_STRICMP_STATIC(&z_args[1], "off") + )) { + redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[1]), Z_STRLEN(z_args[1])); + } else if (zval_is_true(&z_args[1])) { + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "ON"); + } else { + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "OFF"); + } + *ctx = PHPREDIS_CTX_PTR + 1; + } else if (ZVAL_STRICMP_STATIC(&z_args[0], "pause")) { + if (argc < 2 || Z_TYPE(z_args[1]) != IS_LONG || ( + argc > 2 && ( + Z_TYPE(z_args[2]) != IS_STRING || ( + !ZVAL_STRICMP_STATIC(&z_args[2], "write") && + !ZVAL_STRICMP_STATIC(&z_args[2], "all") + ) + ) + )) { + efree(z_args); + return FAILURE; + } + REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc > 2 ? 3 : 2, "CLIENT"); + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "PAUSE"); + redis_cmd_append_sstr_long(&cmdstr, Z_LVAL(z_args[1])); + if (argc > 2) { + redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[2]), Z_STRLEN(z_args[2])); + } + *ctx = PHPREDIS_CTX_PTR + 1; + } else if (ZVAL_STRICMP_STATIC(&z_args[0], "reply")) { + if (argc > 1 && ( + Z_TYPE(z_args[1]) != IS_STRING || ( + !ZVAL_STRICMP_STATIC(&z_args[1], "on") && + !ZVAL_STRICMP_STATIC(&z_args[1], "off") && + !ZVAL_STRICMP_STATIC(&z_args[1], "skip") + ) + )) { + efree(z_args); + return FAILURE; + } + REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc > 1 ? 2 : 1, "CLIENT"); + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "REPLY"); + if (argc > 1) { + redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[1]), Z_STRLEN(z_args[1])); + } + *ctx = PHPREDIS_CTX_PTR + 1; + } else if (ZVAL_STRICMP_STATIC(&z_args[0], "setname")) { + if (argc < 2 || Z_TYPE(z_args[1]) != IS_STRING) { + efree(z_args); + return FAILURE; + } + REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 2, "CLIENT"); + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "SETNAME"); + redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[1]), Z_STRLEN(z_args[1])); + *ctx = PHPREDIS_CTX_PTR + 1; + } else if (ZVAL_STRICMP_STATIC(&z_args[0], "tracking")) { + if (redis_build_client_tracking_command(&cmdstr, argc, z_args) != 0) { + efree(z_args); + return FAILURE; + } + *ctx = PHPREDIS_CTX_PTR + 1; + } else if (ZVAL_STRICMP_STATIC(&z_args[0], "trackinginfo")) { + REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 1, "CLIENT"); + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "TRACKINGINFO"); + *ctx = PHPREDIS_CTX_PTR + 4; + } else if (ZVAL_STRICMP_STATIC(&z_args[0], "unblock")) { + if (argc < 2 || Z_TYPE(z_args[1]) != IS_STRING || ( + argc > 2 && ( + Z_TYPE(z_args[2]) != IS_STRING || ( + !ZVAL_STRICMP_STATIC(&z_args[2], "timeout") && + !ZVAL_STRICMP_STATIC(&z_args[2], "error") + ) + ) + )) { + efree(z_args); + return FAILURE; + } + REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc > 2 ? 3 : 2, "CLIENT"); + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "UNBLOCK"); + redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[1]), Z_STRLEN(z_args[1])); + if (argc > 2) { + redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[2]), Z_STRLEN(z_args[2])); + } + *ctx = PHPREDIS_CTX_PTR + 2; + } else if (ZVAL_STRICMP_STATIC(&z_args[0], "unpause")) { + REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 2, "CLIENT"); + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "UNPAUSE"); + *ctx = PHPREDIS_CTX_PTR + 1; + } else { + efree(z_args); + return FAILURE; + } + + // Push out values + *cmd = cmdstr.c; + *cmd_len = cmdstr.len; + + // Cleanup arg array + efree(z_args); + + return SUCCESS; +} + /* COMMAND */ int redis_command_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) diff --git a/redis_commands.h b/redis_commands.h index e412d828..662d0500 100644 --- a/redis_commands.h +++ b/redis_commands.h @@ -293,6 +293,9 @@ int redis_sdiff_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, int redis_sdiffstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); +int redis_client_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + char **cmd, int *cmd_len, short *slot, void **ctx); + int redis_command_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); diff --git a/redis_legacy_arginfo.h b/redis_legacy_arginfo.h index 5cced37a..11803eb4 100644 --- a/redis_legacy_arginfo.h +++ b/redis_legacy_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 7fc0b991dc8404945a0081aef8a422c9c670eab9 */ + * Stub hash: 8d3ef188b058066309394ffaaf00489572d7b629 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 0, 0, 0) ZEND_ARG_INFO(0, options) @@ -86,7 +86,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_client, 0, 0, 1) ZEND_ARG_INFO(0, opt) - ZEND_ARG_INFO(0, arg) + ZEND_ARG_VARIADIC_INFO(0, args) ZEND_END_ARG_INFO() #define arginfo_class_Redis_close arginfo_class_Redis___destruct diff --git a/tests/RedisTest.php b/tests/RedisTest.php index de76e9b4..c2e413ae 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -1991,8 +1991,26 @@ class Redis_Test extends TestSuite /* CLIENT GETNAME */ $this->assertTrue($this->redis->client('getname'), 'phpredis_unit_tests'); - if (version_compare($this->version, "6.2.0") >= 0) { - $this->assertFalse(empty($this->redis->client('info'))); + if (version_compare($this->version, '5.0.0') >= 0) { + $this->assertLess(0, $this->redis->client('id')); + if (version_compare($this->version, '6.0.0') >= 0) { + $this->assertEquals($this->redis->client('getredir'), -1); + $this->assertTrue($this->redis->client('tracking', 'on', ['optin' => true])); + $this->assertEquals($this->redis->client('getredir'), 0); + $this->assertTrue($this->redis->client('caching', 'yes')); + $this->assertTrue($this->redis->client('tracking', 'off')); + if (version_compare($this->version, '6.2.0') >= 0) { + $this->assertFalse(empty($this->redis->client('info'))); + $this->assertEquals($this->redis->client('trackinginfo'), [ + 'flags' => ['off'], + 'redirect' => -1, + 'prefixes' => [], + ]); + if (version_compare($this->version, '7.0.0') >= 0) { + $this->assertTrue($this->redis->client('no-evict', 'on')); + } + } + } } /* CLIENT KILL -- phpredis will reconnect, so we can do this */ |