Add quicklist implementation

This replaces individual ziplist vs. linkedlist representations
for Redis list operations.

Big thanks for all the reviews and feedback from everybody in
https://github.com/antirez/redis/pull/2143
This commit is contained in:
Matt Stancliff 2014-11-13 14:11:47 -05:00
parent d956d809ac
commit 5e362b84ab
16 changed files with 2495 additions and 480 deletions

View File

@ -117,7 +117,7 @@ endif
REDIS_SERVER_NAME=redis-server
REDIS_SENTINEL_NAME=redis-sentinel
REDIS_SERVER_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 release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o
REDIS_SERVER_OBJ=adlist.o quicklist.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 release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o
REDIS_CLI_NAME=redis-cli
REDIS_CLI_OBJ=anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o
REDIS_BENCHMARK_NAME=redis-benchmark

View File

@ -770,52 +770,29 @@ int rioWriteBulkObject(rio *r, robj *obj) {
int rewriteListObject(rio *r, robj *key, robj *o) {
long long count = 0, items = listTypeLength(o);
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *zl = o->ptr;
unsigned char *p = ziplistIndex(zl,0);
unsigned char *vstr;
unsigned int vlen;
long long vlong;
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
quicklist *list = o->ptr;
quicklistIter *li = quicklistGetIterator(list, AL_START_HEAD);
quicklistEntry entry;
while(ziplistGet(p,&vstr,&vlen,&vlong)) {
while (quicklistNext(li,&entry)) {
if (count == 0) {
int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ?
REDIS_AOF_REWRITE_ITEMS_PER_CMD : items;
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0;
}
if (vstr) {
if (rioWriteBulkString(r,(char*)vstr,vlen) == 0) return 0;
if (entry.value) {
if (rioWriteBulkString(r,(char*)entry.value,entry.sz) == 0) return 0;
} else {
if (rioWriteBulkLongLong(r,vlong) == 0) return 0;
if (rioWriteBulkLongLong(r,entry.longval) == 0) return 0;
}
p = ziplistNext(zl,p);
if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0;
items--;
}
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
list *list = o->ptr;
listNode *ln;
listIter li;
listRewind(list,&li);
while((ln = listNext(&li))) {
robj *eleobj = listNodeValue(ln);
if (count == 0) {
int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ?
REDIS_AOF_REWRITE_ITEMS_PER_CMD : items;
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0;
}
if (rioWriteBulkObject(r,eleobj) == 0) return 0;
if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0;
items--;
}
quicklistReleaseIterator(li);
} else {
redisPanic("Unknown list encoding");
}

View File

@ -180,11 +180,10 @@ robj *dupStringObject(robj *o) {
}
}
robj *createListObject(void) {
list *l = listCreate();
robj *createQuicklistObject(void) {
quicklist *l = quicklistCreate();
robj *o = createObject(REDIS_LIST,l);
listSetFreeMethod(l,decrRefCountVoid);
o->encoding = REDIS_ENCODING_LINKEDLIST;
o->encoding = REDIS_ENCODING_QUICKLIST;
return o;
}
@ -242,11 +241,8 @@ void freeStringObject(robj *o) {
void freeListObject(robj *o) {
switch (o->encoding) {
case REDIS_ENCODING_LINKEDLIST:
listRelease((list*) o->ptr);
break;
case REDIS_ENCODING_ZIPLIST:
zfree(o->ptr);
case REDIS_ENCODING_QUICKLIST:
quicklistRelease(o->ptr);
break;
default:
redisPanic("Unknown list encoding type");
@ -678,7 +674,7 @@ char *strEncoding(int encoding) {
case REDIS_ENCODING_RAW: return "raw";
case REDIS_ENCODING_INT: return "int";
case REDIS_ENCODING_HT: return "hashtable";
case REDIS_ENCODING_LINKEDLIST: return "linkedlist";
case REDIS_ENCODING_QUICKLIST: return "quicklist";
case REDIS_ENCODING_ZIPLIST: return "ziplist";
case REDIS_ENCODING_INTSET: return "intset";
case REDIS_ENCODING_SKIPLIST: return "skiplist";

2155
src/quicklist.c Normal file

File diff suppressed because it is too large Load Diff

120
src/quicklist.h Normal file
View File

@ -0,0 +1,120 @@
/* quicklist.h - A generic doubly linked quicklist implementation
*
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this quicklist of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this quicklist of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __QUICKLIST_H__
#define __QUICKLIST_H__
/* Node, quicklist, and Iterator are the only data structures used currently. */
typedef struct quicklistNode {
struct quicklistNode *prev;
struct quicklistNode *next;
unsigned char *zl;
unsigned int count; /* cached count of items in ziplist */
} quicklistNode;
typedef struct quicklist {
quicklistNode *head;
quicklistNode *tail;
unsigned long len; /* number of quicklistNodes */
unsigned long count; /* total count of all entries in all ziplists */
} quicklist;
typedef struct quicklistIter {
const quicklist *quicklist;
quicklistNode *current;
unsigned char *zi;
long offset; /* offset in current ziplist */
int direction;
} quicklistIter;
typedef struct quicklistEntry {
const quicklist *quicklist;
quicklistNode *node;
unsigned char *zi;
unsigned char *value;
unsigned int sz;
long long longval;
int offset;
} quicklistEntry;
#define QUICKLIST_HEAD 0
#define QUICKLIST_TAIL -1
/* Prototypes */
quicklist *quicklistCreate(void);
void quicklistRelease(quicklist *quicklist);
quicklist *quicklistPushHead(quicklist *quicklist, const size_t fill,
void *value, const size_t sz);
quicklist *quicklistPushTail(quicklist *quicklist, const size_t fill,
void *value, const size_t sz);
void quicklistPush(quicklist *quicklist, const size_t fill, void *value,
const size_t sz, int where);
void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl);
quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist,
const size_t fill,
unsigned char *zl);
quicklist *quicklistCreateFromZiplist(size_t fill, unsigned char *zl);
void quicklistInsertAfter(quicklist *quicklist, const size_t fill,
quicklistEntry *node, void *value, const size_t sz);
void quicklistInsertBefore(quicklist *quicklist, const size_t fill,
quicklistEntry *node, void *value, const size_t sz);
void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry);
int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data,
int sz);
int quicklistDelRange(quicklist *quicklist, const long start, const long stop);
quicklistIter *quicklistGetIterator(const quicklist *quicklist, int direction);
quicklistIter *quicklistGetIteratorAtIdx(const quicklist *quicklist,
int direction, const long long idx);
int quicklistNext(quicklistIter *iter, quicklistEntry *node);
void quicklistReleaseIterator(quicklistIter *iter);
quicklist *quicklistDup(quicklist *orig);
int quicklistIndex(const quicklist *quicklist, const long long index,
quicklistEntry *entry);
void quicklistRewind(quicklist *quicklist, quicklistIter *li);
void quicklistRewindTail(quicklist *quicklist, quicklistIter *li);
void quicklistRotate(quicklist *quicklist, const size_t fill);
int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
unsigned int *sz, long long *sval,
void *(*saver)(unsigned char *data, unsigned int sz));
int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
unsigned int *sz, long long *slong);
unsigned int quicklistCount(quicklist *ql);
int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len);
#ifdef REDIS_TEST
int quicklistTest(int argc, char *argv[]);
#endif
/* Directions for iterators */
#define AL_START_HEAD 0
#define AL_START_TAIL 1
#endif /* __QUICKLIST_H__ */

View File

@ -433,9 +433,7 @@ int rdbSaveObjectType(rio *rdb, robj *o) {
case REDIS_STRING:
return rdbSaveType(rdb,REDIS_RDB_TYPE_STRING);
case REDIS_LIST:
if (o->encoding == REDIS_ENCODING_ZIPLIST)
return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST_ZIPLIST);
else if (o->encoding == REDIS_ENCODING_LINKEDLIST)
if (o->encoding == REDIS_ENCODING_QUICKLIST)
return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST);
else
redisPanic("Unknown list encoding");
@ -477,7 +475,7 @@ int rdbLoadObjectType(rio *rdb) {
/* Save a Redis object. Returns -1 on error, number of bytes written on success. */
int rdbSaveObject(rio *rdb, robj *o) {
int n, nwritten = 0;
int n = 0, nwritten = 0;
if (o->type == REDIS_STRING) {
/* Save a string value */
@ -485,25 +483,23 @@ int rdbSaveObject(rio *rdb, robj *o) {
nwritten += n;
} else if (o->type == REDIS_LIST) {
/* Save a list value */
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
size_t l = ziplistBlobLen((unsigned char*)o->ptr);
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
quicklist *list = o->ptr;
quicklistIter *li = quicklistGetIterator(list, AL_START_HEAD);
quicklistEntry entry;
if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
nwritten += n;
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
list *list = o->ptr;
listIter li;
listNode *ln;
if ((n = rdbSaveLen(rdb,listLength(list))) == -1) return -1;
if ((n = rdbSaveLen(rdb,quicklistCount(list))) == -1) return -1;
nwritten += n;
listRewind(list,&li);
while((ln = listNext(&li))) {
robj *eleobj = listNodeValue(ln);
if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
while (quicklistNext(li,&entry)) {
if (entry.value) {
if ((n = rdbSaveRawString(rdb,entry.value,entry.sz)) == -1) return -1;
} else {
if ((n = rdbSaveLongLongAsStringObject(rdb,entry.longval)) == -1) return -1;
}
nwritten += n;
}
quicklistReleaseIterator(li);
} else {
redisPanic("Unknown list encoding");
}
@ -831,33 +827,17 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
/* Read list value */
if ((len = rdbLoadLen(rdb,NULL)) == REDIS_RDB_LENERR) return NULL;
/* Use a real list when there are too many entries */
if (len > server.list_max_ziplist_entries) {
o = createListObject();
} else {
o = createZiplistObject();
}
o = createQuicklistObject();
/* Load every single element of the list */
while(len--) {
if ((ele = rdbLoadEncodedStringObject(rdb)) == 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 &&
sdsEncodedObject(ele) &&
sdslen(ele->ptr) > server.list_max_ziplist_value)
listTypeConvert(o,REDIS_ENCODING_LINKEDLIST);
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);
}
dec = getDecodedObject(ele);
size_t len = sdslen(dec->ptr);
size_t zlen = server.list_max_ziplist_entries;
o->ptr = quicklistPushTail(o->ptr, zlen, dec->ptr, len);
decrRefCount(dec);
decrRefCount(ele);
}
} else if (rdbtype == REDIS_RDB_TYPE_SET) {
/* Read list/set value */
@ -1048,8 +1028,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
case REDIS_RDB_TYPE_LIST_ZIPLIST:
o->type = REDIS_LIST;
o->encoding = REDIS_ENCODING_ZIPLIST;
if (ziplistLen(o->ptr) > server.list_max_ziplist_entries)
listTypeConvert(o,REDIS_ENCODING_LINKEDLIST);
listTypeConvert(o,REDIS_ENCODING_QUICKLIST);
break;
case REDIS_RDB_TYPE_SET_INTSET:
o->type = REDIS_SET;

View File

@ -3659,6 +3659,8 @@ int main(int argc, char **argv) {
if (argc == 3 && !strcasecmp(argv[1], "test")) {
if (!strcasecmp(argv[2], "ziplist")) {
return ziplistTest(argc, argv);
} else if (!strcasecmp(argv[2], "quicklist")) {
quicklistTest(argc, argv);
} else if (!strcasecmp(argv[2], "intset")) {
return intsetTest(argc, argv);
} else if (!strcasecmp(argv[2], "zipmap")) {

View File

@ -65,6 +65,7 @@ typedef long long mstime_t; /* millisecond time type. */
#include "util.h" /* Misc functions useful in many places */
#include "latency.h" /* Latency monitor API */
#include "sparkline.h" /* ASII graphs API */
#include "quicklist.h"
/* Following includes allow test functions to be called from Redis main() */
#include "zipmap.h"
@ -204,6 +205,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
#define REDIS_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
#define REDIS_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
/* Defines related to the dump file format. To store 32 bits lengths for short
* keys requires a lot of space, so we check the most significant 2 bits of
@ -964,15 +966,13 @@ typedef struct {
robj *subject;
unsigned char encoding;
unsigned char direction; /* Iteration direction */
unsigned char *zi;
listNode *ln;
quicklistIter *iter;
} 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 */
quicklistEntry entry; /* Entry in quicklist */
} listTypeEntry;
/* Structure to hold set iteration abstraction. */
@ -1099,7 +1099,7 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry);
robj *listTypeGet(listTypeEntry *entry);
void listTypeInsert(listTypeEntry *entry, robj *value, int where);
int listTypeEqual(listTypeEntry *entry, robj *o);
void listTypeDelete(listTypeEntry *entry);
void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry);
void listTypeConvert(robj *subject, int enc);
void unblockClientWaitingData(redisClient *c);
void handleClientsBlockedOnLists(void);
@ -1137,7 +1137,7 @@ robj *getDecodedObject(robj *o);
size_t stringObjectLen(robj *o);
robj *createStringObjectFromLongLong(long long value);
robj *createStringObjectFromLongDouble(long double value, int humanfriendly);
robj *createListObject(void);
robj *createQuicklistObject(void);
robj *createZiplistObject(void);
robj *createSetObject(void);
robj *createIntsetObject(void);

View File

@ -220,7 +220,7 @@ void sortCommand(redisClient *c) {
if (sortval)
incrRefCount(sortval);
else
sortval = createListObject();
sortval = createQuicklistObject();
/* The SORT command has an SQL-alike syntax, parse it */
while(j < c->argc) {
@ -420,6 +420,7 @@ void sortCommand(redisClient *c) {
} else {
redisPanic("Unknown type");
}
printf("j: %d; vectorlen: %d\n", j, vectorlen);
redisAssertWithInfo(c,sortval,j == vectorlen);
/* Now it's time to load the right scores in the sorting vector */
@ -509,7 +510,7 @@ void sortCommand(redisClient *c) {
}
}
} else {
robj *sobj = createZiplistObject();
robj *sobj = createQuicklistObject();
/* STORE option specified, set the sorting result as a List object */
for (j = start; j <= end; j++) {

View File

@ -33,75 +33,40 @@
* List API
*----------------------------------------------------------------------------*/
/* 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. */
void listTypeTryConversion(robj *subject, robj *value) {
if (subject->encoding != REDIS_ENCODING_ZIPLIST) return;
if (sdsEncodedObject(value) &&
sdslen(value->ptr) > server.list_max_ziplist_value)
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
}
/* The function pushes an element to the specified list object 'subject',
* at head or tail position as specified by 'where'.
*
* There is no need for the caller to increment the refcount of 'value' as
* the function takes care of it if needed. */
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_LINKEDLIST);
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
int pos = (where == REDIS_HEAD) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
if (subject->encoding == REDIS_ENCODING_QUICKLIST) {
int pos = (where == REDIS_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL;
value = getDecodedObject(value);
subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),pos);
size_t len = sdslen(value->ptr);
size_t zlen = server.list_max_ziplist_entries;
/* If this value is greater than our allowed values, create it in
* an isolated ziplist */
quicklistPush(subject->ptr, zlen, value->ptr, len, pos);
decrRefCount(value);
} else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
if (where == REDIS_HEAD) {
listAddNodeHead(subject->ptr,value);
} else {
listAddNodeTail(subject->ptr,value);
}
incrRefCount(value);
} else {
redisPanic("Unknown list encoding");
}
}
void *listPopSaver(unsigned char *data, unsigned int sz) {
return createStringObject((char*)data,sz);
}
robj *listTypePop(robj *subject, int where) {
long long vlong;
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 {
int ql_where = where == REDIS_HEAD ? QUICKLIST_HEAD : QUICKLIST_TAIL;
if (subject->encoding == REDIS_ENCODING_QUICKLIST) {
if (quicklistPopCustom(subject->ptr, ql_where, (unsigned char **)&value,
NULL, &vlong, listPopSaver)) {
if (!value)
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_LINKEDLIST) {
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");
@ -110,25 +75,28 @@ robj *listTypePop(robj *subject, int where) {
}
unsigned long listTypeLength(robj *subject) {
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
return ziplistLen(subject->ptr);
} else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
return listLength((list*)subject->ptr);
if (subject->encoding == REDIS_ENCODING_QUICKLIST) {
return quicklistCount(subject->ptr);
} else {
redisPanic("Unknown list encoding");
}
}
/* Initialize an iterator at the specified index. */
listTypeIterator *listTypeInitIterator(robj *subject, long index, unsigned char direction) {
listTypeIterator *listTypeInitIterator(robj *subject, long 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_LINKEDLIST) {
li->ln = listIndex(subject->ptr,index);
li->iter = NULL;
/* REDIS_HEAD means start at TAIL and move *towards* head.
* REDIS_TAIL means start at HEAD and move *towards tail. */
int iter_direction =
direction == REDIS_HEAD ? AL_START_TAIL : AL_START_HEAD;
if (li->encoding == REDIS_ENCODING_QUICKLIST) {
li->iter = quicklistGetIteratorAtIdx(li->subject->ptr,
iter_direction, index);
} else {
redisPanic("Unknown list encoding");
}
@ -137,6 +105,7 @@ listTypeIterator *listTypeInitIterator(robj *subject, long index, unsigned char
/* Clean up the iterator. */
void listTypeReleaseIterator(listTypeIterator *li) {
zfree(li->iter);
zfree(li);
}
@ -148,24 +117,8 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry) {
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_LINKEDLIST) {
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;
}
if (li->encoding == REDIS_ENCODING_QUICKLIST) {
return quicklistNext(li->iter, &entry->entry);
} else {
redisPanic("Unknown list encoding");
}
@ -174,24 +127,14 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry) {
/* Return entry or NULL at the current position of the iterator. */
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);
}
if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
if (entry->entry.value) {
value = createStringObject((char *)entry->entry.value,
entry->entry.sz);
} else {
value = createStringObjectFromLongLong(entry->entry.longval);
}
} else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
redisAssert(entry->ln != NULL);
value = listNodeValue(entry->ln);
incrRefCount(value);
} else {
redisPanic("Unknown list encoding");
}
@ -199,30 +142,19 @@ robj *listTypeGet(listTypeEntry *entry) {
}
void listTypeInsert(listTypeEntry *entry, robj *value, int where) {
robj *subject = entry->li->subject;
if (entry->li->encoding == REDIS_ENCODING_ZIPLIST) {
if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
value = getDecodedObject(value);
sds str = value->ptr;
size_t len = sdslen(str);
size_t zlen = server.list_max_ziplist_entries;
if (where == REDIS_TAIL) {
unsigned char *next = ziplistNext(subject->ptr,entry->zi);
/* When we insert after the current element, but the current element
* is the tail of the list, we need to do a push. */
if (next == NULL) {
subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),REDIS_TAIL);
} else {
subject->ptr = ziplistInsert(subject->ptr,next,value->ptr,sdslen(value->ptr));
}
} else {
subject->ptr = ziplistInsert(subject->ptr,entry->zi,value->ptr,sdslen(value->ptr));
quicklistInsertAfter((quicklist *)entry->entry.quicklist, zlen,
&entry->entry, str, len);
} else if (where == REDIS_HEAD) {
quicklistInsertBefore((quicklist *)entry->entry.quicklist, zlen,
&entry->entry, str, len);
}
decrRefCount(value);
} else if (entry->li->encoding == REDIS_ENCODING_LINKEDLIST) {
if (where == REDIS_TAIL) {
listInsertNode(subject->ptr,entry->ln,value,AL_START_TAIL);
} else {
listInsertNode(subject->ptr,entry->ln,value,AL_START_HEAD);
}
incrRefCount(value);
} else {
redisPanic("Unknown list encoding");
}
@ -230,59 +162,33 @@ void listTypeInsert(listTypeEntry *entry, robj *value, int where) {
/* Compare the given object with the entry at the current position. */
int listTypeEqual(listTypeEntry *entry, robj *o) {
listTypeIterator *li = entry->li;
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
redisAssertWithInfo(NULL,o,sdsEncodedObject(o));
return ziplistCompare(entry->zi,o->ptr,sdslen(o->ptr));
} else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
return equalStringObjects(o,listNodeValue(entry->ln));
return quicklistCompare(entry->entry.zi,o->ptr,sdslen(o->ptr));
} else {
redisPanic("Unknown list encoding");
}
}
/* Delete the element pointed to. */
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_LINKEDLIST) {
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;
void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry) {
if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
quicklistDelEntry(iter->iter, &entry->entry);
} else {
redisPanic("Unknown list encoding");
}
}
/* Create a quicklist from a single ziplist */
void listTypeConvert(robj *subject, int enc) {
listTypeIterator *li;
listTypeEntry entry;
redisAssertWithInfo(NULL,subject,subject->type == REDIS_LIST);
redisAssertWithInfo(NULL,subject,subject->type==REDIS_LIST);
redisAssertWithInfo(NULL,subject,subject->encoding==REDIS_ENCODING_ZIPLIST);
if (enc == REDIS_ENCODING_LINKEDLIST) {
list *l = listCreate();
listSetFreeMethod(l,decrRefCountVoid);
if (enc == REDIS_ENCODING_QUICKLIST) {
size_t zlen = server.list_max_ziplist_entries;
/* 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_LINKEDLIST;
zfree(subject->ptr);
subject->ptr = l;
subject->encoding = REDIS_ENCODING_QUICKLIST;
subject->ptr = quicklistCreateFromZiplist(zlen, subject->ptr);
} else {
redisPanic("Unsupported list conversion");
}
@ -304,7 +210,7 @@ void pushGenericCommand(redisClient *c, int where) {
for (j = 2; j < c->argc; j++) {
c->argv[j] = tryObjectEncoding(c->argv[j]);
if (!lobj) {
lobj = createZiplistObject();
lobj = createQuicklistObject();
dbAdd(c->db,c->argv[1],lobj);
}
listTypePush(lobj,c->argv[j],where);
@ -338,13 +244,6 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
checkType(c,subject,REDIS_LIST)) return;
if (refval != NULL) {
/* We're not sure if this value can be inserted yet, but we cannot
* convert the list inside the iterator. We don't want to loop over
* the list twice (once to see if the value can be inserted and once
* to do the actual insert), so we assume this value can be inserted
* and convert the ziplist to a regular list if necessary. */
listTypeTryConversion(subject,val);
/* Seek refval from head to tail */
iter = listTypeInitIterator(subject,0,REDIS_TAIL);
while (listTypeNext(iter,&entry)) {
@ -357,10 +256,6 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
listTypeReleaseIterator(iter);
if (inserted) {
/* Check if the length exceeds the ziplist length threshold. */
if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
ziplistLen(subject->ptr) > server.list_max_ziplist_entries)
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"linsert",
c->argv[1],c->db->id);
@ -418,31 +313,19 @@ void lindexCommand(redisClient *c) {
if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != REDIS_OK))
return;
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *p;
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);
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
quicklistEntry entry;
if (quicklistIndex(o->ptr, index, &entry)) {
if (entry.value) {
value = createStringObject((char*)entry.value,entry.sz);
} else {
value = createStringObjectFromLongLong(vlong);
value = createStringObjectFromLongLong(entry.longval);
}
addReplyBulk(c,value);
decrRefCount(value);
} else {
addReply(c,shared.nullbulk);
}
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
listNode *ln = listIndex(o->ptr,index);
if (ln != NULL) {
value = listNodeValue(ln);
addReplyBulk(c,value);
} else {
addReply(c,shared.nullbulk);
}
} else {
redisPanic("Unknown list encoding");
}
@ -452,35 +335,18 @@ void lsetCommand(redisClient *c) {
robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);
if (o == NULL || checkType(c,o,REDIS_LIST)) return;
long index;
robj *value = (c->argv[3] = tryObjectEncoding(c->argv[3]));
robj *value = c->argv[3];
if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != REDIS_OK))
return;
listTypeTryConversion(o,value);
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *p, *zl = o->ptr;
p = ziplistIndex(zl,index);
if (p == NULL) {
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
quicklist *ql = o->ptr;
int replaced = quicklistReplaceAtIndex(ql, index,
value->ptr, sdslen(value->ptr));
if (!replaced) {
addReply(c,shared.outofrangeerr);
} else {
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);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lset",c->argv[1],c->db->id);
server.dirty++;
}
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
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);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lset",c->argv[1],c->db->id);
@ -549,43 +415,28 @@ void lrangeCommand(redisClient *c) {
/* Return the result in form of a multi-bulk reply */
addReplyMultiBulkLen(c,rangelen);
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *p = ziplistIndex(o->ptr,start);
unsigned char *vstr;
unsigned int vlen;
long long vlong;
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
listTypeIterator *iter = listTypeInitIterator(o, start, REDIS_TAIL);
while(rangelen--) {
ziplistGet(p,&vstr,&vlen,&vlong);
if (vstr) {
addReplyBulkCBuffer(c,vstr,vlen);
listTypeEntry entry;
listTypeNext(iter, &entry);
quicklistEntry *qe = &entry.entry;
if (qe->value) {
addReplyBulkCBuffer(c,qe->value,qe->sz);
} else {
addReplyBulkLongLong(c,vlong);
addReplyBulkLongLong(c,qe->longval);
}
p = ziplistNext(o->ptr,p);
}
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
listNode *ln;
/* If we are nearest to the end of the list, reach the element
* starting from tail and going backward, as it is faster. */
if (start > llen/2) start -= llen;
ln = listIndex(o->ptr,start);
while(rangelen--) {
addReplyBulk(c,ln->value);
ln = ln->next;
}
listTypeReleaseIterator(iter);
} else {
redisPanic("List encoding is not LINKEDLIST nor ZIPLIST!");
redisPanic("List encoding is not QUICKLIST!");
}
}
void ltrimCommand(redisClient *c) {
robj *o;
long start, end, llen, j, ltrim, rtrim;
list *list;
listNode *ln;
long start, end, llen, ltrim, rtrim;
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;
@ -612,19 +463,9 @@ void ltrimCommand(redisClient *c) {
}
/* Remove list elements to perform the trim */
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
o->ptr = ziplistDeleteRange(o->ptr,0,ltrim);
o->ptr = ziplistDeleteRange(o->ptr,-rtrim,rtrim);
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
list = o->ptr;
for (j = 0; j < ltrim; j++) {
ln = listFirst(list);
listDelNode(list,ln);
}
for (j = 0; j < rtrim; j++) {
ln = listLast(list);
listDelNode(list,ln);
}
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
quicklistDelRange(o->ptr,0,ltrim);
quicklistDelRange(o->ptr,-rtrim,rtrim);
} else {
redisPanic("Unknown list encoding");
}
@ -641,10 +482,9 @@ void ltrimCommand(redisClient *c) {
void lremCommand(redisClient *c) {
robj *subject, *obj;
obj = c->argv[3] = tryObjectEncoding(c->argv[3]);
obj = c->argv[3];
long toremove;
long removed = 0;
listTypeEntry entry;
if ((getLongFromObjectOrReply(c, c->argv[2], &toremove, NULL) != REDIS_OK))
return;
@ -652,10 +492,6 @@ void lremCommand(redisClient *c) {
subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero);
if (subject == NULL || checkType(c,subject,REDIS_LIST)) return;
/* 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) {
toremove = -toremove;
@ -664,9 +500,10 @@ void lremCommand(redisClient *c) {
li = listTypeInitIterator(subject,0,REDIS_TAIL);
}
listTypeEntry entry;
while (listTypeNext(li,&entry)) {
if (listTypeEqual(&entry,obj)) {
listTypeDelete(&entry);
listTypeDelete(li, &entry);
server.dirty++;
removed++;
if (toremove && removed == toremove) break;
@ -674,11 +511,10 @@ void lremCommand(redisClient *c) {
}
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]);
}
if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
addReplyLongLong(c,removed);
if (removed) signalModifiedKey(c->db,c->argv[1]);
}
@ -702,7 +538,7 @@ void lremCommand(redisClient *c) {
void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value) {
/* Create the list if the key does not exist */
if (!dstobj) {
dstobj = createZiplistObject();
dstobj = createQuicklistObject();
dbAdd(c->db,dstkey,dstobj);
}
signalModifiedKey(c->db,dstkey);
@ -1010,7 +846,9 @@ void handleClientsBlockedOnLists(void) {
}
}
if (listTypeLength(o) == 0) dbDelete(rl->db,rl->key);
if (listTypeLength(o) == 0) {
dbDelete(rl->db,rl->key);
}
/* We don't call signalModifiedKey() as it was already called
* when an element was pushed on the list. */
}

View File

@ -19,9 +19,12 @@ proc assert_match {pattern value} {
}
}
proc assert_equal {expected value} {
proc assert_equal {expected value {detail ""}} {
if {$expected ne $value} {
error "assertion:Expected '$value' to be equal to '$expected'"
if {$detail ne ""} {
set detail " (detail: $detail)"
}
error "assertion:Expected '$value' to be equal to '$expected'$detail"
}
}

View File

@ -77,10 +77,10 @@ start_server {tags {"aofrw"}} {
}
foreach d {string int} {
foreach e {ziplist linkedlist} {
foreach e {quicklist} {
test "AOF rewrite of list with $e encoding, $d data" {
r flushall
if {$e eq {ziplist}} {set len 10} else {set len 1000}
set len 1000
for {set j 0} {$j < $len} {incr j} {
if {$d eq {string}} {
set data [randstring 0 16 alpha]

View File

@ -36,9 +36,9 @@ start_server {
}
foreach {num cmd enc title} {
16 lpush ziplist "Ziplist"
1000 lpush linkedlist "Linked list"
10000 lpush linkedlist "Big Linked list"
16 lpush quicklist "Old Ziplist"
1000 lpush quicklist "Old Linked list"
10000 lpush quicklist "Old Big Linked list"
16 sadd intset "Intset"
1000 sadd hashtable "Hash table"
10000 sadd hashtable "Big Hash table"
@ -85,14 +85,14 @@ start_server {
r sort tosort BY weight_* store sort-res
assert_equal $result [r lrange sort-res 0 -1]
assert_equal 16 [r llen sort-res]
assert_encoding ziplist sort-res
assert_encoding quicklist sort-res
}
test "SORT BY hash field STORE" {
r sort tosort BY wobj_*->weight store sort-res
assert_equal $result [r lrange sort-res 0 -1]
assert_equal 16 [r llen sort-res]
assert_encoding ziplist sort-res
assert_encoding quicklist sort-res
}
test "SORT extracts STORE correctly" {

View File

@ -2,7 +2,7 @@ start_server {
tags {"list"}
overrides {
"list-max-ziplist-value" 16
"list-max-ziplist-entries" 256
"list-max-ziplist-entries" 4
}
} {
source "tests/unit/type/list-common.tcl"
@ -28,14 +28,18 @@ start_server {
for {set i 0} {$i < 1000} {incr i} {
set min [expr {int(rand()*$startlen)}]
set max [expr {$min+int(rand()*$startlen)}]
set before_len [llength $mylist]
set before_len_r [r llen mylist]
set mylist [lrange $mylist $min $max]
r ltrim mylist $min $max
assert_equal $mylist [r lrange mylist 0 -1]
assert_equal $mylist [r lrange mylist 0 -1] "failed trim"
set starting [r llen mylist]
for {set j [r llen mylist]} {$j < $startlen} {incr j} {
set str [randomInt 9223372036854775807]
r rpush mylist $str
lappend mylist $str
assert_equal $mylist [r lrange mylist 0 -1] "failed append match"
}
}
}

View File

@ -2,7 +2,7 @@ start_server {
tags {list ziplist}
overrides {
"list-max-ziplist-value" 200000
"list-max-ziplist-entries" 256
"list-max-ziplist-entries" 16
}
} {
test {Explicit regression for a list bug} {

View File

@ -2,24 +2,24 @@ start_server {
tags {"list"}
overrides {
"list-max-ziplist-value" 16
"list-max-ziplist-entries" 256
"list-max-ziplist-entries" 5
}
} {
source "tests/unit/type/list-common.tcl"
test {LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - ziplist} {
# first lpush then rpush
assert_equal 1 [r lpush myziplist1 a]
assert_equal 2 [r rpush myziplist1 b]
assert_equal 3 [r rpush myziplist1 c]
assert_equal 1 [r lpush myziplist1 aa]
assert_equal 2 [r rpush myziplist1 bb]
assert_equal 3 [r rpush myziplist1 cc]
assert_equal 3 [r llen myziplist1]
assert_equal a [r lindex myziplist1 0]
assert_equal b [r lindex myziplist1 1]
assert_equal c [r lindex myziplist1 2]
assert_equal aa [r lindex myziplist1 0]
assert_equal bb [r lindex myziplist1 1]
assert_equal cc [r lindex myziplist1 2]
assert_equal {} [r lindex myziplist2 3]
assert_equal c [r rpop myziplist1]
assert_equal a [r lpop myziplist1]
assert_encoding ziplist myziplist1
assert_equal cc [r rpop myziplist1]
assert_equal aa [r lpop myziplist1]
assert_encoding quicklist myziplist1
# first rpush then lpush
assert_equal 1 [r rpush myziplist2 a]
@ -32,13 +32,13 @@ start_server {
assert_equal {} [r lindex myziplist2 3]
assert_equal a [r rpop myziplist2]
assert_equal c [r lpop myziplist2]
assert_encoding ziplist myziplist2
assert_encoding quicklist myziplist2
}
test {LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - regular list} {
# first lpush then rpush
assert_equal 1 [r lpush mylist1 $largevalue(linkedlist)]
assert_encoding linkedlist mylist1
assert_encoding quicklist mylist1
assert_equal 2 [r rpush mylist1 b]
assert_equal 3 [r rpush mylist1 c]
assert_equal 3 [r llen mylist1]
@ -51,7 +51,7 @@ start_server {
# first rpush then lpush
assert_equal 1 [r rpush mylist2 $largevalue(linkedlist)]
assert_encoding linkedlist mylist2
assert_encoding quicklist mylist2
assert_equal 2 [r lpush mylist2 b]
assert_equal 3 [r lpush mylist2 c]
assert_equal 3 [r llen mylist2]
@ -74,34 +74,22 @@ start_server {
assert_equal {d c b a 0 1 2 3} [r lrange mylist 0 -1]
}
test {DEL a list - ziplist} {
assert_equal 1 [r del myziplist2]
assert_equal 0 [r exists myziplist2]
assert_equal 0 [r llen myziplist2]
}
test {DEL a list - regular list} {
test {DEL a list} {
assert_equal 1 [r del mylist2]
assert_equal 0 [r exists mylist2]
assert_equal 0 [r llen mylist2]
}
proc create_ziplist {key entries} {
proc create_list {key entries} {
r del $key
foreach entry $entries { r rpush $key $entry }
assert_encoding ziplist $key
}
proc create_linkedlist {key entries} {
r del $key
foreach entry $entries { r rpush $key $entry }
assert_encoding linkedlist $key
assert_encoding quicklist $key
}
foreach {type large} [array get largevalue] {
test "BLPOP, BRPOP: single existing list - $type" {
set rd [redis_deferring_client]
create_$type blist "a b $large c d"
create_list blist "a b $large c d"
$rd blpop blist 1
assert_equal {blist a} [$rd read]
@ -116,8 +104,8 @@ start_server {
test "BLPOP, BRPOP: multiple existing lists - $type" {
set rd [redis_deferring_client]
create_$type blist1 "a $large c"
create_$type blist2 "d $large f"
create_list blist1 "a $large c"
create_list blist2 "d $large f"
$rd blpop blist1 blist2 1
assert_equal {blist1 a} [$rd read]
@ -137,7 +125,7 @@ start_server {
test "BLPOP, BRPOP: second list has an entry - $type" {
set rd [redis_deferring_client]
r del blist1
create_$type blist2 "d $large f"
create_list blist2 "d $large f"
$rd blpop blist1 blist2 1
assert_equal {blist2 d} [$rd read]
@ -151,7 +139,7 @@ start_server {
r del target
set rd [redis_deferring_client]
create_$type blist "a b $large c d"
create_list blist "a b $large c d"
$rd brpoplpush blist target 1
assert_equal d [$rd read]
@ -517,28 +505,28 @@ start_server {
foreach {type large} [array get largevalue] {
test "LPUSHX, RPUSHX - $type" {
create_$type xlist "$large c"
create_list xlist "$large c"
assert_equal 3 [r rpushx xlist d]
assert_equal 4 [r lpushx xlist a]
assert_equal "a $large c d" [r lrange xlist 0 -1]
}
test "LINSERT - $type" {
create_$type xlist "a $large c d"
assert_equal 5 [r linsert xlist before c zz]
assert_equal "a $large zz c d" [r lrange xlist 0 10]
assert_equal 6 [r linsert xlist after c yy]
assert_equal "a $large zz c yy d" [r lrange xlist 0 10]
assert_equal 7 [r linsert xlist after d dd]
assert_equal -1 [r linsert xlist after bad ddd]
assert_equal "a $large zz c yy d dd" [r lrange xlist 0 10]
assert_equal 8 [r linsert xlist before a aa]
assert_equal -1 [r linsert xlist before bad aaa]
assert_equal "aa a $large zz c yy d dd" [r lrange xlist 0 10]
create_list xlist "a $large c d"
assert_equal 5 [r linsert xlist before c zz] "before c"
assert_equal "a $large zz c d" [r lrange xlist 0 10] "lrangeA"
assert_equal 6 [r linsert xlist after c yy] "after c"
assert_equal "a $large zz c yy d" [r lrange xlist 0 10] "lrangeB"
assert_equal 7 [r linsert xlist after d dd] "after d"
assert_equal -1 [r linsert xlist after bad ddd] "after bad"
assert_equal "a $large zz c yy d dd" [r lrange xlist 0 10] "lrangeC"
assert_equal 8 [r linsert xlist before a aa] "before a"
assert_equal -1 [r linsert xlist before bad aaa] "before bad"
assert_equal "aa a $large zz c yy d dd" [r lrange xlist 0 10] "lrangeD"
# check inserting integer encoded value
assert_equal 9 [r linsert xlist before aa 42]
assert_equal 42 [r lrange xlist 0 0]
assert_equal 9 [r linsert xlist before aa 42] "before aa"
assert_equal 42 [r lrange xlist 0 0] "lrangeE"
}
}
@ -547,55 +535,7 @@ start_server {
set e
} {*ERR*syntax*error*}
test {LPUSHX, RPUSHX convert from ziplist to list} {
set large $largevalue(linkedlist)
# convert when a large value is pushed
create_ziplist xlist a
assert_equal 2 [r rpushx xlist $large]
assert_encoding linkedlist xlist
create_ziplist xlist a
assert_equal 2 [r lpushx xlist $large]
assert_encoding linkedlist xlist
# convert when the length threshold is exceeded
create_ziplist xlist [lrepeat 256 a]
assert_equal 257 [r rpushx xlist b]
assert_encoding linkedlist xlist
create_ziplist xlist [lrepeat 256 a]
assert_equal 257 [r lpushx xlist b]
assert_encoding linkedlist xlist
}
test {LINSERT convert from ziplist to list} {
set large $largevalue(linkedlist)
# convert when a large value is inserted
create_ziplist xlist a
assert_equal 2 [r linsert xlist before a $large]
assert_encoding linkedlist xlist
create_ziplist xlist a
assert_equal 2 [r linsert xlist after a $large]
assert_encoding linkedlist xlist
# convert when the length threshold is exceeded
create_ziplist xlist [lrepeat 256 a]
assert_equal 257 [r linsert xlist before a a]
assert_encoding linkedlist xlist
create_ziplist xlist [lrepeat 256 a]
assert_equal 257 [r linsert xlist after a a]
assert_encoding linkedlist xlist
# don't convert when the value could not be inserted
create_ziplist xlist [lrepeat 256 a]
assert_equal -1 [r linsert xlist before foo a]
assert_encoding ziplist xlist
create_ziplist xlist [lrepeat 256 a]
assert_equal -1 [r linsert xlist after foo a]
assert_encoding ziplist xlist
}
foreach {type num} {ziplist 250 linkedlist 500} {
foreach {type num} {quicklist 250 quicklist 500} {
proc check_numbered_list_consistency {key} {
set len [r llen $key]
for {set i 0} {$i < $len} {incr i} {
@ -664,16 +604,16 @@ start_server {
foreach {type large} [array get largevalue] {
test "RPOPLPUSH base case - $type" {
r del mylist1 mylist2
create_$type mylist1 "a $large c d"
create_list mylist1 "a $large c d"
assert_equal d [r rpoplpush mylist1 mylist2]
assert_equal c [r rpoplpush mylist1 mylist2]
assert_equal "a $large" [r lrange mylist1 0 -1]
assert_equal "c d" [r lrange mylist2 0 -1]
assert_encoding ziplist mylist2
assert_encoding quicklist mylist2
}
test "RPOPLPUSH with the same list as src and dst - $type" {
create_$type mylist "a $large c"
create_list mylist "a $large c"
assert_equal "a $large c" [r lrange mylist 0 -1]
assert_equal c [r rpoplpush mylist mylist]
assert_equal "c a $large" [r lrange mylist 0 -1]
@ -681,8 +621,8 @@ start_server {
foreach {othertype otherlarge} [array get largevalue] {
test "RPOPLPUSH with $type source and existing target $othertype" {
create_$type srclist "a b c $large"
create_$othertype dstlist "$otherlarge"
create_list srclist "a b c $large"
create_list dstlist "$otherlarge"
assert_equal $large [r rpoplpush srclist dstlist]
assert_equal c [r rpoplpush srclist dstlist]
assert_equal "a b" [r lrange srclist 0 -1]
@ -691,7 +631,7 @@ start_server {
# When we rpoplpush'ed a large value, dstlist should be
# converted to the same encoding as srclist.
if {$type eq "linkedlist"} {
assert_encoding linkedlist dstlist
assert_encoding quicklist dstlist
}
}
}
@ -713,7 +653,7 @@ start_server {
}
test {RPOPLPUSH against non list dst key} {
create_ziplist srclist {a b c d}
create_list srclist {a b c d}
r set dstlist x
assert_error WRONGTYPE* {r rpoplpush srclist dstlist}
assert_type string dstlist
@ -727,7 +667,7 @@ start_server {
foreach {type large} [array get largevalue] {
test "Basic LPOP/RPOP - $type" {
create_$type mylist "$large 1 2"
create_list mylist "$large 1 2"
assert_equal $large [r lpop mylist]
assert_equal 2 [r rpop mylist]
assert_equal 1 [r lpop mylist]
@ -745,7 +685,7 @@ start_server {
assert_error WRONGTYPE* {r rpop notalist}
}
foreach {type num} {ziplist 250 linkedlist 500} {
foreach {type num} {quicklist 250 quicklist 500} {
test "Mass RPOP/LPOP - $type" {
r del mylist
set sum1 0
@ -765,24 +705,24 @@ start_server {
foreach {type large} [array get largevalue] {
test "LRANGE basics - $type" {
create_$type mylist "$large 1 2 3 4 5 6 7 8 9"
create_list mylist "$large 1 2 3 4 5 6 7 8 9"
assert_equal {1 2 3 4 5 6 7 8} [r lrange mylist 1 -2]
assert_equal {7 8 9} [r lrange mylist -3 -1]
assert_equal {4} [r lrange mylist 4 4]
}
test "LRANGE inverted indexes - $type" {
create_$type mylist "$large 1 2 3 4 5 6 7 8 9"
create_list mylist "$large 1 2 3 4 5 6 7 8 9"
assert_equal {} [r lrange mylist 6 2]
}
test "LRANGE out of range indexes including the full list - $type" {
create_$type mylist "$large 1 2 3"
create_list mylist "$large 1 2 3"
assert_equal "$large 1 2 3" [r lrange mylist -1000 1000]
}
test "LRANGE out of range negative end index - $type" {
create_$type mylist "$large 1 2 3"
create_list mylist "$large 1 2 3"
assert_equal $large [r lrange mylist 0 -4]
assert_equal {} [r lrange mylist 0 -5]
}
@ -796,7 +736,7 @@ start_server {
proc trim_list {type min max} {
upvar 1 large large
r del mylist
create_$type mylist "1 2 3 4 $large"
create_list mylist "1 2 3 4 $large"
r ltrim mylist $min $max
r lrange mylist 0 -1
}
@ -825,7 +765,7 @@ start_server {
foreach {type large} [array get largevalue] {
test "LSET - $type" {
create_$type mylist "99 98 $large 96 95"
create_list mylist "99 98 $large 96 95"
r lset mylist 1 foo
r lset mylist -1 bar
assert_equal "99 foo $large 96 bar" [r lrange mylist 0 -1]
@ -847,7 +787,7 @@ start_server {
foreach {type e} [array get largevalue] {
test "LREM remove all the occurrences - $type" {
create_$type mylist "$e foo bar foobar foobared zap bar test foo"
create_list mylist "$e foo bar foobar foobared zap bar test foo"
assert_equal 2 [r lrem mylist 0 bar]
assert_equal "$e foo foobar foobared zap test foo" [r lrange mylist 0 -1]
}
@ -863,7 +803,7 @@ start_server {
}
test "LREM starting from tail with negative count - $type" {
create_$type mylist "$e foo bar foobar foobared zap bar test foo foo"
create_list mylist "$e foo bar foobar foobared zap bar test foo foo"
assert_equal 1 [r lrem mylist -1 bar]
assert_equal "$e foo bar foobar foobared zap test foo foo" [r lrange mylist 0 -1]
}
@ -874,7 +814,7 @@ start_server {
}
test "LREM deleting objects that may be int encoded - $type" {
create_$type myotherlist "$e 1 2 3"
create_list myotherlist "$e 1 2 3"
assert_equal 1 [r lrem myotherlist 1 2]
assert_equal 3 [r llen myotherlist]
}