diff --git a/src/module.c b/src/module.c index 9e7b7369..5c8af588 100644 --- a/src/module.c +++ b/src/module.c @@ -167,6 +167,9 @@ int moduleCreateEmtpyKey(RedisModuleKey *key, int type) { case REDISMODULE_KEYTYPE_ZSET: obj = createZsetZiplistObject(); break; + case REDISMODULE_KEYTYPE_HASH: + obj = createHashObject(); + break; default: return REDISMODULE_ERR; } dbAdd(key->db,key->key,obj); @@ -999,8 +1002,8 @@ int RM_StringTruncate(RedisModuleKey *key, size_t newlen) { * type) REDISMODULE_ERR is returned, otherwise REDISMODULE_OK is returned. */ int RM_ListPush(RedisModuleKey *key, int where, RedisModuleString *ele) { if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR; + if (key->value && key->value->type != OBJ_LIST) return REDISMODULE_ERR; if (key->value == NULL) moduleCreateEmtpyKey(key,REDISMODULE_KEYTYPE_LIST); - if (key->value->type != OBJ_LIST) return REDISMODULE_ERR; listTypePush(key->value, ele, (where == REDISMODULE_LIST_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL); return REDISMODULE_OK; @@ -1079,7 +1082,7 @@ int RM_ZsetAddFlagsFromCoreFlags(int flags) { int RM_ZsetAdd(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr) { int flags = 0; if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR; - if (key->value->type != OBJ_ZSET) return REDISMODULE_ERR; + if (key->value && key->value->type != OBJ_ZSET) return REDISMODULE_ERR; if (key->value == NULL) moduleCreateEmtpyKey(key,REDISMODULE_KEYTYPE_ZSET); if (flagsptr) flags = RM_ZsetAddFlagsToCoreFlags(*flagsptr); if (zsetAdd(key->value,score,ele->ptr,&flags,NULL) == 0) { @@ -1106,7 +1109,7 @@ int RM_ZsetAdd(RedisModuleKey *key, double score, RedisModuleString *ele, int *f int RM_ZsetIncrby(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore) { int flags = 0; if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR; - if (key->value->type != OBJ_ZSET) return REDISMODULE_ERR; + if (key->value && key->value->type != OBJ_ZSET) return REDISMODULE_ERR; if (key->value == NULL) moduleCreateEmtpyKey(key,REDISMODULE_KEYTYPE_ZSET); if (flagsptr) flags = RM_ZsetAddFlagsToCoreFlags(*flagsptr); if (zsetAdd(key->value,score,ele->ptr,&flags,newscore) == 0) { @@ -1142,7 +1145,7 @@ int RM_ZsetIncrby(RedisModuleKey *key, double score, RedisModuleString *ele, int * Empty keys will be handled correctly by doing nothing. */ int RM_ZsetRem(RedisModuleKey *key, RedisModuleString *ele, int *deleted) { if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR; - if (key->value->type != OBJ_ZSET) return REDISMODULE_ERR; + if (key->value && key->value->type != OBJ_ZSET) return REDISMODULE_ERR; if (key->value != NULL && zsetDel(key->value,ele->ptr)) { if (deleted) *deleted = 1; } else { @@ -1160,8 +1163,8 @@ int RM_ZsetRem(RedisModuleKey *key, RedisModuleString *ele, int *deleted) { * - The key is an open empty key. */ int RM_ZsetScore(RedisModuleKey *key, RedisModuleString *ele, double *score) { - if (key->value->type != OBJ_ZSET) return REDISMODULE_ERR; if (key->value == NULL) return REDISMODULE_ERR; + if (key->value->type != OBJ_ZSET) return REDISMODULE_ERR; if (zsetScore(key->value,ele->ptr,score) == C_ERR) return REDISMODULE_ERR; return REDISMODULE_OK; } @@ -1422,7 +1425,7 @@ int RM_ZsetRangePrev(RedisModuleKey *key) { /* Fetch the previous element score for the * range check. */ unsigned char *saved_prev = prev; - prev = ziplistNext(zl,prev); /* Skip element to get the score. */ + prev = ziplistNext(zl,prev); /* Skip element to get the score.*/ double score = zzlGetScore(prev); /* Obtain the prev score. */ if (!zslValueGteMin(score,&key->zrs)) { key->zer = 1; @@ -1464,6 +1467,155 @@ int RM_ZsetRangePrev(RedisModuleKey *key) { } } +/* -------------------------------------------------------------------------- + * Key API for Hash type + * -------------------------------------------------------------------------- */ + +/* Set the field of the specified hash field to the specified value. + * If the key is an empty key open for writing, it is created with an empty + * hash value, in order to set the specified field. + * + * The function is variadic and the user must specify pairs of field + * names and values, both as RedisModuleString pointers (unless the + * CFIELD option is set, see later). + * + * Example to set the hash argv[1] to the value argv[2]: + * + * RedisModule_HashSet(key,REDISMODULE_HSET_NONE,argv[1],argv[2],NULL); + * + * The function can also be used in order to delete fields (if they exist) + * by setting them to the specified value of REDISMODULE_HSET_DELETE: + * + * RedisModule_HashSet(key,REDISMODULE_HSET_NONE,argv[1], + * REDISMODULE_HSET_DELETE,NULL); + * + * The behavior of the command changes with the specified flags, that can be + * set to REDISMODULE_HSET_NONE if no special behavior is needed. + * + * REDISMODULE_HSET_NX: The operation is performed only if the field was not + * already existing in the hash. + * REDISMODULE_HSET_XX: The operation is performed only if the field was + * already existing, so that a new value could be + * associated to an existing filed, but no new fields + * are created. + * REDISMODULE_HSET_CFIELDS: The field names passed are null terminated C + * strings instead of RedisModuleString objects. + * + * Unless NX is specified, the command overwrites the old field value with + * the new one. + * + * When using REDISMODULE_HSET_CFIELDS, field names are reported using + * normal C strings, so for example to delete the field "foo" the following + * code can be used: + * + * RedisModule_HashSet(key,REDISMODULE_HSET_CFIELDS,"foo", + * REDISMODULE_HSET_DELETE,NULL); + * + * Return value: + * + * The number of fields updated (that may be less than the number of fields + * specified because of the XX or NX options). + * + * In the following case the return value is always zero: + * + * - The key was not open for writing. + * - The key was associated with a non Hash value. + */ +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->value == NULL) moduleCreateEmtpyKey(key,REDISMODULE_KEYTYPE_HASH); + + int updated = 0; + va_start(ap, flags); + while(1) { + RedisModuleString *field, *value; + /* Get the field and value objects. */ + if (flags & REDISMODULE_HSET_CFIELDS) { + char *cfield = va_arg(ap,char*); + if (cfield == NULL) break; + field = createRawStringObject(cfield,strlen(cfield)); + } else { + field = va_arg(ap,RedisModuleString*); + if (field == NULL) break; + } + value = va_arg(ap,RedisModuleString*); + + /* Handle XX and NX */ + if (flags & (REDISMODULE_HSET_XX|REDISMODULE_HSET_NX)) { + int exists = hashTypeExists(key->value, field->ptr); + if (((flags & REDISMODULE_HSET_XX) && !exists) || + ((flags & REDISMODULE_HSET_NX) && exists)) + { + if (flags & REDISMODULE_HSET_CFIELDS) decrRefCount(field); + continue; + } + } + + /* Handle deletion if value is REDISMODULE_HSET_DELETE. */ + if (value == REDISMODULE_HSET_DELETE) { + updated += hashTypeDelete(key->value, field->ptr); + if (flags & REDISMODULE_HSET_CFIELDS) decrRefCount(field); + continue; + } + + /* If CFIELDS is active, we can pass the ownership of the + * SDS object to the low level function that sets the field + * to avoid a useless copy. */ + int low_flags = HASH_SET_COPY; + if (flags & REDISMODULE_HSET_CFIELDS) + low_flags |= HASH_SET_TAKE_FIELD; + updated += hashTypeSet(key->value, field->ptr, value->ptr, low_flags); + field->ptr = NULL; /* Ownership is now of hashTypeSet() */ + + /* Cleanup */ + if (flags & REDISMODULE_HSET_CFIELDS) decrRefCount(field); + } + va_end(ap); + moduleDelKeyIfEmpty(key); + return updated; +} + +/* Get fields from an hash value. This function is called using a variable + * number of arguments, alternating a field name (as a StringRedisModule + * pointer) with a pointer to a StringRedisModule pointer, that is set to the + * value of the field if the field exist, or NULL if the field did not exist. + * At the end of the field/value-ptr pairs, NULL must be specified as last + * argument to signal the end of the arguments in the variadic function. + * + * This is an example usage: + * + * RedisModuleString *first, *second; + * RedisModule_HashGet(mykey,REDISMODULE_HGET_NONE,argv[1],&first, + * argv[2],&second,NULL); + * + * As with RedisModule_HashSet() the behavior of the command can be specified + * passing flags different than REDISMODULE_HGET_NONE: + * + * REDISMODULE_HGET_CFIELD: field names as null terminated C strings. + * + * REDISMODULE_HGET_EXISTS: instead of setting the value of the field + * expecting a RedisModuleString pointer to pointer, the function just + * reports if the field esists or not and expects an integer pointer + * as the second element of each pair. + * + * Example of REDISMODULE_HGET_CFIELD: + * + * RedisModuleString *username, *hashedpass; + * RedisModule_HashGet(mykey,"username",&username,"hp",&hashedpass); + * + * Example of REDISMODULE_HGET_EXISTS: + * + * int exists; + * RedisModule_HashGet(mykey,argv[1],&exists,NULL); + * + * The function returns REDISMODULE_OK on success and REDISMODULE_ERR if + * the key is not an hash value. + */ +int RM_HashGet(RedisModuleKey *key, int flags, ...) { +} + /* -------------------------------------------------------------------------- * Redis <-> Modules generic Call() API * -------------------------------------------------------------------------- */ @@ -1925,6 +2077,7 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(ZsetRangeNext); REGISTER_API(ZsetRangePrev); REGISTER_API(ZsetRangeEndReached); + REGISTER_API(HashSet); } /* Global initialization at Redis startup. */ diff --git a/src/modules/helloworld.c b/src/modules/helloworld.c index 31a078b0..e635d0df 100644 --- a/src/modules/helloworld.c +++ b/src/modules/helloworld.c @@ -418,6 +418,33 @@ int HelloLexRange_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, in return REDISMODULE_OK; } +/* HELLO.HCOPY key srcfield dstfield + * This is just an example command that sets the hash field dstfield to the + * same value of srcfield. If srcfield does not exist no operation is + * performed. + * + * The command returns 1 if the copy is performed (srcfield exists) otherwise + * 0 is returned. */ +int HelloHCopy_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + RedisModule_AutoMemory(ctx); /* Use automatic memory management. */ + + if (argc != 4) return RedisModule_WrongArity(ctx); + RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1], + REDISMODULE_READ|REDISMODULE_WRITE); + int type = RedisModule_KeyType(key); + if (type != REDISMODULE_KEYTYPE_HASH && + type != REDISMODULE_KEYTYPE_EMPTY) + { + return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE); + } + + /* XXX modify me. */ + RedisModule_HashSet(key,REDISMODULE_HSET_NONE,argv[2],argv[3],NULL); + RedisModule_HashSet(key,REDISMODULE_HSET_CFIELDS,"foo",argv[3],NULL); + RedisModule_ReplyWithLongLong(ctx,0); + return REDISMODULE_OK; +} + /* This function must be present on each Redis module. It is used in order to * register the commands into the Redis server. */ int RedisModule_OnLoad(RedisModuleCtx *ctx) { @@ -480,5 +507,9 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx) { HelloLexRange_RedisCommand) == REDISMODULE_ERR) return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"hello.hcopy", + HelloHCopy_RedisCommand) == REDISMODULE_ERR) + return REDISMODULE_ERR; + return REDISMODULE_OK; } diff --git a/src/redismodule.h b/src/redismodule.h index 5e483a64..f911298e 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -50,6 +50,21 @@ #define REDISMODULE_ZADD_UPDATED (1<<3) #define REDISMODULE_ZADD_NOP (1<<4) +/* Hash API flags. */ +#define REDISMODULE_HSET_NONE 0 +#define REDISMODULE_HGET_NONE 0 +#define REDISMODULE_HSET_NX (1<<0) +#define REDISMODULE_HSET_XX (1<<1) +#define REDISMODULE_HSET_CFIELDS (1<<2) +/* Set GET_CFIELD to the same value as SET_CFIELD so that misuses will not + * result into surprising behaviors. */ +#define REDISMODULE_HGET_CFIELDS REDISMODULE_HSET_CFIELDS +#define REDISMODULE_HGET_EXISTS (1<<3) + +/* A special pointer that we can use between the core and the module to signal + * field deletion, and that is impossible to be a valid pointer. */ +#define REDISMODULE_HSET_DELETE ((RedisModuleString*)(long)1) + /* Error messages. */ #define REDISMODULE_ERRORMSG_WRONGTYPE "WRONGTYPE Operation against a key holding the wrong kind of value" @@ -134,6 +149,7 @@ RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ZsetRangeCurrentElement)(Red int REDISMODULE_API_FUNC(RedisModule_ZsetRangeNext)(RedisModuleKey *key); int REDISMODULE_API_FUNC(RedisModule_ZsetRangePrev)(RedisModuleKey *key); int REDISMODULE_API_FUNC(RedisModule_ZsetRangeEndReached)(RedisModuleKey *key); +int REDISMODULE_API_FUNC(RedisModule_HashSet)(RedisModuleKey *key, int flags, ...); /* This is included inline inside each Redis module. */ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) { @@ -198,6 +214,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(ZsetRangeNext); REDISMODULE_GET_API(ZsetRangePrev); REDISMODULE_GET_API(ZsetRangeEndReached); + REDISMODULE_GET_API(HashSet); RedisModule_SetModuleAttribs(ctx,name,ver,apiver); return REDISMODULE_OK; diff --git a/src/server.h b/src/server.h index 1e0a027e..9277196a 100644 --- a/src/server.h +++ b/src/server.h @@ -1414,6 +1414,10 @@ unsigned long setTypeSize(robj *subject); void setTypeConvert(robj *subject, int enc); /* Hash data type */ +#define HASH_SET_TAKE_FIELD (1<<0) +#define HASH_SET_TAKE_VALUE (1<<1) +#define HASH_SET_COPY 0 + void hashTypeConvert(robj *o, int enc); void hashTypeTryConversion(robj *subject, robj **argv, int start, int end); void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2); @@ -1432,6 +1436,7 @@ void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr, sds hashTypeCurrentObjectNewSds(hashTypeIterator *hi, int what); robj *hashTypeLookupWriteOrCreate(client *c, robj *key); robj *hashTypeGetValueObject(robj *o, sds field); +int hashTypeSet(robj *o, sds field, sds value, int flags); /* Pub / Sub */ int pubsubUnsubscribeAllChannels(client *c, int notify);