From acae5ce38260f9be455bc11999d42c3d7fa1e0da Mon Sep 17 00:00:00 2001 From: zh1029 Date: Mon, 18 Dec 2023 13:25:18 +0800 Subject: [PATCH 1/7] Add modules API for set APIs added for these single set operations: add, rem and ismember. The functions are prefixed by RM_Set. * RM_SetAdd * RM_SetRem * RM_SetIsMember --- runtest-moduleapi | 1 + src/module.c | 111 +++++++++++++++++++++++++++++++++++++++++ src/redismodule.h | 6 +++ tests/modules/Makefile | 3 +- 4 files changed, 120 insertions(+), 1 deletion(-) diff --git a/runtest-moduleapi b/runtest-moduleapi index ff685afb6..41f847da7 100755 --- a/runtest-moduleapi +++ b/runtest-moduleapi @@ -40,6 +40,7 @@ $TCLSH tests/test_helper.tcl \ --single unit/moduleapi/zset \ --single unit/moduleapi/list \ --single unit/moduleapi/stream \ +--single unit/moduleapi/set \ --single unit/moduleapi/mallocsize \ --single unit/moduleapi/datatype2 \ --single unit/moduleapi/cluster \ diff --git a/src/module.c b/src/module.c index b966998c6..f75d3fc57 100644 --- a/src/module.c +++ b/src/module.c @@ -705,6 +705,9 @@ int moduleCreateEmptyKey(RedisModuleKey *key, int type) { case REDISMODULE_KEYTYPE_STREAM: obj = createStreamObject(); break; + case REDISMODULE_KEYTYPE_SET: + obj = createSetObject(); + break; default: return REDISMODULE_ERR; } dbAdd(key->db,key->key,obj); @@ -13524,6 +13527,111 @@ int RM_GetDbIdFromDefragCtx(RedisModuleDefragCtx *ctx) { return ctx->dbid; } +/* -------------------------------------------------------------------------- + * ## Key API for Set type + * + * See also RM_ValueLength(), which returns the cardinality of a set. + * -------------------------------------------------------------------------- */ + +/* Add new elements into a set. + * + * Returns REDISMODULE_OK if elements have been added successfully. + * On failure, REDISMODULE_ERR is returned and `errno` is set as follows: + * + * - EBADF if the key was not opened for writing + * - ENOTSUP if the key is of another type than set. + * + * In order to know the number of elements were added, the additional argument + * 'added' must be passed, that populates the integer by reference + * setting it to the number of elements added depending on the outcome of the operation. + * The 'added' argument can be NULL if the caller is not interested + * to know if the number of elements were really added. + * + * Empty keys will be created with set key type and continue. */ +int RM_SetAdd(RedisModuleKey *key, RedisModuleString **eles, size_t numeles, size_t *added) { + if (!(key->mode & REDISMODULE_WRITE)) { + errno = EBADF; + return REDISMODULE_ERR; + } + if (key->value && key->value->type != OBJ_SET) { + errno = ENOTSUP; + return REDISMODULE_ERR; + } + if (key->value == NULL) moduleCreateEmptyKey(key,REDISMODULE_KEYTYPE_SET); + size_t i, numadded = 0; + for (i = 0; i < numeles; i++) { + numadded += setTypeAdd(key->value,eles[i]->ptr); + } + if (added) *added = numadded; + + return REDISMODULE_OK; +} + +/* Remove the specified element from the set and the key will be + * removed if has no any element in after remove elements operation. + + * Returns REDISMODULE_OK on success. On failure, REDISMODULE_ERR is returned + * and `errno` is set as follows: + * + * - EINVAL if called with invalid arguments + * - ENOTSUP if the key refers to a value of a type other than set + * - EBADF if the key was not opened for writing + * + * Key will be removed if has no any element in after remove elements operation + * + * The return value does NOT indicate the fact the element was really + * removed (since it existed) or not, just if the function was executed + * with success. + * + * In order to know the number of elements were removed, the additional argument + * 'deleted' must be passed, that populates the integer by reference + * setting it to the number of elements removed depending on the outcome of the operation. + * The 'deleted' argument can be NULL if the caller is not interested + * to know if the number of elements were really removed. + * + * Empty keys will be handled correctly by doing nothing. */ +int RM_SetRem(RedisModuleKey *key, RedisModuleString **eles, size_t numeles, size_t *deleted) { + size_t numdeleted = 0; + if (!key) { + errno = EINVAL; + return REDISMODULE_ERR; + } else if (!key->value) { + /*return 0 for empty key*/ + if (deleted) *deleted = numdeleted; + return REDISMODULE_OK; + } else if (key->value->type != OBJ_SET) { + errno = ENOTSUP; /* wrong type */ + return REDISMODULE_ERR; + } else if (!(key->mode & REDISMODULE_WRITE)) { + errno = EBADF; /* key not opened for writing */ + return REDISMODULE_ERR; + } + for (size_t i = 0; i < numeles; i++) { + numdeleted += setTypeRemove(key->value,eles[i]->ptr); + if (moduleDelKeyIfEmpty(key)) break; + } + if (deleted) *deleted = numdeleted; + return REDISMODULE_OK; +} + +/* Query if member is a member of the set stored at key + + * Returns 0 if member is not a member of the set stored at key. + * Returns 1 if member is a member of the set stored at key. + * On failure, -1 is returned and `errno` is set as follows: + * + * - ENOTSUP if the key refers to a value of a type other than set */ +int RM_SetIsMember(RedisModuleKey *key, RedisModuleString *ele) { + if (!key || !key->value) { + /*return 0 for empty key*/ + return 0; + } else if (key->value->type != OBJ_SET) { + errno = ENOTSUP; /* wrong type */ + return -1; + } + return setTypeIsMember(key->value,ele->ptr); +} + /* Register all the APIs we export. Keep this function at the end of the * file so that's easy to seek it to add new entries. */ void moduleRegisterCoreAPI(void) { @@ -13882,4 +13990,7 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(RdbStreamFree); REGISTER_API(RdbLoad); REGISTER_API(RdbSave); + REGISTER_API(SetAdd); + REGISTER_API(SetRem); + REGISTER_API(SetIsMember); } diff --git a/src/redismodule.h b/src/redismodule.h index de533d049..61b8b4480 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -1086,6 +1086,9 @@ REDISMODULE_API int (*RedisModule_StreamIteratorNextField)(RedisModuleKey *key, REDISMODULE_API int (*RedisModule_StreamIteratorDelete)(RedisModuleKey *key) REDISMODULE_ATTR; REDISMODULE_API long long (*RedisModule_StreamTrimByLength)(RedisModuleKey *key, int flags, long long length) REDISMODULE_ATTR; REDISMODULE_API long long (*RedisModule_StreamTrimByID)(RedisModuleKey *key, int flags, RedisModuleStreamID *id) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SetAdd)(RedisModuleKey *key, RedisModuleString **eles, int numeles, size_t *added) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SetRem)(RedisModuleKey *key, RedisModuleString **eles, int numeles, size_t *deleted) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SetIsMember)(RedisModuleKey *key, RedisModuleString *ele) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_KeyAtPosWithFlags)(RedisModuleCtx *ctx, int pos, int flags) REDISMODULE_ATTR; @@ -1448,6 +1451,9 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(StreamIteratorDelete); REDISMODULE_GET_API(StreamTrimByLength); REDISMODULE_GET_API(StreamTrimByID); + REDISMODULE_GET_API(SetAdd); + REDISMODULE_GET_API(SetRem); + REDISMODULE_GET_API(SetIsMember); REDISMODULE_GET_API(IsKeysPositionRequest); REDISMODULE_GET_API(KeyAtPos); REDISMODULE_GET_API(KeyAtPosWithFlags); diff --git a/tests/modules/Makefile b/tests/modules/Makefile index d63c8548d..38e453954 100644 --- a/tests/modules/Makefile +++ b/tests/modules/Makefile @@ -62,7 +62,8 @@ TEST_MODULES = \ usercall.so \ postnotifications.so \ moduleauthtwo.so \ - rdbloadsave.so + rdbloadsave.so \ + set.so .PHONY: all From 2a732d88d89d563839a39ec09dd93c085c8e20ef Mon Sep 17 00:00:00 2001 From: zh1029 Date: Wed, 10 Jan 2024 11:06:53 +0800 Subject: [PATCH 2/7] Add test files to test modules API for set --- tests/modules/set.c | 79 ++++++++++++++++++++++++++++++++++++ tests/unit/moduleapi/set.tcl | 41 +++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 tests/modules/set.c create mode 100644 tests/unit/moduleapi/set.tcl diff --git a/tests/modules/set.c b/tests/modules/set.c new file mode 100644 index 000000000..efde5c01d --- /dev/null +++ b/tests/modules/set.c @@ -0,0 +1,79 @@ +#include "redismodule.h" +#include +#include + +/* SET.REM key element [element ...] + * + * Removes elements from a set. Replies with the + * number of removed elements. + */ +int set_rem(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc < 3) return RedisModule_WrongArity(ctx); + RedisModule_AutoMemory(ctx); + int keymode = REDISMODULE_READ | REDISMODULE_WRITE; + RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], keymode); + size_t deleted; + if (RedisModule_SetRem(key, &argv[2], argc-2, &deleted) == REDISMODULE_OK) { + RedisModule_ReplyWithLongLong(ctx, deleted); + } else { + RedisModule_ReplyWithError(ctx, "ERR SetRem failed"); + } + RedisModule_CloseKey(key); + return REDISMODULE_OK; +} + +/* SET.ADD key member [member ...] + * + * Adds members to the set stored at key. Replies with the + * number of added elements. + */ +int set_add(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc < 3) return RedisModule_WrongArity(ctx); + RedisModule_AutoMemory(ctx); + int keymode = REDISMODULE_READ | REDISMODULE_WRITE; + RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], keymode); + size_t added; + if (RedisModule_SetAdd(key, &argv[2], argc-2, &added) == REDISMODULE_OK) { + RedisModule_ReplyWithLongLong(ctx, added); + } else { + RedisModule_ReplyWithError(ctx, "ERR SetAdd failed"); + } + RedisModule_CloseKey(key); + return REDISMODULE_OK; +} + +/* SET.ISMEMBER key member + * + * Is member of the set stored at key. Replies with 1 as member of the key + * or 0 as not member of the key + */ +int set_ismember(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 3) return RedisModule_WrongArity(ctx); + RedisModule_AutoMemory(ctx); + int keymode = REDISMODULE_READ; + RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], keymode); + RedisModule_ReplyWithLongLong(ctx, RedisModule_SetIsMember(key, argv[2])); + RedisModule_CloseKey(key); + return REDISMODULE_OK; +} + +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + if (RedisModule_Init(ctx, "set", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx, "set.rem", set_rem, "write", + 1, 1, 1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx, "set.add", set_add, "write", + 1, 1, 1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx, "set.ismember", set_ismember, "readonly", + 1, 1, 1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + return REDISMODULE_OK; +} diff --git a/tests/unit/moduleapi/set.tcl b/tests/unit/moduleapi/set.tcl new file mode 100644 index 000000000..8dd23f00a --- /dev/null +++ b/tests/unit/moduleapi/set.tcl @@ -0,0 +1,41 @@ +set testmodule [file normalize tests/modules/set.so] + +start_server {tags {"modules"}} { + r module load $testmodule + + test {Module set add} { + r del k + # Check that failure does not create empty key + assert_error "ERR wrong number of arguments for 'set.add' command" {r set.add k} + assert_equal 0 [r exists k] + + assert_equal 1 [r set.add k hello] + assert_equal 1 [r set.ismember k hello] + assert_equal 0 [r set.ismember k world] + } + + test {Module set rem} { + r del k + r sadd k hello world + assert_equal 1 [r set.rem k hello] + assert_equal 1 [r exists k] + # Check that removing the last element deletes the key + assert_equal 1 [r set.rem k world] + assert_equal 0 [r exists k] + # Removing element from empty key shall work. + assert_equal 0 [r set.rem k world] + assert_equal 0 [r exists k] + } + + test {Module set ismember} { + r del k + assert_equal 0 [r set.ismember k hello] + assert_equal 1 [r set.add k hello] + assert_equal 1 [r set.ismember k hello] + assert_equal 0 [r set.ismember k world] + + r del k + assert_equal OK [r set k hello] + assert_equal -1 [r set.ismember k hello] + } +} From 7b935db4290f9d06d0a69b465b4a0fb0636f071d Mon Sep 17 00:00:00 2001 From: zh1029 Date: Wed, 10 Jan 2024 13:31:14 +0800 Subject: [PATCH 3/7] Update src/module.c Co-authored-by: debing.sun --- src/module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module.c b/src/module.c index f75d3fc57..25710873c 100644 --- a/src/module.c +++ b/src/module.c @@ -13569,7 +13569,7 @@ int RM_SetAdd(RedisModuleKey *key, RedisModuleString **eles, size_t numeles, siz /* Remove the specified element from the set and the key will be * removed if has no any element in after remove elements operation. - + * * Returns REDISMODULE_OK on success. On failure, REDISMODULE_ERR is returned * and `errno` is set as follows: * From 21625d834a165d364b01ef570c6ea84b28e6b798 Mon Sep 17 00:00:00 2001 From: zh1029 Date: Wed, 10 Jan 2024 13:31:28 +0800 Subject: [PATCH 4/7] Update src/module.c Co-authored-by: debing.sun --- src/module.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/module.c b/src/module.c index 25710873c..b20202f68 100644 --- a/src/module.c +++ b/src/module.c @@ -13615,8 +13615,8 @@ int RM_SetRem(RedisModuleKey *key, RedisModuleString **eles, size_t numeles, siz } /* Query if member is a member of the set stored at key - - * Returns 0 if member is not a member of the set stored at key. + * + * Returns 0 if member is not a member of the set stored at key. * Returns 1 if member is a member of the set stored at key. * On failure, -1 is returned and `errno` is set as follows: * From 53b20e5455069d99db8fd891177462c2ffd588c1 Mon Sep 17 00:00:00 2001 From: zh1029 Date: Wed, 28 Feb 2024 16:08:45 +0800 Subject: [PATCH 5/7] Update src/module.c Co-authored-by: debing.sun --- src/module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module.c b/src/module.c index b20202f68..68b70d2c3 100644 --- a/src/module.c +++ b/src/module.c @@ -13623,7 +13623,7 @@ int RM_SetRem(RedisModuleKey *key, RedisModuleString **eles, size_t numeles, siz * - ENOTSUP if the key refers to a value of a type other than set */ int RM_SetIsMember(RedisModuleKey *key, RedisModuleString *ele) { if (!key || !key->value) { - /*return 0 for empty key*/ + /* return 0 for empty key */ return 0; } else if (key->value->type != OBJ_SET) { errno = ENOTSUP; /* wrong type */ From 35562801865c7d200e2c564a71f4c638ef06dce7 Mon Sep 17 00:00:00 2001 From: zh1029 Date: Wed, 28 Feb 2024 16:17:13 +0800 Subject: [PATCH 6/7] Updated for findings. --- src/module.c | 6 +++--- tests/modules/Makefile | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/module.c b/src/module.c index 68b70d2c3..5e3f04e47 100644 --- a/src/module.c +++ b/src/module.c @@ -13765,6 +13765,9 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(StreamIteratorDelete); REGISTER_API(StreamTrimByLength); REGISTER_API(StreamTrimByID); + REGISTER_API(SetAdd); + REGISTER_API(SetRem); + REGISTER_API(SetIsMember); REGISTER_API(IsKeysPositionRequest); REGISTER_API(KeyAtPos); REGISTER_API(KeyAtPosWithFlags); @@ -13990,7 +13993,4 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(RdbStreamFree); REGISTER_API(RdbLoad); REGISTER_API(RdbSave); - REGISTER_API(SetAdd); - REGISTER_API(SetRem); - REGISTER_API(SetIsMember); } diff --git a/tests/modules/Makefile b/tests/modules/Makefile index 38e453954..6de59ad81 100644 --- a/tests/modules/Makefile +++ b/tests/modules/Makefile @@ -49,6 +49,7 @@ TEST_MODULES = \ hash.so \ zset.so \ stream.so \ + set.so \ mallocsize.so \ aclcheck.so \ list.so \ @@ -62,8 +63,7 @@ TEST_MODULES = \ usercall.so \ postnotifications.so \ moduleauthtwo.so \ - rdbloadsave.so \ - set.so + rdbloadsave.so .PHONY: all From 65db598134aa1ba91f0fc4da1b55451357b5c67b Mon Sep 17 00:00:00 2001 From: y39chen Date: Mon, 2 Sep 2024 13:44:29 +0800 Subject: [PATCH 7/7] Fix variable name spellcheck error. Change variable name "eles" to "elements" to fix spellcheck error. --- src/module.c | 8 ++++---- src/redismodule.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/module.c b/src/module.c index 7addbae4d..f6f1a587a 100644 --- a/src/module.c +++ b/src/module.c @@ -13661,7 +13661,7 @@ int RM_GetDbIdFromDefragCtx(RedisModuleDefragCtx *ctx) { * to know if the number of elements were really added. * * Empty keys will be created with set key type and continue. */ -int RM_SetAdd(RedisModuleKey *key, RedisModuleString **eles, size_t numeles, size_t *added) { +int RM_SetAdd(RedisModuleKey *key, RedisModuleString **elements, size_t numeles, size_t *added) { if (!(key->mode & REDISMODULE_WRITE)) { errno = EBADF; return REDISMODULE_ERR; @@ -13673,7 +13673,7 @@ int RM_SetAdd(RedisModuleKey *key, RedisModuleString **eles, size_t numeles, siz if (key->value == NULL) moduleCreateEmptyKey(key,REDISMODULE_KEYTYPE_SET); size_t i, numadded = 0; for (i = 0; i < numeles; i++) { - numadded += setTypeAdd(key->value,eles[i]->ptr); + numadded += setTypeAdd(key->value,elements[i]->ptr); } if (added) *added = numadded; @@ -13703,7 +13703,7 @@ int RM_SetAdd(RedisModuleKey *key, RedisModuleString **eles, size_t numeles, siz * to know if the number of elements were really removed. * * Empty keys will be handled correctly by doing nothing. */ -int RM_SetRem(RedisModuleKey *key, RedisModuleString **eles, size_t numeles, size_t *deleted) { +int RM_SetRem(RedisModuleKey *key, RedisModuleString **elements, size_t numeles, size_t *deleted) { size_t numdeleted = 0; if (!key) { errno = EINVAL; @@ -13720,7 +13720,7 @@ int RM_SetRem(RedisModuleKey *key, RedisModuleString **eles, size_t numeles, siz return REDISMODULE_ERR; } for (size_t i = 0; i < numeles; i++) { - numdeleted += setTypeRemove(key->value,eles[i]->ptr); + numdeleted += setTypeRemove(key->value,elements[i]->ptr); if (moduleDelKeyIfEmpty(key)) break; } if (deleted) *deleted = numdeleted; diff --git a/src/redismodule.h b/src/redismodule.h index 7f9791a74..e60396dac 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -1088,8 +1088,8 @@ REDISMODULE_API int (*RedisModule_StreamIteratorNextField)(RedisModuleKey *key, REDISMODULE_API int (*RedisModule_StreamIteratorDelete)(RedisModuleKey *key) REDISMODULE_ATTR; REDISMODULE_API long long (*RedisModule_StreamTrimByLength)(RedisModuleKey *key, int flags, long long length) REDISMODULE_ATTR; REDISMODULE_API long long (*RedisModule_StreamTrimByID)(RedisModuleKey *key, int flags, RedisModuleStreamID *id) REDISMODULE_ATTR; -REDISMODULE_API int (*RedisModule_SetAdd)(RedisModuleKey *key, RedisModuleString **eles, int numeles, size_t *added) REDISMODULE_ATTR; -REDISMODULE_API int (*RedisModule_SetRem)(RedisModuleKey *key, RedisModuleString **eles, int numeles, size_t *deleted) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SetAdd)(RedisModuleKey *key, RedisModuleString **elements, int numeles, size_t *added) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SetRem)(RedisModuleKey *key, RedisModuleString **elements, int numeles, size_t *deleted) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_SetIsMember)(RedisModuleKey *key, RedisModuleString *ele) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos) REDISMODULE_ATTR;