Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 90a0e9c

Browse files
committed
Add Redis::function command
1 parent 856b350 commit 90a0e9c

10 files changed

Lines changed: 239 additions & 3 deletions

library.c

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1708,6 +1708,50 @@ static void array_zip_values_and_scores(RedisSock *redis_sock, zval *z_tab,
17081708
ZVAL_ZVAL(z_tab, &z_ret, 0, 0);
17091709
}
17101710

1711+
static int
1712+
array_zip_values_recursive(zval *z_tab)
1713+
{
1714+
zend_string *zkey;
1715+
zval z_ret, z_sub, *zv;
1716+
1717+
array_init(&z_ret);
1718+
for (zend_hash_internal_pointer_reset(Z_ARRVAL_P(z_tab));
1719+
zend_hash_has_more_elements(Z_ARRVAL_P(z_tab)) == SUCCESS;
1720+
zend_hash_move_forward(Z_ARRVAL_P(z_tab))
1721+
) {
1722+
if ((zv = zend_hash_get_current_data(Z_ARRVAL_P(z_tab))) == NULL) {
1723+
zval_dtor(&z_ret);
1724+
return FAILURE;
1725+
}
1726+
if (Z_TYPE_P(zv) == IS_STRING) {
1727+
zkey = zval_get_string(zv);
1728+
zend_hash_move_forward(Z_ARRVAL_P(z_tab));
1729+
if ((zv = zend_hash_get_current_data(Z_ARRVAL_P(z_tab))) == NULL) {
1730+
zend_string_release(zkey);
1731+
zval_dtor(&z_ret);
1732+
return FAILURE;
1733+
}
1734+
if (Z_TYPE_P(zv) == IS_ARRAY && array_zip_values_recursive(zv) != SUCCESS) {
1735+
zend_string_release(zkey);
1736+
zval_dtor(&z_ret);
1737+
return FAILURE;
1738+
}
1739+
ZVAL_ZVAL(&z_sub, zv, 1, 0);
1740+
add_assoc_zval_ex(&z_ret, ZSTR_VAL(zkey), ZSTR_LEN(zkey), &z_sub);
1741+
zend_string_release(zkey);
1742+
} else {
1743+
if (Z_TYPE_P(zv) == IS_ARRAY && array_zip_values_recursive(zv) != SUCCESS) {
1744+
zval_dtor(&z_ret);
1745+
return FAILURE;
1746+
}
1747+
ZVAL_ZVAL(&z_sub, zv, 1, 0);
1748+
add_next_index_zval(&z_ret, &z_sub);
1749+
}
1750+
}
1751+
zval_dtor(z_tab);
1752+
ZVAL_ZVAL(z_tab, &z_ret, 0, 0);
1753+
return SUCCESS;
1754+
}
17111755

17121756
static int
17131757
redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
@@ -1966,6 +2010,50 @@ redis_client_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval
19662010
}
19672011
}
19682012

2013+
static int
2014+
redis_function_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
2015+
{
2016+
int numElems;
2017+
zval z_ret;
2018+
2019+
if (read_mbulk_header(redis_sock, &numElems) < 0) {
2020+
if (IS_ATOMIC(redis_sock)) {
2021+
RETVAL_FALSE;
2022+
} else {
2023+
add_next_index_bool(z_tab, 0);
2024+
}
2025+
return FAILURE;
2026+
}
2027+
2028+
array_init(&z_ret);
2029+
redis_read_multibulk_recursive(redis_sock, numElems, 0, &z_ret);
2030+
array_zip_values_recursive(&z_ret);
2031+
2032+
if (IS_ATOMIC(redis_sock)) {
2033+
RETVAL_ZVAL(&z_ret, 0, 1);
2034+
} else {
2035+
add_next_index_zval(z_tab, &z_ret);
2036+
}
2037+
2038+
return SUCCESS;
2039+
}
2040+
2041+
2042+
PHP_REDIS_API int
2043+
redis_function_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
2044+
{
2045+
if (ctx == NULL) {
2046+
return redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, NULL);
2047+
} else if (ctx == PHPREDIS_CTX_PTR) {
2048+
return redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, NULL);
2049+
} else if (ctx == PHPREDIS_CTX_PTR + 1) {
2050+
return redis_function_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, NULL);
2051+
} else {
2052+
ZEND_ASSERT(!"memory corruption?");
2053+
return FAILURE;
2054+
}
2055+
}
2056+
19692057
static int
19702058
redis_command_info_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
19712059
{

library.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,8 @@ PHP_REDIS_API int redis_lpos_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *r
195195
PHP_REDIS_API int redis_object_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
196196
PHP_REDIS_API int redis_read_lpos_response(zval *zdst, RedisSock *redis_sock, char reply_type, long long elements, void *ctx);
197197

198-
199198
PHP_REDIS_API int redis_client_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
199+
PHP_REDIS_API int redis_function_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
200200
PHP_REDIS_API int redis_command_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
201201
PHP_REDIS_API int redis_select_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
202202

redis.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,12 @@ PHP_METHOD(Redis, flushAll)
14941494
}
14951495
/* }}} */
14961496

1497+
/* {{{ proto mixed Redis::function(string op, mixed ...args) */
1498+
PHP_METHOD(Redis, function)
1499+
{
1500+
REDIS_PROCESS_CMD(function, redis_function_response)
1501+
}
1502+
14971503
/* {{{ proto int Redis::dbSize() */
14981504
PHP_METHOD(Redis, dbSize)
14991505
{

redis.stub.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,6 +1184,26 @@ public function flushAll(?bool $sync = null): Redis|bool;
11841184
*/
11851185
public function flushDB(?bool $sync = null): Redis|bool;
11861186

1187+
/**
1188+
* Functions is an API for managing code to be executed on the server.
1189+
*
1190+
* @param string $operation The subcommand you intend to execute. Valid options are as follows
1191+
* 'LOAD' - Create a new library with the given library name and code.
1192+
* 'DELETE' - Delete the given library.
1193+
* 'LIST' - Return general information on all the libraries
1194+
* 'STATS' - Return information about the current function running
1195+
* 'KILL' - Kill the current running function
1196+
* 'FLUSH' - Delete all the libraries
1197+
* 'DUMP' - Return a serialized payload representing the current libraries
1198+
* 'RESTORE' - Restore the libraries represented by the given payload
1199+
* @param member $args Additional arguments
1200+
*
1201+
* @return Redis|bool|string|array Depends on subcommand.
1202+
*
1203+
* @see https://redis.io/commands/function
1204+
*/
1205+
public function function(string $operation, mixed ...$args): Redis|bool|string|array;
1206+
11871207
/**
11881208
* Add one or more members to a geospacial sorted set
11891209
*

redis_arginfo.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: b0d5c56084a89230807e6ba582d2fab536d2e897 */
2+
* Stub hash: 3d369227b8f6d01fffa0ffda01f379f0138fa226 */
33

44
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 0, 0, 0)
55
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 0, "null")
@@ -237,6 +237,11 @@ ZEND_END_ARG_INFO()
237237

238238
#define arginfo_class_Redis_flushDB arginfo_class_Redis_flushAll
239239

240+
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)
241+
ZEND_ARG_TYPE_INFO(0, operation, IS_STRING, 0)
242+
ZEND_ARG_VARIADIC_TYPE_INFO(0, args, IS_MIXED, 0)
243+
ZEND_END_ARG_INFO()
244+
240245
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Redis_geoadd, 0, 4, Redis, MAY_BE_LONG|MAY_BE_FALSE)
241246
ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0)
242247
ZEND_ARG_TYPE_INFO(0, lng, IS_DOUBLE, 0)
@@ -1204,6 +1209,7 @@ ZEND_METHOD(Redis, expiretime);
12041209
ZEND_METHOD(Redis, pexpiretime);
12051210
ZEND_METHOD(Redis, flushAll);
12061211
ZEND_METHOD(Redis, flushDB);
1212+
ZEND_METHOD(Redis, function);
12071213
ZEND_METHOD(Redis, geoadd);
12081214
ZEND_METHOD(Redis, geodist);
12091215
ZEND_METHOD(Redis, geohash);
@@ -1456,6 +1462,7 @@ static const zend_function_entry class_Redis_methods[] = {
14561462
ZEND_ME(Redis, pexpiretime, arginfo_class_Redis_pexpiretime, ZEND_ACC_PUBLIC)
14571463
ZEND_ME(Redis, flushAll, arginfo_class_Redis_flushAll, ZEND_ACC_PUBLIC)
14581464
ZEND_ME(Redis, flushDB, arginfo_class_Redis_flushDB, ZEND_ACC_PUBLIC)
1465+
ZEND_ME(Redis, function, arginfo_class_Redis_function, ZEND_ACC_PUBLIC)
14591466
ZEND_ME(Redis, geoadd, arginfo_class_Redis_geoadd, ZEND_ACC_PUBLIC)
14601467
ZEND_ME(Redis, geodist, arginfo_class_Redis_geodist, ZEND_ACC_PUBLIC)
14611468
ZEND_ME(Redis, geohash, arginfo_class_Redis_geohash, ZEND_ACC_PUBLIC)

redis_commands.c

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,98 @@ redis_config_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
937937
return res;
938938
}
939939

940+
int
941+
redis_function_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
942+
char **cmd, int *cmd_len, short *slot, void **ctx)
943+
{
944+
smart_string cmdstr = {0};
945+
zend_string *op = NULL, *arg;
946+
zval *argv = NULL;
947+
int i, argc = 0;
948+
949+
ZEND_PARSE_PARAMETERS_START(1, -1)
950+
Z_PARAM_STR(op)
951+
Z_PARAM_OPTIONAL
952+
Z_PARAM_VARIADIC('*', argv, argc)
953+
ZEND_PARSE_PARAMETERS_END_EX(return FAILURE);
954+
955+
for (i = 0; i < argc; ++i) {
956+
if (Z_TYPE(argv[i]) != IS_STRING) {
957+
php_error_docref(NULL, E_WARNING, "invalid argument");
958+
return FAILURE;
959+
}
960+
}
961+
962+
if (zend_string_equals_literal_ci(op, "DELETE")) {
963+
if (argc < 1) {
964+
php_error_docref(NULL, E_WARNING, "argument required");
965+
return FAILURE;
966+
}
967+
} else if (zend_string_equals_literal_ci(op, "DUMP")) {
968+
*ctx = PHPREDIS_CTX_PTR;
969+
} else if (zend_string_equals_literal_ci(op, "FLUSH")) {
970+
if (argc > 0 &&
971+
!zend_string_equals_literal_ci(Z_STR(argv[0]), "SYNC") &&
972+
!zend_string_equals_literal_ci(Z_STR(argv[0]), "ASYNC")
973+
) {
974+
php_error_docref(NULL, E_WARNING, "invalid argument");
975+
return FAILURE;
976+
}
977+
} else if (zend_string_equals_literal_ci(op, "KILL")) {
978+
// noop
979+
} else if (zend_string_equals_literal_ci(op, "LIST")) {
980+
if (argc > 0) {
981+
if (zend_string_equals_literal_ci(Z_STR(argv[0]), "LIBRARYNAME")) {
982+
if (argc < 2) {
983+
php_error_docref(NULL, E_WARNING, "argument required");
984+
return FAILURE;
985+
}
986+
} else if (!zend_string_equals_literal_ci(Z_STR(argv[0]), "WITHCODE")) {
987+
php_error_docref(NULL, E_WARNING, "invalid argument");
988+
return FAILURE;
989+
}
990+
}
991+
*ctx = PHPREDIS_CTX_PTR + 1;
992+
} else if (zend_string_equals_literal_ci(op, "LOAD")) {
993+
if (argc < 1 || (
994+
zend_string_equals_literal_ci(Z_STR(argv[0]), "REPLACE") && argc < 2
995+
)) {
996+
php_error_docref(NULL, E_WARNING, "argument required");
997+
return FAILURE;
998+
}
999+
*ctx = PHPREDIS_CTX_PTR;
1000+
} else if (zend_string_equals_literal_ci(op, "RESTORE")) {
1001+
if (argc < 1 || (
1002+
argc > 1 &&
1003+
!zend_string_equals_literal_ci(Z_STR(argv[1]), "FLUSH") &&
1004+
!zend_string_equals_literal_ci(Z_STR(argv[1]), "APPEND") &&
1005+
!zend_string_equals_literal_ci(Z_STR(argv[1]), "REPLACE")
1006+
)) {
1007+
php_error_docref(NULL, E_WARNING, "invalid argument");
1008+
return FAILURE;
1009+
}
1010+
} else if (zend_string_equals_literal_ci(op, "STATS")) {
1011+
*ctx = PHPREDIS_CTX_PTR + 1;
1012+
} else {
1013+
php_error_docref(NULL, E_WARNING, "Unknown operation '%s'", ZSTR_VAL(op));
1014+
return FAILURE;
1015+
}
1016+
1017+
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 1 + argc, "FUNCTION");
1018+
redis_cmd_append_sstr_zstr(&cmdstr, op);
1019+
1020+
for (i = 0; i < argc; i++) {
1021+
arg = zval_get_string(&argv[i]);
1022+
redis_cmd_append_sstr_zstr(&cmdstr, arg);
1023+
zend_string_release(arg);
1024+
}
1025+
1026+
*cmd = cmdstr.c;
1027+
*cmd_len = cmdstr.len;
1028+
1029+
return SUCCESS;
1030+
}
1031+
9401032
int
9411033
redis_zrandmember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
9421034
char **cmd, int *cmd_len, short *slot, void **ctx)

redis_commands.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ int redis_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
110110
int redis_config_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
111111
char **cmd, int *cmd_len, short *slot, void **ctx);
112112

113+
int redis_function_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
114+
char **cmd, int *cmd_len, short *slot, void **ctx);
115+
113116
int redis_zrandmember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
114117
char **cmd, int *cmd_len, short *slot, void **ctx);
115118

redis_legacy_arginfo.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: b0d5c56084a89230807e6ba582d2fab536d2e897 */
2+
* Stub hash: 3d369227b8f6d01fffa0ffda01f379f0138fa226 */
33

44
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 0, 0, 0)
55
ZEND_ARG_INFO(0, options)
@@ -219,6 +219,11 @@ ZEND_END_ARG_INFO()
219219

220220
#define arginfo_class_Redis_flushDB arginfo_class_Redis_flushAll
221221

222+
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_function, 0, 0, 1)
223+
ZEND_ARG_INFO(0, operation)
224+
ZEND_ARG_VARIADIC_INFO(0, args)
225+
ZEND_END_ARG_INFO()
226+
222227
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_geoadd, 0, 0, 4)
223228
ZEND_ARG_INFO(0, key)
224229
ZEND_ARG_INFO(0, lng)
@@ -1046,6 +1051,7 @@ ZEND_METHOD(Redis, expiretime);
10461051
ZEND_METHOD(Redis, pexpiretime);
10471052
ZEND_METHOD(Redis, flushAll);
10481053
ZEND_METHOD(Redis, flushDB);
1054+
ZEND_METHOD(Redis, function);
10491055
ZEND_METHOD(Redis, geoadd);
10501056
ZEND_METHOD(Redis, geodist);
10511057
ZEND_METHOD(Redis, geohash);
@@ -1298,6 +1304,7 @@ static const zend_function_entry class_Redis_methods[] = {
12981304
ZEND_ME(Redis, pexpiretime, arginfo_class_Redis_pexpiretime, ZEND_ACC_PUBLIC)
12991305
ZEND_ME(Redis, flushAll, arginfo_class_Redis_flushAll, ZEND_ACC_PUBLIC)
13001306
ZEND_ME(Redis, flushDB, arginfo_class_Redis_flushDB, ZEND_ACC_PUBLIC)
1307+
ZEND_ME(Redis, function, arginfo_class_Redis_function, ZEND_ACC_PUBLIC)
13011308
ZEND_ME(Redis, geoadd, arginfo_class_Redis_geoadd, ZEND_ACC_PUBLIC)
13021309
ZEND_ME(Redis, geodist, arginfo_class_Redis_geodist, ZEND_ACC_PUBLIC)
13031310
ZEND_ME(Redis, geohash, arginfo_class_Redis_geohash, ZEND_ACC_PUBLIC)

tests/RedisClusterTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public function testScanErrors() { return $this->markTestSkipped(); }
5454
/* These 'directed node' commands work differently in RedisCluster */
5555
public function testConfig() { return $this->markTestSkipped(); }
5656
public function testFlushDB() { return $this->markTestSkipped(); }
57+
public function testFunction() { return $this->markTestSkipped(); }
5758

5859
/* Session locking feature is currently not supported in in context of Redis Cluster.
5960
The biggest issue for this is the distribution nature of Redis cluster */

tests/RedisTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7564,6 +7564,18 @@ public function testCommand()
75647564
}
75657565
}
75667566

7567+
public function testFunction() {
7568+
$this->assertTrue($this->redis->function('flush', 'sync'));
7569+
$this->assertEquals('mylib', $this->redis->function('load', "#!lua name=mylib\nredis.register_function('myfunc', function(keys, args) return args[1] end)"));
7570+
$this->assertEquals('mylib', $this->redis->function('load', 'replace', "#!lua name=mylib\nredis.register_function('myfunc', function(keys, args) return args[1] end)"));
7571+
$this->assertEquals($this->redis->function('stats'), ['running_script' => false, 'engines' => ['LUA' => ['libraries_count' => 1, 'functions_count' => 1]]]);
7572+
$payload = $this->redis->function('dump');
7573+
$this->assertTrue($this->redis->function('delete', 'mylib'));
7574+
$this->assertTrue($this->redis->function('restore', $payload));
7575+
$this->assertEquals($this->redis->function('list'), [['library_name' => 'mylib', 'engine' => 'LUA', 'functions' => [['name' => 'myfunc', 'description' => false,'flags' => []]]]]);
7576+
$this->assertTrue($this->redis->function('delete', 'mylib'));
7577+
}
7578+
75677579
/* Make sure we handle a bad option value gracefully */
75687580
public function testBadOptionValue() {
75697581
$this->assertFalse(@$this->redis->setOption(pow(2, 32), false));

0 commit comments

Comments
 (0)