This commit is contained in:
zh1029 2026-02-04 04:53:41 +07:00 committed by GitHub
commit fb09662794
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 239 additions and 0 deletions

View file

@ -41,6 +41,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 \

View file

@ -715,6 +715,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;
}
@ -15152,6 +15155,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 **elements, 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,elements[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 **elements, 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,elements[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) {
@ -15292,6 +15400,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);

View file

@ -1212,6 +1212,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 **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;
REDISMODULE_API void (*RedisModule_KeyAtPosWithFlags)(RedisModuleCtx *ctx, int pos, int flags) REDISMODULE_ATTR;
@ -1611,6 +1614,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);

View file

@ -67,6 +67,7 @@ TEST_MODULES = \
hash.so \
zset.so \
stream.so \
set.so \
mallocsize.so \
aclcheck.so \
list.so \

79
tests/modules/set.c Normal file
View file

@ -0,0 +1,79 @@
#include "redismodule.h"
#include <math.h>
#include <errno.h>
/* 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;
}

View file

@ -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]
}
}