diff options
author | michael-grunder <michael.grunder@gmail.com> | 2022-10-22 22:46:26 +0300 |
---|---|---|
committer | Michael Grunder <michael.grunder@gmail.com> | 2022-10-23 21:37:20 +0300 |
commit | 1343f5008301a256fcbfa0161931e2a39791055f (patch) | |
tree | 998e0199f979755ea09e2ab3209353211a4dee3e | |
parent | 71bcbcb973413ae933cf30d58254dfb02425c1ed (diff) |
XGROUP DELCONSUMER and ENTRIESREAD
Refactor XGROUP and implement the new DELCONSUMER (Redis 6.2.0) and
ENTRIESREAD (Redis 7.0.0) options. Additionally, add a proper phpdoc
block to the stub file.
See #2068
-rw-r--r-- | redis.stub.php | 39 | ||||
-rw-r--r-- | redis_arginfo.h | 9 | ||||
-rw-r--r-- | redis_commands.c | 90 | ||||
-rw-r--r-- | redis_legacy_arginfo.h | 9 | ||||
-rw-r--r-- | tests/RedisTest.php | 34 |
5 files changed, 137 insertions, 44 deletions
diff --git a/redis.stub.php b/redis.stub.php index 51dd1fe3..3c3c78e5 100644 --- a/redis.stub.php +++ b/redis.stub.php @@ -660,7 +660,44 @@ class Redis { public function xdel(string $key, array $ids): Redis|int|false; - public function xgroup(string $operation, string $key = null, string $arg1 = null, string $arg2 = null, bool $arg3 = false): mixed; + /** + * XGROUP + * + * Perform various operation on consumer groups for a particular Redis STREAM. + * What the command does is primarily based on which operation is passed. + * + * @see https://redis.io/commands/xgroup/ + * + * @param string $operation The subcommand you intend to execute. Valid options are as follows + * 'HELP' - Redis will return information about the command + * Requires: none + * 'CREATE' - Create a consumer group. + * Requires: Key, group, consumer. + * 'SETID' - Set the ID of an existing consumer group for the stream. + * Requires: Key, group, id. + * 'CREATECONSUMER - Create a new consumer group for the stream. You must + * also pass key, group, and the consumer name you wish to + * create. + * Requires: Key, group, consumer. + * 'DELCONSUMER' - Delete a consumer from group attached to the stream. + * Requires: Key, group, consumer. + * 'DESTROY' - Delete a consumer group from a stream. + * Requires: Key, group. + * @param string $key The STREAM we're operating on. + * @param string $group The consumer group wse want to create/modify/delete. + * @param string $id_or_consumer The STREAM id (e.g. '$') or consumer group. See the operation section + * for information about which to send. + * @param bool $mkstream This flag may be sent in combination with the 'CREATE' operation, and + * cause Redis to also create the STREAM if it doesn't currently exist. + * + * @param bool $entriesread Allows you to set Redis's 'entries-read' STREAM value. This argument is + * only relevant to the 'CREATE' and 'SETID' operations. + * Note: Requires Redis >= 7.0.0. + * + * @return mixed This command return various results depending on the operation performed. + */ + 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_arginfo.h b/redis_arginfo.h index 38b11ccf..7d350910 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: 73bbd79b67c155a90acfa2b5ca1be49effdaf8ba */ + * Stub hash: c04531e86379ab5c0de12e8e82868b7c7f024068 */ 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") @@ -909,9 +909,10 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Redis_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_Redis_xinfo, 0, 1, IS_MIXED, 0) diff --git a/redis_commands.c b/redis_commands.c index 08992c2c..416972d6 100644 --- a/redis_commands.c +++ b/redis_commands.c @@ -5817,52 +5817,72 @@ int redis_xclaim_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, } /* XGROUP HELP - * XGROUP CREATE key groupname id [MKSTREAM] - * XGROUP SETID key group id - * XGROUP DESTROY key groupname - * XGROUP DELCONSUMER key groupname consumername */ + * XGROUP CREATE key group id [MKSTREAM] [ENTRIESREAD <n>] + * XGROUP SETID key group id [ENTRIESREAD <n>] + * XGROUP CREATECONSUMER key group consumer + * XGROUP DELCONSUMER key group consumer + * XGROUP DESTROY key group + */ int redis_xgroup_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) { - char *op, *key = NULL, *arg1 = NULL, *arg2 = NULL; - size_t oplen, keylen, arg1len, arg2len; + zend_string *op = NULL, *key = NULL, *group = NULL, *id_or_consumer = NULL; + int nargs, is_create = 0, is_setid = 0; + zend_long entries_read = -2; + smart_string cmdstr = {0}; zend_bool mkstream = 0; - int argc = ZEND_NUM_ARGS(); - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|sssb", &op, &oplen, - &key, &keylen, &arg1, &arg1len, &arg2, &arg2len, - &mkstream) == FAILURE) + ZEND_PARSE_PARAMETERS_START(1, 6) + Z_PARAM_STR(op) + Z_PARAM_OPTIONAL + Z_PARAM_STR(key) + Z_PARAM_STR(group) + Z_PARAM_STR(id_or_consumer) + Z_PARAM_BOOL(mkstream) + Z_PARAM_LONG(entries_read) + ZEND_PARSE_PARAMETERS_END_EX(return FAILURE); + + if (zend_string_equals_literal_ci(op, "HELP")) { + nargs = 0; + } else if ((is_create = zend_string_equals_literal_ci(op, "CREATE")) || + (is_setid = zend_string_equals_literal_ci(op, "SETID")) || + zend_string_equals_literal_ci(op, "CREATECONSUMER") || + zend_string_equals_literal_ci(op, "DELCONSUMER")) { + nargs = 3; + } else if (zend_string_equals_literal_ci(op, "DESTROY")) { + nargs = 2; + } else { + php_error_docref(NULL, E_WARNING, "Unknown XGROUP operation '%s'", ZSTR_VAL(op)); return FAILURE; } - if (argc == 1 && oplen == 4 && !strncasecmp(op, "HELP", 4)) { - *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "s", "HELP", 4); - return SUCCESS; - } else if (argc >= 4 && (oplen == 6 && !strncasecmp(op, "CREATE", 6))) { - if (mkstream) { - *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "sksss", op, oplen, key, keylen, - arg1, arg1len, arg2, arg2len, "MKSTREAM", - sizeof("MKSTREAM") - 1); - } else { - *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "skss", op, oplen, key, keylen, - arg1, arg1len, arg2, arg2len); - } - return SUCCESS; - } else if (argc == 4 && ((oplen == 5 && !strncasecmp(op, "SETID", 5)) || - (oplen == 11 && !strncasecmp(op, "DELCONSUMER", 11)))) - { - *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "skss", op, oplen, key, keylen, - arg1, arg1len, arg2, arg2len); - return SUCCESS; - } else if (argc == 3 && ((oplen == 7 && !strncasecmp(op, "DESTROY", 7)))) { - *cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "sks", op, oplen, key, - keylen, arg1, arg1len); - return SUCCESS; + if (ZEND_NUM_ARGS() < nargs) { + php_error_docref(NULL, E_WARNING, "Operation '%s' requires %d arguments", ZSTR_VAL(op), nargs); + return FAILURE; } - /* Didn't detect any valid XGROUP command pattern */ - return FAILURE; + mkstream &= is_create; + if (!(is_create || is_setid)) + entries_read = -2; + + REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 1 + nargs + !!mkstream + (entries_read != -2 ? 2 : 0), "XGROUP"); + redis_cmd_append_sstr_zstr(&cmdstr, op); + + if (nargs-- > 0) redis_cmd_append_sstr_key_zstr(&cmdstr, key, redis_sock, slot); + if (nargs-- > 0) redis_cmd_append_sstr_zstr(&cmdstr, group); + if (nargs-- > 0) redis_cmd_append_sstr_zstr(&cmdstr, id_or_consumer); + + REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, !!mkstream, "MKSTREAM"); + if (entries_read != -2) { + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "ENTRIESREAD"); + redis_cmd_append_sstr_long(&cmdstr, entries_read); + } + + *cmd = cmdstr.c; + *cmd_len = cmdstr.len; + + return SUCCESS; } /* XINFO CONSUMERS key group diff --git a/redis_legacy_arginfo.h b/redis_legacy_arginfo.h index b5b39586..0b6fbd23 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: 73bbd79b67c155a90acfa2b5ca1be49effdaf8ba */ + * Stub hash: c04531e86379ab5c0de12e8e82868b7c7f024068 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 0, 0, 0) ZEND_ARG_INFO(0, options) @@ -769,9 +769,10 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_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_Redis_xinfo, 0, 0, 1) diff --git a/tests/RedisTest.php b/tests/RedisTest.php index 149bbb84..ea036ebc 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -6521,6 +6521,40 @@ class Redis_Test extends TestSuite $this->assertFalse($this->redis->xGroup('SETID', 's', 'mygroup', 'BAD_ID')); $this->assertEquals($this->redis->xGroup('DELCONSUMER', 's', 'mygroup', 'myconsumer'),0); + + if (!$this->minVersionCheck('6.2.0')) + return; + + /* CREATECONSUMER */ + $this->assertTrue($this->redis->del('s')); + $this->assertTrue($this->redis->xgroup('create', 's', 'mygroup', '$', true)); + for ($i = 0; $i < 3; $i++) { + $this->assertTrue($this->redis->xgroup('createconsumer', 's', 'mygroup', "c:$i")); + $info = $this->redis->xinfo('consumers', 's', 'mygroup'); + $this->assertTrue(is_array($info) && count($info) == $i + 1); + for ($j = 0; $j <= $i; $j++) { + $this->assertTrue(isset($info[$j]) && isset($info[$j]['name']) && $info[$j]['name'] == "c:$j"); + } + } + + /* Make sure we don't erroneously send options that don't belong to the operation */ + $this->assertTrue($this->redis->xGroup('CREATECONSUMER', 's', 'mygroup', 'fake-consumer', true, 1337)); + + /* Make sure we handle the case where the user doesn't send enough arguments */ + $this->redis->clearLastError(); + $this->assertFalse(@$this->redis->xGroup('CREATECONSUMER')); + $this->assertEquals(NULL, $this->redis->getLastError()); + $this->assertFalse(@$this->redis->xGroup('create')); + $this->assertEquals(NULL, $this->redis->getLastError()); + + if (!$this->minVersionCheck('7.0.0')) + return; + + /* ENTRIESREAD */ + $this->assertTrue($this->redis->del('s')); + $this->assertTrue($this->redis->xGroup('create', 's', 'mygroup', '$', true, 1337)); + $info = $this->redis->xinfo('groups', 's'); + $this->assertTrue(isset($info[0]['entries-read']) && 1337 == (int)$info[0]['entries-read']); } public function testXAck() { |