提交 7fb16bac 编写于 作者: P Pieter Noordhuis

refactor of hash commands to use specialized api that abstracts zipmap and dict apis

上级 a71f072f
......@@ -6042,325 +6042,315 @@ static void zrevrankCommand(redisClient *c) {
zrankGenericCommand(c, 1);
}
/* =================================== Hashes =============================== */
static void hsetCommand(redisClient *c) {
int update = 0;
robj *o = lookupKeyWrite(c->db,c->argv[1]);
/* ========================= Hashes utility functions ======================= */
#define REDIS_HASH_KEY 1
#define REDIS_HASH_VALUE 2
/* Check the length of a number of objects to see if we need to convert a
* zipmap to a real hash. Note that we only check string encoded objects
* as their string length can be queried in constant time. */
static void hashTryConversion(robj *subject, robj **argv, int start, int end) {
int i;
if (subject->encoding != REDIS_ENCODING_ZIPMAP) return;
if (o == NULL) {
o = createHashObject();
dictAdd(c->db->dict,c->argv[1],o);
incrRefCount(c->argv[1]);
} else {
if (o->type != REDIS_HASH) {
addReply(c,shared.wrongtypeerr);
for (i = start; i <= end; i++) {
if (argv[i]->encoding == REDIS_ENCODING_RAW &&
sdslen(argv[i]->ptr) > server.hash_max_zipmap_value)
{
convertToRealHash(subject);
return;
}
}
/* We want to convert the zipmap into an hash table right now if the
* entry to be added is too big. Note that we check if the object
* is integer encoded before to try fetching the length in the test below.
* This is because integers are small, but currently stringObjectLen()
* performs a slow conversion: not worth it. */
if (o->encoding == REDIS_ENCODING_ZIPMAP &&
((c->argv[2]->encoding == REDIS_ENCODING_RAW &&
sdslen(c->argv[2]->ptr) > server.hash_max_zipmap_value) ||
(c->argv[3]->encoding == REDIS_ENCODING_RAW &&
sdslen(c->argv[3]->ptr) > server.hash_max_zipmap_value)))
{
convertToRealHash(o);
}
/* Get the value from a hash identified by key. Returns either a string
* object or NULL if the value cannot be found. The refcount of the object
* is always increased by 1 when the value was found. */
static robj *hashGet(robj *o, robj *key) {
robj *value = NULL;
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
unsigned char *v;
unsigned int vlen;
key = getDecodedObject(key);
if (zipmapGet(o->ptr,key->ptr,sdslen(key->ptr),&v,&vlen)) {
value = createStringObject((char*)v,vlen);
}
decrRefCount(key);
} else {
dictEntry *de = dictFind(o->ptr,key);
if (de != NULL) {
value = dictGetEntryVal(de);
incrRefCount(value);
}
}
return value;
}
/* Test if the key exists in the given hash. Returns 1 if the key
* exists and 0 when it doesn't. */
static int hashExists(robj *o, robj *key) {
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
unsigned char *zm = o->ptr;
robj *valobj = getDecodedObject(c->argv[3]);
key = getDecodedObject(key);
if (zipmapExists(o->ptr,key->ptr,sdslen(key->ptr))) {
decrRefCount(key);
return 1;
}
decrRefCount(key);
} else {
if (dictFind(o->ptr,key) != NULL) {
return 1;
}
}
return 0;
}
zm = zipmapSet(zm,c->argv[2]->ptr,sdslen(c->argv[2]->ptr),
valobj->ptr,sdslen(valobj->ptr),&update);
decrRefCount(valobj);
o->ptr = zm;
/* Add an element, discard the old if the key already exists.
* Return 0 on insert and 1 on update. */
static int hashReplace(robj *o, robj *key, robj *value) {
int update = 0;
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
key = getDecodedObject(key);
value = getDecodedObject(value);
o->ptr = zipmapSet(o->ptr,
key->ptr,sdslen(key->ptr),
value->ptr,sdslen(value->ptr), &update);
decrRefCount(key);
decrRefCount(value);
/* And here there is the second check for hash conversion. */
if (zipmapLen(zm) > server.hash_max_zipmap_entries)
/* Check if the zipmap needs to be upgraded to a real hash table */
if (zipmapLen(o->ptr) > server.hash_max_zipmap_entries)
convertToRealHash(o);
} else {
c->argv[2] = tryObjectEncoding(c->argv[2]);
/* note that c->argv[3] is already encoded, as the latest arg
* of a bulk command is always integer encoded if possible. */
if (dictReplace(o->ptr,c->argv[2],c->argv[3])) {
incrRefCount(c->argv[2]);
value = tryObjectEncoding(value);
if (dictReplace(o->ptr,key,value)) {
/* Insert */
incrRefCount(key);
} else {
/* Update */
update = 1;
}
incrRefCount(c->argv[3]);
incrRefCount(value);
}
server.dirty++;
addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",update == 0));
return update;
}
static void hmsetCommand(redisClient *c) {
int i;
robj *o, *key, *val;
if ((c->argc % 2) == 1) {
addReplySds(c,sdsnew("-ERR wrong number of arguments for HMSET\r\n"));
return;
/* Delete an element from a hash.
* Return 1 on deleted and 0 on not found. */
static int hashDelete(robj *o, robj *key) {
int deleted = 0;
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
key = getDecodedObject(key);
o->ptr = zipmapDel(o->ptr,key->ptr,sdslen(key->ptr), &deleted);
decrRefCount(key);
} else {
deleted = dictDelete((dict*)o->ptr,key) == DICT_OK;
/* Always check if the dictionary needs a resize after a delete. */
if (deleted && htNeedsResize(o->ptr)) dictResize(o->ptr);
}
return deleted;
}
if ((o = lookupKeyWrite(c->db,c->argv[1])) == NULL) {
o = createHashObject();
dictAdd(c->db->dict,c->argv[1],o);
incrRefCount(c->argv[1]);
/* Return the number of elements in a hash. */
static inline unsigned long hashLength(robj *o) {
return (o->encoding == REDIS_ENCODING_ZIPMAP) ?
zipmapLen((unsigned char*)o->ptr) : dictSize((dict*)o->ptr);
}
/* Structure to hold hash iteration abstration. Note that iteration over
* hashes involves both fields and values. Because it is possible that
* not both are required, store pointers in the iterator to avoid
* unnecessary memory allocation for fields/values. */
typedef struct {
int encoding;
unsigned char *zi;
unsigned char *zk, *zv;
unsigned int zklen, zvlen;
dictIterator *di;
dictEntry *de;
} hashIterator;
static void hashInitIterator(robj *subject, hashIterator *hi) {
hi->encoding = subject->encoding;
if (hi->encoding == REDIS_ENCODING_ZIPMAP) {
hi->zi = zipmapRewind(subject->ptr);
} else if (hi->encoding == REDIS_ENCODING_HT) {
hi->di = dictGetIterator(subject->ptr);
} else {
if (o->type != REDIS_HASH) {
addReply(c,shared.wrongtypeerr);
return;
}
redisAssert(NULL);
}
}
/* We want to convert the zipmap into an hash table right now if the
* entry to be added is too big. */
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
for (i = 2; i < c->argc; i+=2) {
if ((c->argv[i]->encoding == REDIS_ENCODING_RAW &&
sdslen(c->argv[i]->ptr) > server.hash_max_zipmap_value) ||
(c->argv[i+1]->encoding == REDIS_ENCODING_RAW &&
sdslen(c->argv[i+1]->ptr) > server.hash_max_zipmap_value)) {
convertToRealHash(o);
break;
}
}
static void hashReleaseIterator(hashIterator *hi) {
if (hi->encoding == REDIS_ENCODING_HT) {
dictReleaseIterator(hi->di);
}
}
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
unsigned char *zm = o->ptr;
/* 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. */
static inline int hashNext(hashIterator *hi) {
if (hi->encoding == REDIS_ENCODING_ZIPMAP) {
if ((hi->zi = zipmapNext(hi->zi, &hi->zk, &hi->zklen,
&hi->zv, &hi->zvlen)) == NULL) return REDIS_ERR;
} else {
if ((hi->de = dictNext(hi->di)) == NULL) return REDIS_ERR;
}
return REDIS_OK;
}
for (i = 2; i < c->argc; i+=2) {
key = getDecodedObject(c->argv[i]);
val = getDecodedObject(c->argv[i+1]);
zm = zipmapSet(zm,key->ptr,sdslen(key->ptr),
val->ptr,sdslen(val->ptr),NULL);
decrRefCount(key);
decrRefCount(val);
o->ptr = zm;
/* Get key or value object at current iteration position. This always
* increases the refcount of the field object by 1. */
static inline robj *hashCurrent(hashIterator *hi, int what) {
robj *o;
if (hi->encoding == REDIS_ENCODING_ZIPMAP) {
if (what & REDIS_HASH_KEY) {
o = createStringObject((char*)hi->zk,hi->zklen);
} else {
o = createStringObject((char*)hi->zv,hi->zvlen);
}
/* And here there is the second check for hash conversion. */
if (zipmapLen(zm) > server.hash_max_zipmap_entries)
convertToRealHash(o);
} else {
for (i = 2; i < c->argc; i+=2) {
key = tryObjectEncoding(c->argv[i]);
val = tryObjectEncoding(c->argv[i+1]);
if (dictReplace(o->ptr,key,val)) {
incrRefCount(key);
}
incrRefCount(val);
if (what & REDIS_HASH_KEY) {
o = dictGetEntryKey(hi->de);
} else {
o = dictGetEntryVal(hi->de);
}
incrRefCount(o);
}
addReply(c, shared.ok);
return o;
}
static void hincrbyCommand(redisClient *c) {
long long value = 0, incr = 0;
robj *o = lookupKeyWrite(c->db,c->argv[1]);
if (getLongLongFromObject(c, c->argv[3], &incr) != REDIS_OK) return;
static robj *hashLookupWriteOrCreate(redisClient *c, robj *key) {
robj *o = lookupKeyWrite(c->db,key);
if (o == NULL) {
o = createHashObject();
dictAdd(c->db->dict,c->argv[1],o);
incrRefCount(c->argv[1]);
dictAdd(c->db->dict,key,o);
incrRefCount(key);
} else {
if (o->type != REDIS_HASH) {
addReply(c,shared.wrongtypeerr);
return;
return NULL;
}
}
return o;
}
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
unsigned char *zm = o->ptr;
unsigned char *zval;
unsigned int zvlen;
/* Find value if already present in hash */
if (zipmapGet(zm,c->argv[2]->ptr,sdslen(c->argv[2]->ptr),
&zval,&zvlen)) {
/* strtoll needs the char* to have a trailing \0, but
* the zipmap doesn't include them. */
sds szval = sdsnewlen(zval, zvlen);
value = strtoll(szval,NULL,10);
sdsfree(szval);
}
value += incr;
sds svalue = sdscatprintf(sdsempty(),"%lld",value);
zm = zipmapSet(zm,c->argv[2]->ptr,sdslen(c->argv[2]->ptr),
(unsigned char*)svalue,sdslen(svalue),NULL);
sdsfree(svalue);
o->ptr = zm;
/* Check if the zipmap needs to be converted. */
if (zipmapLen(zm) > server.hash_max_zipmap_entries)
convertToRealHash(o);
} else {
robj *hval;
dictEntry *de;
/* ============================= Hash commands ============================== */
static void hsetCommand(redisClient *c) {
int update = 0;
robj *o;
/* Find value if already present in hash */
de = dictFind(o->ptr,c->argv[2]);
if (de != NULL) {
hval = dictGetEntryVal(de);
if (hval->encoding == REDIS_ENCODING_RAW)
value = strtoll(hval->ptr,NULL,10);
else if (hval->encoding == REDIS_ENCODING_INT)
value = (long)hval->ptr;
else
redisAssert(1 != 1);
}
if ((o = hashLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
hashTryConversion(o,c->argv,2,3);
update = hashReplace(o,c->argv[2],c->argv[3]);
addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",update == 0));
server.dirty++;
}
value += incr;
hval = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",value));
hval = tryObjectEncoding(hval);
if (dictReplace(o->ptr,c->argv[2],hval)) {
incrRefCount(c->argv[2]);
}
static void hmsetCommand(redisClient *c) {
int i;
robj *o;
if ((c->argc % 2) == 1) {
addReplySds(c,sdsnew("-ERR wrong number of arguments for HMSET\r\n"));
return;
}
if ((o = hashLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
hashTryConversion(o,c->argv,2,c->argc-1);
for (i = 2; i < c->argc; i += 2) {
hashReplace(o,c->argv[i],c->argv[i+1]);
}
addReply(c, shared.ok);
}
static void hincrbyCommand(redisClient *c) {
long long value, incr;
robj *o, *current, *new;
if (getLongLongFromObject(c,c->argv[3],&incr) != REDIS_OK) return;
if ((o = hashLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
if ((current = hashGet(o,c->argv[2])) != NULL) {
if (current->encoding == REDIS_ENCODING_RAW)
value = strtoll(current->ptr,NULL,10);
else if (current->encoding == REDIS_ENCODING_INT)
value = (long)current->ptr;
else
redisAssert(1 != 1);
decrRefCount(current);
} else {
value = 0;
}
value += incr;
new = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",value));
hashReplace(o,c->argv[2],new);
decrRefCount(new);
addReplyLongLong(c,value);
server.dirty++;
addReplyLongLong(c, value);
}
static void hgetCommand(redisClient *c) {
robj *o;
robj *o, *value;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
unsigned char *zm = o->ptr;
unsigned char *val;
unsigned int vlen;
robj *field;
field = getDecodedObject(c->argv[2]);
if (zipmapGet(zm,field->ptr,sdslen(field->ptr), &val,&vlen)) {
addReplySds(c,sdscatprintf(sdsempty(),"$%u\r\n", vlen));
addReplySds(c,sdsnewlen(val,vlen));
addReply(c,shared.crlf);
decrRefCount(field);
return;
} else {
addReply(c,shared.nullbulk);
decrRefCount(field);
return;
}
if ((value = hashGet(o,c->argv[2])) != NULL) {
addReplyBulk(c,value);
decrRefCount(value);
} else {
struct dictEntry *de;
de = dictFind(o->ptr,c->argv[2]);
if (de == NULL) {
addReply(c,shared.nullbulk);
} else {
robj *e = dictGetEntryVal(de);
addReplyBulk(c,e);
}
addReply(c,shared.nullbulk);
}
}
static void hmgetCommand(redisClient *c) {
int i;
robj *o = lookupKeyRead(c->db, c->argv[1]);
if (o == NULL) {
addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-2));
for (i = 2; i < c->argc; i++) {
addReply(c,shared.nullbulk);
}
return;
} else {
if (o->type != REDIS_HASH) {
addReply(c,shared.wrongtypeerr);
return;
}
robj *o, *value;
o = lookupKeyRead(c->db,c->argv[1]);
if (o != NULL && o->type != REDIS_HASH) {
addReply(c,shared.wrongtypeerr);
}
/* Note the check for o != NULL happens inside the loop. This is
* done because objects that cannot be found are considered to be
* an empty hash. The reply should then be a series of NULLs. */
addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-2));
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
unsigned char *zm = o->ptr;
unsigned char *v;
unsigned int vlen;
robj *field;
for (i = 2; i < c->argc; i++) {
field = getDecodedObject(c->argv[i]);
if (zipmapGet(zm,field->ptr,sdslen(field->ptr),&v,&vlen)) {
addReplySds(c,sdscatprintf(sdsempty(),"$%u\r\n", vlen));
addReplySds(c,sdsnewlen(v,vlen));
addReply(c,shared.crlf);
} else {
addReply(c,shared.nullbulk);
}
decrRefCount(field);
}
} else {
dictEntry *de;
for (i = 2; i < c->argc; i++) {
de = dictFind(o->ptr,c->argv[i]);
if (de != NULL) {
addReplyBulk(c,(robj*)dictGetEntryVal(de));
} else {
addReply(c,shared.nullbulk);
}
for (i = 2; i < c->argc; i++) {
if (o != NULL && (value = hashGet(o,c->argv[i])) != NULL) {
addReplyBulk(c,value);
decrRefCount(value);
} else {
addReply(c,shared.nullbulk);
}
}
}
static void hdelCommand(redisClient *c) {
robj *o;
int deleted = 0;
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
robj *field = getDecodedObject(c->argv[2]);
o->ptr = zipmapDel((unsigned char*) o->ptr,
(unsigned char*) field->ptr,
sdslen(field->ptr), &deleted);
decrRefCount(field);
if (zipmapLen((unsigned char*) o->ptr) == 0)
deleteKey(c->db,c->argv[1]);
if (hashDelete(o,c->argv[2])) {
if (hashLength(o) == 0) deleteKey(c->db,c->argv[1]);
addReply(c,shared.cone);
server.dirty++;
} else {
deleted = dictDelete((dict*)o->ptr,c->argv[2]) == DICT_OK;
if (htNeedsResize(o->ptr)) dictResize(o->ptr);
if (dictSize((dict*)o->ptr) == 0) deleteKey(c->db,c->argv[1]);
addReply(c,shared.czero);
}
if (deleted) server.dirty++;
addReply(c,deleted ? shared.cone : shared.czero);
}
static void hlenCommand(redisClient *c) {
robj *o;
unsigned long len;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
len = (o->encoding == REDIS_ENCODING_ZIPMAP) ?
zipmapLen((unsigned char*)o->ptr) : dictSize((dict*)o->ptr);
addReplyUlong(c,len);
addReplyUlong(c,hashLength(o));
}
#define REDIS_GETALL_KEYS 1
#define REDIS_GETALL_VALS 2
static void genericHgetallCommand(redisClient *c, int flags) {
robj *o, *lenobj;
robj *o, *lenobj, *obj;
unsigned long count = 0;
hashIterator hi;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|| checkType(c,o,REDIS_HASH)) return;
......@@ -6369,79 +6359,44 @@ static void genericHgetallCommand(redisClient *c, int flags) {
addReply(c,lenobj);
decrRefCount(lenobj);
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
unsigned char *p = zipmapRewind(o->ptr);
unsigned char *field, *val;
unsigned int flen, vlen;
while((p = zipmapNext(p,&field,&flen,&val,&vlen)) != NULL) {
robj *aux;
if (flags & REDIS_GETALL_KEYS) {
aux = createStringObject((char*)field,flen);
addReplyBulk(c,aux);
decrRefCount(aux);
count++;
}
if (flags & REDIS_GETALL_VALS) {
aux = createStringObject((char*)val,vlen);
addReplyBulk(c,aux);
decrRefCount(aux);
count++;
}
hashInitIterator(o, &hi);
while (hashNext(&hi) != REDIS_ERR) {
if (flags & REDIS_HASH_KEY) {
obj = hashCurrent(&hi,REDIS_HASH_KEY);
addReplyBulk(c,obj);
decrRefCount(obj);
count++;
}
} else {
dictIterator *di = dictGetIterator(o->ptr);
dictEntry *de;
while((de = dictNext(di)) != NULL) {
robj *fieldobj = dictGetEntryKey(de);
robj *valobj = dictGetEntryVal(de);
if (flags & REDIS_GETALL_KEYS) {
addReplyBulk(c,fieldobj);
count++;
}
if (flags & REDIS_GETALL_VALS) {
addReplyBulk(c,valobj);
count++;
}
if (flags & REDIS_HASH_VALUE) {
obj = hashCurrent(&hi,REDIS_HASH_VALUE);
addReplyBulk(c,obj);
decrRefCount(obj);
count++;
}
dictReleaseIterator(di);
}
hashReleaseIterator(&hi);
lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",count);
}
static void hkeysCommand(redisClient *c) {
genericHgetallCommand(c,REDIS_GETALL_KEYS);
genericHgetallCommand(c,REDIS_HASH_KEY);
}
static void hvalsCommand(redisClient *c) {
genericHgetallCommand(c,REDIS_GETALL_VALS);
genericHgetallCommand(c,REDIS_HASH_VALUE);
}
static void hgetallCommand(redisClient *c) {
genericHgetallCommand(c,REDIS_GETALL_KEYS|REDIS_GETALL_VALS);
genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE);
}
static void hexistsCommand(redisClient *c) {
robj *o;
int exists = 0;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
robj *field;
unsigned char *zm = o->ptr;
field = getDecodedObject(c->argv[2]);
exists = zipmapExists(zm,field->ptr,sdslen(field->ptr));
decrRefCount(field);
} else {
exists = dictFind(o->ptr,c->argv[2]) != NULL;
}
addReply(c,exists ? shared.cone : shared.czero);
addReply(c, hashExists(o,c->argv[2]) ? shared.cone : shared.czero);
}
static void convertToRealHash(robj *o) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册