mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-22 08:08:53 -05:00
Modules: In RM_HashSet, add COUNT_ALL flag and set errno (#8446)
The added flag affects the return value of RM_HashSet() to include the number of inserted fields, in addition to updated and deleted fields. errno is set on errors, tests are added and documentation updated.
This commit is contained in:
parent
efccd6353b
commit
0bc8c9c8f9
@ -32,6 +32,7 @@ $TCLSH tests/test_helper.tcl \
|
||||
--single unit/moduleapi/getkeys \
|
||||
--single unit/moduleapi/test_lazyfree \
|
||||
--single unit/moduleapi/defrag \
|
||||
--single unit/moduleapi/hash \
|
||||
--single unit/moduleapi/zset \
|
||||
--single unit/moduleapi/stream \
|
||||
"${@}"
|
||||
|
50
src/module.c
50
src/module.c
@ -2976,6 +2976,10 @@ int RM_ZsetRangePrev(RedisModuleKey *key) {
|
||||
* are created.
|
||||
* REDISMODULE_HASH_CFIELDS: The field names passed are null terminated C
|
||||
* strings instead of RedisModuleString objects.
|
||||
* REDISMODULE_HASH_COUNT_ALL: Include the number of inserted fields in the
|
||||
* returned number, in addition to the number of
|
||||
* updated and deleted fields. (Added in Redis
|
||||
* 6.2.)
|
||||
*
|
||||
* Unless NX is specified, the command overwrites the old field value with
|
||||
* the new one.
|
||||
@ -2989,21 +2993,43 @@ int RM_ZsetRangePrev(RedisModuleKey *key) {
|
||||
*
|
||||
* Return value:
|
||||
*
|
||||
* The number of fields updated (that may be less than the number of fields
|
||||
* specified because of the XX or NX options).
|
||||
* The number of fields existing in the hash prior to the call, which have been
|
||||
* updated (its old value has been replaced by a new value) or deleted. If the
|
||||
* flag REDISMODULE_HASH_COUNT_ALL is set, insterted fields not previously
|
||||
* existing in the hash are also counted.
|
||||
*
|
||||
* In the following case the return value is always zero:
|
||||
* If the return value is zero, `errno` is set (since Redis 6.2) as follows:
|
||||
*
|
||||
* * The key was not open for writing.
|
||||
* * The key was associated with a non Hash value.
|
||||
* - EINVAL if any unknown flags are set or if key is NULL.
|
||||
* - ENOTSUP if the key is associated with a non Hash value.
|
||||
* - EBADF if the key was not opened for writing.
|
||||
* - ENOENT if no fields were counted as described under Return value above.
|
||||
* This is not actually an error. The return value can be zero if all fields
|
||||
* were just created and the COUNT_ALL flag was unset, or if changes were held
|
||||
* back due to the NX and XX flags.
|
||||
*
|
||||
* NOTICE: The return value semantics of this function are very different
|
||||
* between Redis 6.2 and older versions. Modules that use it should determine
|
||||
* the Redis version and handle it accordingly.
|
||||
*/
|
||||
int RM_HashSet(RedisModuleKey *key, int flags, ...) {
|
||||
va_list ap;
|
||||
if (!(key->mode & REDISMODULE_WRITE)) return 0;
|
||||
if (key->value && key->value->type != OBJ_HASH) return 0;
|
||||
if (!key || (flags & ~(REDISMODULE_HASH_NX |
|
||||
REDISMODULE_HASH_XX |
|
||||
REDISMODULE_HASH_CFIELDS |
|
||||
REDISMODULE_HASH_COUNT_ALL))) {
|
||||
errno = EINVAL;
|
||||
return 0;
|
||||
} else if (key->value && key->value->type != OBJ_HASH) {
|
||||
errno = ENOTSUP;
|
||||
return 0;
|
||||
} else if (!(key->mode & REDISMODULE_WRITE)) {
|
||||
errno = EBADF;
|
||||
return 0;
|
||||
}
|
||||
if (key->value == NULL) moduleCreateEmptyKey(key,REDISMODULE_KEYTYPE_HASH);
|
||||
|
||||
int updated = 0;
|
||||
int count = 0;
|
||||
va_start(ap, flags);
|
||||
while(1) {
|
||||
RedisModuleString *field, *value;
|
||||
@ -3031,7 +3057,7 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) {
|
||||
|
||||
/* Handle deletion if value is REDISMODULE_HASH_DELETE. */
|
||||
if (value == REDISMODULE_HASH_DELETE) {
|
||||
updated += hashTypeDelete(key->value, field->ptr);
|
||||
count += hashTypeDelete(key->value, field->ptr);
|
||||
if (flags & REDISMODULE_HASH_CFIELDS) decrRefCount(field);
|
||||
continue;
|
||||
}
|
||||
@ -3045,7 +3071,8 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) {
|
||||
|
||||
robj *argv[2] = {field,value};
|
||||
hashTypeTryConversion(key->value,argv,0,1);
|
||||
updated += hashTypeSet(key->value, field->ptr, value->ptr, low_flags);
|
||||
int updated = hashTypeSet(key->value, field->ptr, value->ptr, low_flags);
|
||||
count += (flags & REDISMODULE_HASH_COUNT_ALL) ? 1 : updated;
|
||||
|
||||
/* If CFIELDS is active, SDS string ownership is now of hashTypeSet(),
|
||||
* however we still have to release the 'field' object shell. */
|
||||
@ -3056,7 +3083,8 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) {
|
||||
}
|
||||
va_end(ap);
|
||||
moduleDelKeyIfEmpty(key);
|
||||
return updated;
|
||||
if (count == 0) errno = ENOENT;
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Get fields from an hash value. This function is called using a variable
|
||||
|
@ -68,6 +68,7 @@
|
||||
#define REDISMODULE_HASH_XX (1<<1)
|
||||
#define REDISMODULE_HASH_CFIELDS (1<<2)
|
||||
#define REDISMODULE_HASH_EXISTS (1<<3)
|
||||
#define REDISMODULE_HASH_COUNT_ALL (1<<4)
|
||||
|
||||
/* StreamID type. */
|
||||
typedef struct RedisModuleStreamID {
|
||||
|
@ -35,6 +35,7 @@ TEST_MODULES = \
|
||||
test_lazyfree.so \
|
||||
timer.so \
|
||||
defragtest.so \
|
||||
hash.so \
|
||||
zset.so \
|
||||
stream.so
|
||||
|
||||
|
90
tests/modules/hash.c
Normal file
90
tests/modules/hash.c
Normal file
@ -0,0 +1,90 @@
|
||||
#include "redismodule.h"
|
||||
#include <strings.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* If a string is ":deleted:", the special value for deleted hash fields is
|
||||
* returned; otherwise the input string is returned. */
|
||||
static RedisModuleString *value_or_delete(RedisModuleString *s) {
|
||||
if (!strcasecmp(RedisModule_StringPtrLen(s, NULL), ":delete:"))
|
||||
return REDISMODULE_HASH_DELETE;
|
||||
else
|
||||
return s;
|
||||
}
|
||||
|
||||
/* HASH.SET key flags field1 value1 [field2 value2 ..]
|
||||
*
|
||||
* Sets 1-4 fields. Returns the same as RedisModule_HashSet().
|
||||
* Flags is a string of "nxa" where n = NX, x = XX, a = COUNT_ALL.
|
||||
* To delete a field, use the value ":delete:".
|
||||
*/
|
||||
int hash_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc < 5 || argc % 2 == 0 || argc > 11)
|
||||
return RedisModule_WrongArity(ctx);
|
||||
|
||||
RedisModule_AutoMemory(ctx);
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
|
||||
|
||||
size_t flags_len;
|
||||
const char *flags_str = RedisModule_StringPtrLen(argv[2], &flags_len);
|
||||
int flags = REDISMODULE_HASH_NONE;
|
||||
for (size_t i = 0; i < flags_len; i++) {
|
||||
switch (flags_str[i]) {
|
||||
case 'n': flags |= REDISMODULE_HASH_NX; break;
|
||||
case 'x': flags |= REDISMODULE_HASH_XX; break;
|
||||
case 'a': flags |= REDISMODULE_HASH_COUNT_ALL; break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Test some varargs. (In real-world, use a loop and set one at a time.) */
|
||||
int result;
|
||||
errno = 0;
|
||||
if (argc == 5) {
|
||||
result = RedisModule_HashSet(key, flags,
|
||||
argv[3], value_or_delete(argv[4]),
|
||||
NULL);
|
||||
} else if (argc == 7) {
|
||||
result = RedisModule_HashSet(key, flags,
|
||||
argv[3], value_or_delete(argv[4]),
|
||||
argv[5], value_or_delete(argv[6]),
|
||||
NULL);
|
||||
} else if (argc == 9) {
|
||||
result = RedisModule_HashSet(key, flags,
|
||||
argv[3], value_or_delete(argv[4]),
|
||||
argv[5], value_or_delete(argv[6]),
|
||||
argv[7], value_or_delete(argv[8]),
|
||||
NULL);
|
||||
} else if (argc == 11) {
|
||||
result = RedisModule_HashSet(key, flags,
|
||||
argv[3], value_or_delete(argv[4]),
|
||||
argv[5], value_or_delete(argv[6]),
|
||||
argv[7], value_or_delete(argv[8]),
|
||||
argv[9], value_or_delete(argv[10]),
|
||||
NULL);
|
||||
} else {
|
||||
return RedisModule_ReplyWithError(ctx, "ERR too many fields");
|
||||
}
|
||||
|
||||
/* Check errno */
|
||||
if (result == 0) {
|
||||
if (errno == ENOTSUP)
|
||||
return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
|
||||
else
|
||||
RedisModule_Assert(errno == ENOENT);
|
||||
}
|
||||
|
||||
return RedisModule_ReplyWithLongLong(ctx, result);
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
if (RedisModule_Init(ctx, "hash", 1, REDISMODULE_APIVER_1) ==
|
||||
REDISMODULE_OK &&
|
||||
RedisModule_CreateCommand(ctx, "hash.set", hash_set, "",
|
||||
1, 1, 1) == REDISMODULE_OK) {
|
||||
return REDISMODULE_OK;
|
||||
} else {
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
}
|
23
tests/unit/moduleapi/hash.tcl
Normal file
23
tests/unit/moduleapi/hash.tcl
Normal file
@ -0,0 +1,23 @@
|
||||
set testmodule [file normalize tests/modules/hash.so]
|
||||
|
||||
start_server {tags {"modules"}} {
|
||||
r module load $testmodule
|
||||
|
||||
test {Module hash set} {
|
||||
r set k mystring
|
||||
assert_error "WRONGTYPE*" {r hash.set k "" hello world}
|
||||
r del k
|
||||
# "" = count updates and deletes of existing fields only
|
||||
assert_equal 0 [r hash.set k "" squirrel yes]
|
||||
# "a" = COUNT_ALL = count inserted, modified and deleted fields
|
||||
assert_equal 2 [r hash.set k "a" banana no sushi whynot]
|
||||
# "n" = NX = only add fields not already existing in the hash
|
||||
# "x" = XX = only replace the value for existing fields
|
||||
assert_equal 0 [r hash.set k "n" squirrel hoho what nothing]
|
||||
assert_equal 1 [r hash.set k "na" squirrel hoho something nice]
|
||||
assert_equal 0 [r hash.set k "xa" new stuff not inserted]
|
||||
assert_equal 1 [r hash.set k "x" squirrel ofcourse]
|
||||
assert_equal 1 [r hash.set k "" sushi :delete: none :delete:]
|
||||
r hgetall k
|
||||
} {squirrel ofcourse banana no what nothing something nice}
|
||||
}
|
Loading…
Reference in New Issue
Block a user