valkey/tests/modules/hash.c
Viktor Söderqvist acf3495eb8
Sort out the mess around writable replicas and lookupKeyRead/Write (#9572)
Writable replicas now no longer use the values of expired keys. Expired keys are
deleted when lookupKeyWrite() is used, even on a writable replica. Previously,
writable replicas could use the value of an expired key in write commands such
as INCR, SUNIONSTORE, etc..

This commit also sorts out the mess around the functions lookupKeyRead() and
lookupKeyWrite() so they now indicate what we intend to do with the key and
are not affected by the command calling them.

Multi-key commands like SUNIONSTORE, ZUNIONSTORE, COPY and SORT with the
store option now use lookupKeyRead() for the keys they're reading from (which will
not allow reading from logically expired keys).

This commit also fixes a bug where PFCOUNT could return a value of an
expired key.

Test modules commands have their readonly and write flags updated to correctly
reflect their lookups for reading or writing. Modules are not required to
correctly reflect this in their command flags, but this change is made for
consistency since the tests serve as usage examples.

Fixes #6842. Fixes #7475.
2021-11-28 11:26:28 +02:00

91 lines
3.4 KiB
C

#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, "write",
1, 1, 1) == REDISMODULE_OK) {
return REDISMODULE_OK;
} else {
return REDISMODULE_ERR;
}
}