mirror of
https://github.com/phpredis/phpredis.git
synced 2026-06-19 07:35:31 +00:00
Merge pull request #2306 from phpredis/issue-2068-function
Add Redis::function command
This commit is contained in:
@@ -1719,6 +1719,50 @@ static void array_zip_values_and_scores(RedisSock *redis_sock, zval *z_tab,
|
||||
ZVAL_ZVAL(z_tab, &z_ret, 0, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
array_zip_values_recursive(zval *z_tab)
|
||||
{
|
||||
zend_string *zkey;
|
||||
zval z_ret, z_sub, *zv;
|
||||
|
||||
array_init(&z_ret);
|
||||
for (zend_hash_internal_pointer_reset(Z_ARRVAL_P(z_tab));
|
||||
zend_hash_has_more_elements(Z_ARRVAL_P(z_tab)) == SUCCESS;
|
||||
zend_hash_move_forward(Z_ARRVAL_P(z_tab))
|
||||
) {
|
||||
if ((zv = zend_hash_get_current_data(Z_ARRVAL_P(z_tab))) == NULL) {
|
||||
zval_dtor(&z_ret);
|
||||
return FAILURE;
|
||||
}
|
||||
if (Z_TYPE_P(zv) == IS_STRING) {
|
||||
zkey = zval_get_string(zv);
|
||||
zend_hash_move_forward(Z_ARRVAL_P(z_tab));
|
||||
if ((zv = zend_hash_get_current_data(Z_ARRVAL_P(z_tab))) == NULL) {
|
||||
zend_string_release(zkey);
|
||||
zval_dtor(&z_ret);
|
||||
return FAILURE;
|
||||
}
|
||||
if (Z_TYPE_P(zv) == IS_ARRAY && array_zip_values_recursive(zv) != SUCCESS) {
|
||||
zend_string_release(zkey);
|
||||
zval_dtor(&z_ret);
|
||||
return FAILURE;
|
||||
}
|
||||
ZVAL_ZVAL(&z_sub, zv, 1, 0);
|
||||
add_assoc_zval_ex(&z_ret, ZSTR_VAL(zkey), ZSTR_LEN(zkey), &z_sub);
|
||||
zend_string_release(zkey);
|
||||
} else {
|
||||
if (Z_TYPE_P(zv) == IS_ARRAY && array_zip_values_recursive(zv) != SUCCESS) {
|
||||
zval_dtor(&z_ret);
|
||||
return FAILURE;
|
||||
}
|
||||
ZVAL_ZVAL(&z_sub, zv, 1, 0);
|
||||
add_next_index_zval(&z_ret, &z_sub);
|
||||
}
|
||||
}
|
||||
zval_dtor(z_tab);
|
||||
ZVAL_ZVAL(z_tab, &z_ret, 0, 0);
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
|
||||
@@ -1977,6 +2021,50 @@ redis_client_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
redis_function_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_recursive(&z_ret);
|
||||
|
||||
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_function_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, NULL);
|
||||
} else if (ctx == PHPREDIS_CTX_PTR) {
|
||||
return redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, NULL);
|
||||
} else if (ctx == PHPREDIS_CTX_PTR + 1) {
|
||||
return redis_function_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, NULL);
|
||||
} else {
|
||||
ZEND_ASSERT(!"memory corruption?");
|
||||
return FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
redis_command_info_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
|
||||
{
|
||||
|
||||
@@ -196,8 +196,8 @@ PHP_REDIS_API int redis_lpos_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *r
|
||||
PHP_REDIS_API int redis_object_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
|
||||
PHP_REDIS_API int redis_read_lpos_response(zval *zdst, RedisSock *redis_sock, char reply_type, long long elements, void *ctx);
|
||||
|
||||
|
||||
PHP_REDIS_API int redis_client_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
|
||||
PHP_REDIS_API int redis_function_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
|
||||
PHP_REDIS_API int redis_command_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
|
||||
PHP_REDIS_API int redis_select_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
|
||||
|
||||
|
||||
@@ -1494,6 +1494,12 @@ PHP_METHOD(Redis, flushAll)
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ proto mixed Redis::function(string op, mixed ...args) */
|
||||
PHP_METHOD(Redis, function)
|
||||
{
|
||||
REDIS_PROCESS_CMD(function, redis_function_response)
|
||||
}
|
||||
|
||||
/* {{{ proto int Redis::dbSize() */
|
||||
PHP_METHOD(Redis, dbSize)
|
||||
{
|
||||
|
||||
@@ -1184,6 +1184,26 @@ class Redis {
|
||||
*/
|
||||
public function flushDB(?bool $sync = null): Redis|bool;
|
||||
|
||||
/**
|
||||
* Functions is an API for managing code to be executed on the server.
|
||||
*
|
||||
* @param string $operation The subcommand you intend to execute. Valid options are as follows
|
||||
* 'LOAD' - Create a new library with the given library name and code.
|
||||
* 'DELETE' - Delete the given library.
|
||||
* 'LIST' - Return general information on all the libraries
|
||||
* 'STATS' - Return information about the current function running
|
||||
* 'KILL' - Kill the current running function
|
||||
* 'FLUSH' - Delete all the libraries
|
||||
* 'DUMP' - Return a serialized payload representing the current libraries
|
||||
* 'RESTORE' - Restore the libraries represented by the given payload
|
||||
* @param member $args Additional arguments
|
||||
*
|
||||
* @return Redis|bool|string|array Depends on subcommand.
|
||||
*
|
||||
* @see https://redis.io/commands/function
|
||||
*/
|
||||
public function function(string $operation, mixed ...$args): Redis|bool|string|array;
|
||||
|
||||
/**
|
||||
* Add one or more members to a geospacial sorted set
|
||||
*
|
||||
|
||||
+8
-1
@@ -1,5 +1,5 @@
|
||||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: b0d5c56084a89230807e6ba582d2fab536d2e897 */
|
||||
* Stub hash: 3d369227b8f6d01fffa0ffda01f379f0138fa226 */
|
||||
|
||||
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")
|
||||
@@ -237,6 +237,11 @@ ZEND_END_ARG_INFO()
|
||||
|
||||
#define arginfo_class_Redis_flushDB arginfo_class_Redis_flushAll
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Redis_function, 0, 1, Redis, MAY_BE_BOOL|MAY_BE_STRING|MAY_BE_ARRAY)
|
||||
ZEND_ARG_TYPE_INFO(0, operation, IS_STRING, 0)
|
||||
ZEND_ARG_VARIADIC_TYPE_INFO(0, args, IS_MIXED, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Redis_geoadd, 0, 4, Redis, MAY_BE_LONG|MAY_BE_FALSE)
|
||||
ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, lng, IS_DOUBLE, 0)
|
||||
@@ -1204,6 +1209,7 @@ ZEND_METHOD(Redis, expiretime);
|
||||
ZEND_METHOD(Redis, pexpiretime);
|
||||
ZEND_METHOD(Redis, flushAll);
|
||||
ZEND_METHOD(Redis, flushDB);
|
||||
ZEND_METHOD(Redis, function);
|
||||
ZEND_METHOD(Redis, geoadd);
|
||||
ZEND_METHOD(Redis, geodist);
|
||||
ZEND_METHOD(Redis, geohash);
|
||||
@@ -1456,6 +1462,7 @@ static const zend_function_entry class_Redis_methods[] = {
|
||||
ZEND_ME(Redis, pexpiretime, arginfo_class_Redis_pexpiretime, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Redis, flushAll, arginfo_class_Redis_flushAll, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Redis, flushDB, arginfo_class_Redis_flushDB, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Redis, function, arginfo_class_Redis_function, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Redis, geoadd, arginfo_class_Redis_geoadd, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Redis, geodist, arginfo_class_Redis_geodist, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Redis, geohash, arginfo_class_Redis_geohash, ZEND_ACC_PUBLIC)
|
||||
|
||||
@@ -937,6 +937,98 @@ redis_config_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
|
||||
return res;
|
||||
}
|
||||
|
||||
int
|
||||
redis_function_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
|
||||
char **cmd, int *cmd_len, short *slot, void **ctx)
|
||||
{
|
||||
smart_string cmdstr = {0};
|
||||
zend_string *op = NULL, *arg;
|
||||
zval *argv = NULL;
|
||||
int i, argc = 0;
|
||||
|
||||
ZEND_PARSE_PARAMETERS_START(1, -1)
|
||||
Z_PARAM_STR(op)
|
||||
Z_PARAM_OPTIONAL
|
||||
Z_PARAM_VARIADIC('*', argv, argc)
|
||||
ZEND_PARSE_PARAMETERS_END_EX(return FAILURE);
|
||||
|
||||
for (i = 0; i < argc; ++i) {
|
||||
if (Z_TYPE(argv[i]) != IS_STRING) {
|
||||
php_error_docref(NULL, E_WARNING, "invalid argument");
|
||||
return FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (zend_string_equals_literal_ci(op, "DELETE")) {
|
||||
if (argc < 1) {
|
||||
php_error_docref(NULL, E_WARNING, "argument required");
|
||||
return FAILURE;
|
||||
}
|
||||
} else if (zend_string_equals_literal_ci(op, "DUMP")) {
|
||||
*ctx = PHPREDIS_CTX_PTR;
|
||||
} else if (zend_string_equals_literal_ci(op, "FLUSH")) {
|
||||
if (argc > 0 &&
|
||||
!zend_string_equals_literal_ci(Z_STR(argv[0]), "SYNC") &&
|
||||
!zend_string_equals_literal_ci(Z_STR(argv[0]), "ASYNC")
|
||||
) {
|
||||
php_error_docref(NULL, E_WARNING, "invalid argument");
|
||||
return FAILURE;
|
||||
}
|
||||
} else if (zend_string_equals_literal_ci(op, "KILL")) {
|
||||
// noop
|
||||
} else if (zend_string_equals_literal_ci(op, "LIST")) {
|
||||
if (argc > 0) {
|
||||
if (zend_string_equals_literal_ci(Z_STR(argv[0]), "LIBRARYNAME")) {
|
||||
if (argc < 2) {
|
||||
php_error_docref(NULL, E_WARNING, "argument required");
|
||||
return FAILURE;
|
||||
}
|
||||
} else if (!zend_string_equals_literal_ci(Z_STR(argv[0]), "WITHCODE")) {
|
||||
php_error_docref(NULL, E_WARNING, "invalid argument");
|
||||
return FAILURE;
|
||||
}
|
||||
}
|
||||
*ctx = PHPREDIS_CTX_PTR + 1;
|
||||
} else if (zend_string_equals_literal_ci(op, "LOAD")) {
|
||||
if (argc < 1 || (
|
||||
zend_string_equals_literal_ci(Z_STR(argv[0]), "REPLACE") && argc < 2
|
||||
)) {
|
||||
php_error_docref(NULL, E_WARNING, "argument required");
|
||||
return FAILURE;
|
||||
}
|
||||
*ctx = PHPREDIS_CTX_PTR;
|
||||
} else if (zend_string_equals_literal_ci(op, "RESTORE")) {
|
||||
if (argc < 1 || (
|
||||
argc > 1 &&
|
||||
!zend_string_equals_literal_ci(Z_STR(argv[1]), "FLUSH") &&
|
||||
!zend_string_equals_literal_ci(Z_STR(argv[1]), "APPEND") &&
|
||||
!zend_string_equals_literal_ci(Z_STR(argv[1]), "REPLACE")
|
||||
)) {
|
||||
php_error_docref(NULL, E_WARNING, "invalid argument");
|
||||
return FAILURE;
|
||||
}
|
||||
} else if (zend_string_equals_literal_ci(op, "STATS")) {
|
||||
*ctx = PHPREDIS_CTX_PTR + 1;
|
||||
} else {
|
||||
php_error_docref(NULL, E_WARNING, "Unknown operation '%s'", ZSTR_VAL(op));
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 1 + argc, "FUNCTION");
|
||||
redis_cmd_append_sstr_zstr(&cmdstr, op);
|
||||
|
||||
for (i = 0; i < argc; i++) {
|
||||
arg = zval_get_string(&argv[i]);
|
||||
redis_cmd_append_sstr_zstr(&cmdstr, arg);
|
||||
zend_string_release(arg);
|
||||
}
|
||||
|
||||
*cmd = cmdstr.c;
|
||||
*cmd_len = cmdstr.len;
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
int
|
||||
redis_zrandmember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
|
||||
char **cmd, int *cmd_len, short *slot, void **ctx)
|
||||
|
||||
@@ -110,6 +110,9 @@ int redis_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
|
||||
int redis_config_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
|
||||
char **cmd, int *cmd_len, short *slot, void **ctx);
|
||||
|
||||
int redis_function_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
|
||||
char **cmd, int *cmd_len, short *slot, void **ctx);
|
||||
|
||||
int redis_zrandmember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
|
||||
char **cmd, int *cmd_len, short *slot, void **ctx);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: b0d5c56084a89230807e6ba582d2fab536d2e897 */
|
||||
* Stub hash: 3d369227b8f6d01fffa0ffda01f379f0138fa226 */
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 0, 0, 0)
|
||||
ZEND_ARG_INFO(0, options)
|
||||
@@ -219,6 +219,11 @@ ZEND_END_ARG_INFO()
|
||||
|
||||
#define arginfo_class_Redis_flushDB arginfo_class_Redis_flushAll
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_function, 0, 0, 1)
|
||||
ZEND_ARG_INFO(0, operation)
|
||||
ZEND_ARG_VARIADIC_INFO(0, args)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_geoadd, 0, 0, 4)
|
||||
ZEND_ARG_INFO(0, key)
|
||||
ZEND_ARG_INFO(0, lng)
|
||||
@@ -1046,6 +1051,7 @@ ZEND_METHOD(Redis, expiretime);
|
||||
ZEND_METHOD(Redis, pexpiretime);
|
||||
ZEND_METHOD(Redis, flushAll);
|
||||
ZEND_METHOD(Redis, flushDB);
|
||||
ZEND_METHOD(Redis, function);
|
||||
ZEND_METHOD(Redis, geoadd);
|
||||
ZEND_METHOD(Redis, geodist);
|
||||
ZEND_METHOD(Redis, geohash);
|
||||
@@ -1298,6 +1304,7 @@ static const zend_function_entry class_Redis_methods[] = {
|
||||
ZEND_ME(Redis, pexpiretime, arginfo_class_Redis_pexpiretime, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Redis, flushAll, arginfo_class_Redis_flushAll, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Redis, flushDB, arginfo_class_Redis_flushDB, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Redis, function, arginfo_class_Redis_function, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Redis, geoadd, arginfo_class_Redis_geoadd, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Redis, geodist, arginfo_class_Redis_geodist, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(Redis, geohash, arginfo_class_Redis_geohash, ZEND_ACC_PUBLIC)
|
||||
|
||||
@@ -54,6 +54,7 @@ class Redis_Cluster_Test extends Redis_Test {
|
||||
/* These 'directed node' commands work differently in RedisCluster */
|
||||
public function testConfig() { return $this->markTestSkipped(); }
|
||||
public function testFlushDB() { return $this->markTestSkipped(); }
|
||||
public function testFunction() { return $this->markTestSkipped(); }
|
||||
|
||||
/* Session locking feature is currently not supported in in context of Redis Cluster.
|
||||
The biggest issue for this is the distribution nature of Redis cluster */
|
||||
|
||||
@@ -7564,6 +7564,18 @@ class Redis_Test extends TestSuite
|
||||
}
|
||||
}
|
||||
|
||||
public function testFunction() {
|
||||
$this->assertTrue($this->redis->function('flush', 'sync'));
|
||||
$this->assertEquals('mylib', $this->redis->function('load', "#!lua name=mylib\nredis.register_function('myfunc', function(keys, args) return args[1] end)"));
|
||||
$this->assertEquals('mylib', $this->redis->function('load', 'replace', "#!lua name=mylib\nredis.register_function('myfunc', function(keys, args) return args[1] end)"));
|
||||
$this->assertEquals($this->redis->function('stats'), ['running_script' => false, 'engines' => ['LUA' => ['libraries_count' => 1, 'functions_count' => 1]]]);
|
||||
$payload = $this->redis->function('dump');
|
||||
$this->assertTrue($this->redis->function('delete', 'mylib'));
|
||||
$this->assertTrue($this->redis->function('restore', $payload));
|
||||
$this->assertEquals($this->redis->function('list'), [['library_name' => 'mylib', 'engine' => 'LUA', 'functions' => [['name' => 'myfunc', 'description' => false,'flags' => []]]]]);
|
||||
$this->assertTrue($this->redis->function('delete', 'mylib'));
|
||||
}
|
||||
|
||||
/* Make sure we handle a bad option value gracefully */
|
||||
public function testBadOptionValue() {
|
||||
$this->assertFalse(@$this->redis->setOption(pow(2, 32), false));
|
||||
|
||||
Reference in New Issue
Block a user