diff options
author | michael-grunder <michael.grunder@gmail.com> | 2022-11-06 05:59:30 +0300 |
---|---|---|
committer | michael-grunder <michael.grunder@gmail.com> | 2022-11-13 07:47:10 +0300 |
commit | bc2f42bdd5237205b5af1b8a4ef59040084615d2 (patch) | |
tree | a9b0b6361e5d983f29ce08e9f0aa729d2b573c6d | |
parent | cf63e96ec5f6c9363bc5c6955d29c726fc7ec6fe (diff) |
Add more docblocks and fix XAUTOCLAIM response handler.more-docblocks
- Finish adding docblocks with examples for all of the stream commands.
- Fix XAUTOCLAIM response handler (the reply has a slightly different
structure to XCLAIM.
-rw-r--r-- | cluster_library.c | 4 | ||||
-rw-r--r-- | library.c | 87 | ||||
-rw-r--r-- | library.h | 6 | ||||
-rw-r--r-- | redis.stub.php | 304 | ||||
-rw-r--r-- | redis_arginfo.h | 6 | ||||
-rw-r--r-- | redis_cluster.c | 4 | ||||
-rw-r--r-- | redis_cluster.stub.php | 5 | ||||
-rw-r--r-- | redis_cluster_arginfo.h | 21 | ||||
-rw-r--r-- | redis_cluster_legacy_arginfo.h | 21 | ||||
-rw-r--r-- | redis_commands.c | 41 | ||||
-rw-r--r-- | redis_legacy_arginfo.h | 2 | ||||
-rw-r--r-- | tests/RedisTest.php | 31 |
12 files changed, 475 insertions, 57 deletions
diff --git a/cluster_library.c b/cluster_library.c index 945c7bc1..0d04593e 100644 --- a/cluster_library.c +++ b/cluster_library.c @@ -2333,7 +2333,9 @@ cluster_xclaim_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx) { array_init(&z_msg); - if (redis_read_xclaim_response(c->cmd_sock, c->reply_len, &z_msg) < 0) { + ZEND_ASSERT(ctx == NULL || ctx == PHPREDIS_CTX_PTR); + + if (redis_read_xclaim_reply(c->cmd_sock, c->reply_len, ctx == PHPREDIS_CTX_PTR, &z_msg) < 0) { zval_dtor(&z_msg); CLUSTER_RETURN_FALSE(c); } @@ -1999,11 +1999,9 @@ failure: return -1; } -/* This helper function does that actual XCLAIM response handling, which can be used by both - * Redis and RedisCluster. Note that XCLAIM is somewhat unique in that its reply type depends - * on whether or not it was called with the JUSTID option */ -PHP_REDIS_API int -redis_read_xclaim_response(RedisSock *redis_sock, int count, zval *rv) { +/* A helper method to read X[AUTO]CLAIM messages into an array. */ +static int +redis_read_xclaim_ids(RedisSock *redis_sock, int count, zval *rv) { zval z_msg; REDIS_REPLY_TYPE type; char *id = NULL; @@ -2011,6 +2009,8 @@ redis_read_xclaim_response(RedisSock *redis_sock, int count, zval *rv) { long li; for (i = 0; i < count; i++) { + id = NULL; + /* Consume inner reply type */ if (redis_read_reply_type(redis_sock, &type, &li) < 0 || (type != TYPE_BULK && type != TYPE_MULTIBULK) || @@ -2043,29 +2043,88 @@ redis_read_xclaim_response(RedisSock *redis_sock, int count, zval *rv) { return 0; } +/* Read an X[AUTO]CLAIM reply having already consumed the reply-type byte. */ +PHP_REDIS_API int +redis_read_xclaim_reply(RedisSock *redis_sock, int count, int is_xautoclaim, zval *rv) { + REDIS_REPLY_TYPE type; + zval z_msgs = {0}; + char *id = NULL; + long id_len = 0; + int messages; + + ZEND_ASSERT(!is_xautoclaim || count == 3); + + ZVAL_UNDEF(rv); + + /* If this is XAUTOCLAIM consume the BULK ID and then the actual number of IDs. + * Otherwise, our 'count' argument is the number of IDs. */ + if (is_xautoclaim) { + if (redis_read_reply_type(redis_sock, &type, &id_len) < 0 || type != TYPE_BULK) + goto failure; + if ((id = redis_sock_read_bulk_reply(redis_sock, id_len)) == NULL) + goto failure; + if (read_mbulk_header(redis_sock, &messages) < 0) + goto failure; + } else { + messages = count; + } + + array_init(&z_msgs); + + if (redis_read_xclaim_ids(redis_sock, messages, &z_msgs) < 0) + goto failure; + + /* If XAUTOCLAIM we now need to consume the final array of message IDs */ + if (is_xautoclaim) { + zval z_deleted = {0}; + + if (redis_sock_read_multibulk_reply_zval(redis_sock, &z_deleted) == NULL) + goto failure; + + array_init(rv); + + // Package up ID, message, and deleted messages in our reply + add_next_index_stringl(rv, id, id_len); + add_next_index_zval(rv, &z_msgs); + add_next_index_zval(rv, &z_deleted); + + efree(id); + } else { + // We just want the messages + ZVAL_COPY_VALUE(rv, &z_msgs); + } + + return 0; + +failure: + zval_dtor(&z_msgs); + zval_dtor(rv); + if (id) efree(id); + + return -1; +} + PHP_REDIS_API int redis_xclaim_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { - zval z_ret; - int messages; + zval z_ret = {0}; + int count; - /* All XCLAIM responses start multibulk */ - if (read_mbulk_header(redis_sock, &messages) < 0) - goto failure; + ZEND_ASSERT(ctx == NULL || ctx == PHPREDIS_CTX_PTR); - array_init(&z_ret); + if (read_mbulk_header(redis_sock, &count) < 0) + goto failure; - if (redis_read_xclaim_response(redis_sock, messages, &z_ret) < 0) { - zval_dtor(&z_ret); + if (redis_read_xclaim_reply(redis_sock, count, ctx == PHPREDIS_CTX_PTR, &z_ret) < 0) goto failure; - } if (IS_ATOMIC(redis_sock)) { RETVAL_ZVAL(&z_ret, 0, 1); } else { add_next_index_zval(z_tab, &z_ret); } + return 0; failure: @@ -104,8 +104,12 @@ PHP_REDIS_API int redis_xrange_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_xread_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); + PHP_REDIS_API int redis_xclaim_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +PHP_REDIS_API int redis_read_xclaim_reply( + RedisSock *redis_sock, int count, int is_xautoclaim, zval *rv); + PHP_REDIS_API int redis_xinfo_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); @@ -149,8 +153,6 @@ redis_read_stream_messages(RedisSock *redis_sock, int count, zval *z_ret); PHP_REDIS_API int redis_read_stream_messages_multi(RedisSock *redis_sock, int count, zval *z_ret); PHP_REDIS_API int -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 diff --git a/redis.stub.php b/redis.stub.php index 12f77301..ab0ebb9d 100644 --- a/redis.stub.php +++ b/redis.stub.php @@ -2469,6 +2469,25 @@ class Redis { * Redis::REDIS_ZSET * Redis::REDIS_HASH * Redis::REDIS_STREAM + * + * <code> + * <?php + * $redis = new Redis(['host' => 'localhost']); + * + * // NOTE: Never use 'KEYS' in production! + * $keys = $redis->keys('*'); + * + * $redis->pipeline(); + * foreach ($keys as $key) { + * $redis->type($key); + * } + * + * $ktypes = array_combine($keys, $redis->exec()); + * + * // Print each key with its corresponding type + * print_r($ktypes); + * ?> + * </code> */ public function type(string $key): Redis|int|false; @@ -2508,6 +2527,22 @@ class Redis { * @see https://redis.io/commands/unsubscribe * @see Redis::subscribe() * + * <code> + * <?php + * $redis = new Redis(['host' => 'localhost']); + * + * $redis->subscribe(['channel-1', 'channel-2'], function ($redis, $channel, $message) { + * if ($message == 'quit') { + * echo "$channel => 'quit' detected, unsubscribing!\n"; + * $redis->unsubscribe([$channel]); + * } else { + * echo "$channel => $message\n"; + * } + * }); + * + * echo "We've unsubscribed from both channels, exiting\n"; + * ?> + * </code> */ public function unsubscribe(array $channels): Redis|array|bool; @@ -2523,9 +2558,48 @@ class Redis { public function unwatch(): Redis|bool; /** - * @return bool|Redis + * Watch one or more keys for conditional execution of a transaction. + * + * @see https://redis.io/commands/watch + * @see https://redis.io/commands/unwatch + * + * @param array|string $key_or_keys Either an array with one or more key names, or a string key name + * @param string $other_keys If the first argument was passed as a string, any number of additional + * string key names may be passed variadically. + * + * @return Redis|bool + * + * <code> + * <?php + * + * $redis1 = new Redis(['host' => 'localhost']); + * $redis2 = new Redis(['host' => 'localhost']); + * + * // Start watching 'incr-key' + * $redis1->watch('incr-key'); + * + * // Retrieve its value. + * $val = $redis1->get('incr-key'); + * + * // A second client modifies 'incr-key' after we read it. + * $redis2->set('incr-key', 0); + * + * // Because another client changed the value of 'incr-key' after we read it, this + * // is no longer a proper increment operation, but because we are `WATCH`ing the + * // key, this transaction will fail and we can try again. + * // + * // If were to comment out the above `$redis2->set('incr-key', 0)` line the + * // transaction would succeed. + * $redis1->multi(); + * $redis1->set('incr-key', $val + 1); + * $res = $redis1->exec(); + * + * // bool(false) + * var_dump($res); + * </code> + * */ - public function watch(array|string $key, string ...$other_keys); + public function watch(array|string $key, string ...$other_keys): Redis|bool; /** * Block the client up to the provided timeout until a certain number of replicas have confirmed @@ -2541,6 +2615,46 @@ class Redis { */ public function wait(int $numreplicas, int $timeout): int|false; + /** + * Acknowledge one ore more messages that are pending (have been consumed using XREADGROUP but + * not yet acknowledged by XACK.) + * + * @see https://redis.io/commands/xack + * @see https://redis.io/commands/xreadgroup + * @see Redis::xack() + * + * <code> + * <?php + * $redis = new Redis(['host' => 'localhost']); + * + * $redis->del('ships'); + * + * $redis->xAdd('ships', '*', ['name' => 'Enterprise']); + * $redis->xAdd('ships', '*', ['name' => 'Defiant']); + * + * $redis->xGroup('CREATE', 'ships', 'Federation', '0-0'); + * + * // Consume a single message with the consumer group 'Federation' + * $ship = $redis->xReadGroup('Federation', 'Picard', ['ships' => '>'], 1); + * + * /* Retrieve the ID of the message we read. + * assert(isset($ship['ships'])); + * $id = key($ship['ships']); + * + * // The message we just read is now pending. + * $res = $redis->xPending('ships', 'Federation')); + * var_dump($res); + * + * // We can tell Redis we were able to process the message by using XACK + * $res = $redis->xAck('ships', 'Federation', [$id]); + * assert($res === 1); + * + * // The message should no longer be pending. + * $res = $redis->xPending('ships', 'Federation'); + * var_dump($res); + * ?> + * </code> + */ public function xack(string $key, string $group, array $ids): int|false; /** @@ -2593,9 +2707,193 @@ class Redis { */ public function xadd(string $key, string $id, array $values, int $maxlen = 0, bool $approx = false, bool $nomkstream = false): Redis|string|false; + /** + * This command allows a consumer to claim pending messages that have been idle for a specified period of time. + * Its purpose is to provide a mechanism for picking up messages that may have had a failed consumer. + * + * @see https://redis.io/commands/xautoclaim + * @see https://redis.io/commands/xclaim + * @see https://redis.io/docs/data-types/streams-tutorial/ + * + * @param string $key The stream to check. + * @param string $group The consumer group to query. + * @param string $consumer Which consumer to check. + * @param int $min_idle The minimum time in milliseconds for the message to have been pending. + * @param string $start The minimum message id to check. + * @param int $count An optional limit on how many messages are returned. + * @param bool $justid If the client only wants message IDs and not all of their data. + * + * @return Redis|array|bool An array of pending IDs or false if there are none, or on failure. + * + * <code> + * <?php + * $redis = new Redis(['host' => 'localhost']); + * + * $redis->del('ships'); + * + * $redis->xGroup('CREATE', 'ships', 'combatants', '0-0', true); + * + * $redis->xAdd('ships', '1424-74205', ['name' => 'Defiant']); + * + * // Consume the ['name' => 'Defiant'] message + * $msgs = $redis->xReadGroup('combatants', "Jem'Hadar", ['ships' => '>'], 1); + * + * // The "Jem'Hadar" consumer has the message presently + * $pending = $redis->xPending('ships', 'combatants'); + * + * //array(4) { + * // [0]=> + * // int(1) + * // [1]=> + * // string(10) "1424-74205" + * // [2]=> + * // string(10) "1424-74205" + * // [3]=> + * // array(1) { + * // [0]=> + * // array(2) { + * // [0]=> + * // string(9) "Jem'Hadar" + * // [1]=> + * // string(1) "1" + * // } + * // } + * //} + * var_dump($pending); + * + * // Asssume control of the pending message with a different consumer. + * $res = $redis->xAutoClaim('ships', 'combatants', 'Sisko', 0, '0-0'); + * + * // Now the 'Sisko' consumer owns the message + * $pending = $redis->xPending('ships', 'combatants'); + * + * // array(4) { + * // [0]=> + * // int(1) + * // [1]=> + * // string(10) "1424-74205" + * // [2]=> + * // string(10) "1424-74205" + * // [3]=> + * // array(1) { + * // [0]=> + * // array(2) { + * // [0]=> + * // string(5) "Sisko" + * // [1]=> + * // string(1) "1" + * // } + * // } + * // } + * var_dump($pending); + * ?> + * </code> + */ public function xautoclaim(string $key, string $group, string $consumer, int $min_idle, string $start, int $count = -1, bool $justid = false): Redis|bool|array; - public function xclaim(string $key, string $group, string $consumer, int $min_idle, array $ids, array $options): Redis|bool|array; + /** + * This method allows a consumer to take ownership of pending stream entries, by ID. Another + * command that does much the same thing but does not require passing specific IDs is `Redis::xAutoClaim`. + * + * @see https://redis.io/commands/xclaim + * @see https://redis.io/commands/xautoclaim. + * + * @param string $key The stream we wish to claim messages for. + * @param string $group Our consumer group. + * @param string $consumer Our consumer. + * @param int $min_idle_time The minimum idle-time in milliseconds a message must have for ownership to be transferred. + * @param array $options An options array that modifies how the command operates. + * + * <code> + * // Following is an options array describing every option you can pass. Note that + * // 'IDLE', and 'TIME' are mutually exclusive. + * $options = [ + * 'IDLE' => 3 // Set the idle time of the message to a 3. By default the + * // idle time is set to zero. + * 'TIME' => 1000*time() // Same as IDLE except it takes a unix timestamp in milliseconds. + * 'RETRYCOUNT' => 0 // Set the retry counter to zero. By default XCLAIM doesn't modify + * // the counter. + * 'FORCE' // Creates the pending message entry even if IDs are not already + * // in the PEL with another client. + * 'JUSTID' // Return only an array of IDs rather than the messages themselves. + * ]; + * </code> + * + * @return Redis|array|bool An array of claimed messags or false on failure. + * + * <code> + * <?php + * $redis = new Redis(['host' => 'localhost']); + * + * $redis->del('ships'); + * + * $redis->xGroup('CREATE', 'ships', 'combatants', '0-0', true); + * + * $redis->xAdd('ships', '1424-74205', ['name' => 'Defiant']); + * + * // Consume the ['name' => 'Defiant'] message + * $msgs = $redis->xReadGroup('combatants', "Jem'Hadar", ['ships' => '>'], 1); + * + * // The "Jem'Hadar" consumer has the message presently + * $pending = $redis->xPending('ships', 'combatants'); + * + * //array(4) { + * // [0]=> + * // int(1) + * // [1]=> + * // string(10) "1424-74205" + * // [2]=> + * // string(10) "1424-74205" + * // [3]=> + * // array(1) { + * // [0]=> + * // array(2) { + * // [0]=> + * // string(9) "Jem'Hadar" + * // [1]=> + * // string(1) "1" + * // } + * // } + * //} + * var_dump($pending); + * + * assert($pending && isset($pending[1])); + * + * // Claim the message by ID. + * $claimed = $redis->xClaim('ships', 'combatants', 'Sisko', 0, [$pending[1]], ['JUSTID']); + * + * // array(1) { + * // [0]=> + * // string(10) "1424-74205" + * // } + * var_dump($claimed); + * + * // Now the 'Sisko' consumer owns the message + * $pending = $redis->xPending('ships', 'combatants'); + * + * // array(4) { + * // [0]=> + * // int(1) + * // [1]=> + * // string(10) "1424-74205" + * // [2]=> + * // string(10) "1424-74205" + * // [3]=> + * // array(1) { + * // [0]=> + * // array(2) { + * // [0]=> + * // string(5) "Sisko" + * // [1]=> + * // string(1) "1" + * // } + * // } + * // } + * var_dump($pending); + * ?> + * </code> + */ + public function xclaim(string $key, string $group, string $consumer, int $min_idle, array $ids, array $options): Redis|array|bool; /** * Remove one or more specific IDs from a stream. diff --git a/redis_arginfo.h b/redis_arginfo.h index a13002a1..84d0e846 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: 42952974e3686f29934dfff1ebba07150942a405 */ + * Stub hash: 1cd52318293e6a57df77b2d4a8f31ef126b6321d */ 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") @@ -867,7 +867,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_Redis_unwatch arginfo_class_Redis_bgSave -ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_watch, 0, 0, 1) +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Redis_watch, 0, 1, Redis, MAY_BE_BOOL) ZEND_ARG_TYPE_MASK(0, key, MAY_BE_ARRAY|MAY_BE_STRING, NULL) ZEND_ARG_VARIADIC_TYPE_INFO(0, other_keys, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -902,7 +902,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Redis_xautoclaim, 0, 5 ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, justid, _IS_BOOL, 0, "false") ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Redis_xclaim, 0, 6, Redis, MAY_BE_BOOL|MAY_BE_ARRAY) +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Redis_xclaim, 0, 6, Redis, MAY_BE_ARRAY|MAY_BE_BOOL) ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, group, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, consumer, IS_STRING, 0) diff --git a/redis_cluster.c b/redis_cluster.c index e611315d..66225e5c 100644 --- a/redis_cluster.c +++ b/redis_cluster.c @@ -2904,6 +2904,10 @@ PHP_METHOD(RedisCluster, xclaim) { CLUSTER_PROCESS_CMD(xclaim, cluster_xclaim_resp, 0); } +PHP_METHOD(RedisCluster, xautoclaim) { + CLUSTER_PROCESS_CMD(xautoclaim, cluster_xclaim_resp, 0); +} + PHP_METHOD(RedisCluster, xdel) { CLUSTER_PROCESS_KW_CMD("XDEL", redis_key_str_arr_cmd, cluster_long_resp, 0); } diff --git a/redis_cluster.stub.php b/redis_cluster.stub.php index f4ff298f..f61413e2 100644 --- a/redis_cluster.stub.php +++ b/redis_cluster.stub.php @@ -518,9 +518,12 @@ class RedisCluster { public function xclaim(string $key, string $group, string $consumer, int $min_iddle, array $ids, array $options): RedisCluster|string|array|false; + public function xautoclaim(string $key, string $group, string $consumer, int $min_idle, string $start, int $count = -1, bool $justid = false): Redis|bool|array; + public function xdel(string $key, array $ids): RedisCluster|int|false; - public function xgroup(string $operation, string $key = null, string $arg1 = null, string $arg2 = null, bool $arg3 = false): mixed; + public function xgroup(string $operation, string $key = null, string $group = null, string $id_or_consumer = null, + bool $mkstream = false, int $entries_read = -2): mixed; public function xinfo(string $operation, ?string $arg1 = null, ?string $arg2 = null, int $count = -1): mixed; diff --git a/redis_cluster_arginfo.h b/redis_cluster_arginfo.h index 88736406..3171891c 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: 65c7830c07ea86720c6089dbd0fa7943df0a2ca8 */ + * Stub hash: 2353428a5f0097623b69a28662ee81e24fbaa89c */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 1) @@ -765,6 +765,16 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_RedisCluster_xclaim, 0 ZEND_ARG_TYPE_INFO(0, options, IS_ARRAY, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_RedisCluster_xautoclaim, 0, 5, Redis, MAY_BE_BOOL|MAY_BE_ARRAY) + ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, group, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, consumer, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, min_idle, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, start, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, count, IS_LONG, 0, "-1") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, justid, _IS_BOOL, 0, "false") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_RedisCluster_xdel, 0, 2, RedisCluster, MAY_BE_LONG|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, ids, IS_ARRAY, 0) @@ -773,9 +783,10 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_RedisCluster_xgroup, 0, 1, IS_MIXED, 0) ZEND_ARG_TYPE_INFO(0, operation, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, key, IS_STRING, 0, "null") - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, arg1, IS_STRING, 0, "null") - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, arg2, IS_STRING, 0, "null") - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, arg3, _IS_BOOL, 0, "false") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, group, IS_STRING, 0, "null") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, id_or_consumer, IS_STRING, 0, "null") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mkstream, _IS_BOOL, 0, "false") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, entries_read, IS_LONG, 0, "-2") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_RedisCluster_xinfo, 0, 1, IS_MIXED, 0) @@ -1120,6 +1131,7 @@ ZEND_METHOD(RedisCluster, watch); ZEND_METHOD(RedisCluster, xack); ZEND_METHOD(RedisCluster, xadd); ZEND_METHOD(RedisCluster, xclaim); +ZEND_METHOD(RedisCluster, xautoclaim); ZEND_METHOD(RedisCluster, xdel); ZEND_METHOD(RedisCluster, xgroup); ZEND_METHOD(RedisCluster, xinfo); @@ -1329,6 +1341,7 @@ static const zend_function_entry class_RedisCluster_methods[] = { ZEND_ME(RedisCluster, xack, arginfo_class_RedisCluster_xack, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, xadd, arginfo_class_RedisCluster_xadd, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, xclaim, arginfo_class_RedisCluster_xclaim, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, xautoclaim, arginfo_class_RedisCluster_xautoclaim, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, xdel, arginfo_class_RedisCluster_xdel, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, xgroup, arginfo_class_RedisCluster_xgroup, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, xinfo, arginfo_class_RedisCluster_xinfo, ZEND_ACC_PUBLIC) diff --git a/redis_cluster_legacy_arginfo.h b/redis_cluster_legacy_arginfo.h index ee4cfafa..ead0ae76 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: 65c7830c07ea86720c6089dbd0fa7943df0a2ca8 */ + * Stub hash: 2353428a5f0097623b69a28662ee81e24fbaa89c */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster___construct, 0, 0, 1) ZEND_ARG_INFO(0, name) @@ -645,6 +645,16 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_xclaim, 0, 0, 6) ZEND_ARG_INFO(0, options) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_xautoclaim, 0, 0, 5) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, group) + ZEND_ARG_INFO(0, consumer) + ZEND_ARG_INFO(0, min_idle) + ZEND_ARG_INFO(0, start) + ZEND_ARG_INFO(0, count) + ZEND_ARG_INFO(0, justid) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_xdel, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, ids) @@ -653,9 +663,10 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_xgroup, 0, 0, 1) ZEND_ARG_INFO(0, operation) ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, arg1) - ZEND_ARG_INFO(0, arg2) - ZEND_ARG_INFO(0, arg3) + ZEND_ARG_INFO(0, group) + ZEND_ARG_INFO(0, id_or_consumer) + ZEND_ARG_INFO(0, mkstream) + ZEND_ARG_INFO(0, entries_read) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_xinfo, 0, 0, 1) @@ -971,6 +982,7 @@ ZEND_METHOD(RedisCluster, watch); ZEND_METHOD(RedisCluster, xack); ZEND_METHOD(RedisCluster, xadd); ZEND_METHOD(RedisCluster, xclaim); +ZEND_METHOD(RedisCluster, xautoclaim); ZEND_METHOD(RedisCluster, xdel); ZEND_METHOD(RedisCluster, xgroup); ZEND_METHOD(RedisCluster, xinfo); @@ -1180,6 +1192,7 @@ static const zend_function_entry class_RedisCluster_methods[] = { ZEND_ME(RedisCluster, xack, arginfo_class_RedisCluster_xack, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, xadd, arginfo_class_RedisCluster_xadd, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, xclaim, arginfo_class_RedisCluster_xclaim, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, xautoclaim, arginfo_class_RedisCluster_xautoclaim, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, xdel, arginfo_class_RedisCluster_xdel, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, xgroup, arginfo_class_RedisCluster_xgroup, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, xinfo, arginfo_class_RedisCluster_xinfo, ZEND_ACC_PUBLIC) diff --git a/redis_commands.c b/redis_commands.c index e75b1892..11c5d59f 100644 --- a/redis_commands.c +++ b/redis_commands.c @@ -5776,10 +5776,8 @@ static int64_t get_xclaim_i64_arg(const char *key, zval *zv) { /* Helper to extract XCLAIM options */ static void get_xclaim_options(zval *z_arr, xclaimOptions *opt) { - HashTable *ht; zend_string *zkey; - char *kval; - size_t klen; + HashTable *ht; zval *zv; /* Initialize options array to sane defaults */ @@ -5795,29 +5793,20 @@ static void get_xclaim_options(zval *z_arr, xclaimOptions *opt) { ht = Z_ARRVAL_P(z_arr); ZEND_HASH_FOREACH_STR_KEY_VAL(ht, zkey, zv) { if (zkey) { - kval = ZSTR_VAL(zkey); - klen = ZSTR_LEN(zkey); - - if (klen == 4) { - if (!strncasecmp(kval, "TIME", 4)) { - opt->idle.type = "TIME"; - opt->idle.time = get_xclaim_i64_arg("TIME", zv); - } else if (!strncasecmp(kval, "IDLE", 4)) { - opt->idle.type = "IDLE"; - opt->idle.time = get_xclaim_i64_arg("IDLE", zv); - } - } else if (klen == 10 && !strncasecmp(kval, "RETRYCOUNT", 10)) { + if (zend_string_equals_literal_ci(zkey, "TIME")) { + opt->idle.type = "TIME"; + opt->idle.time = get_xclaim_i64_arg("TIME", zv); + } else if (zend_string_equals_literal_ci(zkey, "IDLE")) { + opt->idle.type = "IDLE"; + opt->idle.time = get_xclaim_i64_arg("IDLE", zv); + } else if (zend_string_equals_literal_ci(zkey, "RETRYCOUNT")) { opt->retrycount = zval_get_long(zv); } - } else { - if (Z_TYPE_P(zv) == IS_STRING) { - kval = Z_STRVAL_P(zv); - klen = Z_STRLEN_P(zv); - if (klen == 5 && !strncasecmp(kval, "FORCE", 5)) { - opt->force = 1; - } else if (klen == 6 && !strncasecmp(kval, "JUSTID", 6)) { - opt->justid = 1; - } + } else if (Z_TYPE_P(zv) == IS_STRING) { + if (zend_string_equals_literal_ci(Z_STR_P(zv), "FORCE")) { + opt->force = 1; + } else if (zend_string_equals_literal_ci(Z_STR_P(zv), "JUSTID")) { + opt->justid = 1; } } } ZEND_HASH_FOREACH_END(); @@ -5898,6 +5887,10 @@ redis_xautoclaim_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "JUSTID"); } + // Set the context to distinguish XCLAIM from XAUTOCLAIM which + // have slightly different reply structures. + *ctx = PHPREDIS_CTX_PTR; + *cmd = cmdstr.c; *cmd_len = cmdstr.len; return SUCCESS; diff --git a/redis_legacy_arginfo.h b/redis_legacy_arginfo.h index 196ea90a..2e80e2f9 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: 42952974e3686f29934dfff1ebba07150942a405 */ + * Stub hash: 1cd52318293e6a57df77b2d4a8f31ef126b6321d */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 0, 0, 0) ZEND_ARG_INFO(0, options) diff --git a/tests/RedisTest.php b/tests/RedisTest.php index a88d8009..da93e867 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -6821,6 +6821,7 @@ class Redis_Test extends TestSuite for ($n = count($ids); $n >= 0; $n--) { $xp = $this->redis->xPending('s', 'group'); + $this->assertEquals($xp[0], count($ids)); /* Verify we're seeing the IDs themselves */ @@ -6975,6 +6976,36 @@ class Redis_Test extends TestSuite } } + /* Make sure our XAUTOCLAIM handler works */ + public function testXAutoClaim() { + $this->redis->del('ships'); + $this->redis->xGroup('CREATE', 'ships', 'combatants', '0-0', true); + + // Test an empty xautoclaim reply + $res = $this->redis->xAutoClaim('ships', 'combatants', 'Sisko', 0, '0-0'); + $this->assertEquals(['0-0', [], []], $res); + + $this->redis->xAdd('ships', '1424-74205', ['name' => 'Defiant']); + + // Consume the ['name' => 'Defiant'] message + $this->redis->xReadGroup('combatants', "Jem'Hadar", ['ships' => '>'], 1); + + // The "Jem'Hadar" consumer has the message presently + $pending = $this->redis->xPending('ships', 'combatants'); + $this->assertTrue($pending && isset($pending[3][0][0]) && $pending[3][0][0] == "Jem'Hadar"); + + // Asssume control of the pending message with a different consumer. + $res = $this->redis->xAutoClaim('ships', 'combatants', 'Sisko', 0, '0-0'); + + $this->assertTrue($res && count($res) == 3 && $res[0] == '0-0' && + isset($res[1]['1424-74205']['name']) && + $res[1]['1424-74205']['name'] == 'Defiant'); + + // Now the 'Sisko' consumer should own the message + $pending = $this->redis->xPending('ships', 'combatants'); + $this->assertTrue(isset($pending[3][0][0]) && $pending[3][0][0] == 'Sisko'); + } + public function testXInfo() { if (!$this->minVersionCheck("5.0")) |