diff --git a/cluster_library.c b/cluster_library.c index c76c32697f..fe574e50d9 100644 --- a/cluster_library.c +++ b/cluster_library.c @@ -2362,6 +2362,23 @@ cluster_xinfo_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) add_next_index_zval(&c->multi_resp, &z_ret); } +/* LMPOP, ZMPOP, BLMPOP, BZMPOP */ +PHP_REDIS_API void +cluster_mpop_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) +{ + zval z_ret; + + c->cmd_sock->null_mbulk_as_null = c->flags->null_mbulk_as_null; + if (redis_read_mpop_response(c->cmd_sock, &z_ret, c->reply_len, ctx) == FAILURE) { + CLUSTER_RETURN_FALSE(c); + } + + if (CLUSTER_IS_ATOMIC(c)) { + RETURN_ZVAL(&z_ret, 0, 0); + } + add_next_index_zval(&c->multi_resp, &z_ret); +} + static void cluster_acl_custom_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx, int (*cb)(RedisSock*, zval*, long)) diff --git a/cluster_library.h b/cluster_library.h index ddb29f2916..1f18c35a0b 100644 --- a/cluster_library.h +++ b/cluster_library.h @@ -496,6 +496,9 @@ PHP_REDIS_API void cluster_xclaim_resp(INTERNAL_FUNCTION_PARAMETERS, PHP_REDIS_API void cluster_xinfo_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); +PHP_REDIS_API void cluster_mpop_resp(INTERNAL_FUNCTION_PARAMETERS, + redisCluster *c, void *ctx); + /* Custom ACL handlers */ PHP_REDIS_API void cluster_acl_getuser_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); PHP_REDIS_API void cluster_acl_log_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx); diff --git a/library.c b/library.c index 67a49be041..09b31e6c54 100644 --- a/library.c +++ b/library.c @@ -68,6 +68,17 @@ #define SCORE_DECODE_INT 1 #define SCORE_DECODE_DOUBLE 2 +/* PhpRedis often returns either FALSE or NULL depending on whether we have + * an option set, so this macro just wraps that often repeated logic */ +#define REDIS_ZVAL_NULL(sock_, zv_) \ + do { \ + if ((sock_)->null_mbulk_as_null) { \ + ZVAL_NULL((zv_)); \ + } else { \ + ZVAL_FALSE((zv_)); \ + } \ + } while (0) + #ifndef PHP_WIN32 #include /* TCP_NODELAY */ #include /* SO_KEEPALIVE */ @@ -464,9 +475,7 @@ PHP_REDIS_API int redis_subscribe_response(INTERNAL_FUNCTION_PARAMETERS, // Consume response(s) from subscribe, which will vary on argc while(sctx->argc--) { ZVAL_NULL(&z_resp); - if (!redis_sock_read_multibulk_reply_zval( - INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp) - ) { + if (!redis_sock_read_multibulk_reply_zval(redis_sock, &z_resp)) { goto error; } @@ -513,9 +522,7 @@ PHP_REDIS_API int redis_subscribe_response(INTERNAL_FUNCTION_PARAMETERS, int tab_idx = 1, is_pmsg = 0; ZVAL_NULL(&z_resp); - if (!redis_sock_read_multibulk_reply_zval( - INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp) - ) { + if (!redis_sock_read_multibulk_reply_zval(redis_sock, &z_resp)) { goto failure; } @@ -606,8 +613,7 @@ PHP_REDIS_API int redis_unsubscribe_response(INTERNAL_FUNCTION_PARAMETERS, while (sctx->argc--) { ZVAL_NULL(&z_resp); - if (!redis_sock_read_multibulk_reply_zval( - INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp) || + if (!redis_sock_read_multibulk_reply_zval(redis_sock, &z_resp) || (z_chan = zend_hash_index_find(Z_ARRVAL(z_resp), 1)) == NULL ) { efree(sctx); @@ -642,8 +648,7 @@ PHP_REDIS_API int redis_unsubscribe_response(INTERNAL_FUNCTION_PARAMETERS, } PHP_REDIS_API zval * -redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, - RedisSock *redis_sock, zval *z_tab) +redis_sock_read_multibulk_reply_zval(RedisSock *redis_sock, zval *z_tab) { int numElems; @@ -1617,6 +1622,87 @@ geosearch_cast(zval *zv) return SUCCESS; } +PHP_REDIS_API int +redis_read_mpop_response(RedisSock *redis_sock, zval *zdst, int elements, + void *ctx) +{ + int subele, keylen; + zval zele = {0}; + char *key; + + ZEND_ASSERT(ctx == NULL || ctx == PHPREDIS_CTX_PTR); + + if (elements < 0) { + REDIS_ZVAL_NULL(redis_sock, zdst); + return SUCCESS; + } + + /* Invariant: We should have two elements */ + ZEND_ASSERT(elements == 2); + + array_init(zdst); + + /* Key name and number of entries */ + if ((key = redis_sock_read(redis_sock, &keylen)) == NULL || + read_mbulk_header(redis_sock, &elements) < 0 || elements < 0) + { + if (key) efree(key); + goto fail; + } + + add_next_index_stringl(zdst, key, keylen); + efree(key); + + array_init_size(&zele, elements); + + if (ctx == PHPREDIS_CTX_PTR) { + for (int i = 0; i < elements; i++) { + if (read_mbulk_header(redis_sock, &subele) < 0 || subele != 2) { + zval_dtor(&zele); + goto fail; + } + redis_mbulk_reply_loop(redis_sock, &zele, subele, UNSERIALIZE_KEYS); + } + + array_zip_values_and_scores(redis_sock, &zele, SCORE_DECODE_DOUBLE); + } else { + redis_mbulk_reply_loop(redis_sock, &zele, elements, UNSERIALIZE_ALL); + } + + add_next_index_zval(zdst, &zele); + + return SUCCESS; + +fail: + zval_dtor(zdst); + ZVAL_FALSE(zdst); + + return FAILURE; +} + +PHP_REDIS_API int +redis_mpop_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + zval *z_tab, void *ctx) +{ + int elements, res = SUCCESS; + zval zret = {0}; + + if (read_mbulk_header(redis_sock, &elements) == FAILURE || + redis_read_mpop_response(redis_sock, &zret, elements, ctx) == FAILURE) + { + res = FAILURE; + ZVAL_FALSE(&zret); + } + + if (IS_ATOMIC(redis_sock)) { + RETVAL_ZVAL(&zret, 0, 0); + } else { + add_next_index_zval(z_tab, &zret); + } + + return res; +} + PHP_REDIS_API int redis_geosearch_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) diff --git a/library.h b/library.h index 9e1cfd7348..ded5d3c88c 100644 --- a/library.h +++ b/library.h @@ -78,7 +78,7 @@ PHP_REDIS_API void redis_sock_set_auth(RedisSock *redis_sock, zend_string *user, PHP_REDIS_API void redis_sock_set_auth_zval(RedisSock *redis_sock, zval *zv); PHP_REDIS_API void redis_sock_free_auth(RedisSock *redis_sock); PHP_REDIS_API int redis_sock_disconnect(RedisSock *redis_sock, int force); -PHP_REDIS_API zval *redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab); +PHP_REDIS_API zval *redis_sock_read_multibulk_reply_zval(RedisSock *redis_sock, zval *z_tab); PHP_REDIS_API int redis_sock_read_single_line(RedisSock *redis_sock, char *buffer, size_t buflen, size_t *linelen, int set_err); PHP_REDIS_API char *redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes); @@ -148,6 +148,13 @@ redis_read_xclaim_response(RedisSock *redis_sock, int count, zval *rv); PHP_REDIS_API int redis_read_xinfo_response(RedisSock *redis_sock, zval *z_ret, int elements); +PHP_REDIS_API int +redis_mpop_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + zval *z_tab, void *ctx); + +PHP_REDIS_API int +redis_read_mpop_response(RedisSock *redis_sock, zval *zdst, int elements, void *ctx); + /* Specialized ACL reply handlers */ PHP_REDIS_API int redis_read_acl_getuser_reply(RedisSock *redis_sock, zval *zret, long len); PHP_REDIS_API int redis_acl_getuser_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); diff --git a/redis.c b/redis.c index a9d1f2d60d..4a660474e3 100644 --- a/redis.c +++ b/redis.c @@ -368,8 +368,8 @@ static void add_class_constants(zend_class_entry *ce, int is_cluster) { /* Cluster doesn't support pipelining at this time */ zend_declare_class_constant_long(ce, ZEND_STRL("PIPELINE"), PIPELINE); - zend_declare_class_constant_stringl(ce, "LEFT", 4, "left", 4); - zend_declare_class_constant_stringl(ce, "RIGHT", 5, "right", 5); + zend_declare_class_constant_stringl(ce, ZEND_STRL("LEFT"), ZEND_STRL("left")); + zend_declare_class_constant_stringl(ce, ZEND_STRL("RIGHT"), ZEND_STRL("right")); } /* retry/backoff options*/ @@ -2157,6 +2157,29 @@ PHP_METHOD(Redis, bzPopMin) { } /* }}} */ +/* {{{ proto Redis|array|false Redis::lmpop(array $keys, string $from, int $count = 1) */ +PHP_METHOD(Redis, lmpop) { + REDIS_PROCESS_KW_CMD("LMPOP", redis_mpop_cmd, redis_mpop_response); +} +/* }}} */ + +/* {{{ proto Redis|array|false Redis::blmpop(double $timeout, array $keys, string $from, int $count = 1) */ +PHP_METHOD(Redis, blmpop) { + REDIS_PROCESS_KW_CMD("BLMPOP", redis_mpop_cmd, redis_mpop_response); +} +/* }}} */ + +/* {{{ proto Redis|array|false Redis::zmpop(array $keys, string $from, int $count = 1) */ +PHP_METHOD(Redis, zmpop) { + REDIS_PROCESS_KW_CMD("ZMPOP", redis_mpop_cmd, redis_mpop_response); +} + +/* {{{ proto Redis|array|false Redis::bzmpop(double $timeout, array $keys, string $from, int $count = 1) */ +PHP_METHOD(Redis, bzmpop) { + REDIS_PROCESS_KW_CMD("BZMPOP", redis_mpop_cmd, redis_mpop_response); +} + +/* }}} */ /* hashes */ /* {{{ proto long Redis::hset(string key, string mem, string val) */ diff --git a/redis.stub.php b/redis.stub.php index c2576b68b1..c0ae6153c6 100644 --- a/redis.stub.php +++ b/redis.stub.php @@ -62,6 +62,14 @@ public function bzPopMax(string|array $key, string|int $timeout_or_key, mixed .. public function bzPopMin(string|array $key, string|int $timeout_or_key, mixed ...$extra_args): array; + public function bzmpop(float $timeout, array $keys, string $from, int $count = 1): Redis|array|null|false; + + public function zmpop(array $keys, string $from, int $count = 1): Redis|array|null|false; + + public function blmpop(float $timeout, array $keys, string $from, int $count = 1): Redis|array|null|false; + + public function lmpop(array $keys, string $from, int $count = 1): Redis|array|null|false; + public function clearLastError(): bool; public function client(string $opt, mixed ...$args): mixed; diff --git a/redis_arginfo.h b/redis_arginfo.h index a8d4aa93bb..99c22196ed 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: d7e7c4d63f53a7eeeb17a5d54ce3ee1173eb18e6 */ + * Stub hash: f547b5f24c4d373043c89dab57d450d27f959b08 */ 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") @@ -92,6 +92,23 @@ ZEND_END_ARG_INFO() #define arginfo_class_Redis_bzPopMin arginfo_class_Redis_bzPopMax +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Redis_bzmpop, 0, 3, Redis, MAY_BE_ARRAY|MAY_BE_NULL|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, timeout, IS_DOUBLE, 0) + ZEND_ARG_TYPE_INFO(0, keys, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO(0, from, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, count, IS_LONG, 0, "1") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Redis_zmpop, 0, 2, Redis, MAY_BE_ARRAY|MAY_BE_NULL|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, keys, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO(0, from, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, count, IS_LONG, 0, "1") +ZEND_END_ARG_INFO() + +#define arginfo_class_Redis_blmpop arginfo_class_Redis_bzmpop + +#define arginfo_class_Redis_lmpop arginfo_class_Redis_zmpop + #define arginfo_class_Redis_clearLastError arginfo_class_Redis_bgSave ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Redis_client, 0, 1, IS_MIXED, 0) @@ -1073,6 +1090,10 @@ ZEND_METHOD(Redis, brPop); ZEND_METHOD(Redis, brpoplpush); ZEND_METHOD(Redis, bzPopMax); ZEND_METHOD(Redis, bzPopMin); +ZEND_METHOD(Redis, bzmpop); +ZEND_METHOD(Redis, zmpop); +ZEND_METHOD(Redis, blmpop); +ZEND_METHOD(Redis, lmpop); ZEND_METHOD(Redis, clearLastError); ZEND_METHOD(Redis, client); ZEND_METHOD(Redis, close); @@ -1309,6 +1330,10 @@ static const zend_function_entry class_Redis_methods[] = { ZEND_ME(Redis, brpoplpush, arginfo_class_Redis_brpoplpush, ZEND_ACC_PUBLIC) ZEND_ME(Redis, bzPopMax, arginfo_class_Redis_bzPopMax, ZEND_ACC_PUBLIC) ZEND_ME(Redis, bzPopMin, arginfo_class_Redis_bzPopMin, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, bzmpop, arginfo_class_Redis_bzmpop, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, zmpop, arginfo_class_Redis_zmpop, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, blmpop, arginfo_class_Redis_blmpop, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, lmpop, arginfo_class_Redis_lmpop, ZEND_ACC_PUBLIC) ZEND_ME(Redis, clearLastError, arginfo_class_Redis_clearLastError, ZEND_ACC_PUBLIC) ZEND_ME(Redis, client, arginfo_class_Redis_client, ZEND_ACC_PUBLIC) ZEND_ME(Redis, close, arginfo_class_Redis_close, ZEND_ACC_PUBLIC) diff --git a/redis_cluster.c b/redis_cluster.c index 00fa3801e4..9d466bde19 100644 --- a/redis_cluster.c +++ b/redis_cluster.c @@ -1336,7 +1336,30 @@ PHP_METHOD(RedisCluster, lcs) { CLUSTER_PROCESS_CMD(lcs, cluster_variant_resp, 1); } +/* {{{ proto Redis|array|false Redis::lmpop(array $keys, string $from, int $count = 1) */ +PHP_METHOD(RedisCluster, lmpop) { + CLUSTER_PROCESS_KW_CMD("LMPOP", redis_mpop_cmd, cluster_mpop_resp, 0); +} +/* }}} */ + +/* {{{ proto Redis|array|false Redis::blmpop(double $timeout, array $keys, string $from, int $count = 1) */ +PHP_METHOD(RedisCluster, blmpop) { + CLUSTER_PROCESS_KW_CMD("BLMPOP", redis_mpop_cmd, cluster_mpop_resp, 0); +} /* }}} */ + +/* {{{ proto Redis|array|false Redis::zmpop(array $keys, string $from, int $count = 1) */ +PHP_METHOD(RedisCluster, zmpop) { + CLUSTER_PROCESS_KW_CMD("ZMPOP", redis_mpop_cmd, cluster_mpop_resp, 0); +} +/* }}} */ + +/* {{{ proto Redis|array|false Redis::bzmpop(double $timeout, array $keys, sring $from, int $count = 1) */ +PHP_METHOD(RedisCluster, bzmpop) { + CLUSTER_PROCESS_KW_CMD("BZMPOP", redis_mpop_cmd, cluster_mpop_resp, 0); +} +/* }}} */ + /* {{{ proto string RedisCluster::ltrim(string key, long start, long end) */ PHP_METHOD(RedisCluster, ltrim) { CLUSTER_PROCESS_KW_CMD("LTRIM", redis_key_long_long_cmd, cluster_bool_resp, 0); diff --git a/redis_cluster.stub.php b/redis_cluster.stub.php index f750e072df..2acff20107 100644 --- a/redis_cluster.stub.php +++ b/redis_cluster.stub.php @@ -52,6 +52,14 @@ public function bzpopmax(string|array $key, string|int $timeout_or_key, mixed .. public function bzpopmin(string|array $key, string|int $timeout_or_key, mixed ...$extra_args): array; + public function bzmpop(float $timeout, array $keys, string $from, int $count = 1): Redis|array|null|false; + + public function zmpop(array $keys, string $from, int $count = 1): Redis|array|null|false; + + public function blmpop(float $timeout, array $keys, string $from, int $count = 1): Redis|array|null|false; + + public function lmpop(array $keys, string $from, int $count = 1): Redis|array|null|false; + public function clearlasterror(): bool; public function client(string|array $node, string $subcommand, string|null $arg): array|string|bool; diff --git a/redis_cluster_arginfo.h b/redis_cluster_arginfo.h index dbfb302610..4f58d04618 100644 --- a/redis_cluster_arginfo.h +++ b/redis_cluster_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: d6e8120d2edd3cb4a18baa99c6013ac428049448 */ + * Stub hash: 75e03c96590793af52efbea1d6440d3daa57a5d8 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 1) @@ -96,6 +96,23 @@ ZEND_END_ARG_INFO() #define arginfo_class_RedisCluster_bzpopmin arginfo_class_RedisCluster_blpop +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_RedisCluster_bzmpop, 0, 3, Redis, MAY_BE_ARRAY|MAY_BE_NULL|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, timeout, IS_DOUBLE, 0) + ZEND_ARG_TYPE_INFO(0, keys, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO(0, from, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, count, IS_LONG, 0, "1") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_RedisCluster_zmpop, 0, 2, Redis, MAY_BE_ARRAY|MAY_BE_NULL|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, keys, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO(0, from, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, count, IS_LONG, 0, "1") +ZEND_END_ARG_INFO() + +#define arginfo_class_RedisCluster_blmpop arginfo_class_RedisCluster_bzmpop + +#define arginfo_class_RedisCluster_lmpop arginfo_class_RedisCluster_zmpop + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_RedisCluster_clearlasterror, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() @@ -880,6 +897,10 @@ ZEND_METHOD(RedisCluster, brpop); ZEND_METHOD(RedisCluster, brpoplpush); ZEND_METHOD(RedisCluster, bzpopmax); ZEND_METHOD(RedisCluster, bzpopmin); +ZEND_METHOD(RedisCluster, bzmpop); +ZEND_METHOD(RedisCluster, zmpop); +ZEND_METHOD(RedisCluster, blmpop); +ZEND_METHOD(RedisCluster, lmpop); ZEND_METHOD(RedisCluster, clearlasterror); ZEND_METHOD(RedisCluster, client); ZEND_METHOD(RedisCluster, close); @@ -1079,6 +1100,10 @@ static const zend_function_entry class_RedisCluster_methods[] = { ZEND_ME(RedisCluster, brpoplpush, arginfo_class_RedisCluster_brpoplpush, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, bzpopmax, arginfo_class_RedisCluster_bzpopmax, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, bzpopmin, arginfo_class_RedisCluster_bzpopmin, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, bzmpop, arginfo_class_RedisCluster_bzmpop, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, zmpop, arginfo_class_RedisCluster_zmpop, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, blmpop, arginfo_class_RedisCluster_blmpop, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, lmpop, arginfo_class_RedisCluster_lmpop, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, clearlasterror, arginfo_class_RedisCluster_clearlasterror, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, client, arginfo_class_RedisCluster_client, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, close, arginfo_class_RedisCluster_close, ZEND_ACC_PUBLIC) diff --git a/redis_cluster_legacy_arginfo.h b/redis_cluster_legacy_arginfo.h index a153816915..18d2e56ceb 100644 --- a/redis_cluster_legacy_arginfo.h +++ b/redis_cluster_legacy_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: d6e8120d2edd3cb4a18baa99c6013ac428049448 */ + * Stub hash: 75e03c96590793af52efbea1d6440d3daa57a5d8 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster___construct, 0, 0, 1) ZEND_ARG_INFO(0, name) @@ -89,6 +89,23 @@ ZEND_END_ARG_INFO() #define arginfo_class_RedisCluster_bzpopmin arginfo_class_RedisCluster_blpop +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_bzmpop, 0, 0, 3) + ZEND_ARG_INFO(0, timeout) + ZEND_ARG_INFO(0, keys) + ZEND_ARG_INFO(0, from) + ZEND_ARG_INFO(0, count) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_zmpop, 0, 0, 2) + ZEND_ARG_INFO(0, keys) + ZEND_ARG_INFO(0, from) + ZEND_ARG_INFO(0, count) +ZEND_END_ARG_INFO() + +#define arginfo_class_RedisCluster_blmpop arginfo_class_RedisCluster_bzmpop + +#define arginfo_class_RedisCluster_lmpop arginfo_class_RedisCluster_zmpop + #define arginfo_class_RedisCluster_clearlasterror arginfo_class_RedisCluster__masters ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_client, 0, 0, 3) @@ -766,6 +783,10 @@ ZEND_METHOD(RedisCluster, brpop); ZEND_METHOD(RedisCluster, brpoplpush); ZEND_METHOD(RedisCluster, bzpopmax); ZEND_METHOD(RedisCluster, bzpopmin); +ZEND_METHOD(RedisCluster, bzmpop); +ZEND_METHOD(RedisCluster, zmpop); +ZEND_METHOD(RedisCluster, blmpop); +ZEND_METHOD(RedisCluster, lmpop); ZEND_METHOD(RedisCluster, clearlasterror); ZEND_METHOD(RedisCluster, client); ZEND_METHOD(RedisCluster, close); @@ -965,6 +986,10 @@ static const zend_function_entry class_RedisCluster_methods[] = { ZEND_ME(RedisCluster, brpoplpush, arginfo_class_RedisCluster_brpoplpush, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, bzpopmax, arginfo_class_RedisCluster_bzpopmax, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, bzpopmin, arginfo_class_RedisCluster_bzpopmin, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, bzmpop, arginfo_class_RedisCluster_bzmpop, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, zmpop, arginfo_class_RedisCluster_zmpop, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, blmpop, arginfo_class_RedisCluster_blmpop, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, lmpop, arginfo_class_RedisCluster_lmpop, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, clearlasterror, arginfo_class_RedisCluster_clearlasterror, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, client, arginfo_class_RedisCluster_client, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, close, arginfo_class_RedisCluster_close, ZEND_ACC_PUBLIC) diff --git a/redis_commands.c b/redis_commands.c index d3a0a21659..43dbf818ac 100644 --- a/redis_commands.c +++ b/redis_commands.c @@ -1660,6 +1660,92 @@ static int gen_varkey_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, return SUCCESS; } +int redis_mpop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, + char **cmd, int *cmd_len, short *slot, void **ctx) +{ + zend_string *from = NULL, *key; + int argc, blocking, is_zmpop; + smart_string cmdstr = {0}; + HashTable *keys = NULL; + double timeout = 0.0; + zend_long count = 1; + zval *zv; + + /* Sanity check on our keyword */ + ZEND_ASSERT(kw != NULL && *kw != '\0' && *(kw+1) != '\0'); + + blocking = tolower(*kw) == 'b'; + is_zmpop = tolower(kw[blocking]) == 'z'; + + ZEND_PARSE_PARAMETERS_START(2 + blocking, 3 + blocking) { + if (blocking) { + Z_PARAM_DOUBLE(timeout) + } + Z_PARAM_ARRAY_HT(keys) + Z_PARAM_STR(from); + Z_PARAM_OPTIONAL + Z_PARAM_LONG(count); + } ZEND_PARSE_PARAMETERS_END_EX(return FAILURE); + + if (zend_hash_num_elements(keys) == 0) { + php_error_docref(NULL, E_WARNING, "Must pass at least one key"); + return FAILURE; + } else if (count < 1) { + php_error_docref(NULL, E_WARNING, "Count must be > 0"); + return FAILURE; + } else if (!is_zmpop && !(zend_string_equals_literal_ci(from, "LEFT") || + zend_string_equals_literal_ci(from, "RIGHT"))) + { + php_error_docref(NULL, E_WARNING, "from must be either 'LEFT' or 'RIGHT'"); + return FAILURE; + } else if (is_zmpop && !(zend_string_equals_literal_ci(from, "MIN") || + zend_string_equals_literal_ci(from, "MAX"))) + { + php_error_docref(NULL, E_WARNING, "from must be either 'MIN' or 'MAX'"); + return FAILURE; + } + + argc = 2 + !!blocking + zend_hash_num_elements(keys) + (count != 1 ? 2 : 0); + redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw)); + + if (blocking) redis_cmd_append_sstr_dbl(&cmdstr, timeout); + redis_cmd_append_sstr_long(&cmdstr, zend_hash_num_elements(keys)); + + if (slot) *slot = -1; + + ZEND_HASH_FOREACH_VAL(keys, zv) { + key = redis_key_prefix_zval(redis_sock, zv); + + if (slot) { + if (*slot == -1) { + *slot = cluster_hash_key_zstr(key); + } else if (*slot != cluster_hash_key_zstr(key)) { + php_error_docref(NULL, E_WARNING, "All keys don't hash to the same slot"); + zend_string_release(key); + efree(cmdstr.c); + return FAILURE; + } + } + + redis_cmd_append_sstr_zstr(&cmdstr, key); + + zend_string_release(key); + } ZEND_HASH_FOREACH_END(); + + redis_cmd_append_sstr_zstr(&cmdstr, from); + + if (count != 1) { + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "COUNT"); + redis_cmd_append_sstr_long(&cmdstr, count); + } + + *ctx = is_zmpop ? PHPREDIS_CTX_PTR : NULL; + *cmd = cmdstr.c; + *cmd_len = cmdstr.len; + + return SUCCESS; +} + /* Generic handling of every blocking pop command (BLPOP, BZPOP[MIN/MAX], etc */ int redis_blocking_pop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, @@ -2230,7 +2316,7 @@ redis_hstrlen_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, return SUCCESS; } -void redis_get_lcs_options(redisLcsOptions *dst, HashTable *ht) { +static void redis_get_lcs_options(redisLcsOptions *dst, HashTable *ht) { zend_string *key; zval *zv; diff --git a/redis_commands.h b/redis_commands.h index c51896add7..dbd2e71bf1 100644 --- a/redis_commands.h +++ b/redis_commands.h @@ -132,6 +132,9 @@ int redis_intercard_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, int redis_lcs_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); +int redis_mpop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, + char **cmd, int *cmd_len, short *slot, void **ctx); + int redis_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); diff --git a/redis_legacy_arginfo.h b/redis_legacy_arginfo.h index ade85d2ee0..1609080025 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: d7e7c4d63f53a7eeeb17a5d54ce3ee1173eb18e6 */ + * Stub hash: f547b5f24c4d373043c89dab57d450d27f959b08 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 0, 0, 0) ZEND_ARG_INFO(0, options) @@ -83,6 +83,23 @@ ZEND_END_ARG_INFO() #define arginfo_class_Redis_bzPopMin arginfo_class_Redis_blPop +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_bzmpop, 0, 0, 3) + ZEND_ARG_INFO(0, timeout) + ZEND_ARG_INFO(0, keys) + ZEND_ARG_INFO(0, from) + ZEND_ARG_INFO(0, count) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_zmpop, 0, 0, 2) + ZEND_ARG_INFO(0, keys) + ZEND_ARG_INFO(0, from) + ZEND_ARG_INFO(0, count) +ZEND_END_ARG_INFO() + +#define arginfo_class_Redis_blmpop arginfo_class_Redis_bzmpop + +#define arginfo_class_Redis_lmpop arginfo_class_Redis_zmpop + #define arginfo_class_Redis_clearLastError arginfo_class_Redis___destruct ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_client, 0, 0, 1) @@ -921,6 +938,10 @@ ZEND_METHOD(Redis, brPop); ZEND_METHOD(Redis, brpoplpush); ZEND_METHOD(Redis, bzPopMax); ZEND_METHOD(Redis, bzPopMin); +ZEND_METHOD(Redis, bzmpop); +ZEND_METHOD(Redis, zmpop); +ZEND_METHOD(Redis, blmpop); +ZEND_METHOD(Redis, lmpop); ZEND_METHOD(Redis, clearLastError); ZEND_METHOD(Redis, client); ZEND_METHOD(Redis, close); @@ -1157,6 +1178,10 @@ static const zend_function_entry class_Redis_methods[] = { ZEND_ME(Redis, brpoplpush, arginfo_class_Redis_brpoplpush, ZEND_ACC_PUBLIC) ZEND_ME(Redis, bzPopMax, arginfo_class_Redis_bzPopMax, ZEND_ACC_PUBLIC) ZEND_ME(Redis, bzPopMin, arginfo_class_Redis_bzPopMin, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, bzmpop, arginfo_class_Redis_bzmpop, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, zmpop, arginfo_class_Redis_zmpop, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, blmpop, arginfo_class_Redis_blmpop, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, lmpop, arginfo_class_Redis_lmpop, ZEND_ACC_PUBLIC) ZEND_ME(Redis, clearLastError, arginfo_class_Redis_clearLastError, ZEND_ACC_PUBLIC) ZEND_ME(Redis, client, arginfo_class_Redis_client, ZEND_ACC_PUBLIC) ZEND_ME(Redis, close, arginfo_class_Redis_close, ZEND_ACC_PUBLIC) diff --git a/tests/RedisTest.php b/tests/RedisTest.php index 00b456730d..721ce52599 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -273,6 +273,7 @@ public function testBitsets() { } public function testLcs() { + $key1 = '{lcs}1'; $key2 = '{lcs}2'; $this->assertTrue($this->redis->set($key1, '12244447777777')); $this->assertTrue($this->redis->set($key2, '6666662244441')); @@ -293,6 +294,104 @@ public function testLcs() { $this->redis->del([$key1, $key2]); } + public function testLmpop() { + if(version_compare($this->version, "7.0.0") < 0) { + $this->markTestSkipped(); + } + + $key1 = '{l}1'; + $key2 = '{l}2'; + + $this->assertTrue($this->redis->del($key1, $key2) !== false); + + $this->assertEquals(6, $this->redis->rpush($key1, 'A', 'B', 'C', 'D', 'E', 'F')); + $this->assertEquals(6, $this->redis->rpush($key2, 'F', 'E', 'D', 'C', 'B', 'A')); + + $this->assertEquals([$key1, ['A']], $this->redis->lmpop([$key1, $key2], 'LEFT')); + $this->assertEquals([$key1, ['F']], $this->redis->lmpop([$key1, $key2], 'RIGHT')); + $this->assertEquals([$key1, ['B', 'C', 'D']], $this->redis->lmpop([$key1, $key2], 'LEFT', 3)); + + $this->assertEquals(2, $this->redis->del($key1, $key2)); + } + + public function testBLmpop() { + if(version_compare($this->version, "7.0.0") < 0) { + $this->markTestSkipped(); + } + + $key1 = '{bl}1'; + $key2 = '{bl}2'; + + $this->assertTrue($this->redis->del($key1, $key2) !== false); + $this->assertEquals(2, $this->redis->rpush($key1, 'A', 'B')); + $this->assertEquals(2, $this->redis->rpush($key2, 'C', 'D')); + + $this->assertEquals([$key1, ['B', 'A']], $this->redis->blmpop(.2, [$key1, $key2], 'RIGHT', 2)); + $this->assertEquals([$key2, ['C']], $this->redis->blmpop(.2, [$key1, $key2], 'LEFT')); + $this->assertEquals([$key2, ['D']], $this->redis->blmpop(.2, [$key1, $key2], 'LEFT')); + + $st = microtime(true); + $this->assertFalse($this->redis->blmpop(.2, [$key1, $key2], 'LEFT')); + $et = microtime(true); + $this->assertTrue($et - $st >= .2); + } + + function testZmpop() { + if(version_compare($this->version, "7.0.0") < 0) { + $this->markTestSkipped(); + } + + $key1 = '{z}1'; + $key2 = '{z}2'; + + $this->assertTrue($this->redis->del($key1, $key2) !== false); + + $this->assertEquals(4, $this->redis->zadd($key1, 0, 'zero', 2, 'two', 4, 'four', 6, 'six')); + $this->assertEquals(4, $this->redis->zadd($key2, 1, 'one', 3, 'three', 5, 'five', 7, 'seven')); + + $this->assertEquals([$key1, ['zero' => 0.0]], $this->redis->zmpop([$key1, $key2], 'MIN')); + $this->assertEquals([$key1, ['six' => 6.0]], $this->redis->zmpop([$key1, $key2], 'MAX')); + $this->assertEquals([$key1, ['two' => 2.0, 'four' => 4.0]], $this->redis->zmpop([$key1, $key2], 'MIN', 3)); + + $this->assertEquals( + [$key2, ['one' => 1.0, 'three' => 3.0, 'five' => 5.0, 'seven' => 7.0]], + $this->redis->zmpop([$key1, $key2], 'MIN', 128) + ); + + $this->assertFalse($this->redis->zmpop([$key1, $key2], 'MIN')); + + $this->redis->setOption(Redis::OPT_NULL_MULTIBULK_AS_NULL, true); + $this->assertEquals(NULL, $this->redis->zmpop([$key1, $key2], 'MIN')); + $this->redis->setOption(Redis::OPT_NULL_MULTIBULK_AS_NULL, false); + } + + function testBZmpop() { + if(version_compare($this->version, "7.0.0") < 0) { + $this->markTestSkipped(); + } + + $key1 = '{z}1'; + $key2 = '{z}2'; + + $this->assertTrue($this->redis->del($key1, $key2) !== false); + + $this->assertEquals(2, $this->redis->zadd($key1, 0, 'zero', 2, 'two')); + $this->assertEquals(2, $this->redis->zadd($key2, 1, 'one', 3, 'three')); + + $this->assertEquals( + [$key1, ['zero' => 0.0, 'two' => 2.0]], + $this->redis->bzmpop(.1, [$key1, $key2], 'MIN', 2) + ); + + $this->assertEquals([$key2, ['three' => 3.0]], $this->redis->bzmpop(.1, [$key1, $key2], 'MAX')); + $this->assertEquals([$key2, ['one' => 1.0]], $this->redis->bzmpop(.1, [$key1, $key2], 'MAX')); + + $st = microtime(true); + $this->assertFalse($this->redis->bzmpop(.2, [$key1, $key2], 'MIN')); + $et = microtime(true); + $this->assertTrue($et - $st >= .2); + } + public function testBitPos() { if (version_compare($this->version, "2.8.7") < 0) { $this->MarkTestSkipped(); @@ -577,7 +676,6 @@ public function testMultipleBin() { $this->assertEquals([gzcompress('v1'), gzcompress('v2'), gzcompress('v3')], $this->redis->mget(['k1', 'k2', 'k3'])); $this->assertEquals([gzcompress('v1'), gzcompress('v2'), gzcompress('v3')], $this->redis->mget(['k1', 'k2', 'k3'])); - } public function testSetTimeout() {