mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-23 08:38:27 -05:00
803 lines
25 KiB
C
803 lines
25 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;
|
|
}
|
|
|
|
/* 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, robj *field) {
|
|
size_t len = 0;
|
|
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)
|
|
len = vstr ? vlen : sdigits10(vll);
|
|
} else if (o->encoding == REDIS_ENCODING_HT) {
|
|
robj *aux;
|
|
|
|
if (hashTypeGetFromHashTable(o, field, &aux) == 0)
|
|
len = stringObjectLen(aux);
|
|
} else {
|
|
redisPanic("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, 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,1);
|
|
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));
|
|
}
|
|
|
|
void hstrlenCommand(redisClient *c) {
|
|
robj *o;
|
|
|
|
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
|
checkType(c,o,REDIS_HASH)) return;
|
|
addReplyLongLong(c,hashTypeGetValueLength(o,c->argv[2]));
|
|
}
|
|
|
|
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);
|
|
}
|