Commit 422cf21f authored by antirez's avatar antirez
Browse files

Merge branch 'lists' of git://github.com/pietern/redis

parents 4dc1218c dda20542
...@@ -15,7 +15,7 @@ endif ...@@ -15,7 +15,7 @@ endif
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF) CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
DEBUG?= -g -rdynamic -ggdb DEBUG?= -g -rdynamic -ggdb
OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o
BENCHOBJ = ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o BENCHOBJ = ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o
CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o linenoise.o CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o linenoise.o
CHECKDUMPOBJ = redis-check-dump.o lzf_c.o lzf_d.o CHECKDUMPOBJ = redis-check-dump.o lzf_c.o lzf_d.o
...@@ -53,6 +53,7 @@ redis.o: redis.c fmacros.h config.h redis.h ae.h sds.h anet.h dict.h \ ...@@ -53,6 +53,7 @@ redis.o: redis.c fmacros.h config.h redis.h ae.h sds.h anet.h dict.h \
adlist.h zmalloc.h lzf.h pqsort.h zipmap.h staticsymbols.h sha1.h adlist.h zmalloc.h lzf.h pqsort.h zipmap.h staticsymbols.h sha1.h
sds.o: sds.c sds.h zmalloc.h sds.o: sds.c sds.h zmalloc.h
zipmap.o: zipmap.c zmalloc.h zipmap.o: zipmap.c zmalloc.h
ziplist.o: ziplist.c zmalloc.h
zmalloc.o: zmalloc.c config.h zmalloc.o: zmalloc.c config.h
redis-server: $(OBJ) redis-server: $(OBJ)
......
...@@ -75,6 +75,7 @@ ...@@ -75,6 +75,7 @@
#include "lzf.h" /* LZF compression library */ #include "lzf.h" /* LZF compression library */
#include "pqsort.h" /* Partial qsort for SORT+LIMIT */ #include "pqsort.h" /* Partial qsort for SORT+LIMIT */
#include "zipmap.h" /* Compact dictionary-alike data structure */ #include "zipmap.h" /* Compact dictionary-alike data structure */
#include "ziplist.h" /* Compact list data structure */
#include "sha1.h" /* SHA1 is used for DEBUG DIGEST */ #include "sha1.h" /* SHA1 is used for DEBUG DIGEST */
#include "release.h" /* Release and/or git repository information */ #include "release.h" /* Release and/or git repository information */
...@@ -125,13 +126,15 @@ ...@@ -125,13 +126,15 @@
/* Objects encoding. Some kind of objects like Strings and Hashes can be /* Objects encoding. Some kind of objects like Strings and Hashes can be
* internally represented in multiple ways. The 'encoding' field of the object * internally represented in multiple ways. The 'encoding' field of the object
* is set to one of this fields for this object. */ * is set to one of this fields for this object. */
#define REDIS_ENCODING_RAW 0 /* Raw representation */ #define REDIS_ENCODING_RAW 0 /* Raw representation */
#define REDIS_ENCODING_INT 1 /* Encoded as integer */ #define REDIS_ENCODING_INT 1 /* Encoded as integer */
#define REDIS_ENCODING_ZIPMAP 2 /* Encoded as zipmap */ #define REDIS_ENCODING_HT 2 /* Encoded as hash table */
#define REDIS_ENCODING_HT 3 /* Encoded as an hash table */ #define REDIS_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
#define REDIS_ENCODING_LIST 4 /* Encoded as zipmap */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
static char* strencoding[] = { static char* strencoding[] = {
"raw", "int", "zipmap", "hashtable" "raw", "int", "hashtable", "zipmap", "list", "ziplist"
}; };
/* Object types only used for dumping to disk */ /* Object types only used for dumping to disk */
...@@ -234,9 +237,11 @@ static char* strencoding[] = { ...@@ -234,9 +237,11 @@ static char* strencoding[] = {
#define APPENDFSYNC_ALWAYS 1 #define APPENDFSYNC_ALWAYS 1
#define APPENDFSYNC_EVERYSEC 2 #define APPENDFSYNC_EVERYSEC 2
/* Hashes related defaults */ /* Zip structure related defaults */
#define REDIS_HASH_MAX_ZIPMAP_ENTRIES 64 #define REDIS_HASH_MAX_ZIPMAP_ENTRIES 64
#define REDIS_HASH_MAX_ZIPMAP_VALUE 512 #define REDIS_HASH_MAX_ZIPMAP_VALUE 512
#define REDIS_LIST_MAX_ZIPLIST_ENTRIES 1024
#define REDIS_LIST_MAX_ZIPLIST_VALUE 32
/* We can print the stacktrace, so our assert is defined this way: */ /* We can print the stacktrace, so our assert is defined this way: */
#define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1))) #define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1)))
...@@ -422,9 +427,11 @@ struct redisServer { ...@@ -422,9 +427,11 @@ struct redisServer {
off_t vm_page_size; off_t vm_page_size;
off_t vm_pages; off_t vm_pages;
unsigned long long vm_max_memory; unsigned long long vm_max_memory;
/* Hashes config */ /* Zip structure config */
size_t hash_max_zipmap_entries; size_t hash_max_zipmap_entries;
size_t hash_max_zipmap_value; size_t hash_max_zipmap_value;
size_t list_max_ziplist_entries;
size_t list_max_ziplist_value;
/* Virtual memory state */ /* Virtual memory state */
FILE *vm_fp; FILE *vm_fp;
int vm_fd; int vm_fd;
...@@ -643,6 +650,7 @@ static struct redisCommand *lookupCommand(char *name); ...@@ -643,6 +650,7 @@ static struct redisCommand *lookupCommand(char *name);
static void call(redisClient *c, struct redisCommand *cmd); static void call(redisClient *c, struct redisCommand *cmd);
static void resetClient(redisClient *c); static void resetClient(redisClient *c);
static void convertToRealHash(robj *o); static void convertToRealHash(robj *o);
static void listTypeConvert(robj *o, int enc);
static int pubsubUnsubscribeAllChannels(redisClient *c, int notify); static int pubsubUnsubscribeAllChannels(redisClient *c, int notify);
static int pubsubUnsubscribeAllPatterns(redisClient *c, int notify); static int pubsubUnsubscribeAllPatterns(redisClient *c, int notify);
static void freePubsubPattern(void *p); static void freePubsubPattern(void *p);
...@@ -1752,6 +1760,8 @@ static void initServerConfig() { ...@@ -1752,6 +1760,8 @@ static void initServerConfig() {
server.vm_blocked_clients = 0; server.vm_blocked_clients = 0;
server.hash_max_zipmap_entries = REDIS_HASH_MAX_ZIPMAP_ENTRIES; server.hash_max_zipmap_entries = REDIS_HASH_MAX_ZIPMAP_ENTRIES;
server.hash_max_zipmap_value = REDIS_HASH_MAX_ZIPMAP_VALUE; server.hash_max_zipmap_value = REDIS_HASH_MAX_ZIPMAP_VALUE;
server.list_max_ziplist_entries = REDIS_LIST_MAX_ZIPLIST_ENTRIES;
server.list_max_ziplist_value = REDIS_LIST_MAX_ZIPLIST_VALUE;
server.shutdown_asap = 0; server.shutdown_asap = 0;
resetServerSaveParams(); resetServerSaveParams();
...@@ -2030,6 +2040,10 @@ static void loadServerConfig(char *filename) { ...@@ -2030,6 +2040,10 @@ static void loadServerConfig(char *filename) {
server.hash_max_zipmap_entries = memtoll(argv[1], NULL); server.hash_max_zipmap_entries = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"hash-max-zipmap-value") && argc == 2){ } else if (!strcasecmp(argv[0],"hash-max-zipmap-value") && argc == 2){
server.hash_max_zipmap_value = memtoll(argv[1], NULL); server.hash_max_zipmap_value = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
server.list_max_ziplist_entries = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2){
server.list_max_ziplist_value = memtoll(argv[1], NULL);
} else { } else {
err = "Bad directive or wrong number of arguments"; goto loaderr; err = "Bad directive or wrong number of arguments"; goto loaderr;
} }
...@@ -3035,9 +3049,17 @@ static robj *dupStringObject(robj *o) { ...@@ -3035,9 +3049,17 @@ static robj *dupStringObject(robj *o) {
static robj *createListObject(void) { static robj *createListObject(void) {
list *l = listCreate(); list *l = listCreate();
robj *o = createObject(REDIS_LIST,l);
listSetFreeMethod(l,decrRefCount); listSetFreeMethod(l,decrRefCount);
return createObject(REDIS_LIST,l); o->encoding = REDIS_ENCODING_LIST;
return o;
}
static robj *createZiplistObject(void) {
unsigned char *zl = ziplistNew();
robj *o = createObject(REDIS_LIST,zl);
o->encoding = REDIS_ENCODING_ZIPLIST;
return o;
} }
static robj *createSetObject(void) { static robj *createSetObject(void) {
...@@ -3070,7 +3092,16 @@ static void freeStringObject(robj *o) { ...@@ -3070,7 +3092,16 @@ static void freeStringObject(robj *o) {
} }
static void freeListObject(robj *o) { static void freeListObject(robj *o) {
listRelease((list*) o->ptr); switch (o->encoding) {
case REDIS_ENCODING_LIST:
listRelease((list*) o->ptr);
break;
case REDIS_ENCODING_ZIPLIST:
zfree(o->ptr);
break;
default:
redisPanic("Unknown list encoding type");
}
} }
static void freeSetObject(robj *o) { static void freeSetObject(robj *o) {
...@@ -3644,38 +3675,32 @@ static int rdbSaveRawString(FILE *fp, unsigned char *s, size_t len) { ...@@ -3644,38 +3675,32 @@ static int rdbSaveRawString(FILE *fp, unsigned char *s, size_t len) {
return 0; return 0;
} }
/* Save a long long value as either an encoded string or a string. */
static int rdbSaveLongLongAsStringObject(FILE *fp, long long value) {
unsigned char buf[32];
int enclen = rdbEncodeInteger(value,buf);
if (enclen > 0) {
if (fwrite(buf,enclen,1,fp) == 0) return -1;
} else {
/* Encode as string */
enclen = ll2string((char*)buf,32,value);
redisAssert(enclen < 32);
if (rdbSaveLen(fp,enclen) == -1) return -1;
if (fwrite(buf,enclen,1,fp) == 0) return -1;
}
return 0;
}
/* Like rdbSaveStringObjectRaw() but handle encoded objects */ /* Like rdbSaveStringObjectRaw() but handle encoded objects */
static int rdbSaveStringObject(FILE *fp, robj *obj) { static int rdbSaveStringObject(FILE *fp, robj *obj) {
int retval;
/* Avoid to decode the object, then encode it again, if the /* Avoid to decode the object, then encode it again, if the
* object is alrady integer encoded. */ * object is alrady integer encoded. */
if (obj->encoding == REDIS_ENCODING_INT) { if (obj->encoding == REDIS_ENCODING_INT) {
long val = (long) obj->ptr; return rdbSaveLongLongAsStringObject(fp,(long)obj->ptr);
unsigned char buf[5];
int enclen;
if ((enclen = rdbEncodeInteger(val,buf)) > 0) {
if (fwrite(buf,enclen,1,fp) == 0) return -1;
return 0;
}
/* otherwise... fall throught and continue with the usual
* code path. */
}
/* Avoid incr/decr ref count business when possible.
* This plays well with copy-on-write given that we are probably
* in a child process (BGSAVE). Also this makes sure key objects
* of swapped objects are not incRefCount-ed (an assert does not allow
* this in order to avoid bugs) */
if (obj->encoding != REDIS_ENCODING_RAW) {
obj = getDecodedObject(obj);
retval = rdbSaveRawString(fp,obj->ptr,sdslen(obj->ptr));
decrRefCount(obj);
} else { } else {
retval = rdbSaveRawString(fp,obj->ptr,sdslen(obj->ptr)); redisAssert(obj->encoding == REDIS_ENCODING_RAW);
return rdbSaveRawString(fp,obj->ptr,sdslen(obj->ptr));
} }
return retval;
} }
/* Save a double value. Doubles are saved as strings prefixed by an unsigned /* Save a double value. Doubles are saved as strings prefixed by an unsigned
...@@ -3728,16 +3753,37 @@ static int rdbSaveObject(FILE *fp, robj *o) { ...@@ -3728,16 +3753,37 @@ static int rdbSaveObject(FILE *fp, robj *o) {
if (rdbSaveStringObject(fp,o) == -1) return -1; if (rdbSaveStringObject(fp,o) == -1) return -1;
} else if (o->type == REDIS_LIST) { } else if (o->type == REDIS_LIST) {
/* Save a list value */ /* Save a list value */
list *list = o->ptr; if (o->encoding == REDIS_ENCODING_ZIPLIST) {
listIter li; unsigned char *p;
listNode *ln; unsigned char *vstr;
unsigned int vlen;
if (rdbSaveLen(fp,listLength(list)) == -1) return -1; long long vlong;
listRewind(list,&li);
while((ln = listNext(&li))) { if (rdbSaveLen(fp,ziplistLen(o->ptr)) == -1) return -1;
robj *eleobj = listNodeValue(ln); p = ziplistIndex(o->ptr,0);
while(ziplistGet(p,&vstr,&vlen,&vlong)) {
if (vstr) {
if (rdbSaveRawString(fp,vstr,vlen) == -1)
return -1;
} else {
if (rdbSaveLongLongAsStringObject(fp,vlong) == -1)
return -1;
}
p = ziplistNext(o->ptr,p);
}
} else if (o->encoding == REDIS_ENCODING_LIST) {
list *list = o->ptr;
listIter li;
listNode *ln;
if (rdbSaveStringObject(fp,eleobj) == -1) return -1; if (rdbSaveLen(fp,listLength(list)) == -1) return -1;
listRewind(list,&li);
while((ln = listNext(&li))) {
robj *eleobj = listNodeValue(ln);
if (rdbSaveStringObject(fp,eleobj) == -1) return -1;
}
} else {
redisPanic("Unknown list encoding");
} }
} else if (o->type == REDIS_SET) { } else if (o->type == REDIS_SET) {
/* Save a set value */ /* Save a set value */
...@@ -4107,34 +4153,60 @@ static int rdbLoadDoubleValue(FILE *fp, double *val) { ...@@ -4107,34 +4153,60 @@ static int rdbLoadDoubleValue(FILE *fp, double *val) {
/* Load a Redis object of the specified type from the specified file. /* Load a Redis object of the specified type from the specified file.
* On success a newly allocated object is returned, otherwise NULL. */ * On success a newly allocated object is returned, otherwise NULL. */
static robj *rdbLoadObject(int type, FILE *fp) { static robj *rdbLoadObject(int type, FILE *fp) {
robj *o; robj *o, *ele, *dec;
size_t len;
redisLog(REDIS_DEBUG,"LOADING OBJECT %d (at %d)\n",type,ftell(fp)); redisLog(REDIS_DEBUG,"LOADING OBJECT %d (at %d)\n",type,ftell(fp));
if (type == REDIS_STRING) { if (type == REDIS_STRING) {
/* Read string value */ /* Read string value */
if ((o = rdbLoadEncodedStringObject(fp)) == NULL) return NULL; if ((o = rdbLoadEncodedStringObject(fp)) == NULL) return NULL;
o = tryObjectEncoding(o); o = tryObjectEncoding(o);
} else if (type == REDIS_LIST || type == REDIS_SET) { } else if (type == REDIS_LIST) {
/* Read list/set value */ /* Read list value */
uint32_t listlen; if ((len = rdbLoadLen(fp,NULL)) == REDIS_RDB_LENERR) return NULL;
if ((listlen = rdbLoadLen(fp,NULL)) == REDIS_RDB_LENERR) return NULL; /* Use a real list when there are too many entries */
o = (type == REDIS_LIST) ? createListObject() : createSetObject(); if (len > server.list_max_ziplist_entries) {
o = createListObject();
} else {
o = createZiplistObject();
}
/* Load every single element of the list */
while(len--) {
if ((ele = rdbLoadEncodedStringObject(fp)) == NULL) return NULL;
/* If we are using a ziplist and the value is too big, convert
* the object to a real list. */
if (o->encoding == REDIS_ENCODING_ZIPLIST &&
ele->encoding == REDIS_ENCODING_RAW &&
sdslen(ele->ptr) > server.list_max_ziplist_value)
listTypeConvert(o,REDIS_ENCODING_LIST);
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
dec = getDecodedObject(ele);
o->ptr = ziplistPush(o->ptr,dec->ptr,sdslen(dec->ptr),REDIS_TAIL);
decrRefCount(dec);
decrRefCount(ele);
} else {
ele = tryObjectEncoding(ele);
listAddNodeTail(o->ptr,ele);
incrRefCount(ele);
}
}
} else if (type == REDIS_SET) {
/* Read list/set value */
if ((len = rdbLoadLen(fp,NULL)) == REDIS_RDB_LENERR) return NULL;
o = createSetObject();
/* It's faster to expand the dict to the right size asap in order /* It's faster to expand the dict to the right size asap in order
* to avoid rehashing */ * to avoid rehashing */
if (type == REDIS_SET && listlen > DICT_HT_INITIAL_SIZE) if (len > DICT_HT_INITIAL_SIZE)
dictExpand(o->ptr,listlen); dictExpand(o->ptr,len);
/* Load every single element of the list/set */ /* Load every single element of the list/set */
while(listlen--) { while(len--) {
robj *ele;
if ((ele = rdbLoadEncodedStringObject(fp)) == NULL) return NULL; if ((ele = rdbLoadEncodedStringObject(fp)) == NULL) return NULL;
ele = tryObjectEncoding(ele); ele = tryObjectEncoding(ele);
if (type == REDIS_LIST) { dictAdd((dict*)o->ptr,ele,NULL);
listAddNodeTail((list*)o->ptr,ele);
} else {
dictAdd((dict*)o->ptr,ele,NULL);
}
} }
} else if (type == REDIS_ZSET) { } else if (type == REDIS_ZSET) {
/* Read list/set value */ /* Read list/set value */
...@@ -4833,24 +4905,251 @@ static void moveCommand(redisClient *c) { ...@@ -4833,24 +4905,251 @@ static void moveCommand(redisClient *c) {
} }
/* =================================== Lists ================================ */ /* =================================== Lists ================================ */
static void pushGenericCommand(redisClient *c, int where) {
robj *lobj;
list *list;
lobj = lookupKeyWrite(c->db,c->argv[1]);
/* Check the argument length to see if it requires us to convert the ziplist
* to a real list. Only check raw-encoded objects because integer encoded
* objects are never too long. */
static void listTypeTryConversion(robj *subject, robj *value) {
if (subject->encoding != REDIS_ENCODING_ZIPLIST) return;
if (value->encoding == REDIS_ENCODING_RAW &&
sdslen(value->ptr) > server.list_max_ziplist_value)
listTypeConvert(subject,REDIS_ENCODING_LIST);
}
static void listTypePush(robj *subject, robj *value, int where) {
/* Check if we need to convert the ziplist */
listTypeTryConversion(subject,value);
if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
ziplistLen(subject->ptr) > server.list_max_ziplist_entries)
listTypeConvert(subject,REDIS_ENCODING_LIST);
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
int pos = (where == REDIS_HEAD) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
value = getDecodedObject(value);
subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),pos);
decrRefCount(value);
} else if (subject->encoding == REDIS_ENCODING_LIST) {
if (where == REDIS_HEAD) {
listAddNodeHead(subject->ptr,value);
} else {
listAddNodeTail(subject->ptr,value);
}
incrRefCount(value);
} else {
redisPanic("Unknown list encoding");
}
}
static robj *listTypePop(robj *subject, int where) {
robj *value = NULL;
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *p;
unsigned char *vstr;
unsigned int vlen;
long long vlong;
int pos = (where == REDIS_HEAD) ? 0 : -1;
p = ziplistIndex(subject->ptr,pos);
if (ziplistGet(p,&vstr,&vlen,&vlong)) {
if (vstr) {
value = createStringObject((char*)vstr,vlen);
} else {
value = createStringObjectFromLongLong(vlong);
}
/* We only need to delete an element when it exists */
subject->ptr = ziplistDelete(subject->ptr,&p);
}
} else if (subject->encoding == REDIS_ENCODING_LIST) {
list *list = subject->ptr;
listNode *ln;
if (where == REDIS_HEAD) {
ln = listFirst(list);
} else {
ln = listLast(list);
}
if (ln != NULL) {
value = listNodeValue(ln);
incrRefCount(value);
listDelNode(list,ln);
}
} else {
redisPanic("Unknown list encoding");
}
return value;
}
static unsigned long listTypeLength(robj *subject) {
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
return ziplistLen(subject->ptr);
} else if (subject->encoding == REDIS_ENCODING_LIST) {
return listLength((list*)subject->ptr);
} else {
redisPanic("Unknown list encoding");
}
}
/* Structure to hold set iteration abstraction. */
typedef struct {
robj *subject;
unsigned char encoding;
unsigned char direction; /* Iteration direction */
unsigned char *zi;
listNode *ln;
} listTypeIterator;
/* Structure for an entry while iterating over a list. */
typedef struct {
listTypeIterator *li;
unsigned char *zi; /* Entry in ziplist */
listNode *ln; /* Entry in linked list */
} listTypeEntry;
/* Initialize an iterator at the specified index. */
static listTypeIterator *listTypeInitIterator(robj *subject, int index, unsigned char direction) {
listTypeIterator *li = zmalloc(sizeof(listTypeIterator));
li->subject = subject;
li->encoding = subject->encoding;
li->direction = direction;
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
li->zi = ziplistIndex(subject->ptr,index);
} else if (li->encoding == REDIS_ENCODING_LIST) {
li->ln = listIndex(subject->ptr,index);
} else {
redisPanic("Unknown list encoding");
}
return li;
}
/* Clean up the iterator. */
static void listTypeReleaseIterator(listTypeIterator *li) {
zfree(li);
}
/* Stores pointer to current the entry in the provided entry structure
* and advances the position of the iterator. Returns 1 when the current
* entry is in fact an entry, 0 otherwise. */
static int listTypeNext(listTypeIterator *li, listTypeEntry *entry) {
/* Protect from converting when iterating */
redisAssert(li->subject->encoding == li->encoding);
entry->li = li;
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
entry->zi = li->zi;
if (entry->zi != NULL) {
if (li->direction == REDIS_TAIL)
li->zi = ziplistNext(li->subject->ptr,li->zi);
else
li->zi = ziplistPrev(li->subject->ptr,li->zi);
return 1;
}
} else if (li->encoding == REDIS_ENCODING_LIST) {
entry->ln = li->ln;
if (entry->ln != NULL) {
if (li->direction == REDIS_TAIL)
li->ln = li->ln->next;
else
li->ln = li->ln->prev;
return 1;
}
} else {
redisPanic("Unknown list encoding");
}
return 0;
}
/* Return entry or NULL at the current position of the iterator. */
static robj *listTypeGet(listTypeEntry *entry) {
listTypeIterator *li = entry->li;
robj *value = NULL;
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *vstr;
unsigned int vlen;
long long vlong;
redisAssert(entry->zi != NULL);
if (ziplistGet(entry->zi,&vstr,&vlen,&vlong)) {
if (vstr) {
value = createStringObject((char*)vstr,vlen);
} else {
value = createStringObjectFromLongLong(vlong);
}
}
} else if (li->encoding == REDIS_ENCODING_LIST) {
redisAssert(entry->ln != NULL);
value = listNodeValue(entry->ln);
incrRefCount(value);
} else {
redisPanic("Unknown list encoding");
}
return value;
}
/* Compare the given object with the entry at the current position. */
static int listTypeEqual(listTypeEntry *entry, robj *o) {
listTypeIterator *li = entry->li;
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
redisAssert(o->encoding == REDIS_ENCODING_RAW);
return ziplistCompare(entry->zi,o->ptr,sdslen(o->ptr));
} else if (li->encoding == REDIS_ENCODING_LIST) {
return equalStringObjects(o,listNodeValue(entry->ln));
} else {
redisPanic("Unknown list encoding");
}
}
/* Delete the element pointed to. */
static void listTypeDelete(listTypeEntry *entry) {
listTypeIterator *li = entry->li;
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *p = entry->zi;
li->subject->ptr = ziplistDelete(li->subject->ptr,&p);
/* Update position of the iterator depending on the direction */
if (li->direction == REDIS_TAIL)
li->zi = p;
else
li->zi = ziplistPrev(li->subject->ptr,p);
} else if (entry->li->encoding == REDIS_ENCODING_LIST) {
listNode *next;
if (li->direction == REDIS_TAIL)
next = entry->ln->next;
else
next = entry->ln->prev;
listDelNode(li->subject->ptr,entry->ln);
li->ln = next;
} else {
redisPanic("Unknown list encoding");
}
}
static void listTypeConvert(robj *subject, int enc) {
listTypeIterator *li;
listTypeEntry entry;
redisAssert(subject->type == REDIS_LIST);
if (enc == REDIS_ENCODING_LIST) {
list *l = listCreate();
/* listTypeGet returns a robj with incremented refcount */
li = listTypeInitIterator(subject,0,REDIS_TAIL);
while (listTypeNext(li,&entry)) listAddNodeTail(l,listTypeGet(&entry));
listTypeReleaseIterator(li);
subject->encoding = REDIS_ENCODING_LIST;
zfree(subject->ptr);
subject->ptr = l;
} else {
redisPanic("Unsupported list conversion");
}
}
static void pushGenericCommand(redisClient *c, int where) {
robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
if (lobj == NULL) { if (lobj == NULL) {
if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) { if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) {
addReply(c,shared.cone); addReply(c,shared.cone);
return; return;
} }
lobj = createListObject(); lobj = createZiplistObject();
list = lobj->ptr;
if (where == REDIS_HEAD) {
listAddNodeHead(list,c->argv[2]);
} else {
listAddNodeTail(list,c->argv[2]);
}
incrRefCount(c->argv[2]);
dbAdd(c->db,c->argv[1],lobj); dbAdd(c->db,c->argv[1],lobj);
} else { } else {
if (lobj->type != REDIS_LIST) { if (lobj->type != REDIS_LIST) {
...@@ -4861,16 +5160,10 @@ static void pushGenericCommand(redisClient *c, int where) { ...@@ -4861,16 +5160,10 @@ static void pushGenericCommand(redisClient *c, int where) {
addReply(c,shared.cone); addReply(c,shared.cone);
return; return;
} }
list = lobj->ptr;
if (where == REDIS_HEAD) {
listAddNodeHead(list,c->argv[2]);
} else {
listAddNodeTail(list,c->argv[2]);
}
incrRefCount(c->argv[2]);
} }
listTypePush(lobj,c->argv[2],where);
addReplyLongLong(c,listTypeLength(lobj));
server.dirty++; server.dirty++;
addReplyLongLong(c,listLength(list));
} }
static void lpushCommand(redisClient *c) { static void lpushCommand(redisClient *c) {
...@@ -4882,80 +5175,94 @@ static void rpushCommand(redisClient *c) { ...@@ -4882,80 +5175,94 @@ static void rpushCommand(redisClient *c) {
} }
static void llenCommand(redisClient *c) { static void llenCommand(redisClient *c) {
robj *o; robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.czero);
list *l; if (o == NULL || checkType(c,o,REDIS_LIST)) return;
addReplyUlong(c,listTypeLength(o));
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_LIST)) return;
l = o->ptr;
addReplyUlong(c,listLength(l));
} }
static void lindexCommand(redisClient *c) { static void lindexCommand(redisClient *c) {
robj *o; robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk);
if (o == NULL || checkType(c,o,REDIS_LIST)) return;
int index = atoi(c->argv[2]->ptr); int index = atoi(c->argv[2]->ptr);
list *list; robj *value = NULL;
listNode *ln;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,o,REDIS_LIST)) return;
list = o->ptr;
ln = listIndex(list, index); if (o->encoding == REDIS_ENCODING_ZIPLIST) {
if (ln == NULL) { unsigned char *p;
addReply(c,shared.nullbulk); unsigned char *vstr;
unsigned int vlen;
long long vlong;
p = ziplistIndex(o->ptr,index);
if (ziplistGet(p,&vstr,&vlen,&vlong)) {
if (vstr) {
value = createStringObject((char*)vstr,vlen);
} else {
value = createStringObjectFromLongLong(vlong);
}
addReplyBulk(c,value);
decrRefCount(value);
} else {
addReply(c,shared.nullbulk);
}
} else if (o->encoding == REDIS_ENCODING_LIST) {
listNode *ln = listIndex(o->ptr,index);
if (ln != NULL) {
value = listNodeValue(ln);
addReplyBulk(c,value);
} else {
addReply(c,shared.nullbulk);
}
} else { } else {
robj *ele = listNodeValue(ln); redisPanic("Unknown list encoding");
addReplyBulk(c,ele);
} }
} }
static void lsetCommand(redisClient *c) { static void lsetCommand(redisClient *c) {
robj *o; robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);
if (o == NULL || checkType(c,o,REDIS_LIST)) return;
int index = atoi(c->argv[2]->ptr); int index = atoi(c->argv[2]->ptr);
list *list; robj *value = c->argv[3];
listNode *ln;
listTypeTryConversion(o,value);
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL || if (o->encoding == REDIS_ENCODING_ZIPLIST) {
checkType(c,o,REDIS_LIST)) return; unsigned char *p, *zl = o->ptr;
list = o->ptr; p = ziplistIndex(zl,index);
if (p == NULL) {
ln = listIndex(list, index); addReply(c,shared.outofrangeerr);
if (ln == NULL) { } else {
addReply(c,shared.outofrangeerr); o->ptr = ziplistDelete(o->ptr,&p);
value = getDecodedObject(value);
o->ptr = ziplistInsert(o->ptr,p,value->ptr,sdslen(value->ptr));
decrRefCount(value);
addReply(c,shared.ok);
server.dirty++;
}
} else if (o->encoding == REDIS_ENCODING_LIST) {
listNode *ln = listIndex(o->ptr,index);
if (ln == NULL) {
addReply(c,shared.outofrangeerr);
} else {
decrRefCount((robj*)listNodeValue(ln));
listNodeValue(ln) = value;
incrRefCount(value);
addReply(c,shared.ok);
server.dirty++;
}
} else { } else {
robj *ele = listNodeValue(ln); redisPanic("Unknown list encoding");
decrRefCount(ele);
listNodeValue(ln) = c->argv[3];
incrRefCount(c->argv[3]);
addReply(c,shared.ok);
server.dirty++;
} }
} }
static void popGenericCommand(redisClient *c, int where) { static void popGenericCommand(redisClient *c, int where) {
robj *o; robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk);
list *list; if (o == NULL || checkType(c,o,REDIS_LIST)) return;
listNode *ln;
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,o,REDIS_LIST)) return;
list = o->ptr;
if (where == REDIS_HEAD)
ln = listFirst(list);
else
ln = listLast(list);
if (ln == NULL) { robj *value = listTypePop(o,where);
if (value == NULL) {
addReply(c,shared.nullbulk); addReply(c,shared.nullbulk);
} else { } else {
robj *ele = listNodeValue(ln); addReplyBulk(c,value);
addReplyBulk(c,ele); decrRefCount(value);
listDelNode(list,ln); if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
if (listLength(list) == 0) dbDelete(c->db,c->argv[1]);
server.dirty++; server.dirty++;
} }
} }
...@@ -4969,19 +5276,16 @@ static void rpopCommand(redisClient *c) { ...@@ -4969,19 +5276,16 @@ static void rpopCommand(redisClient *c) {
} }
static void lrangeCommand(redisClient *c) { static void lrangeCommand(redisClient *c) {
robj *o; robj *o, *value;
int start = atoi(c->argv[2]->ptr); int start = atoi(c->argv[2]->ptr);
int end = atoi(c->argv[3]->ptr); int end = atoi(c->argv[3]->ptr);
int llen; int llen;
int rangelen, j; int rangelen, j;
list *list; listTypeEntry entry;
listNode *ln;
robj *ele;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|| checkType(c,o,REDIS_LIST)) return; || checkType(c,o,REDIS_LIST)) return;
list = o->ptr; llen = listTypeLength(o);
llen = listLength(list);
/* convert negative indexes */ /* convert negative indexes */
if (start < 0) start = llen+start; if (start < 0) start = llen+start;
...@@ -4999,13 +5303,15 @@ static void lrangeCommand(redisClient *c) { ...@@ -4999,13 +5303,15 @@ static void lrangeCommand(redisClient *c) {
rangelen = (end-start)+1; rangelen = (end-start)+1;
/* Return the result in form of a multi-bulk reply */ /* Return the result in form of a multi-bulk reply */
ln = listIndex(list, start);
addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",rangelen)); addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",rangelen));
listTypeIterator *li = listTypeInitIterator(o,start,REDIS_TAIL);
for (j = 0; j < rangelen; j++) { for (j = 0; j < rangelen; j++) {
ele = listNodeValue(ln); redisAssert(listTypeNext(li,&entry));
addReplyBulk(c,ele); value = listTypeGet(&entry);
ln = ln->next; addReplyBulk(c,value);
decrRefCount(value);
} }
listTypeReleaseIterator(li);
} }
static void ltrimCommand(redisClient *c) { static void ltrimCommand(redisClient *c) {
...@@ -5019,8 +5325,7 @@ static void ltrimCommand(redisClient *c) { ...@@ -5019,8 +5325,7 @@ static void ltrimCommand(redisClient *c) {
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.ok)) == NULL || if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.ok)) == NULL ||
checkType(c,o,REDIS_LIST)) return; checkType(c,o,REDIS_LIST)) return;
list = o->ptr; llen = listTypeLength(o);
llen = listLength(list);
/* convert negative indexes */ /* convert negative indexes */
if (start < 0) start = llen+start; if (start < 0) start = llen+start;
...@@ -5040,49 +5345,63 @@ static void ltrimCommand(redisClient *c) { ...@@ -5040,49 +5345,63 @@ static void ltrimCommand(redisClient *c) {
} }
/* Remove list elements to perform the trim */ /* Remove list elements to perform the trim */
for (j = 0; j < ltrim; j++) { if (o->encoding == REDIS_ENCODING_ZIPLIST) {
ln = listFirst(list); o->ptr = ziplistDeleteRange(o->ptr,0,ltrim);
listDelNode(list,ln); o->ptr = ziplistDeleteRange(o->ptr,-rtrim,rtrim);
} } else if (o->encoding == REDIS_ENCODING_LIST) {
for (j = 0; j < rtrim; j++) { list = o->ptr;
ln = listLast(list); for (j = 0; j < ltrim; j++) {
listDelNode(list,ln); ln = listFirst(list);
listDelNode(list,ln);
}
for (j = 0; j < rtrim; j++) {
ln = listLast(list);
listDelNode(list,ln);
}
} else {
redisPanic("Unknown list encoding");
} }
if (listLength(list) == 0) dbDelete(c->db,c->argv[1]); if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
server.dirty++; server.dirty++;
addReply(c,shared.ok); addReply(c,shared.ok);
} }
static void lremCommand(redisClient *c) { static void lremCommand(redisClient *c) {
robj *o; robj *subject, *obj = c->argv[3];
list *list;
listNode *ln, *next;
int toremove = atoi(c->argv[2]->ptr); int toremove = atoi(c->argv[2]->ptr);
int removed = 0; int removed = 0;
int fromtail = 0; listTypeEntry entry;
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero);
checkType(c,o,REDIS_LIST)) return; if (subject == NULL || checkType(c,subject,REDIS_LIST)) return;
list = o->ptr;
/* Make sure obj is raw when we're dealing with a ziplist */
if (subject->encoding == REDIS_ENCODING_ZIPLIST)
obj = getDecodedObject(obj);
listTypeIterator *li;
if (toremove < 0) { if (toremove < 0) {
toremove = -toremove; toremove = -toremove;
fromtail = 1; li = listTypeInitIterator(subject,-1,REDIS_HEAD);
} else {
li = listTypeInitIterator(subject,0,REDIS_TAIL);
} }
ln = fromtail ? list->tail : list->head;
while (ln) {
robj *ele = listNodeValue(ln);
next = fromtail ? ln->prev : ln->next; while (listTypeNext(li,&entry)) {
if (equalStringObjects(ele,c->argv[3])) { if (listTypeEqual(&entry,obj)) {
listDelNode(list,ln); listTypeDelete(&entry);
server.dirty++; server.dirty++;
removed++; removed++;
if (toremove && removed == toremove) break; if (toremove && removed == toremove) break;
} }
ln = next;
} }
if (listLength(list) == 0) dbDelete(c->db,c->argv[1]); listTypeReleaseIterator(li);
/* Clean up raw encoded object */
if (subject->encoding == REDIS_ENCODING_ZIPLIST)
decrRefCount(obj);
if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",removed)); addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",removed));
} }
...@@ -5102,46 +5421,36 @@ static void lremCommand(redisClient *c) { ...@@ -5102,46 +5421,36 @@ static void lremCommand(redisClient *c) {
* as well. This command was originally proposed by Ezra Zygmuntowicz. * as well. This command was originally proposed by Ezra Zygmuntowicz.
*/ */
static void rpoplpushcommand(redisClient *c) { static void rpoplpushcommand(redisClient *c) {
robj *sobj; robj *sobj, *value;
list *srclist;
listNode *ln;
if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL || if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,sobj,REDIS_LIST)) return; checkType(c,sobj,REDIS_LIST)) return;
srclist = sobj->ptr;
ln = listLast(srclist);
if (ln == NULL) { if (listTypeLength(sobj) == 0) {
addReply(c,shared.nullbulk); addReply(c,shared.nullbulk);
} else { } else {
robj *dobj = lookupKeyWrite(c->db,c->argv[2]); robj *dobj = lookupKeyWrite(c->db,c->argv[2]);
robj *ele = listNodeValue(ln); if (dobj && checkType(c,dobj,REDIS_LIST)) return;
list *dstlist; value = listTypePop(sobj,REDIS_TAIL);
if (dobj && dobj->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerr);
return;
}
/* Add the element to the target list (unless it's directly /* Add the element to the target list (unless it's directly
* passed to some BLPOP-ing client */ * passed to some BLPOP-ing client */
if (!handleClientsWaitingListPush(c,c->argv[2],ele)) { if (!handleClientsWaitingListPush(c,c->argv[2],value)) {
if (dobj == NULL) { /* Create the list if the key does not exist */
/* Create the list if the key does not exist */ if (!dobj) {
dobj = createListObject(); dobj = createZiplistObject();
dbAdd(c->db,c->argv[2],dobj); dbAdd(c->db,c->argv[2],dobj);
} }
dstlist = dobj->ptr; listTypePush(dobj,value,REDIS_HEAD);
listAddNodeHead(dstlist,ele);
incrRefCount(ele);
} }
/* Send the element to the client as reply as well */ /* Send the element to the client as reply as well */
addReplyBulk(c,ele); addReplyBulk(c,value);
/* Finally remove the element from the source list */ /* listTypePop returns an object with its refcount incremented */
listDelNode(srclist,ln); decrRefCount(value);
if (listLength(srclist) == 0) dbDelete(c->db,c->argv[1]);
/* Delete the source list when it is empty */
if (listTypeLength(sobj) == 0) dbDelete(c->db,c->argv[1]);
server.dirty++; server.dirty++;
} }
} }
...@@ -5764,7 +6073,7 @@ static zskiplistNode *zslFirstWithScore(zskiplist *zsl, double score) { ...@@ -5764,7 +6073,7 @@ static zskiplistNode *zslFirstWithScore(zskiplist *zsl, double score) {
* Returns 0 when the element cannot be found, rank otherwise. * Returns 0 when the element cannot be found, rank otherwise.
* Note that the rank is 1-based due to the span of zsl->header to the * Note that the rank is 1-based due to the span of zsl->header to the
* first element. */ * first element. */
static unsigned long zslGetRank(zskiplist *zsl, double score, robj *o) { static unsigned long zslistTypeGetRank(zskiplist *zsl, double score, robj *o) {
zskiplistNode *x; zskiplistNode *x;
unsigned long rank = 0; unsigned long rank = 0;
int i; int i;
...@@ -5788,7 +6097,7 @@ static unsigned long zslGetRank(zskiplist *zsl, double score, robj *o) { ...@@ -5788,7 +6097,7 @@ static unsigned long zslGetRank(zskiplist *zsl, double score, robj *o) {
} }
/* Finds an element by its rank. The rank argument needs to be 1-based. */ /* Finds an element by its rank. The rank argument needs to be 1-based. */
zskiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank) { zskiplistNode* zslistTypeGetElementByRank(zskiplist *zsl, unsigned long rank) {
zskiplistNode *x; zskiplistNode *x;
unsigned long traversed = 0; unsigned long traversed = 0;
int i; int i;
...@@ -6254,10 +6563,10 @@ static void zrangeGenericCommand(redisClient *c, int reverse) { ...@@ -6254,10 +6563,10 @@ static void zrangeGenericCommand(redisClient *c, int reverse) {
/* check if starting point is trivial, before searching /* check if starting point is trivial, before searching
* the element in log(N) time */ * the element in log(N) time */
if (reverse) { if (reverse) {
ln = start == 0 ? zsl->tail : zslGetElementByRank(zsl, llen-start); ln = start == 0 ? zsl->tail : zslistTypeGetElementByRank(zsl, llen-start);
} else { } else {
ln = start == 0 ? ln = start == 0 ?
zsl->header->forward[0] : zslGetElementByRank(zsl, start+1); zsl->header->forward[0] : zslistTypeGetElementByRank(zsl, start+1);
} }
/* Return the result in form of a multi-bulk reply */ /* Return the result in form of a multi-bulk reply */
...@@ -6454,7 +6763,7 @@ static void zrankGenericCommand(redisClient *c, int reverse) { ...@@ -6454,7 +6763,7 @@ static void zrankGenericCommand(redisClient *c, int reverse) {
} }
score = dictGetEntryVal(de); score = dictGetEntryVal(de);
rank = zslGetRank(zsl, *score, c->argv[2]); rank = zslistTypeGetRank(zsl, *score, c->argv[2]);
if (rank) { if (rank) {
if (reverse) { if (reverse) {
addReplyLongLong(c, zsl->length - rank); addReplyLongLong(c, zsl->length - rank);
...@@ -7031,7 +7340,7 @@ static int sortCompare(const void *s1, const void *s2) { ...@@ -7031,7 +7340,7 @@ static int sortCompare(const void *s1, const void *s2) {
* is optimized for speed and a bit less for readability */ * is optimized for speed and a bit less for readability */
static void sortCommand(redisClient *c) { static void sortCommand(redisClient *c) {
list *operations; list *operations;
int outputlen = 0; unsigned int outputlen = 0;
int desc = 0, alpha = 0; int desc = 0, alpha = 0;
int limit_start = 0, limit_count = -1, start, end; int limit_start = 0, limit_count = -1, start, end;
int j, dontsort = 0, vectorlen; int j, dontsort = 0, vectorlen;
...@@ -7101,7 +7410,7 @@ static void sortCommand(redisClient *c) { ...@@ -7101,7 +7410,7 @@ static void sortCommand(redisClient *c) {
/* Load the sorting vector with all the objects to sort */ /* Load the sorting vector with all the objects to sort */
switch(sortval->type) { switch(sortval->type) {
case REDIS_LIST: vectorlen = listLength((list*)sortval->ptr); break; case REDIS_LIST: vectorlen = listTypeLength(sortval); break;
case REDIS_SET: vectorlen = dictSize((dict*)sortval->ptr); break; case REDIS_SET: vectorlen = dictSize((dict*)sortval->ptr); break;
case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break; case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break;
default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */ default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */
...@@ -7110,18 +7419,15 @@ static void sortCommand(redisClient *c) { ...@@ -7110,18 +7419,15 @@ static void sortCommand(redisClient *c) {
j = 0; j = 0;
if (sortval->type == REDIS_LIST) { if (sortval->type == REDIS_LIST) {
list *list = sortval->ptr; listTypeIterator *li = listTypeInitIterator(sortval,0,REDIS_TAIL);
listNode *ln; listTypeEntry entry;
listIter li; while(listTypeNext(li,&entry)) {
vector[j].obj = listTypeGet(&entry);
listRewind(list,&li);
while((ln = listNext(&li))) {
robj *ele = ln->value;
vector[j].obj = ele;
vector[j].u.score = 0; vector[j].u.score = 0;
vector[j].u.cmpobj = NULL; vector[j].u.cmpobj = NULL;
j++; j++;
} }
listTypeReleaseIterator(li);
} else { } else {
dict *set; dict *set;
dictIterator *di; dictIterator *di;
...@@ -7231,8 +7537,7 @@ static void sortCommand(redisClient *c) { ...@@ -7231,8 +7537,7 @@ static void sortCommand(redisClient *c) {
} }
} }
} else { } else {
robj *listObject = createListObject(); robj *sobj = createZiplistObject();
list *listPtr = (list*) listObject->ptr;
/* STORE option specified, set the sorting result as a List object */ /* STORE option specified, set the sorting result as a List object */
for (j = start; j <= end; j++) { for (j = start; j <= end; j++) {
...@@ -7240,31 +7545,30 @@ static void sortCommand(redisClient *c) { ...@@ -7240,31 +7545,30 @@ static void sortCommand(redisClient *c) {
listIter li; listIter li;
if (!getop) { if (!getop) {
listAddNodeTail(listPtr,vector[j].obj); listTypePush(sobj,vector[j].obj,REDIS_TAIL);
incrRefCount(vector[j].obj); } else {
} listRewind(operations,&li);
listRewind(operations,&li); while((ln = listNext(&li))) {
while((ln = listNext(&li))) { redisSortOperation *sop = ln->value;
redisSortOperation *sop = ln->value; robj *val = lookupKeyByPattern(c->db,sop->pattern,
robj *val = lookupKeyByPattern(c->db,sop->pattern, vector[j].obj);
vector[j].obj);
if (sop->type == REDIS_SORT_GET) { if (sop->type == REDIS_SORT_GET) {
if (!val) { if (!val) val = createStringObject("",0);
listAddNodeTail(listPtr,createStringObject("",0));
/* listTypePush does an incrRefCount, so we should take care
* care of the incremented refcount caused by either
* lookupKeyByPattern or createStringObject("",0) */
listTypePush(sobj,val,REDIS_TAIL);
decrRefCount(val);
} else { } else {
/* We should do a incrRefCount on val because it is /* always fails */
* added to the list, but also a decrRefCount because redisAssert(sop->type == REDIS_SORT_GET);
* it is returned by lookupKeyByPattern. This results
* in doing nothing at all. */
listAddNodeTail(listPtr,val);
} }
} else {
redisAssert(sop->type == REDIS_SORT_GET); /* always fails */
} }
} }
} }
dbReplace(c->db,storekey,listObject); dbReplace(c->db,storekey,sobj);
/* Note: we add 1 because the DB is dirty anyway since even if the /* Note: we add 1 because the DB is dirty anyway since even if the
* SORT result is empty a new key is set and maybe the old content * SORT result is empty a new key is set and maybe the old content
* replaced. */ * replaced. */
...@@ -7273,6 +7577,9 @@ static void sortCommand(redisClient *c) { ...@@ -7273,6 +7577,9 @@ static void sortCommand(redisClient *c) {
} }
/* Cleanup */ /* Cleanup */
if (sortval->type == REDIS_LIST)
for (j = 0; j < vectorlen; j++)
decrRefCount(vector[j].obj);
decrRefCount(sortval); decrRefCount(sortval);
listRelease(operations); listRelease(operations);
for (j = 0; j < vectorlen; j++) { for (j = 0; j < vectorlen; j++) {
...@@ -8597,40 +8904,17 @@ fmterr: ...@@ -8597,40 +8904,17 @@ fmterr:
exit(1); exit(1);
} }
/* Write an object into a file in the bulk format $<count>\r\n<payload>\r\n */
static int fwriteBulkObject(FILE *fp, robj *obj) {
char buf[128];
int decrrc = 0;
/* Avoid the incr/decr ref count business if possible to help
* copy-on-write (we are often in a child process when this function
* is called).
* Also makes sure that key objects don't get incrRefCount-ed when VM
* is enabled */
if (obj->encoding != REDIS_ENCODING_RAW) {
obj = getDecodedObject(obj);
decrrc = 1;
}
snprintf(buf,sizeof(buf),"$%ld\r\n",(long)sdslen(obj->ptr));
if (fwrite(buf,strlen(buf),1,fp) == 0) goto err;
if (sdslen(obj->ptr) && fwrite(obj->ptr,sdslen(obj->ptr),1,fp) == 0)
goto err;
if (fwrite("\r\n",2,1,fp) == 0) goto err;
if (decrrc) decrRefCount(obj);
return 1;
err:
if (decrrc) decrRefCount(obj);
return 0;
}
/* Write binary-safe string into a file in the bulkformat /* Write binary-safe string into a file in the bulkformat
* $<count>\r\n<payload>\r\n */ * $<count>\r\n<payload>\r\n */
static int fwriteBulkString(FILE *fp, char *s, unsigned long len) { static int fwriteBulkString(FILE *fp, char *s, unsigned long len) {
char buf[128]; char cbuf[128];
int clen;
snprintf(buf,sizeof(buf),"$%ld\r\n",(unsigned long)len); cbuf[0] = '$';
if (fwrite(buf,strlen(buf),1,fp) == 0) return 0; clen = 1+ll2string(cbuf+1,sizeof(cbuf)-1,len);
if (len && fwrite(s,len,1,fp) == 0) return 0; cbuf[clen++] = '\r';
cbuf[clen++] = '\n';
if (fwrite(cbuf,clen,1,fp) == 0) return 0;
if (len > 0 && fwrite(s,len,1,fp) == 0) return 0;
if (fwrite("\r\n",2,1,fp) == 0) return 0; if (fwrite("\r\n",2,1,fp) == 0) return 0;
return 1; return 1;
} }
...@@ -8647,16 +8931,28 @@ static int fwriteBulkDouble(FILE *fp, double d) { ...@@ -8647,16 +8931,28 @@ static int fwriteBulkDouble(FILE *fp, double d) {
} }
/* Write a long value in bulk format $<count>\r\n<payload>\r\n */ /* Write a long value in bulk format $<count>\r\n<payload>\r\n */
static int fwriteBulkLong(FILE *fp, long l) { static int fwriteBulkLongLong(FILE *fp, long long l) {
char buf[128], lbuf[128]; char bbuf[128], lbuf[128];
unsigned int blen, llen;
snprintf(lbuf,sizeof(lbuf),"%ld\r\n",l); llen = ll2string(lbuf,32,l);
snprintf(buf,sizeof(buf),"$%lu\r\n",(unsigned long)strlen(lbuf)-2); blen = snprintf(bbuf,sizeof(bbuf),"$%u\r\n%s\r\n",llen,lbuf);
if (fwrite(buf,strlen(buf),1,fp) == 0) return 0; if (fwrite(bbuf,blen,1,fp) == 0) return 0;
if (fwrite(lbuf,strlen(lbuf),1,fp) == 0) return 0;
return 1; return 1;
} }
/* Delegate writing an object to writing a bulk string or bulk long long. */
static int fwriteBulkObject(FILE *fp, robj *obj) {
/* Avoid using getDecodedObject to help copy-on-write (we are often
* in a child process when this function is called). */
if (obj->encoding == REDIS_ENCODING_INT) {
return fwriteBulkLongLong(fp,(long)obj->ptr);
} else if (obj->encoding == REDIS_ENCODING_RAW) {
return fwriteBulkString(fp,obj->ptr,sdslen(obj->ptr));
} else {
redisPanic("Unknown string encoding");
}
}
/* Write a sequence of commands able to fully rebuild the dataset into /* Write a sequence of commands able to fully rebuild the dataset into
* "filename". Used both by REWRITEAOF and BGREWRITEAOF. */ * "filename". Used both by REWRITEAOF and BGREWRITEAOF. */
static int rewriteAppendOnlyFile(char *filename) { static int rewriteAppendOnlyFile(char *filename) {
...@@ -8688,7 +8984,7 @@ static int rewriteAppendOnlyFile(char *filename) { ...@@ -8688,7 +8984,7 @@ static int rewriteAppendOnlyFile(char *filename) {
/* SELECT the new DB */ /* SELECT the new DB */
if (fwrite(selectcmd,sizeof(selectcmd)-1,1,fp) == 0) goto werr; if (fwrite(selectcmd,sizeof(selectcmd)-1,1,fp) == 0) goto werr;
if (fwriteBulkLong(fp,j) == 0) goto werr; if (fwriteBulkLongLong(fp,j) == 0) goto werr;
/* Iterate this DB writing every entry */ /* Iterate this DB writing every entry */
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
...@@ -8723,18 +9019,41 @@ static int rewriteAppendOnlyFile(char *filename) { ...@@ -8723,18 +9019,41 @@ static int rewriteAppendOnlyFile(char *filename) {
if (fwriteBulkObject(fp,o) == 0) goto werr; if (fwriteBulkObject(fp,o) == 0) goto werr;
} else if (o->type == REDIS_LIST) { } else if (o->type == REDIS_LIST) {
/* Emit the RPUSHes needed to rebuild the list */ /* Emit the RPUSHes needed to rebuild the list */
list *list = o->ptr; char cmd[]="*3\r\n$5\r\nRPUSH\r\n";
listNode *ln; if (o->encoding == REDIS_ENCODING_ZIPLIST) {
listIter li; unsigned char *zl = o->ptr;
unsigned char *p = ziplistIndex(zl,0);
unsigned char *vstr;
unsigned int vlen;
long long vlong;
while(ziplistGet(p,&vstr,&vlen,&vlong)) {
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
if (fwriteBulkObject(fp,&key) == 0) goto werr;
if (vstr) {
if (fwriteBulkString(fp,(char*)vstr,vlen) == 0)
goto werr;
} else {
if (fwriteBulkLongLong(fp,vlong) == 0)
goto werr;
}
p = ziplistNext(zl,p);
}
} else if (o->encoding == REDIS_ENCODING_LIST) {
list *list = o->ptr;
listNode *ln;
listIter li;
listRewind(list,&li); listRewind(list,&li);
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
char cmd[]="*3\r\n$5\r\nRPUSH\r\n"; robj *eleobj = listNodeValue(ln);
robj *eleobj = listNodeValue(ln);
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr; if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
if (fwriteBulkObject(fp,&key) == 0) goto werr; if (fwriteBulkObject(fp,&key) == 0) goto werr;
if (fwriteBulkObject(fp,eleobj) == 0) goto werr; if (fwriteBulkObject(fp,eleobj) == 0) goto werr;
}
} else {
redisPanic("Unknown list encoding");
} }
} else if (o->type == REDIS_SET) { } else if (o->type == REDIS_SET) {
/* Emit the SADDs needed to rebuild the set */ /* Emit the SADDs needed to rebuild the set */
...@@ -8810,7 +9129,7 @@ static int rewriteAppendOnlyFile(char *filename) { ...@@ -8810,7 +9129,7 @@ static int rewriteAppendOnlyFile(char *filename) {
if (expiretime < now) continue; if (expiretime < now) continue;
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr; if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
if (fwriteBulkObject(fp,&key) == 0) goto werr; if (fwriteBulkObject(fp,&key) == 0) goto werr;
if (fwriteBulkLong(fp,expiretime) == 0) goto werr; if (fwriteBulkLongLong(fp,expiretime) == 0) goto werr;
} }
if (swapped) decrRefCount(o); if (swapped) decrRefCount(o);
} }
...@@ -10759,16 +11078,14 @@ static void computeDatasetDigest(unsigned char *final) { ...@@ -10759,16 +11078,14 @@ static void computeDatasetDigest(unsigned char *final) {
if (o->type == REDIS_STRING) { if (o->type == REDIS_STRING) {
mixObjectDigest(digest,o); mixObjectDigest(digest,o);
} else if (o->type == REDIS_LIST) { } else if (o->type == REDIS_LIST) {
list *list = o->ptr; listTypeIterator *li = listTypeInitIterator(o,0,REDIS_TAIL);
listNode *ln; listTypeEntry entry;
listIter li; while(listTypeNext(li,&entry)) {
robj *eleobj = listTypeGet(&entry);
listRewind(list,&li);
while((ln = listNext(&li))) {
robj *eleobj = listNodeValue(ln);
mixObjectDigest(digest,eleobj); mixObjectDigest(digest,eleobj);
decrRefCount(eleobj);
} }
listTypeReleaseIterator(li);
} else if (o->type == REDIS_SET) { } else if (o->type == REDIS_SET) {
dict *set = o->ptr; dict *set = o->ptr;
dictIterator *di = dictGetIterator(set); dictIterator *di = dictGetIterator(set);
......
...@@ -100,7 +100,7 @@ static struct redisFunctionSym symsTable[] = { ...@@ -100,7 +100,7 @@ static struct redisFunctionSym symsTable[] = {
{"freeStringObject",(unsigned long)freeStringObject}, {"freeStringObject",(unsigned long)freeStringObject},
{"freeZsetObject",(unsigned long)freeZsetObject}, {"freeZsetObject",(unsigned long)freeZsetObject},
{"fwriteBulkDouble",(unsigned long)fwriteBulkDouble}, {"fwriteBulkDouble",(unsigned long)fwriteBulkDouble},
{"fwriteBulkLong",(unsigned long)fwriteBulkLong}, {"fwriteBulkLongLong",(unsigned long)fwriteBulkLongLong},
{"fwriteBulkObject",(unsigned long)fwriteBulkObject}, {"fwriteBulkObject",(unsigned long)fwriteBulkObject},
{"fwriteBulkString",(unsigned long)fwriteBulkString}, {"fwriteBulkString",(unsigned long)fwriteBulkString},
{"genRedisInfoString",(unsigned long)genRedisInfoString}, {"genRedisInfoString",(unsigned long)genRedisInfoString},
......
/* Memory layout of a ziplist, containing "foo", "bar", "quux":
* <zlbytes><zllen><len>"foo"<len>"bar"<len>"quux"
*
* <zlbytes> is an unsigned integer to hold the number of bytes that
* the ziplist occupies. This is stored to not have to traverse the ziplist
* to know the new length when pushing.
*
* <zllen> is the number of items in the ziplist. When this value is
* greater than 254, we need to traverse the entire list to know
* how many items it holds.
*
* <len> is the number of bytes occupied by a single entry. When this
* number is greater than 253, the length will occupy 5 bytes, where
* the extra bytes contain an unsigned integer to hold the length.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <limits.h>
#include "zmalloc.h"
#include "sds.h"
#include "ziplist.h"
/* Important note: the ZIP_END value is used to depict the end of the
* ziplist structure. When a pointer contains an entry, the first couple
* of bytes contain the encoded length of the previous entry. This length
* is encoded as ZIP_ENC_RAW length, so the first two bits will contain 00
* and the byte will therefore never have a value of 255. */
#define ZIP_END 255
#define ZIP_BIGLEN 254
/* Entry encoding */
#define ZIP_ENC_RAW 0
#define ZIP_ENC_INT16 1
#define ZIP_ENC_INT32 2
#define ZIP_ENC_INT64 3
#define ZIP_ENCODING(p) ((p)[0] >> 6)
/* Length encoding for raw entries */
#define ZIP_LEN_INLINE 0
#define ZIP_LEN_UINT16 1
#define ZIP_LEN_UINT32 2
/* Utility macros */
#define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl)))
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
#define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
#define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t))
#define ZIPLIST_ENTRY_HEAD(zl) ((zl)+ZIPLIST_HEADER_SIZE)
#define ZIPLIST_ENTRY_TAIL(zl) ((zl)+ZIPLIST_TAIL_OFFSET(zl))
#define ZIPLIST_ENTRY_END(zl) ((zl)+ZIPLIST_BYTES(zl)-1)
/* We know a positive increment can only be 1 because entries can only be
* pushed one at a time. */
#define ZIPLIST_INCR_LENGTH(zl,incr) { \
if (ZIPLIST_LENGTH(zl) < UINT16_MAX) ZIPLIST_LENGTH(zl)+=incr; }
typedef struct zlentry {
unsigned int prevrawlensize, prevrawlen;
unsigned int lensize, len;
unsigned int headersize;
unsigned char encoding;
unsigned char *p;
} zlentry;
/* Return bytes needed to store integer encoded by 'encoding' */
static unsigned int zipEncodingSize(unsigned char encoding) {
if (encoding == ZIP_ENC_INT16) {
return sizeof(int16_t);
} else if (encoding == ZIP_ENC_INT32) {
return sizeof(int32_t);
} else if (encoding == ZIP_ENC_INT64) {
return sizeof(int64_t);
}
assert(NULL);
}
/* Decode the encoded length pointed by 'p'. If a pointer to 'lensize' is
* provided, it is set to the number of bytes required to encode the length. */
static unsigned int zipDecodeLength(unsigned char *p, unsigned int *lensize) {
unsigned char encoding = ZIP_ENCODING(p), lenenc;
unsigned int len;
if (encoding == ZIP_ENC_RAW) {
lenenc = (p[0] >> 4) & 0x3;
if (lenenc == ZIP_LEN_INLINE) {
len = p[0] & 0xf;
if (lensize) *lensize = 1;
} else if (lenenc == ZIP_LEN_UINT16) {
len = p[1] | (p[2] << 8);
if (lensize) *lensize = 3;
} else {
len = p[1] | (p[2] << 8) | (p[3] << 16) | (p[4] << 24);
if (lensize) *lensize = 5;
}
} else {
len = zipEncodingSize(encoding);
if (lensize) *lensize = 1;
}
return len;
}
/* Encode the length 'l' writing it in 'p'. If p is NULL it just returns
* the amount of bytes required to encode such a length. */
static unsigned int zipEncodeLength(unsigned char *p, char encoding, unsigned int rawlen) {
unsigned char len = 1, lenenc, buf[5];
if (encoding == ZIP_ENC_RAW) {
if (rawlen <= 0xf) {
if (!p) return len;
lenenc = ZIP_LEN_INLINE;
buf[0] = rawlen;
} else if (rawlen <= 0xffff) {
len += 2;
if (!p) return len;
lenenc = ZIP_LEN_UINT16;
buf[1] = (rawlen ) & 0xff;
buf[2] = (rawlen >> 8) & 0xff;
} else {
len += 4;
if (!p) return len;
lenenc = ZIP_LEN_UINT32;
buf[1] = (rawlen ) & 0xff;
buf[2] = (rawlen >> 8) & 0xff;
buf[3] = (rawlen >> 16) & 0xff;
buf[4] = (rawlen >> 24) & 0xff;
}
buf[0] = (lenenc << 4) | (buf[0] & 0xf);
}
if (!p) return len;
/* Apparently we need to store the length in 'p' */
buf[0] = (encoding << 6) | (buf[0] & 0x3f);
memcpy(p,buf,len);
return len;
}
/* Decode the length of the previous element stored at "p". */
static unsigned int zipPrevDecodeLength(unsigned char *p, unsigned int *lensize) {
unsigned int len = *p;
if (len < ZIP_BIGLEN) {
if (lensize) *lensize = 1;
} else {
if (lensize) *lensize = 1+sizeof(len);
memcpy(&len,p+1,sizeof(len));
}
return len;
}
/* Encode the length of the previous entry and write it to "p". Return the
* number of bytes needed to encode this length if "p" is NULL. */
static unsigned int zipPrevEncodeLength(unsigned char *p, unsigned int len) {
if (p == NULL) {
return (len < ZIP_BIGLEN) ? 1 : sizeof(len)+1;
} else {
if (len < ZIP_BIGLEN) {
p[0] = len;
return 1;
} else {
p[0] = ZIP_BIGLEN;
memcpy(p+1,&len,sizeof(len));
return 1+sizeof(len);
}
}
}
/* Return the difference in number of bytes needed to store the new length
* "len" on the entry pointed to by "p". */
static int zipPrevLenByteDiff(unsigned char *p, unsigned int len) {
unsigned int prevlensize;
zipPrevDecodeLength(p,&prevlensize);
return zipPrevEncodeLength(NULL,len)-prevlensize;
}
/* Check if string pointed to by 'entry' can be encoded as an integer.
* Stores the integer value in 'v' and its encoding in 'encoding'.
* Warning: this function requires a NULL-terminated string! */
static int zipTryEncoding(unsigned char *entry, long long *v, unsigned char *encoding) {
long long value;
char *eptr;
if (entry[0] == '-' || (entry[0] >= '0' && entry[0] <= '9')) {
value = strtoll((char*)entry,&eptr,10);
if (eptr[0] != '\0') return 0;
if (value >= INT16_MIN && value <= INT16_MAX) {
*encoding = ZIP_ENC_INT16;
} else if (value >= INT32_MIN && value <= INT32_MAX) {
*encoding = ZIP_ENC_INT32;
} else {
*encoding = ZIP_ENC_INT64;
}
*v = value;
return 1;
}
return 0;
}
/* Store integer 'value' at 'p', encoded as 'encoding' */
static void zipSaveInteger(unsigned char *p, int64_t value, unsigned char encoding) {
int16_t i16;
int32_t i32;
int64_t i64;
if (encoding == ZIP_ENC_INT16) {
i16 = value;
memcpy(p,&i16,sizeof(i16));
} else if (encoding == ZIP_ENC_INT32) {
i32 = value;
memcpy(p,&i32,sizeof(i32));
} else if (encoding == ZIP_ENC_INT64) {
i64 = value;
memcpy(p,&i64,sizeof(i64));
} else {
assert(NULL);
}
}
/* Read integer encoded as 'encoding' from 'p' */
static int64_t zipLoadInteger(unsigned char *p, unsigned char encoding) {
int16_t i16;
int32_t i32;
int64_t i64, ret;
if (encoding == ZIP_ENC_INT16) {
memcpy(&i16,p,sizeof(i16));
ret = i16;
} else if (encoding == ZIP_ENC_INT32) {
memcpy(&i32,p,sizeof(i32));
ret = i32;
} else if (encoding == ZIP_ENC_INT64) {
memcpy(&i64,p,sizeof(i64));
ret = i64;
} else {
assert(NULL);
}
return ret;
}
/* Return a struct with all information about an entry. */
static zlentry zipEntry(unsigned char *p) {
zlentry e;
e.prevrawlen = zipPrevDecodeLength(p,&e.prevrawlensize);
e.len = zipDecodeLength(p+e.prevrawlensize,&e.lensize);
e.headersize = e.prevrawlensize+e.lensize;
e.encoding = ZIP_ENCODING(p+e.prevrawlensize);
e.p = p;
return e;
}
/* Return the total number of bytes used by the entry at "p". */
static unsigned int zipRawEntryLength(unsigned char *p) {
zlentry e = zipEntry(p);
return e.headersize + e.len;
}
/* Create a new empty ziplist. */
unsigned char *ziplistNew(void) {
unsigned int bytes = ZIPLIST_HEADER_SIZE+1;
unsigned char *zl = zmalloc(bytes);
ZIPLIST_BYTES(zl) = bytes;
ZIPLIST_TAIL_OFFSET(zl) = ZIPLIST_HEADER_SIZE;
ZIPLIST_LENGTH(zl) = 0;
zl[bytes-1] = ZIP_END;
return zl;
}
/* Resize the ziplist. */
static unsigned char *ziplistResize(unsigned char *zl, unsigned int len) {
zl = zrealloc(zl,len);
ZIPLIST_BYTES(zl) = len;
zl[len-1] = ZIP_END;
return zl;
}
/* Delete "num" entries, starting at "p". Returns pointer to the ziplist. */
static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num) {
unsigned int i, totlen, deleted = 0;
int nextdiff = 0;
zlentry first = zipEntry(p);
for (i = 0; p[0] != ZIP_END && i < num; i++) {
p += zipRawEntryLength(p);
deleted++;
}
totlen = p-first.p;
if (totlen > 0) {
if (p[0] != ZIP_END) {
/* Tricky: storing the prevlen in this entry might reduce or
* increase the number of bytes needed, compared to the current
* prevlen. Note that we can always store this length because
* it was previously stored by an entry that is being deleted. */
nextdiff = zipPrevLenByteDiff(p,first.prevrawlen);
zipPrevEncodeLength(p-nextdiff,first.prevrawlen);
/* Update offset for tail */
ZIPLIST_TAIL_OFFSET(zl) -= totlen+nextdiff;
/* Move tail to the front of the ziplist */
memmove(first.p,p-nextdiff,ZIPLIST_BYTES(zl)-(p-zl)-1+nextdiff);
} else {
/* The entire tail was deleted. No need to move memory. */
ZIPLIST_TAIL_OFFSET(zl) = (first.p-zl)-first.prevrawlen;
}
/* Resize and update length */
zl = ziplistResize(zl, ZIPLIST_BYTES(zl)-totlen+nextdiff);
ZIPLIST_INCR_LENGTH(zl,-deleted);
}
return zl;
}
/* Insert item at "p". */
static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
unsigned int curlen = ZIPLIST_BYTES(zl), reqlen, prevlen = 0;
unsigned int offset, nextdiff = 0;
unsigned char *tail;
unsigned char encoding = ZIP_ENC_RAW;
long long value;
zlentry entry;
/* Find out prevlen for the entry that is inserted. */
if (p[0] != ZIP_END) {
entry = zipEntry(p);
prevlen = entry.prevrawlen;
} else {
tail = ZIPLIST_ENTRY_TAIL(zl);
if (tail[0] != ZIP_END) {
prevlen = zipRawEntryLength(tail);
}
}
/* See if the entry can be encoded */
if (zipTryEncoding(s,&value,&encoding)) {
reqlen = zipEncodingSize(encoding);
} else {
reqlen = slen;
}
/* We need space for both the length of the previous entry and
* the length of the payload. */
reqlen += zipPrevEncodeLength(NULL,prevlen);
reqlen += zipEncodeLength(NULL,encoding,slen);
/* When the insert position is not equal to the tail, we need to
* make sure that the next entry can hold this entry's length in
* its prevlen field. */
nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
/* Store offset because a realloc may change the address of zl. */
offset = p-zl;
zl = ziplistResize(zl,curlen+reqlen+nextdiff);
p = zl+offset;
/* Apply memory move when necessary and update tail offset. */
if (p[0] != ZIP_END) {
/* Subtract one because of the ZIP_END bytes */
memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);
/* Encode this entry's raw length in the next entry. */
zipPrevEncodeLength(p+reqlen,reqlen);
/* Update offset for tail */
ZIPLIST_TAIL_OFFSET(zl) += reqlen+nextdiff;
} else {
/* This element will be the new tail. */
ZIPLIST_TAIL_OFFSET(zl) = p-zl;
}
/* Write the entry */
p += zipPrevEncodeLength(p,prevlen);
p += zipEncodeLength(p,encoding,slen);
if (encoding != ZIP_ENC_RAW) {
zipSaveInteger(p,value,encoding);
} else {
memcpy(p,s,slen);
}
ZIPLIST_INCR_LENGTH(zl,1);
return zl;
}
unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where) {
unsigned char *p;
p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl);
return __ziplistInsert(zl,p,s,slen);
}
unsigned char *ziplistPop(unsigned char *zl, sds *target, int where) {
zlentry entry;
unsigned char *p;
long long value;
if (target) *target = NULL;
/* Get pointer to element to remove */
p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_TAIL(zl);
if (*p == ZIP_END) return zl;
entry = zipEntry(p);
if (target) {
if (entry.encoding == ZIP_ENC_RAW) {
*target = sdsnewlen(p+entry.headersize,entry.len);
} else {
value = zipLoadInteger(p+entry.headersize,entry.encoding);
*target = sdscatprintf(sdsempty(), "%lld", value);
}
}
zl = __ziplistDelete(zl,p,1);
return zl;
}
/* Returns an offset to use for iterating with ziplistNext. When the given
* index is negative, the list is traversed back to front. When the list
* doesn't contain an element at the provided index, NULL is returned. */
unsigned char *ziplistIndex(unsigned char *zl, int index) {
unsigned char *p;
zlentry entry;
if (index < 0) {
index = (-index)-1;
p = ZIPLIST_ENTRY_TAIL(zl);
if (p[0] != ZIP_END) {
entry = zipEntry(p);
while (entry.prevrawlen > 0 && index--) {
p -= entry.prevrawlen;
entry = zipEntry(p);
}
}
} else {
p = ZIPLIST_ENTRY_HEAD(zl);
while (p[0] != ZIP_END && index--) {
p += zipRawEntryLength(p);
}
}
return (p[0] == ZIP_END || index > 0) ? NULL : p;
}
/* Return pointer to next entry in ziplist. */
unsigned char *ziplistNext(unsigned char *zl, unsigned char *p) {
((void) zl);
/* "p" could be equal to ZIP_END, caused by ziplistDelete,
* and we should return NULL. Otherwise, we should return NULL
* when the *next* element is ZIP_END (there is no next entry). */
if (p[0] == ZIP_END) {
return NULL;
} else {
p = p+zipRawEntryLength(p);
return (p[0] == ZIP_END) ? NULL : p;
}
}
/* Return pointer to previous entry in ziplist. */
unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p) {
zlentry entry;
/* Iterating backwards from ZIP_END should return the tail. When "p" is
* equal to the first element of the list, we're already at the head,
* and should return NULL. */
if (p[0] == ZIP_END) {
p = ZIPLIST_ENTRY_TAIL(zl);
return (p[0] == ZIP_END) ? NULL : p;
} else if (p == ZIPLIST_ENTRY_HEAD(zl)) {
return NULL;
} else {
entry = zipEntry(p);
return p-entry.prevrawlen;
}
}
/* Get entry pointer to by 'p' and store in either 'e' or 'v' depending
* on the encoding of the entry. 'e' is always set to NULL to be able
* to find out whether the string pointer or the integer value was set.
* Return 0 if 'p' points to the end of the zipmap, 1 otherwise. */
unsigned int ziplistGet(unsigned char *p, unsigned char **sstr, unsigned int *slen, long long *sval) {
zlentry entry;
if (p == NULL || p[0] == ZIP_END) return 0;
if (sstr) *sstr = NULL;
entry = zipEntry(p);
if (entry.encoding == ZIP_ENC_RAW) {
if (sstr) {
*slen = entry.len;
*sstr = p+entry.headersize;
}
} else {
if (sval) {
*sval = zipLoadInteger(p+entry.headersize,entry.encoding);
}
}
return 1;
}
/* Insert an entry at "p". */
unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
return __ziplistInsert(zl,p,s,slen);
}
/* Delete a single entry from the ziplist, pointed to by *p.
* Also update *p in place, to be able to iterate over the
* ziplist, while deleting entries. */
unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p) {
unsigned int offset = *p-zl;
zl = __ziplistDelete(zl,*p,1);
/* Store pointer to current element in p, because ziplistDelete will
* do a realloc which might result in a different "zl"-pointer.
* When the delete direction is back to front, we might delete the last
* entry and end up with "p" pointing to ZIP_END, so check this. */
*p = zl+offset;
return zl;
}
/* Delete a range of entries from the ziplist. */
unsigned char *ziplistDeleteRange(unsigned char *zl, unsigned int index, unsigned int num) {
unsigned char *p = ziplistIndex(zl,index);
return (p == NULL) ? zl : __ziplistDelete(zl,p,num);
}
/* Compare entry pointer to by 'p' with 'entry'. Return 1 if equal. */
unsigned int ziplistCompare(unsigned char *p, unsigned char *sstr, unsigned int slen) {
zlentry entry;
unsigned char sencoding;
long long zval, sval;
if (p[0] == ZIP_END) return 0;
entry = zipEntry(p);
if (entry.encoding == ZIP_ENC_RAW) {
/* Raw compare */
if (entry.len == slen) {
return memcmp(p+entry.headersize,sstr,slen) == 0;
} else {
return 0;
}
} else {
/* Try to compare encoded values */
if (zipTryEncoding(sstr,&sval,&sencoding)) {
if (entry.encoding == sencoding) {
zval = zipLoadInteger(p+entry.headersize,entry.encoding);
return zval == sval;
}
}
}
return 0;
}
/* Return length of ziplist. */
unsigned int ziplistLen(unsigned char *zl) {
unsigned int len = 0;
if (ZIPLIST_LENGTH(zl) < UINT16_MAX) {
len = ZIPLIST_LENGTH(zl);
} else {
unsigned char *p = zl+ZIPLIST_HEADER_SIZE;
while (*p != ZIP_END) {
p += zipRawEntryLength(p);
len++;
}
/* Re-store length if small enough */
if (len < UINT16_MAX) ZIPLIST_LENGTH(zl) = len;
}
return len;
}
/* Return size in bytes of ziplist. */
unsigned int ziplistSize(unsigned char *zl) {
return ZIPLIST_BYTES(zl);
}
void ziplistRepr(unsigned char *zl) {
unsigned char *p;
zlentry entry;
printf("{total bytes %d} {length %u}\n",ZIPLIST_BYTES(zl), ZIPLIST_LENGTH(zl));
p = ZIPLIST_ENTRY_HEAD(zl);
while(*p != ZIP_END) {
entry = zipEntry(p);
printf("{offset %ld, header %u, payload %u} ",p-zl,entry.headersize,entry.len);
p += entry.headersize;
if (entry.encoding == ZIP_ENC_RAW) {
fwrite(p,entry.len,1,stdout);
} else {
printf("%lld", zipLoadInteger(p,entry.encoding));
}
printf("\n");
p += entry.len;
}
printf("{end}\n\n");
}
#ifdef ZIPLIST_TEST_MAIN
#include <sys/time.h>
unsigned char *createList() {
unsigned char *zl = ziplistNew();
zl = ziplistPush(zl, (unsigned char*)"foo", 3, ZIPLIST_TAIL);
zl = ziplistPush(zl, (unsigned char*)"quux", 4, ZIPLIST_TAIL);
zl = ziplistPush(zl, (unsigned char*)"hello", 5, ZIPLIST_HEAD);
zl = ziplistPush(zl, (unsigned char*)"1024", 4, ZIPLIST_TAIL);
return zl;
}
unsigned char *createIntList() {
unsigned char *zl = ziplistNew();
char buf[32];
sprintf(buf, "100");
zl = ziplistPush(zl, (unsigned char*)buf, strlen(buf), ZIPLIST_TAIL);
sprintf(buf, "128000");
zl = ziplistPush(zl, (unsigned char*)buf, strlen(buf), ZIPLIST_TAIL);
sprintf(buf, "-100");
zl = ziplistPush(zl, (unsigned char*)buf, strlen(buf), ZIPLIST_HEAD);
sprintf(buf, "4294967296");
zl = ziplistPush(zl, (unsigned char*)buf, strlen(buf), ZIPLIST_HEAD);
sprintf(buf, "non integer");
zl = ziplistPush(zl, (unsigned char*)buf, strlen(buf), ZIPLIST_TAIL);
sprintf(buf, "much much longer non integer");
zl = ziplistPush(zl, (unsigned char*)buf, strlen(buf), ZIPLIST_TAIL);
return zl;
}
long long usec(void) {
struct timeval tv;
gettimeofday(&tv,NULL);
return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
}
void stress(int pos, int num, int maxsize, int dnum) {
int i,j,k;
unsigned char *zl;
char posstr[2][5] = { "HEAD", "TAIL" };
long long start;
for (i = 0; i < maxsize; i+=dnum) {
zl = ziplistNew();
for (j = 0; j < i; j++) {
zl = ziplistPush(zl,(unsigned char*)"quux",4,ZIPLIST_TAIL);
}
/* Do num times a push+pop from pos */
start = usec();
for (k = 0; k < num; k++) {
zl = ziplistPush(zl,(unsigned char*)"quux",4,pos);
zl = ziplistDeleteRange(zl,0,1);
}
printf("List size: %8d, bytes: %8d, %dx push+pop (%s): %6lld usec\n",
i,ZIPLIST_BYTES(zl),num,posstr[pos],usec()-start);
zfree(zl);
}
}
int main(int argc, char **argv) {
unsigned char *zl, *p;
unsigned char *entry;
unsigned int elen;
long long value;
sds s;
zl = createIntList();
ziplistRepr(zl);
zl = createList();
ziplistRepr(zl);
zl = ziplistPop(zl, &s, ZIPLIST_TAIL);
printf("Pop tail: %s (length %ld)\n", s, sdslen(s));
ziplistRepr(zl);
zl = ziplistPop(zl, &s, ZIPLIST_HEAD);
printf("Pop head: %s (length %ld)\n", s, sdslen(s));
ziplistRepr(zl);
zl = ziplistPop(zl, &s, ZIPLIST_TAIL);
printf("Pop tail: %s (length %ld)\n", s, sdslen(s));
ziplistRepr(zl);
zl = ziplistPop(zl, &s, ZIPLIST_TAIL);
printf("Pop tail: %s (length %ld)\n", s, sdslen(s));
ziplistRepr(zl);
printf("Get element at index 3:\n");
{
zl = createList();
p = ziplistIndex(zl, 3);
if (!ziplistGet(p, &entry, &elen, &value)) {
printf("ERROR: Could not access index 3\n");
return 1;
}
if (entry) {
fwrite(entry,elen,1,stdout);
printf("\n");
} else {
printf("%lld\n", value);
}
printf("\n");
}
printf("Get element at index 4 (out of range):\n");
{
zl = createList();
p = ziplistIndex(zl, 4);
if (p == NULL) {
printf("No entry\n");
} else {
printf("ERROR: Out of range index should return NULL, returned offset: %ld\n", p-zl);
return 1;
}
printf("\n");
}
printf("Get element at index -1 (last element):\n");
{
zl = createList();
p = ziplistIndex(zl, -1);
if (!ziplistGet(p, &entry, &elen, &value)) {
printf("ERROR: Could not access index -1\n");
return 1;
}
if (entry) {
fwrite(entry,elen,1,stdout);
printf("\n");
} else {
printf("%lld\n", value);
}
printf("\n");
}
printf("Get element at index -4 (first element):\n");
{
zl = createList();
p = ziplistIndex(zl, -4);
if (!ziplistGet(p, &entry, &elen, &value)) {
printf("ERROR: Could not access index -4\n");
return 1;
}
if (entry) {
fwrite(entry,elen,1,stdout);
printf("\n");
} else {
printf("%lld\n", value);
}
printf("\n");
}
printf("Get element at index -5 (reverse out of range):\n");
{
zl = createList();
p = ziplistIndex(zl, -5);
if (p == NULL) {
printf("No entry\n");
} else {
printf("ERROR: Out of range index should return NULL, returned offset: %ld\n", p-zl);
return 1;
}
printf("\n");
}
printf("Iterate list from 0 to end:\n");
{
zl = createList();
p = ziplistIndex(zl, 0);
while (ziplistGet(p, &entry, &elen, &value)) {
printf("Entry: ");
if (entry) {
fwrite(entry,elen,1,stdout);
} else {
printf("%lld", value);
}
p = ziplistNext(zl,p);
printf("\n");
}
printf("\n");
}
printf("Iterate list from 1 to end:\n");
{
zl = createList();
p = ziplistIndex(zl, 1);
while (ziplistGet(p, &entry, &elen, &value)) {
printf("Entry: ");
if (entry) {
fwrite(entry,elen,1,stdout);
} else {
printf("%lld", value);
}
p = ziplistNext(zl,p);
printf("\n");
}
printf("\n");
}
printf("Iterate list from 2 to end:\n");
{
zl = createList();
p = ziplistIndex(zl, 2);
while (ziplistGet(p, &entry, &elen, &value)) {
printf("Entry: ");
if (entry) {
fwrite(entry,elen,1,stdout);
} else {
printf("%lld", value);
}
p = ziplistNext(zl,p);
printf("\n");
}
printf("\n");
}
printf("Iterate starting out of range:\n");
{
zl = createList();
p = ziplistIndex(zl, 4);
if (!ziplistGet(p, &entry, &elen, &value)) {
printf("No entry\n");
} else {
printf("ERROR\n");
}
printf("\n");
}
printf("Iterate from back to front:\n");
{
zl = createList();
p = ziplistIndex(zl, -1);
while (ziplistGet(p, &entry, &elen, &value)) {
printf("Entry: ");
if (entry) {
fwrite(entry,elen,1,stdout);
} else {
printf("%lld", value);
}
p = ziplistPrev(zl,p);
printf("\n");
}
printf("\n");
}
printf("Iterate from back to front, deleting all items:\n");
{
zl = createList();
p = ziplistIndex(zl, -1);
while (ziplistGet(p, &entry, &elen, &value)) {
printf("Entry: ");
if (entry) {
fwrite(entry,elen,1,stdout);
} else {
printf("%lld", value);
}
zl = ziplistDelete(zl,&p);
p = ziplistPrev(zl,p);
printf("\n");
}
printf("\n");
}
printf("Delete inclusive range 0,0:\n");
{
zl = createList();
zl = ziplistDeleteRange(zl, 0, 1);
ziplistRepr(zl);
}
printf("Delete inclusive range 0,1:\n");
{
zl = createList();
zl = ziplistDeleteRange(zl, 0, 2);
ziplistRepr(zl);
}
printf("Delete inclusive range 1,2:\n");
{
zl = createList();
zl = ziplistDeleteRange(zl, 1, 2);
ziplistRepr(zl);
}
printf("Delete with start index out of range:\n");
{
zl = createList();
zl = ziplistDeleteRange(zl, 5, 1);
ziplistRepr(zl);
}
printf("Delete with num overflow:\n");
{
zl = createList();
zl = ziplistDeleteRange(zl, 1, 5);
ziplistRepr(zl);
}
printf("Delete foo while iterating:\n");
{
zl = createList();
p = ziplistIndex(zl,0);
while (ziplistGet(p,&entry,&elen,&value)) {
if (entry && strncmp("foo",(char*)entry,elen) == 0) {
printf("Delete foo\n");
zl = ziplistDelete(zl,&p);
} else {
printf("Entry: ");
if (entry) {
fwrite(entry,elen,1,stdout);
} else {
printf("%lld",value);
}
p = ziplistNext(zl,p);
printf("\n");
}
}
printf("\n");
ziplistRepr(zl);
}
printf("Create long list and check indices:\n");
{
zl = ziplistNew();
char buf[32];
int i,len;
for (i = 0; i < 1000; i++) {
len = sprintf(buf,"%d",i);
zl = ziplistPush(zl,(unsigned char*)buf,len,ZIPLIST_TAIL);
}
for (i = 0; i < 1000; i++) {
p = ziplistIndex(zl,i);
assert(ziplistGet(p,NULL,NULL,&value));
assert(i == value);
p = ziplistIndex(zl,-i-1);
assert(ziplistGet(p,NULL,NULL,&value));
assert(999-i == value);
}
printf("SUCCESS\n\n");
}
printf("Compare strings with ziplist entries:\n");
{
zl = createList();
p = ziplistIndex(zl,0);
if (!ziplistCompare(p,(unsigned char*)"hello",5)) {
printf("ERROR: not \"hello\"\n");
return 1;
}
if (ziplistCompare(p,(unsigned char*)"hella",5)) {
printf("ERROR: \"hella\"\n");
return 1;
}
p = ziplistIndex(zl,3);
if (!ziplistCompare(p,(unsigned char*)"1024",4)) {
printf("ERROR: not \"1024\"\n");
return 1;
}
if (ziplistCompare(p,(unsigned char*)"1025",4)) {
printf("ERROR: \"1025\"\n");
return 1;
}
printf("SUCCESS\n");
}
printf("Stress with variable ziplist size:\n");
{
stress(ZIPLIST_HEAD,100000,16384,256);
stress(ZIPLIST_TAIL,100000,16384,256);
}
return 0;
}
#endif
#define ZIPLIST_HEAD 0
#define ZIPLIST_TAIL 1
unsigned char *ziplistNew(void);
unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where);
unsigned char *ziplistPop(unsigned char *zl, sds *target, int where);
unsigned char *ziplistIndex(unsigned char *zl, int index);
unsigned char *ziplistNext(unsigned char *zl, unsigned char *p);
unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p);
unsigned int ziplistGet(unsigned char *p, unsigned char **sval, unsigned int *slen, long long *lval);
unsigned char *ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen);
unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p);
unsigned char *ziplistDeleteRange(unsigned char *zl, unsigned int index, unsigned int num);
unsigned int ziplistCompare(unsigned char *p, unsigned char *s, unsigned int slen);
unsigned int ziplistLen(unsigned char *zl);
unsigned int ziplistSize(unsigned char *zl);
...@@ -176,7 +176,6 @@ static unsigned long zipmapRequiredLength(unsigned int klen, unsigned int vlen) ...@@ -176,7 +176,6 @@ static unsigned long zipmapRequiredLength(unsigned int klen, unsigned int vlen)
/* Return the total amount used by a key (encoded length + payload) */ /* Return the total amount used by a key (encoded length + payload) */
static unsigned int zipmapRawKeyLength(unsigned char *p) { static unsigned int zipmapRawKeyLength(unsigned char *p) {
unsigned int l = zipmapDecodeLength(p); unsigned int l = zipmapDecodeLength(p);
return zipmapEncodeLength(NULL,l) + l; return zipmapEncodeLength(NULL,l) + l;
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment