mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-24 00:59:02 -05:00
ebcb6251e6
The previous implementation of SCAN parsed the cursor in the generic function implementing SCAN, SSCAN, HSCAN and ZSCAN. The actual higher-level command implementation only checked for empty keys and return ASAP in that case. The result was that inverting the arguments of, for instance, SSCAN for example and write: SSCAN 0 key Instead of SSCAN key 0 Resulted into no error, since 0 is a non-existing key name very likely. Just the iterator returned no elements at all. In order to fix this issue the code was refactored to extract the function to parse the cursor and return the error. Every higher level command implementation now parses the cursor and later checks if the key exist or not.
772 lines
24 KiB
C
772 lines
24 KiB
C
/*
|
|
* 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 "redis.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 != REDIS_ENCODING_ZIPLIST) return;
|
|
|
|
for (i = start; i <= end; i++) {
|
|
if (sdsEncodedObject(argv[i]) &&
|
|
sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
|
|
{
|
|
hashTypeConvert(o, REDIS_ENCODING_HT);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Encode given objects in-place when the hash uses a dict. */
|
|
void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2) {
|
|
if (subject->encoding == REDIS_ENCODING_HT) {
|
|
if (o1) *o1 = tryObjectEncoding(*o1);
|
|
if (o2) *o2 = tryObjectEncoding(*o2);
|
|
}
|
|
}
|
|
|
|
/* Get the value from a ziplist encoded hash, identified by field.
|
|
* Returns -1 when the field cannot be found. */
|
|
int hashTypeGetFromZiplist(robj *o, robj *field,
|
|
unsigned char **vstr,
|
|
unsigned int *vlen,
|
|
long long *vll)
|
|
{
|
|
unsigned char *zl, *fptr = NULL, *vptr = NULL;
|
|
int ret;
|
|
|
|
redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST);
|
|
|
|
field = getDecodedObject(field);
|
|
|
|
zl = o->ptr;
|
|
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
|
|
if (fptr != NULL) {
|
|
fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
|
|
if (fptr != NULL) {
|
|
/* Grab pointer to the value (fptr points to the field) */
|
|
vptr = ziplistNext(zl, fptr);
|
|
redisAssert(vptr != NULL);
|
|
}
|
|
}
|
|
|
|
decrRefCount(field);
|
|
|
|
if (vptr != NULL) {
|
|
ret = ziplistGet(vptr, vstr, vlen, vll);
|
|
redisAssert(ret);
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Get the value from a hash table encoded hash, identified by field.
|
|
* Returns -1 when the field cannot be found. */
|
|
int hashTypeGetFromHashTable(robj *o, robj *field, robj **value) {
|
|
dictEntry *de;
|
|
|
|
redisAssert(o->encoding == REDIS_ENCODING_HT);
|
|
|
|
de = dictFind(o->ptr, field);
|
|
if (de == NULL) return -1;
|
|
*value = dictGetVal(de);
|
|
return 0;
|
|
}
|
|
|
|
/* Higher level function of hashTypeGet*() that always returns a Redis
|
|
* object (either new or with refcount incremented), so that the caller
|
|
* can retain a reference or call decrRefCount after the usage.
|
|
*
|
|
* The lower level function can prevent copy on write so it is
|
|
* the preferred way of doing read operations. */
|
|
robj *hashTypeGetObject(robj *o, robj *field) {
|
|
robj *value = NULL;
|
|
|
|
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
|
|
unsigned char *vstr = NULL;
|
|
unsigned int vlen = UINT_MAX;
|
|
long long vll = LLONG_MAX;
|
|
|
|
if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) {
|
|
if (vstr) {
|
|
value = createStringObject((char*)vstr, vlen);
|
|
} else {
|
|
value = createStringObjectFromLongLong(vll);
|
|
}
|
|
}
|
|
|
|
} else if (o->encoding == REDIS_ENCODING_HT) {
|
|
robj *aux;
|
|
|
|
if (hashTypeGetFromHashTable(o, field, &aux) == 0) {
|
|
incrRefCount(aux);
|
|
value = aux;
|
|
}
|
|
} else {
|
|
redisPanic("Unknown hash encoding");
|
|
}
|
|
return value;
|
|
}
|
|
|
|
/* 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, robj *field) {
|
|
if (o->encoding == REDIS_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 == REDIS_ENCODING_HT) {
|
|
robj *aux;
|
|
|
|
if (hashTypeGetFromHashTable(o, field, &aux) == 0) return 1;
|
|
} else {
|
|
redisPanic("Unknown hash encoding");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Add an element, discard the old if the key already exists.
|
|
* Return 0 on insert and 1 on update.
|
|
* This function will take care of incrementing the reference count of the
|
|
* retained fields and value objects. */
|
|
int hashTypeSet(robj *o, robj *field, robj *value) {
|
|
int update = 0;
|
|
|
|
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
|
|
unsigned char *zl, *fptr, *vptr;
|
|
|
|
field = getDecodedObject(field);
|
|
value = getDecodedObject(value);
|
|
|
|
zl = o->ptr;
|
|
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
|
|
if (fptr != NULL) {
|
|
fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
|
|
if (fptr != NULL) {
|
|
/* Grab pointer to the value (fptr points to the field) */
|
|
vptr = ziplistNext(zl, fptr);
|
|
redisAssert(vptr != NULL);
|
|
update = 1;
|
|
|
|
/* Delete value */
|
|
zl = ziplistDelete(zl, &vptr);
|
|
|
|
/* Insert new value */
|
|
zl = ziplistInsert(zl, vptr, value->ptr, sdslen(value->ptr));
|
|
}
|
|
}
|
|
|
|
if (!update) {
|
|
/* Push new field/value pair onto the tail of the ziplist */
|
|
zl = ziplistPush(zl, field->ptr, sdslen(field->ptr), ZIPLIST_TAIL);
|
|
zl = ziplistPush(zl, value->ptr, sdslen(value->ptr), ZIPLIST_TAIL);
|
|
}
|
|
o->ptr = zl;
|
|
decrRefCount(field);
|
|
decrRefCount(value);
|
|
|
|
/* Check if the ziplist needs to be converted to a hash table */
|
|
if (hashTypeLength(o) > server.hash_max_ziplist_entries)
|
|
hashTypeConvert(o, REDIS_ENCODING_HT);
|
|
} else if (o->encoding == REDIS_ENCODING_HT) {
|
|
if (dictReplace(o->ptr, field, value)) { /* Insert */
|
|
incrRefCount(field);
|
|
} else { /* Update */
|
|
update = 1;
|
|
}
|
|
incrRefCount(value);
|
|
} else {
|
|
redisPanic("Unknown hash encoding");
|
|
}
|
|
return update;
|
|
}
|
|
|
|
/* Delete an element from a hash.
|
|
* Return 1 on deleted and 0 on not found. */
|
|
int hashTypeDelete(robj *o, robj *field) {
|
|
int deleted = 0;
|
|
|
|
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
|
|
unsigned char *zl, *fptr;
|
|
|
|
field = getDecodedObject(field);
|
|
|
|
zl = o->ptr;
|
|
fptr = ziplistIndex(zl, ZIPLIST_HEAD);
|
|
if (fptr != NULL) {
|
|
fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
|
|
if (fptr != NULL) {
|
|
zl = ziplistDelete(zl,&fptr);
|
|
zl = ziplistDelete(zl,&fptr);
|
|
o->ptr = zl;
|
|
deleted = 1;
|
|
}
|
|
}
|
|
|
|
decrRefCount(field);
|
|
|
|
} else if (o->encoding == REDIS_ENCODING_HT) {
|
|
if (dictDelete((dict*)o->ptr, field) == REDIS_OK) {
|
|
deleted = 1;
|
|
|
|
/* Always check if the dictionary needs a resize after a delete. */
|
|
if (htNeedsResize(o->ptr)) dictResize(o->ptr);
|
|
}
|
|
|
|
} else {
|
|
redisPanic("Unknown hash encoding");
|
|
}
|
|
|
|
return deleted;
|
|
}
|
|
|
|
/* Return the number of elements in a hash. */
|
|
unsigned long hashTypeLength(robj *o) {
|
|
unsigned long length = ULONG_MAX;
|
|
|
|
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
|
|
length = ziplistLen(o->ptr) / 2;
|
|
} else if (o->encoding == REDIS_ENCODING_HT) {
|
|
length = dictSize((dict*)o->ptr);
|
|
} else {
|
|
redisPanic("Unknown hash encoding");
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
hashTypeIterator *hashTypeInitIterator(robj *subject) {
|
|
hashTypeIterator *hi = zmalloc(sizeof(hashTypeIterator));
|
|
hi->subject = subject;
|
|
hi->encoding = subject->encoding;
|
|
|
|
if (hi->encoding == REDIS_ENCODING_ZIPLIST) {
|
|
hi->fptr = NULL;
|
|
hi->vptr = NULL;
|
|
} else if (hi->encoding == REDIS_ENCODING_HT) {
|
|
hi->di = dictGetIterator(subject->ptr);
|
|
} else {
|
|
redisPanic("Unknown hash encoding");
|
|
}
|
|
|
|
return hi;
|
|
}
|
|
|
|
void hashTypeReleaseIterator(hashTypeIterator *hi) {
|
|
if (hi->encoding == REDIS_ENCODING_HT) {
|
|
dictReleaseIterator(hi->di);
|
|
}
|
|
|
|
zfree(hi);
|
|
}
|
|
|
|
/* Move to the next entry in the hash. Return REDIS_OK when the next entry
|
|
* could be found and REDIS_ERR when the iterator reaches the end. */
|
|
int hashTypeNext(hashTypeIterator *hi) {
|
|
if (hi->encoding == REDIS_ENCODING_ZIPLIST) {
|
|
unsigned char *zl;
|
|
unsigned char *fptr, *vptr;
|
|
|
|
zl = hi->subject->ptr;
|
|
fptr = hi->fptr;
|
|
vptr = hi->vptr;
|
|
|
|
if (fptr == NULL) {
|
|
/* Initialize cursor */
|
|
redisAssert(vptr == NULL);
|
|
fptr = ziplistIndex(zl, 0);
|
|
} else {
|
|
/* Advance cursor */
|
|
redisAssert(vptr != NULL);
|
|
fptr = ziplistNext(zl, vptr);
|
|
}
|
|
if (fptr == NULL) return REDIS_ERR;
|
|
|
|
/* Grab pointer to the value (fptr points to the field) */
|
|
vptr = ziplistNext(zl, fptr);
|
|
redisAssert(vptr != NULL);
|
|
|
|
/* fptr, vptr now point to the first or next pair */
|
|
hi->fptr = fptr;
|
|
hi->vptr = vptr;
|
|
} else if (hi->encoding == REDIS_ENCODING_HT) {
|
|
if ((hi->de = dictNext(hi->di)) == NULL) return REDIS_ERR;
|
|
} else {
|
|
redisPanic("Unknown hash encoding");
|
|
}
|
|
return REDIS_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;
|
|
|
|
redisAssert(hi->encoding == REDIS_ENCODING_ZIPLIST);
|
|
|
|
if (what & REDIS_HASH_KEY) {
|
|
ret = ziplistGet(hi->fptr, vstr, vlen, vll);
|
|
redisAssert(ret);
|
|
} else {
|
|
ret = ziplistGet(hi->vptr, vstr, vlen, vll);
|
|
redisAssert(ret);
|
|
}
|
|
}
|
|
|
|
/* Get the field or value at iterator cursor, for an iterator on a hash value
|
|
* encoded as a ziplist. Prototype is similar to `hashTypeGetFromHashTable`. */
|
|
void hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what, robj **dst) {
|
|
redisAssert(hi->encoding == REDIS_ENCODING_HT);
|
|
|
|
if (what & REDIS_HASH_KEY) {
|
|
*dst = dictGetKey(hi->de);
|
|
} else {
|
|
*dst = dictGetVal(hi->de);
|
|
}
|
|
}
|
|
|
|
/* A non copy-on-write friendly but higher level version of hashTypeCurrent*()
|
|
* that returns an object with incremented refcount (or a new object). It is up
|
|
* to the caller to decrRefCount() the object if no reference is retained. */
|
|
robj *hashTypeCurrentObject(hashTypeIterator *hi, int what) {
|
|
robj *dst;
|
|
|
|
if (hi->encoding == REDIS_ENCODING_ZIPLIST) {
|
|
unsigned char *vstr = NULL;
|
|
unsigned int vlen = UINT_MAX;
|
|
long long vll = LLONG_MAX;
|
|
|
|
hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll);
|
|
if (vstr) {
|
|
dst = createStringObject((char*)vstr, vlen);
|
|
} else {
|
|
dst = createStringObjectFromLongLong(vll);
|
|
}
|
|
} else if (hi->encoding == REDIS_ENCODING_HT) {
|
|
hashTypeCurrentFromHashTable(hi, what, &dst);
|
|
incrRefCount(dst);
|
|
} else {
|
|
redisPanic("Unknown hash encoding");
|
|
}
|
|
return dst;
|
|
}
|
|
|
|
robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) {
|
|
robj *o = lookupKeyWrite(c->db,key);
|
|
if (o == NULL) {
|
|
o = createHashObject();
|
|
dbAdd(c->db,key,o);
|
|
} else {
|
|
if (o->type != REDIS_HASH) {
|
|
addReply(c,shared.wrongtypeerr);
|
|
return NULL;
|
|
}
|
|
}
|
|
return o;
|
|
}
|
|
|
|
void hashTypeConvertZiplist(robj *o, int enc) {
|
|
redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST);
|
|
|
|
if (enc == REDIS_ENCODING_ZIPLIST) {
|
|
/* Nothing to do... */
|
|
|
|
} else if (enc == REDIS_ENCODING_HT) {
|
|
hashTypeIterator *hi;
|
|
dict *dict;
|
|
int ret;
|
|
|
|
hi = hashTypeInitIterator(o);
|
|
dict = dictCreate(&hashDictType, NULL);
|
|
|
|
while (hashTypeNext(hi) != REDIS_ERR) {
|
|
robj *field, *value;
|
|
|
|
field = hashTypeCurrentObject(hi, REDIS_HASH_KEY);
|
|
field = tryObjectEncoding(field);
|
|
value = hashTypeCurrentObject(hi, REDIS_HASH_VALUE);
|
|
value = tryObjectEncoding(value);
|
|
ret = dictAdd(dict, field, value);
|
|
if (ret != DICT_OK) {
|
|
redisLogHexDump(REDIS_WARNING,"ziplist with dup elements dump",
|
|
o->ptr,ziplistBlobLen(o->ptr));
|
|
redisAssert(ret == DICT_OK);
|
|
}
|
|
}
|
|
|
|
hashTypeReleaseIterator(hi);
|
|
zfree(o->ptr);
|
|
|
|
o->encoding = REDIS_ENCODING_HT;
|
|
o->ptr = dict;
|
|
|
|
} else {
|
|
redisPanic("Unknown hash encoding");
|
|
}
|
|
}
|
|
|
|
void hashTypeConvert(robj *o, int enc) {
|
|
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
|
|
hashTypeConvertZiplist(o, enc);
|
|
} else if (o->encoding == REDIS_ENCODING_HT) {
|
|
redisPanic("Not implemented");
|
|
} else {
|
|
redisPanic("Unknown hash encoding");
|
|
}
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
* Hash type commands
|
|
*----------------------------------------------------------------------------*/
|
|
|
|
void hsetCommand(redisClient *c) {
|
|
int update;
|
|
robj *o;
|
|
|
|
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
|
hashTypeTryConversion(o,c->argv,2,3);
|
|
hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
|
|
update = hashTypeSet(o,c->argv[2],c->argv[3]);
|
|
addReply(c, update ? shared.czero : shared.cone);
|
|
signalModifiedKey(c->db,c->argv[1]);
|
|
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
|
server.dirty++;
|
|
}
|
|
|
|
void hsetnxCommand(redisClient *c) {
|
|
robj *o;
|
|
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
|
hashTypeTryConversion(o,c->argv,2,3);
|
|
|
|
if (hashTypeExists(o, c->argv[2])) {
|
|
addReply(c, shared.czero);
|
|
} else {
|
|
hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
|
|
hashTypeSet(o,c->argv[2],c->argv[3]);
|
|
addReply(c, shared.cone);
|
|
signalModifiedKey(c->db,c->argv[1]);
|
|
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
|
server.dirty++;
|
|
}
|
|
}
|
|
|
|
void hmsetCommand(redisClient *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) {
|
|
hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]);
|
|
hashTypeSet(o,c->argv[i],c->argv[i+1]);
|
|
}
|
|
addReply(c, shared.ok);
|
|
signalModifiedKey(c->db,c->argv[1]);
|
|
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
|
server.dirty++;
|
|
}
|
|
|
|
void hincrbyCommand(redisClient *c) {
|
|
long long value, incr, oldvalue;
|
|
robj *o, *current, *new;
|
|
|
|
if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
|
|
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
|
if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
|
|
if (getLongLongFromObjectOrReply(c,current,&value,
|
|
"hash value is not an integer") != REDIS_OK) {
|
|
decrRefCount(current);
|
|
return;
|
|
}
|
|
decrRefCount(current);
|
|
} 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 = createStringObjectFromLongLong(value);
|
|
hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
|
|
hashTypeSet(o,c->argv[2],new);
|
|
decrRefCount(new);
|
|
addReplyLongLong(c,value);
|
|
signalModifiedKey(c->db,c->argv[1]);
|
|
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hincrby",c->argv[1],c->db->id);
|
|
server.dirty++;
|
|
}
|
|
|
|
void hincrbyfloatCommand(redisClient *c) {
|
|
double long value, incr;
|
|
robj *o, *current, *new, *aux;
|
|
|
|
if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
|
|
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
|
if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
|
|
if (getLongDoubleFromObjectOrReply(c,current,&value,
|
|
"hash value is not a valid float") != REDIS_OK) {
|
|
decrRefCount(current);
|
|
return;
|
|
}
|
|
decrRefCount(current);
|
|
} else {
|
|
value = 0;
|
|
}
|
|
|
|
value += incr;
|
|
new = createStringObjectFromLongDouble(value);
|
|
hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
|
|
hashTypeSet(o,c->argv[2],new);
|
|
addReplyBulk(c,new);
|
|
signalModifiedKey(c->db,c->argv[1]);
|
|
notifyKeyspaceEvent(REDIS_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. */
|
|
aux = createStringObject("HSET",4);
|
|
rewriteClientCommandArgument(c,0,aux);
|
|
decrRefCount(aux);
|
|
rewriteClientCommandArgument(c,3,new);
|
|
decrRefCount(new);
|
|
}
|
|
|
|
static void addHashFieldToReply(redisClient *c, robj *o, robj *field) {
|
|
int ret;
|
|
|
|
if (o == NULL) {
|
|
addReply(c, shared.nullbulk);
|
|
return;
|
|
}
|
|
|
|
if (o->encoding == REDIS_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 == REDIS_ENCODING_HT) {
|
|
robj *value;
|
|
|
|
ret = hashTypeGetFromHashTable(o, field, &value);
|
|
if (ret < 0) {
|
|
addReply(c, shared.nullbulk);
|
|
} else {
|
|
addReplyBulk(c, value);
|
|
}
|
|
|
|
} else {
|
|
redisPanic("Unknown hash encoding");
|
|
}
|
|
}
|
|
|
|
void hgetCommand(redisClient *c) {
|
|
robj *o;
|
|
|
|
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
|
|
checkType(c,o,REDIS_HASH)) return;
|
|
|
|
addHashFieldToReply(c, o, c->argv[2]);
|
|
}
|
|
|
|
void hmgetCommand(redisClient *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 != REDIS_HASH) {
|
|
addReply(c, shared.wrongtypeerr);
|
|
return;
|
|
}
|
|
|
|
addReplyMultiBulkLen(c, c->argc-2);
|
|
for (i = 2; i < c->argc; i++) {
|
|
addHashFieldToReply(c, o, c->argv[i]);
|
|
}
|
|
}
|
|
|
|
void hdelCommand(redisClient *c) {
|
|
robj *o;
|
|
int j, deleted = 0, keyremoved = 0;
|
|
|
|
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
|
checkType(c,o,REDIS_HASH)) return;
|
|
|
|
for (j = 2; j < c->argc; j++) {
|
|
if (hashTypeDelete(o,c->argv[j])) {
|
|
deleted++;
|
|
if (hashTypeLength(o) == 0) {
|
|
dbDelete(c->db,c->argv[1]);
|
|
keyremoved = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (deleted) {
|
|
signalModifiedKey(c->db,c->argv[1]);
|
|
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hdel",c->argv[1],c->db->id);
|
|
if (keyremoved)
|
|
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],
|
|
c->db->id);
|
|
server.dirty += deleted;
|
|
}
|
|
addReplyLongLong(c,deleted);
|
|
}
|
|
|
|
void hlenCommand(redisClient *c) {
|
|
robj *o;
|
|
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
|
checkType(c,o,REDIS_HASH)) return;
|
|
|
|
addReplyLongLong(c,hashTypeLength(o));
|
|
}
|
|
|
|
static void addHashIteratorCursorToReply(redisClient *c, hashTypeIterator *hi, int what) {
|
|
if (hi->encoding == REDIS_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 == REDIS_ENCODING_HT) {
|
|
robj *value;
|
|
|
|
hashTypeCurrentFromHashTable(hi, what, &value);
|
|
addReplyBulk(c, value);
|
|
|
|
} else {
|
|
redisPanic("Unknown hash encoding");
|
|
}
|
|
}
|
|
|
|
void genericHgetallCommand(redisClient *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,REDIS_HASH)) return;
|
|
|
|
if (flags & REDIS_HASH_KEY) multiplier++;
|
|
if (flags & REDIS_HASH_VALUE) multiplier++;
|
|
|
|
length = hashTypeLength(o) * multiplier;
|
|
addReplyMultiBulkLen(c, length);
|
|
|
|
hi = hashTypeInitIterator(o);
|
|
while (hashTypeNext(hi) != REDIS_ERR) {
|
|
if (flags & REDIS_HASH_KEY) {
|
|
addHashIteratorCursorToReply(c, hi, REDIS_HASH_KEY);
|
|
count++;
|
|
}
|
|
if (flags & REDIS_HASH_VALUE) {
|
|
addHashIteratorCursorToReply(c, hi, REDIS_HASH_VALUE);
|
|
count++;
|
|
}
|
|
}
|
|
|
|
hashTypeReleaseIterator(hi);
|
|
redisAssert(count == length);
|
|
}
|
|
|
|
void hkeysCommand(redisClient *c) {
|
|
genericHgetallCommand(c,REDIS_HASH_KEY);
|
|
}
|
|
|
|
void hvalsCommand(redisClient *c) {
|
|
genericHgetallCommand(c,REDIS_HASH_VALUE);
|
|
}
|
|
|
|
void hgetallCommand(redisClient *c) {
|
|
genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE);
|
|
}
|
|
|
|
void hexistsCommand(redisClient *c) {
|
|
robj *o;
|
|
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
|
checkType(c,o,REDIS_HASH)) return;
|
|
|
|
addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero);
|
|
}
|
|
|
|
void hscanCommand(redisClient *c) {
|
|
robj *o;
|
|
unsigned long cursor;
|
|
|
|
if (parseScanCursorOrReply(c,c->argv[2],&cursor) == REDIS_ERR) return;
|
|
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL ||
|
|
checkType(c,o,REDIS_HASH)) return;
|
|
scanGenericCommand(c,o,cursor);
|
|
}
|