diff options
author | michael-grunder <michael.grunder@gmail.com> | 2022-10-01 20:26:11 +0300 |
---|---|---|
committer | Michael Grunder <michael.grunder@gmail.com> | 2022-10-01 20:42:23 +0300 |
commit | c0e839f6ac45d51adbb109b246c9b3565c3b61e1 (patch) | |
tree | 16c20e37e1c39971a5c788e8e2e508c00aa23d71 | |
parent | 643005080839b35c40d78af40eda3e2743ad4d6f (diff) |
LCS command
Implement the Redis 7.0.0 LCS command with tests.
-rw-r--r-- | common.h | 7 | ||||
-rw-r--r-- | redis.c | 6 | ||||
-rw-r--r-- | redis.stub.php | 2 | ||||
-rw-r--r-- | redis_arginfo.h | 10 | ||||
-rw-r--r-- | redis_cluster.c | 9 | ||||
-rw-r--r-- | redis_cluster.stub.php | 2 | ||||
-rw-r--r-- | redis_cluster_arginfo.h | 10 | ||||
-rw-r--r-- | redis_cluster_legacy_arginfo.h | 10 | ||||
-rw-r--r-- | redis_commands.c | 103 | ||||
-rw-r--r-- | redis_commands.h | 3 | ||||
-rw-r--r-- | redis_legacy_arginfo.h | 10 | ||||
-rw-r--r-- | tests/RedisTest.php | 21 |
12 files changed, 187 insertions, 6 deletions
@@ -146,6 +146,13 @@ typedef enum { #define PHPREDIS_DEBUG_LOGGING 0 +#if PHP_VERSION_ID < 80000 +#define Z_PARAM_ARRAY_HT_OR_NULL(dest) \ + Z_PARAM_ARRAY_HT_EX(dest, 1, 0) +#define Z_PARAM_STR_OR_NULL(dest) \ + Z_PARAM_STR_EX(dest, 1, 0) +#endif + #if PHPREDIS_DEBUG_LOGGING == 1 #define redisDbgFmt(fmt, ...) \ php_printf("%s:%d:%s(): " fmt "\n", __FILE__, __LINE__, __func__, __VA_ARGS__) @@ -1182,6 +1182,12 @@ PHP_METHOD(Redis, getRange) } /* }}} */ +/* {{{ proto mixed Redis::lcs(string $key1, string $key2, ?array $options = NULL); */ +PHP_METHOD(Redis, lcs) { + REDIS_PROCESS_CMD(lcs, redis_read_variant_reply); +} +/* }}} */ + /* {{{ proto string Redis::setRange(string key, long start, string value) */ PHP_METHOD(Redis, setRange) { diff --git a/redis.stub.php b/redis.stub.php index 7f125ddf..c3740b8d 100644 --- a/redis.stub.php +++ b/redis.stub.php @@ -173,6 +173,8 @@ class Redis { /** @return string|Redis */ public function getRange(string $key, int $start, int $end); + public function lcs(string $key1, string $key2, ?array $options = NULL): Redis|string|array|int|false; + public function getReadTimeout(): int; /** @return string|Redis */ diff --git a/redis_arginfo.h b/redis_arginfo.h index 33526de0..7c2cdd43 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: f43a528bc5874c419161b5eb874537fbe9c00e87 */ + * Stub hash: 1c32099810101448fc32ce331e2b494dabc22cc4 */ 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") @@ -300,6 +300,12 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_getRange, 0, 0, 3) ZEND_ARG_TYPE_INFO(0, end, IS_LONG, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Redis_lcs, 0, 2, Redis, MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_LONG|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, key1, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, key2, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "NULL") +ZEND_END_ARG_INFO() + #define arginfo_class_Redis_getReadTimeout arginfo_class_Redis_dbSize #define arginfo_class_Redis_getset arginfo_class_Redis_append @@ -1061,6 +1067,7 @@ ZEND_METHOD(Redis, getOption); ZEND_METHOD(Redis, getPersistentID); ZEND_METHOD(Redis, getPort); ZEND_METHOD(Redis, getRange); +ZEND_METHOD(Redis, lcs); ZEND_METHOD(Redis, getReadTimeout); ZEND_METHOD(Redis, getset); ZEND_METHOD(Redis, getTimeout); @@ -1295,6 +1302,7 @@ static const zend_function_entry class_Redis_methods[] = { ZEND_ME(Redis, getPersistentID, arginfo_class_Redis_getPersistentID, ZEND_ACC_PUBLIC) ZEND_ME(Redis, getPort, arginfo_class_Redis_getPort, ZEND_ACC_PUBLIC) ZEND_ME(Redis, getRange, arginfo_class_Redis_getRange, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, lcs, arginfo_class_Redis_lcs, ZEND_ACC_PUBLIC) ZEND_ME(Redis, getReadTimeout, arginfo_class_Redis_getReadTimeout, ZEND_ACC_PUBLIC) ZEND_ME(Redis, getset, arginfo_class_Redis_getset, ZEND_ACC_PUBLIC) ZEND_ME(Redis, getTimeout, arginfo_class_Redis_getTimeout, ZEND_ACC_PUBLIC) diff --git a/redis_cluster.c b/redis_cluster.c index 4be54bf8..cee00ae2 100644 --- a/redis_cluster.c +++ b/redis_cluster.c @@ -1315,13 +1315,18 @@ PHP_METHOD(RedisCluster, lget) { } /* }}} */ -/* {{{ proto string RedisCluster::getrange(string key, long start, long end) */ -PHP_METHOD(RedisCluster, getrange) { +/* {{{ proto string RedisCluster::getrange(string key, long start, long end) */ PHP_METHOD(RedisCluster, getrange) { CLUSTER_PROCESS_KW_CMD("GETRANGE", redis_key_long_long_cmd, cluster_bulk_resp, 1); } /* }}} */ +/* {{{ prot RedisCluster::lcs(string $key1, string $key2, ?array $options = NULL): mixed; */ +PHP_METHOD(RedisCluster, lcs) { + CLUSTER_PROCESS_CMD(lcs, cluster_variant_resp, 1); +} + +/* }}} */ /* {{{ 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 924b9c04..31ba4ef6 100644 --- a/redis_cluster.stub.php +++ b/redis_cluster.stub.php @@ -124,6 +124,8 @@ class RedisCluster { public function getrange(string $key, int $start, int $end): string; + public function lcs(string $key1, string $key2, ?array $options = NULL): Redis|string|array|int|false; + public function getset(string $key, mixed $value): string; public function hdel(string $key, string $member, string ...$other_members): int; diff --git a/redis_cluster_arginfo.h b/redis_cluster_arginfo.h index 35f2ab11..bbf10c92 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: c9bfd7de5c930c9d7190139e207eb6025e33bdb2 */ + * Stub hash: 45d120a35a1c256964c45c71ab91c025ea00e862 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 1) @@ -253,6 +253,12 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_RedisCluster_getrange, 0, ZEND_ARG_TYPE_INFO(0, end, IS_LONG, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_RedisCluster_lcs, 0, 2, Redis, MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_LONG|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, key1, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, key2, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 1, "NULL") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_RedisCluster_getset, 0, 2, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, key, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) @@ -904,6 +910,7 @@ ZEND_METHOD(RedisCluster, getlasterror); ZEND_METHOD(RedisCluster, getmode); ZEND_METHOD(RedisCluster, getoption); ZEND_METHOD(RedisCluster, getrange); +ZEND_METHOD(RedisCluster, lcs); ZEND_METHOD(RedisCluster, getset); ZEND_METHOD(RedisCluster, hdel); ZEND_METHOD(RedisCluster, hexists); @@ -1100,6 +1107,7 @@ static const zend_function_entry class_RedisCluster_methods[] = { ZEND_ME(RedisCluster, getmode, arginfo_class_RedisCluster_getmode, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, getoption, arginfo_class_RedisCluster_getoption, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, getrange, arginfo_class_RedisCluster_getrange, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, lcs, arginfo_class_RedisCluster_lcs, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, getset, arginfo_class_RedisCluster_getset, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, hdel, arginfo_class_RedisCluster_hdel, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, hexists, arginfo_class_RedisCluster_hexists, ZEND_ACC_PUBLIC) diff --git a/redis_cluster_legacy_arginfo.h b/redis_cluster_legacy_arginfo.h index bff23cd6..5669c8be 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: c9bfd7de5c930c9d7190139e207eb6025e33bdb2 */ + * Stub hash: 45d120a35a1c256964c45c71ab91c025ea00e862 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster___construct, 0, 0, 1) ZEND_ARG_INFO(0, name) @@ -232,6 +232,12 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_getrange, 0, 0, 3) ZEND_ARG_INFO(0, end) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_RedisCluster_lcs, 0, 0, 2) + ZEND_ARG_INFO(0, key1) + ZEND_ARG_INFO(0, key2) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + #define arginfo_class_RedisCluster_getset arginfo_class_RedisCluster_append #define arginfo_class_RedisCluster_hdel arginfo_class_RedisCluster_geohash @@ -792,6 +798,7 @@ ZEND_METHOD(RedisCluster, getlasterror); ZEND_METHOD(RedisCluster, getmode); ZEND_METHOD(RedisCluster, getoption); ZEND_METHOD(RedisCluster, getrange); +ZEND_METHOD(RedisCluster, lcs); ZEND_METHOD(RedisCluster, getset); ZEND_METHOD(RedisCluster, hdel); ZEND_METHOD(RedisCluster, hexists); @@ -988,6 +995,7 @@ static const zend_function_entry class_RedisCluster_methods[] = { ZEND_ME(RedisCluster, getmode, arginfo_class_RedisCluster_getmode, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, getoption, arginfo_class_RedisCluster_getoption, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, getrange, arginfo_class_RedisCluster_getrange, ZEND_ACC_PUBLIC) + ZEND_ME(RedisCluster, lcs, arginfo_class_RedisCluster_lcs, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, getset, arginfo_class_RedisCluster_getset, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, hdel, arginfo_class_RedisCluster_hdel, ZEND_ACC_PUBLIC) ZEND_ME(RedisCluster, hexists, arginfo_class_RedisCluster_hexists, ZEND_ACC_PUBLIC) diff --git a/redis_commands.c b/redis_commands.c index 1db729a6..22c19a8d 100644 --- a/redis_commands.c +++ b/redis_commands.c @@ -58,6 +58,13 @@ typedef struct geoOptions { zend_string *key; } geoOptions; +typedef struct redisLcsOptions { + zend_bool len; + zend_bool idx; + zend_long minmatchlen; + zend_bool withmatchlen; +} redisLcsOptions; + /* Local passthrough macro for command construction. Given that these methods * are generic (so they work whether the caller is Redis or RedisCluster) we * will always have redis_sock, slot*, and */ @@ -2222,6 +2229,102 @@ redis_hstrlen_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, return SUCCESS; } +void redis_get_lcs_options(redisLcsOptions *dst, HashTable *ht) { + zend_string *key; + zval *zv; + + ZEND_ASSERT(dst != NULL); + + memset(dst, 0, sizeof(*dst)); + + if (ht == NULL) + return; + + ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, zv) { + if (key) { + if (zend_string_equals_literal_ci(key, "LEN")) { + dst->idx = 0; + dst->len = zval_is_true(zv); + } else if (zend_string_equals_literal_ci(key, "IDX")) { + dst->len = 0; + dst->idx = zval_is_true(zv); + } else if (zend_string_equals_literal_ci(key, "MINMATCHLEN")) { + dst->minmatchlen = zval_get_long(zv); + } else if (zend_string_equals_literal_ci(key, "WITHMATCHLEN")) { + dst->withmatchlen = zval_is_true(zv); + } else { + php_error_docref(NULL, E_WARNING, "Unknown LCS option '%s'", ZSTR_VAL(key)); + } + } else if (Z_TYPE_P(zv) == IS_STRING) { + if (zend_string_equals_literal_ci(Z_STR_P(zv), "LEN")) { + dst->idx = 0; + dst->len = 1; + } else if (zend_string_equals_literal_ci(Z_STR_P(zv), "IDX")) { + dst->idx = 1; + dst->len = 0; + } else if (zend_string_equals_literal_ci(Z_STR_P(zv), "WITHMATCHLEN")) { + dst->withmatchlen = 1; + } + } + } ZEND_HASH_FOREACH_END(); +} + +/* LCS */ +int redis_lcs_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + char **cmd, int *cmd_len, short *slot, void **ctx) +{ + zend_string *key1 = NULL, *key2 = NULL; + smart_string cmdstr = {0}; + HashTable *ht = NULL; + redisLcsOptions opt; + int argc; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_STR(key1) + Z_PARAM_STR(key2) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_HT_OR_NULL(ht) + ZEND_PARSE_PARAMETERS_END_EX(return FAILURE); + + key1 = redis_key_prefix_zstr(redis_sock, key1); + key2 = redis_key_prefix_zstr(redis_sock, key2); + + if (slot) { + *slot = cluster_hash_key_zstr(key1); + if (*slot != cluster_hash_key_zstr(key2)) { + php_error_docref(NULL, E_WARNING, "Warning, not all keys hash to the same slot!"); + zend_string_release(key1); + zend_string_release(key2); + return FAILURE; + } + } + + redis_get_lcs_options(&opt, ht); + + argc = 2 + !!opt.idx + !!opt.len + !!opt.withmatchlen + (opt.minmatchlen ? 2 : 0); + REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "LCS"); + + redis_cmd_append_sstr_zstr(&cmdstr, key1); + redis_cmd_append_sstr_zstr(&cmdstr, key2); + + REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, opt.idx, "IDX"); + REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, opt.len, "LEN"); + REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, opt.withmatchlen, "WITHMATCHLEN"); + + if (opt.minmatchlen) { + REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "MINMATCHLEN"); + redis_cmd_append_sstr_long(&cmdstr, opt.minmatchlen); + } + + zend_string_release(key1); + zend_string_release(key2); + + *cmd = cmdstr.c; + *cmd_len = cmdstr.len; + return SUCCESS; +} + + /* BITPOS */ int redis_bitpos_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx) diff --git a/redis_commands.h b/redis_commands.h index 24fbe92c..c51896ad 100644 --- a/redis_commands.h +++ b/redis_commands.h @@ -129,6 +129,9 @@ int redis_intercard_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); +int redis_lcs_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + 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 aaf02d19..7954c6f2 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: f43a528bc5874c419161b5eb874537fbe9c00e87 */ + * Stub hash: 1c32099810101448fc32ce331e2b494dabc22cc4 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis___construct, 0, 0, 0) ZEND_ARG_INFO(0, options) @@ -279,6 +279,12 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_getRange, 0, 0, 3) ZEND_ARG_INFO(0, end) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Redis_lcs, 0, 0, 2) + ZEND_ARG_INFO(0, key1) + ZEND_ARG_INFO(0, key2) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + #define arginfo_class_Redis_getReadTimeout arginfo_class_Redis___destruct #define arginfo_class_Redis_getset arginfo_class_Redis_append @@ -953,6 +959,7 @@ ZEND_METHOD(Redis, getOption); ZEND_METHOD(Redis, getPersistentID); ZEND_METHOD(Redis, getPort); ZEND_METHOD(Redis, getRange); +ZEND_METHOD(Redis, lcs); ZEND_METHOD(Redis, getReadTimeout); ZEND_METHOD(Redis, getset); ZEND_METHOD(Redis, getTimeout); @@ -1187,6 +1194,7 @@ static const zend_function_entry class_Redis_methods[] = { ZEND_ME(Redis, getPersistentID, arginfo_class_Redis_getPersistentID, ZEND_ACC_PUBLIC) ZEND_ME(Redis, getPort, arginfo_class_Redis_getPort, ZEND_ACC_PUBLIC) ZEND_ME(Redis, getRange, arginfo_class_Redis_getRange, ZEND_ACC_PUBLIC) + ZEND_ME(Redis, lcs, arginfo_class_Redis_lcs, ZEND_ACC_PUBLIC) ZEND_ME(Redis, getReadTimeout, arginfo_class_Redis_getReadTimeout, ZEND_ACC_PUBLIC) ZEND_ME(Redis, getset, arginfo_class_Redis_getset, ZEND_ACC_PUBLIC) ZEND_ME(Redis, getTimeout, arginfo_class_Redis_getTimeout, ZEND_ACC_PUBLIC) diff --git a/tests/RedisTest.php b/tests/RedisTest.php index 551c6346..2e4402f3 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -272,6 +272,27 @@ class Redis_Test extends TestSuite $this->assertEquals(1, $this->redis->getBit('key', 0x7fffffff)); } + public function testLcs() { + $key1 = '{lcs}1'; $key2 = '{lcs}2'; + $this->assertTrue($this->redis->set($key1, '12244447777777')); + $this->assertTrue($this->redis->set($key2, '6666662244441')); + + $this->assertEquals('224444', $this->redis->lcs($key1, $key2)); + + $this->assertEquals( + ['matches', [[[1, 6], [6, 11]]], 'len', 6], + $this->redis->lcs($key1, $key2, ['idx']) + ); + $this->assertEquals( + ['matches', [[[1, 6], [6, 11], 6]], 'len', 6], + $this->redis->lcs($key1, $key2, ['idx', 'withmatchlen']) + ); + + $this->assertEquals(6, $this->redis->lcs($key1, $key2, ['len'])); + + $this->redis->del([$key1, $key2]); + } + public function testBitPos() { if (version_compare($this->version, "2.8.7") < 0) { $this->MarkTestSkipped(); |