diff options
Diffstat (limited to 'src/t_hash.c')
| -rw-r--r-- | src/t_hash.c | 829 |
1 files changed, 829 insertions, 0 deletions
diff --git a/src/t_hash.c b/src/t_hash.c new file mode 100644 index 0000000..a495593 --- /dev/null +++ b/src/t_hash.c @@ -0,0 +1,829 @@ +/* + * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "server.h" +#include <math.h> + +/*----------------------------------------------------------------------------- + * Hash type API + *----------------------------------------------------------------------------*/ + +/* Check the length of a number of objects to see if we need to convert a + * ziplist to a real hash. Note that we only check string encoded objects + * as their string length can be queried in constant time. */ +void hashTypeTryConversion(robj *o, robj **argv, int start, int end) { + int i; + + if (o->encoding != OBJ_ENCODING_ZIPLIST) return; + + for (i = start; i <= end; i++) { + if (sdsEncodedObject(argv[i]) && + sdslen(argv[i]->ptr) > server.hash_max_ziplist_value) + { + hashTypeConvert(o, OBJ_ENCODING_HT); + break; + } + } +} + +/* Get the value from a ziplist encoded hash, identified by field. + * Returns -1 when the field cannot be found. */ +int hashTypeGetFromZiplist(robj *o, sds field, + unsigned char **vstr, + unsigned int *vlen, + long long *vll) +{ + unsigned char *zl, *fptr = NULL, *vptr = NULL; + int ret; + + serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST); + + zl = o->ptr; + fptr = ziplistIndex(zl, ZIPLIST_HEAD); + if (fptr != NULL) { + fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1); + if (fptr != NULL) { + /* Grab pointer to the value (fptr points to the field) */ + vptr = ziplistNext(zl, fptr); + serverAssert(vptr != NULL); + } + } + + if (vptr != NULL) { + ret = ziplistGet(vptr, vstr, vlen, vll); + serverAssert(ret); + return 0; + } + + return -1; +} + +/* Get the value from a hash table encoded hash, identified by field. + * Returns NULL when the field cannot be found, otherwise the SDS value + * is returned. */ +sds hashTypeGetFromHashTable(robj *o, sds field) { + dictEntry *de; + + serverAssert(o->encoding == OBJ_ENCODING_HT); + + de = dictFind(o->ptr, field); + if (de == NULL) return NULL; + return dictGetVal(de); +} + +/* Higher level function of hashTypeGet*() that returns the hash value + * associated with the specified field. If the field is found C_OK + * is returned, otherwise C_ERR. The returned object is returned by + * reference in either *vstr and *vlen if it's returned in string form, + * or stored in *vll if it's returned as a number. + * + * If *vll is populated *vstr is set to NULL, so the caller + * can always check the function return by checking the return value + * for C_OK and checking if vll (or vstr) is NULL. */ +int hashTypeGetValue(robj *o, sds field, unsigned char **vstr, unsigned int *vlen, long long *vll) { + if (o->encoding == OBJ_ENCODING_ZIPLIST) { + *vstr = NULL; + if (hashTypeGetFromZiplist(o, field, vstr, vlen, vll) == 0) + return C_OK; + } else if (o->encoding == OBJ_ENCODING_HT) { + sds value; + if ((value = hashTypeGetFromHashTable(o, field)) != NULL) { + *vstr = (unsigned char*) value; + *vlen = sdslen(value); + return C_OK; + } + } else { + serverPanic("Unknown hash encoding"); + } + return C_ERR; +} + +/* Like hashTypeGetValue() but returns a Redis object, which is useful for + * interaction with the hash type outside t_hash.c. + * The function returns NULL if the field is not found in the hash. Otherwise + * a newly allocated string object with the value is returned. */ +robj *hashTypeGetValueObject(robj *o, sds field) { + unsigned char *vstr; + unsigned int vlen; + long long vll; + + if (hashTypeGetValue(o,field,&vstr,&vlen,&vll) == C_ERR) return NULL; + if (vstr) return createStringObject((char*)vstr,vlen); + else return createStringObjectFromLongLong(vll); +} + +/* Higher level function using hashTypeGet*() to return the length of the + * object associated with the requested field, or 0 if the field does not + * exist. */ +size_t hashTypeGetValueLength(robj *o, sds field) { + size_t len = 0; + if (o->encoding == OBJ_ENCODING_ZIPLIST) { + unsigned char *vstr = NULL; + unsigned int vlen = UINT_MAX; + long long vll = LLONG_MAX; + + if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) + len = vstr ? vlen : sdigits10(vll); + } else if (o->encoding == OBJ_ENCODING_HT) { + sds aux; + + if ((aux = hashTypeGetFromHashTable(o, field)) != NULL) + len = sdslen(aux); + } else { + serverPanic("Unknown hash encoding"); + } + return len; +} + +/* Test if the specified field exists in the given hash. Returns 1 if the field + * exists, and 0 when it doesn't. */ +int hashTypeExists(robj *o, sds field) { + if (o->encoding == OBJ_ENCODING_ZIPLIST) { + unsigned char *vstr = NULL; + unsigned int vlen = UINT_MAX; + long long vll = LLONG_MAX; + + if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) return 1; + } else if (o->encoding == OBJ_ENCODING_HT) { + if (hashTypeGetFromHashTable(o, field) != NULL) return 1; + } else { + serverPanic("Unknown hash encoding"); + } + return 0; +} + +/* Add a new field, overwrite the old with the new value if it already exists. + * Return 0 on insert and 1 on update. + * + * By default, the key and value SDS strings are copied if needed, so the + * caller retains ownership of the strings passed. However this behavior + * can be effected by passing appropriate flags (possibly bitwise OR-ed): + * + * HASH_SET_TAKE_FIELD -- The SDS field ownership passes to the function. + * HASH_SET_TAKE_VALUE -- The SDS value ownership passes to the function. + * + * When the flags are used the caller does not need to release the passed + * SDS string(s). It's up to the function to use the string to create a new + * entry or to free the SDS string before returning to the caller. + * + * HASH_SET_COPY corresponds to no flags passed, and means the default + * semantics of copying the values if needed. + * + */ +#define HASH_SET_TAKE_FIELD (1<<0) +#define HASH_SET_TAKE_VALUE (1<<1) +#define HASH_SET_COPY 0 +int hashTypeSet(robj *o, sds field, sds value, int flags) { + int update = 0; + + if (o->encoding == OBJ_ENCODING_ZIPLIST) { + unsigned char *zl, *fptr, *vptr; + + zl = o->ptr; + fptr = ziplistIndex(zl, ZIPLIST_HEAD); + if (fptr != NULL) { + fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1); + if (fptr != NULL) { + /* Grab pointer to the value (fptr points to the field) */ + vptr = ziplistNext(zl, fptr); + serverAssert(vptr != NULL); + update = 1; + + /* Delete value */ + zl = ziplistDelete(zl, &vptr); + + /* Insert new value */ + zl = ziplistInsert(zl, vptr, (unsigned char*)value, + sdslen(value)); + } + } + + if (!update) { + /* Push new field/value pair onto the tail of the ziplist */ + zl = ziplistPush(zl, (unsigned char*)field, sdslen(field), + ZIPLIST_TAIL); + zl = ziplistPush(zl, (unsigned char*)value, sdslen(value), + ZIPLIST_TAIL); + } + o->ptr = zl; + + /* Check if the ziplist needs to be converted to a hash table */ + if (hashTypeLength(o) > server.hash_max_ziplist_entries) + hashTypeConvert(o, OBJ_ENCODING_HT); + } else if (o->encoding == OBJ_ENCODING_HT) { + dictEntry *de = dictFind(o->ptr,field); + if (de) { + sdsfree(dictGetVal(de)); + if (flags & HASH_SET_TAKE_VALUE) { + dictGetVal(de) = value; + value = NULL; + } else { + dictGetVal(de) = sdsdup(value); + } + update = 1; + } else { + sds f,v; + if (flags & HASH_SET_TAKE_FIELD) { + f = field; + field = NULL; + } else { + f = sdsdup(field); + } + if (flags & HASH_SET_TAKE_VALUE) { + v = value; + value = NULL; + } else { + v = sdsdup(value); + } + dictAdd(o->ptr,f,v); + } + } else { + serverPanic("Unknown hash encoding"); + } + + /* Free SDS strings we did not referenced elsewhere if the flags + * want this function to be responsible. */ + if (flags & HASH_SET_TAKE_FIELD && field) sdsfree(field); + if (flags & HASH_SET_TAKE_VALUE && value) sdsfree(value); + return update; +} + +/* Delete an element from a hash. + * Return 1 on deleted and 0 on not found. */ +int hashTypeDelete(robj *o, sds field) { + int deleted = 0; + + if (o->encoding == OBJ_ENCODING_ZIPLIST) { + unsigned char *zl, *fptr; + + zl = o->ptr; + fptr = ziplistIndex(zl, ZIPLIST_HEAD); + if (fptr != NULL) { + fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1); + if (fptr != NULL) { + zl = ziplistDelete(zl,&fptr); + zl = ziplistDelete(zl,&fptr); + o->ptr = zl; + deleted = 1; + } + } + } else if (o->encoding == OBJ_ENCODING_HT) { + if (dictDelete((dict*)o->ptr, field) == C_OK) { + deleted = 1; + + /* Always check if the dictionary needs a resize after a delete. */ + if (htNeedsResize(o->ptr)) dictResize(o->ptr); + } + + } else { + serverPanic("Unknown hash encoding"); + } + return deleted; +} + +/* Return the number of elements in a hash. */ +unsigned long hashTypeLength(const robj *o) { + unsigned long length = ULONG_MAX; + + if (o->encoding == OBJ_ENCODING_ZIPLIST) { + length = ziplistLen(o->ptr) / 2; + } else if (o->encoding == OBJ_ENCODING_HT) { + length = dictSize((const dict*)o->ptr); + } else { + serverPanic("Unknown hash encoding"); + } + return length; +} + +hashTypeIterator *hashTypeInitIterator(robj *subject) { + hashTypeIterator *hi = zmalloc(sizeof(hashTypeIterator)); + hi->subject = subject; + hi->encoding = subject->encoding; + + if (hi->encoding == OBJ_ENCODING_ZIPLIST) { + hi->fptr = NULL; + hi->vptr = NULL; + } else if (hi->encoding == OBJ_ENCODING_HT) { + hi->di = dictGetIterator(subject->ptr); + } else { + serverPanic("Unknown hash encoding"); + } + return hi; +} + +void hashTypeReleaseIterator(hashTypeIterator *hi) { + if (hi->encoding == OBJ_ENCODING_HT) + dictReleaseIterator(hi->di); + zfree(hi); +} + +/* Move to the next entry in the hash. Return C_OK when the next entry + * could be found and C_ERR when the iterator reaches the end. */ +int hashTypeNext(hashTypeIterator *hi) { + if (hi->encoding == OBJ_ENCODING_ZIPLIST) { + unsigned char *zl; + unsigned char *fptr, *vptr; + + zl = hi->subject->ptr; + fptr = hi->fptr; + vptr = hi->vptr; + + if (fptr == NULL) { + /* Initialize cursor */ + serverAssert(vptr == NULL); + fptr = ziplistIndex(zl, 0); + } else { + /* Advance cursor */ + serverAssert(vptr != NULL); + fptr = ziplistNext(zl, vptr); + } + if (fptr == NULL) return C_ERR; + + /* Grab pointer to the value (fptr points to the field) */ + vptr = ziplistNext(zl, fptr); + serverAssert(vptr != NULL); + + /* fptr, vptr now point to the first or next pair */ + hi->fptr = fptr; + hi->vptr = vptr; + } else if (hi->encoding == OBJ_ENCODING_HT) { + if ((hi->de = dictNext(hi->di)) == NULL) return C_ERR; + } else { + serverPanic("Unknown hash encoding"); + } + return C_OK; +} + +/* Get the field or value at iterator cursor, for an iterator on a hash value + * encoded as a ziplist. Prototype is similar to `hashTypeGetFromZiplist`. */ +void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what, + unsigned char **vstr, + unsigned int *vlen, + long long *vll) +{ + int ret; + + serverAssert(hi->encoding == OBJ_ENCODING_ZIPLIST); + + if (what & OBJ_HASH_KEY) { + ret = ziplistGet(hi->fptr, vstr, vlen, vll); + serverAssert(ret); + } else { + ret = ziplistGet(hi->vptr, vstr, vlen, vll); + serverAssert(ret); + } +} + +/* Get the field or value at iterator cursor, for an iterator on a hash value + * encoded as a hash table. Prototype is similar to + * `hashTypeGetFromHashTable`. */ +sds hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what) { + serverAssert(hi->encoding == OBJ_ENCODING_HT); + + if (what & OBJ_HASH_KEY) { + return dictGetKey(hi->de); + } else { + return dictGetVal(hi->de); + } +} + +/* Higher level function of hashTypeCurrent*() that returns the hash value + * at current iterator position. + * + * The returned element is returned by reference in either *vstr and *vlen if + * it's returned in string form, or stored in *vll if it's returned as + * a number. + * + * If *vll is populated *vstr is set to NULL, so the caller + * can always check the function return by checking the return value + * type checking if vstr == NULL. */ +void hashTypeCurrentObject(hashTypeIterator *hi, int what, unsigned char **vstr, unsigned int *vlen, long long *vll) { + if (hi->encoding == OBJ_ENCODING_ZIPLIST) { + *vstr = NULL; + hashTypeCurrentFromZiplist(hi, what, vstr, vlen, vll); + } else if (hi->encoding == OBJ_ENCODING_HT) { + sds ele = hashTypeCurrentFromHashTable(hi, what); + *vstr = (unsigned char*) ele; + *vlen = sdslen(ele); + } else { + serverPanic("Unknown hash encoding"); + } +} + +/* Return the key or value at the current iterator position as a new + * SDS string. */ +sds hashTypeCurrentObjectNewSds(hashTypeIterator *hi, int what) { + unsigned char *vstr; + unsigned int vlen; + long long vll; + + hashTypeCurrentObject(hi,what,&vstr,&vlen,&vll); + if (vstr) return sdsnewlen(vstr,vlen); + return sdsfromlonglong(vll); +} + +robj *hashTypeLookupWriteOrCreate(client *c, robj *key) { + robj *o = lookupKeyWrite(c->db,key); + if (o == NULL) { + o = createHashObject(); + dbAdd(c->db,key,o); + } else { + if (o->type != OBJ_HASH) { + addReply(c,shared.wrongtypeerr); + return NULL; + } + } + return o; +} + +void hashTypeConvertZiplist(robj *o, int enc) { + serverAssert(o->encoding == OBJ_ENCODING_ZIPLIST); + + if (enc == OBJ_ENCODING_ZIPLIST) { + /* Nothing to do... */ + + } else if (enc == OBJ_ENCODING_HT) { + hashTypeIterator *hi; + dict *dict; + int ret; + + hi = hashTypeInitIterator(o); + dict = dictCreate(&hashDictType, NULL); + + while (hashTypeNext(hi) != C_ERR) { + sds key, value; + + key = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY); + value = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE); + ret = dictAdd(dict, key, value); + if (ret != DICT_OK) { + serverLogHexDump(LL_WARNING,"ziplist with dup elements dump", + o->ptr,ziplistBlobLen(o->ptr)); + serverPanic("Ziplist corruption detected"); + } + } + hashTypeReleaseIterator(hi); + zfree(o->ptr); + o->encoding = OBJ_ENCODING_HT; + o->ptr = dict; + } else { + serverPanic("Unknown hash encoding"); + } +} + +void hashTypeConvert(robj *o, int enc) { + if (o->encoding == OBJ_ENCODING_ZIPLIST) { + hashTypeConvertZiplist(o, enc); + } else if (o->encoding == OBJ_ENCODING_HT) { + serverPanic("Not implemented"); + } else { + serverPanic("Unknown hash encoding"); + } +} + +/*----------------------------------------------------------------------------- + * Hash type commands + *----------------------------------------------------------------------------*/ + +void hsetCommand(client *c) { + int update; + robj *o; + + if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + hashTypeTryConversion(o,c->argv,2,3); + update = hashTypeSet(o,c->argv[2]->ptr,c->argv[3]->ptr,HASH_SET_COPY); + addReply(c, update ? shared.czero : shared.cone); + signalModifiedKey(c->db,c->argv[1]); + notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id); + server.dirty++; +} + +void hsetnxCommand(client *c) { + robj *o; + if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + hashTypeTryConversion(o,c->argv,2,3); + + if (hashTypeExists(o, c->argv[2]->ptr)) { + addReply(c, shared.czero); + } else { + hashTypeSet(o,c->argv[2]->ptr,c->argv[3]->ptr,HASH_SET_COPY); + addReply(c, shared.cone); + signalModifiedKey(c->db,c->argv[1]); + notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id); + server.dirty++; + } +} + +void hmsetCommand(client *c) { + int i; + robj *o; + + if ((c->argc % 2) == 1) { + addReplyError(c,"wrong number of arguments for HMSET"); + return; + } + + if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + hashTypeTryConversion(o,c->argv,2,c->argc-1); + for (i = 2; i < c->argc; i += 2) { + hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY); + } + addReply(c, shared.ok); + signalModifiedKey(c->db,c->argv[1]); + notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id); + server.dirty++; +} + +void hincrbyCommand(client *c) { + long long value, incr, oldvalue; + robj *o; + sds new; + unsigned char *vstr; + unsigned int vlen; + + if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return; + if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + if (hashTypeGetValue(o,c->argv[2]->ptr,&vstr,&vlen,&value) == C_OK) { + if (vstr) { + if (string2ll((char*)vstr,vlen,&value) == 0) { + addReplyError(c,"hash value is not an integer"); + return; + } + } /* Else hashTypeGetValue() already stored it into &value */ + } else { + value = 0; + } + + oldvalue = value; + if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) || + (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) { + addReplyError(c,"increment or decrement would overflow"); + return; + } + value += incr; + new = sdsfromlonglong(value); + hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE); + addReplyLongLong(c,value); + signalModifiedKey(c->db,c->argv[1]); + notifyKeyspaceEvent(NOTIFY_HASH,"hincrby",c->argv[1],c->db->id); + server.dirty++; +} + +void hincrbyfloatCommand(client *c) { + long double value, incr; + long long ll; + robj *o; + sds new; + unsigned char *vstr; + unsigned int vlen; + + if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != C_OK) return; + if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + if (hashTypeGetValue(o,c->argv[2]->ptr,&vstr,&vlen,&ll) == C_OK) { + if (vstr) { + if (string2ld((char*)vstr,vlen,&value) == 0) { + addReplyError(c,"hash value is not a float"); + return; + } + } else { + value = (long double)ll; + } + } else { + value = 0; + } + + value += incr; + + char buf[256]; + int len = ld2string(buf,sizeof(buf),value,1); + new = sdsnewlen(buf,len); + hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE); + addReplyBulkCBuffer(c,buf,len); + signalModifiedKey(c->db,c->argv[1]); + notifyKeyspaceEvent(NOTIFY_HASH,"hincrbyfloat",c->argv[1],c->db->id); + server.dirty++; + + /* Always replicate HINCRBYFLOAT as an HSET command with the final value + * in order to make sure that differences in float pricision or formatting + * will not create differences in replicas or after an AOF restart. */ + robj *aux, *newobj; + aux = createStringObject("HSET",4); + newobj = createRawStringObject(buf,len); + rewriteClientCommandArgument(c,0,aux); + decrRefCount(aux); + rewriteClientCommandArgument(c,3,newobj); + decrRefCount(newobj); +} + +static void addHashFieldToReply(client *c, robj *o, sds field) { + int ret; + + if (o == NULL) { + addReply(c, shared.nullbulk); + return; + } + + if (o->encoding == OBJ_ENCODING_ZIPLIST) { + unsigned char *vstr = NULL; + unsigned int vlen = UINT_MAX; + long long vll = LLONG_MAX; + + ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll); + if (ret < 0) { + addReply(c, shared.nullbulk); + } else { + if (vstr) { + addReplyBulkCBuffer(c, vstr, vlen); + } else { + addReplyBulkLongLong(c, vll); + } + } + + } else if (o->encoding == OBJ_ENCODING_HT) { + sds value = hashTypeGetFromHashTable(o, field); + if (value == NULL) + addReply(c, shared.nullbulk); + else + addReplyBulkCBuffer(c, value, sdslen(value)); + } else { + serverPanic("Unknown hash encoding"); + } +} + +void hgetCommand(client *c) { + robj *o; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || + checkType(c,o,OBJ_HASH)) return; + + addHashFieldToReply(c, o, c->argv[2]->ptr); +} + +void hmgetCommand(client *c) { + robj *o; + int i; + + /* Don't abort when the key cannot be found. Non-existing keys are empty + * hashes, where HMGET should respond with a series of null bulks. */ + o = lookupKeyRead(c->db, c->argv[1]); + if (o != NULL && o->type != OBJ_HASH) { + addReply(c, shared.wrongtypeerr); + return; + } + + addReplyMultiBulkLen(c, c->argc-2); + for (i = 2; i < c->argc; i++) { + addHashFieldToReply(c, o, c->argv[i]->ptr); + } +} + +void hdelCommand(client *c) { + robj *o; + int j, deleted = 0, keyremoved = 0; + + if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,o,OBJ_HASH)) return; + + for (j = 2; j < c->argc; j++) { + if (hashTypeDelete(o,c->argv[j]->ptr)) { + deleted++; + if (hashTypeLength(o) == 0) { + dbDelete(c->db,c->argv[1]); + keyremoved = 1; + break; + } + } + } + if (deleted) { + signalModifiedKey(c->db,c->argv[1]); + notifyKeyspaceEvent(NOTIFY_HASH,"hdel",c->argv[1],c->db->id); + if (keyremoved) + notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1], + c->db->id); + server.dirty += deleted; + } + addReplyLongLong(c,deleted); +} + +void hlenCommand(client *c) { + robj *o; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,o,OBJ_HASH)) return; + + addReplyLongLong(c,hashTypeLength(o)); +} + +void hstrlenCommand(client *c) { + robj *o; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,o,OBJ_HASH)) return; + addReplyLongLong(c,hashTypeGetValueLength(o,c->argv[2]->ptr)); +} + +static void addHashIteratorCursorToReply(client *c, hashTypeIterator *hi, int what) { + if (hi->encoding == OBJ_ENCODING_ZIPLIST) { + unsigned char *vstr = NULL; + unsigned int vlen = UINT_MAX; + long long vll = LLONG_MAX; + + hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll); + if (vstr) + addReplyBulkCBuffer(c, vstr, vlen); + else + addReplyBulkLongLong(c, vll); + } else if (hi->encoding == OBJ_ENCODING_HT) { + sds value = hashTypeCurrentFromHashTable(hi, what); + addReplyBulkCBuffer(c, value, sdslen(value)); + } else { + serverPanic("Unknown hash encoding"); + } +} + +void genericHgetallCommand(client *c, int flags) { + robj *o; + hashTypeIterator *hi; + int multiplier = 0; + int length, count = 0; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL + || checkType(c,o,OBJ_HASH)) return; + + if (flags & OBJ_HASH_KEY) multiplier++; + if (flags & OBJ_HASH_VALUE) multiplier++; + + length = hashTypeLength(o) * multiplier; + addReplyMultiBulkLen(c, length); + + hi = hashTypeInitIterator(o); + while (hashTypeNext(hi) != C_ERR) { + if (flags & OBJ_HASH_KEY) { + addHashIteratorCursorToReply(c, hi, OBJ_HASH_KEY); + count++; + } + if (flags & OBJ_HASH_VALUE) { + addHashIteratorCursorToReply(c, hi, OBJ_HASH_VALUE); + count++; + } + } + + hashTypeReleaseIterator(hi); + serverAssert(count == length); +} + +void hkeysCommand(client *c) { + genericHgetallCommand(c,OBJ_HASH_KEY); +} + +void hvalsCommand(client *c) { + genericHgetallCommand(c,OBJ_HASH_VALUE); +} + +void hgetallCommand(client *c) { + genericHgetallCommand(c,OBJ_HASH_KEY|OBJ_HASH_VALUE); +} + +void hexistsCommand(client *c) { + robj *o; + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,o,OBJ_HASH)) return; + + addReply(c, hashTypeExists(o,c->argv[2]->ptr) ? shared.cone : shared.czero); +} + +void hscanCommand(client *c) { + robj *o; + unsigned long cursor; + + if (parseScanCursorOrReply(c,c->argv[2],&cursor) == C_ERR) return; + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL || + checkType(c,o,OBJ_HASH)) return; + scanGenericCommand(c,o,cursor); +} |
