Commit ff9e5b21 authored by Matt Stancliff's avatar Matt Stancliff
Browse files

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
parent d956d809
...@@ -117,7 +117,7 @@ endif ...@@ -117,7 +117,7 @@ endif
REDIS_SERVER_NAME=redis-server REDIS_SERVER_NAME=redis-server
REDIS_SENTINEL_NAME=redis-sentinel 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_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_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 REDIS_BENCHMARK_NAME=redis-benchmark
......
...@@ -770,52 +770,29 @@ int rioWriteBulkObject(rio *r, robj *obj) { ...@@ -770,52 +770,29 @@ int rioWriteBulkObject(rio *r, robj *obj) {
int rewriteListObject(rio *r, robj *key, robj *o) { int rewriteListObject(rio *r, robj *key, robj *o) {
long long count = 0, items = listTypeLength(o); long long count = 0, items = listTypeLength(o);
if (o->encoding == REDIS_ENCODING_ZIPLIST) { if (o->encoding == REDIS_ENCODING_QUICKLIST) {
unsigned char *zl = o->ptr; quicklist *list = o->ptr;
unsigned char *p = ziplistIndex(zl,0); quicklistIter *li = quicklistGetIterator(list, AL_START_HEAD);
unsigned char *vstr; quicklistEntry entry;
unsigned int vlen;
long long vlong;
while(ziplistGet(p,&vstr,&vlen,&vlong)) { while (quicklistNext(li,&entry)) {
if (count == 0) { if (count == 0) {
int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ? int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ?
REDIS_AOF_REWRITE_ITEMS_PER_CMD : items; REDIS_AOF_REWRITE_ITEMS_PER_CMD : items;
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0; if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0; if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0; if (rioWriteBulkObject(r,key) == 0) return 0;
} }
if (vstr) {
if (rioWriteBulkString(r,(char*)vstr,vlen) == 0) return 0;
} else {
if (rioWriteBulkLongLong(r,vlong) == 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 (entry.value) {
if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0; if (rioWriteBulkString(r,(char*)entry.value,entry.sz) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0; } else {
if (rioWriteBulkLongLong(r,entry.longval) == 0) return 0;
} }
if (rioWriteBulkObject(r,eleobj) == 0) return 0;
if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0; if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0;
items--; items--;
} }
quicklistReleaseIterator(li);
} else { } else {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
} }
......
...@@ -180,11 +180,10 @@ robj *dupStringObject(robj *o) { ...@@ -180,11 +180,10 @@ robj *dupStringObject(robj *o) {
} }
} }
robj *createListObject(void) { robj *createQuicklistObject(void) {
list *l = listCreate(); quicklist *l = quicklistCreate();
robj *o = createObject(REDIS_LIST,l); robj *o = createObject(REDIS_LIST,l);
listSetFreeMethod(l,decrRefCountVoid); o->encoding = REDIS_ENCODING_QUICKLIST;
o->encoding = REDIS_ENCODING_LINKEDLIST;
return o; return o;
} }
...@@ -242,11 +241,8 @@ void freeStringObject(robj *o) { ...@@ -242,11 +241,8 @@ void freeStringObject(robj *o) {
void freeListObject(robj *o) { void freeListObject(robj *o) {
switch (o->encoding) { switch (o->encoding) {
case REDIS_ENCODING_LINKEDLIST: case REDIS_ENCODING_QUICKLIST:
listRelease((list*) o->ptr); quicklistRelease(o->ptr);
break;
case REDIS_ENCODING_ZIPLIST:
zfree(o->ptr);
break; break;
default: default:
redisPanic("Unknown list encoding type"); redisPanic("Unknown list encoding type");
...@@ -678,7 +674,7 @@ char *strEncoding(int encoding) { ...@@ -678,7 +674,7 @@ char *strEncoding(int encoding) {
case REDIS_ENCODING_RAW: return "raw"; case REDIS_ENCODING_RAW: return "raw";
case REDIS_ENCODING_INT: return "int"; case REDIS_ENCODING_INT: return "int";
case REDIS_ENCODING_HT: return "hashtable"; 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_ZIPLIST: return "ziplist";
case REDIS_ENCODING_INTSET: return "intset"; case REDIS_ENCODING_INTSET: return "intset";
case REDIS_ENCODING_SKIPLIST: return "skiplist"; case REDIS_ENCODING_SKIPLIST: return "skiplist";
......
/* quicklist.c - A doubly linked list of ziplists
*
* 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 start 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.
*/
#include <string.h> /* for memcpy */
#include "quicklist.h"
#include "zmalloc.h"
#include "ziplist.h"
#include "util.h" /* for ll2string */
#if defined(REDIS_TEST) || defined(REDIS_TEST_VERBOSE)
#include <stdio.h> /* for printf (debug printing), snprintf (genstr) */
#endif
/* If not verbose testing, remove all debug printing. */
#ifndef REDIS_TEST_VERBOSE
#define D(...)
#else
#define D(...) \
do { \
printf("%s:%s:%d:\t", __FILE__, __FUNCTION__, __LINE__); \
printf(__VA_ARGS__); \
printf("\n"); \
} while (0);
#endif
/* Simple way to give quicklistEntry structs default values with one call. */
#define initEntry(e) \
do { \
(e)->zi = (e)->value = NULL; \
(e)->longval = -123456789; \
(e)->quicklist = NULL; \
(e)->node = NULL; \
(e)->offset = 123456789; \
(e)->sz = 0; \
} while (0);
/* Create a new quicklist.
* Free with quicklistRelease().
*
* On error, NULL is returned. Otherwise the pointer to the new quicklist. */
quicklist *quicklistCreate(void) {
struct quicklist *quicklist;
if ((quicklist = zmalloc(sizeof(*quicklist))) == NULL)
return NULL;
quicklist->head = quicklist->tail = NULL;
quicklist->len = 0;
quicklist->count = 0;
return quicklist;
}
static quicklistNode *quicklistCreateNode(void) {
quicklistNode *node;
if ((node = zmalloc(sizeof(*node))) == NULL)
return NULL;
node->zl = NULL;
node->count = 0;
node->next = node->prev = NULL;
return node;
}
/* Return cached quicklist count */
unsigned int quicklistCount(quicklist *ql) { return ql->count; }
/* Free entire quicklist. */
void quicklistRelease(quicklist *quicklist) {
unsigned long len;
quicklistNode *current, *next;
current = quicklist->head;
len = quicklist->len;
while (len--) {
next = current->next;
zfree(current->zl);
quicklist->count -= current->count;
zfree(current);
quicklist->len--;
current = next;
}
zfree(quicklist);
}
/* Insert 'new_node' after 'old_node' if 'after' is 1.
* Insert 'new_node' before 'old_node' if 'after' is 0. */
static void __quicklistInsertNode(quicklist *quicklist, quicklistNode *old_node,
quicklistNode *new_node, int after) {
if (after) {
new_node->prev = old_node;
if (old_node) {
new_node->next = old_node->next;
if (old_node->next)
old_node->next->prev = new_node;
old_node->next = new_node;
}
if (quicklist->tail == old_node)
quicklist->tail = new_node;
} else {
new_node->next = old_node;
if (old_node) {
new_node->prev = old_node->prev;
if (old_node->prev)
old_node->prev->next = new_node;
old_node->prev = new_node;
}
if (quicklist->head == old_node)
quicklist->head = new_node;
}
/* If this insert creates the first element in this quicklist, we
* need to initialize head/tail too. */
if (quicklist->len == 0) {
quicklist->head = quicklist->tail = new_node;
}
quicklist->len++;
}
/* Wrappers for node inserting around existing node. */
static void _quicklistInsertNodeBefore(quicklist *quicklist,
quicklistNode *old_node,
quicklistNode *new_node) {
__quicklistInsertNode(quicklist, old_node, new_node, 0);
}
static void _quicklistInsertNodeAfter(quicklist *quicklist,
quicklistNode *old_node,
quicklistNode *new_node) {
__quicklistInsertNode(quicklist, old_node, new_node, 1);
}
/* Add new entry to head of quicklist.
*
* Returns 'quicklist' argument. */
quicklist *quicklistPushHead(quicklist *quicklist, const size_t fill,
void *value, size_t sz) {
if (quicklist->head && quicklist->head->count < fill) {
quicklist->head->zl =
ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);
} else {
quicklistNode *node = quicklistCreateNode();
node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
_quicklistInsertNodeBefore(quicklist, quicklist->head, node);
}
quicklist->count++;
quicklist->head->count++;
return quicklist;
}
/* Add new node to tail of quicklist.
*
* Returns 'quicklist' argument. */
quicklist *quicklistPushTail(quicklist *quicklist, const size_t fill,
void *value, size_t sz) {
if (quicklist->tail && quicklist->tail->count < fill) {
quicklist->tail->zl =
ziplistPush(quicklist->tail->zl, value, sz, ZIPLIST_TAIL);
} else {
quicklistNode *node = quicklistCreateNode();
node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_TAIL);
_quicklistInsertNodeAfter(quicklist, quicklist->tail, node);
}
quicklist->count++;
quicklist->tail->count++;
return quicklist;
}
/* Create new node consisting of a pre-formed ziplist.
* Used for loading RDBs where entire ziplists have been stored
* to be retrieved later. */
void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl) {
quicklistNode *node = quicklistCreateNode();
node->zl = zl;
node->count = ziplistLen(node->zl);
_quicklistInsertNodeAfter(quicklist, quicklist->tail, node);
quicklist->count += node->count;
}
/* Append all values of ziplist 'zl' individually into 'quicklist'.
*
* This allows us to restore old RDB ziplists into new quicklists
* with smaller ziplist sizes than the saved RDB ziplist.
*
* Returns 'quicklist' argument. Frees passed-in ziplist 'zl' */
quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist,
const size_t fill,
unsigned char *zl) {
unsigned char *value;
unsigned int sz;
long long longval;
char longstr[32] = { 0 };
unsigned char *p = ziplistIndex(zl, 0);
while (ziplistGet(p, &value, &sz, &longval)) {
if (!value) {
/* Write the longval as a string so we can re-add it */
sz = ll2string(longstr, sizeof(longstr), longval);
value = (unsigned char *)longstr;
}
quicklistPushTail(quicklist, fill, value, sz);
p = ziplistNext(zl, p);
}
zfree(zl);
return quicklist;
}
/* Create new (potentially multi-node) quicklist from a single existing ziplist.
*
* Returns new quicklist. Frees passed-in ziplist 'zl'. */
quicklist *quicklistCreateFromZiplist(size_t fill, unsigned char *zl) {
return quicklistAppendValuesFromZiplist(quicklistCreate(), fill, zl);
}
#define quicklistDeleteIfEmpty(ql, n) \
do { \
if ((n)->count == 0) { \
__quicklistDelNode((ql), (n)); \
} \
} while (0)
static void __quicklistDelNode(quicklist *quicklist, quicklistNode *node) {
if (node->next)
node->next->prev = node->prev;
if (node->prev)
node->prev->next = node->next;
if (node == quicklist->tail)
quicklist->tail = node->prev;
if (node == quicklist->head)
quicklist->head = node->next;
quicklist->count -= node->count;
zfree(node->zl);
zfree(node);
quicklist->len--;
}
/* Delete one entry from list given the node for the entry and a pointer
* to the entry in the node.
*
* Returns 1 if the entire node was deleted, 0 if node still exists.
* Also updates in/out param 'p' with the next offset in the ziplist. */
static int quicklistDelIndex(quicklist *quicklist, quicklistNode *node,
unsigned char **p) {
int gone = 0;
node->zl = ziplistDelete(node->zl, p);
node->count--;
if (node->count == 0)
gone = 1;
quicklist->count--;
quicklistDeleteIfEmpty(quicklist, node);
/* If we deleted all the nodes, our returned pointer is no longer valid */
return gone ? 1 : 0;
}
/* Delete one element represented by 'entry'
*
* 'entry' stores enough metadata to delete the proper position in
* the correct ziplist in the correct quicklist node. */
void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry) {
quicklistNode *prev = entry->node->prev;
quicklistNode *next = entry->node->next;
int deleted_node = quicklistDelIndex((quicklist *)entry->quicklist,
entry->node, &entry->zi);
/* after delete, the zi is now invalid for any future usage. */
iter->zi = NULL;
/* If current node is deleted, we must update iterator node and offset. */
if (deleted_node) {
if (iter->direction == AL_START_HEAD) {
iter->current = next;
iter->offset = 0;
} else if (iter->direction == AL_START_TAIL) {
iter->current = prev;
iter->offset = -1;
}
}
/* else if (!deleted_node), no changes needed.
* we already reset iter->zi above, and the existing iter->offset
* doesn't move again because:
* - [1, 2, 3] => delete offset 1 => [1, 3]: next element still offset 1
* - [1, 2, 3] => delete offset 0 => [2, 3]: next element still offset 0
* if we deleted the last element at offet N and now
* length of this ziplist is N-1, the next call into
* quicklistNext() will jump to the next node. */
}
/* Replace quicklist entry at offset 'index' by 'data' with length 'sz'.
*
* Returns 1 if replace happened.
* Returns 0 if replace failed and no changes happened. */
int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data,
int sz) {
quicklistEntry entry;
if (quicklistIndex(quicklist, index, &entry)) {
entry.node->zl = ziplistDelete(entry.node->zl, &entry.zi);
entry.node->zl = ziplistInsert(entry.node->zl, entry.zi, data, sz);
return 1;
} else {
return 0;
}
}
/* Given two nodes, try to merge their ziplists.
*
* This helps us not have a quicklist with 3 element ziplists if
* our fill factor can handle much higher levels.
*
* Note: 'a' must be to the LEFT of 'b'.
*
* After calling this function, both 'a' and 'b' should be considered
* unusable. The return value from this function must be used
* instead of re-using any of the quicklistNode input arguments.
*
* Returns the input node picked to merge against or NULL if
* merging was not possible. */
static quicklistNode *_quicklistZiplistMerge(quicklist *quicklist,
quicklistNode *a,
quicklistNode *b) {
/* Merge into node with largest initial count */
quicklistNode *target = a->count > b->count ? a : b;
if (a->count == 0 || b->count == 0)
return NULL;
D("Requested merge (a,b) (%u, %u) and picked target %u", a->count, b->count,
target->count);
int where;
unsigned char *p = NULL;
if (target == a) {
/* If target is node a, we append node b to node a, in-order */
where = ZIPLIST_TAIL;
p = ziplistIndex(b->zl, 0);
D("WILL TRAVERSE B WITH LENGTH: %u, %u", b->count, ziplistLen(b->zl));
} else {
/* If target b, we prepend node a to node b, in reverse order of a */
where = ZIPLIST_HEAD;
p = ziplistIndex(a->zl, -1);
D("WILL TRAVERSE A WITH LENGTH: %u, %u", a->count, ziplistLen(a->zl));
}
unsigned char *val;
unsigned int sz;
long long longval;
char lv[32] = { 0 };
/* NOTE: We could potentially create a built-in ziplist operation
* allowing direct merging of two ziplists. It would be more memory
* efficient (one big realloc instead of incremental), but it's more
* complex than using the existing ziplist API to read/push as below. */
while (ziplistGet(p, &val, &sz, &longval)) {
if (!val) {
sz = ll2string(lv, sizeof(lv), longval);
val = (unsigned char *)lv;
}
target->zl = ziplistPush(target->zl, val, sz, where);
if (target == a) {
p = ziplistNext(b->zl, p);
b->count--;
a->count++;
} else {
p = ziplistPrev(a->zl, p);
a->count--;
b->count++;
}
D("Loop A: %u, B: %u", a->count, b->count);
}
/* At this point, target is populated and not-target needs
* to be free'd and removed from the quicklist. */
if (target == a) {
D("Deleting node B with current count: %d", b->count);
__quicklistDelNode(quicklist, b);
} else if (target == b) {
D("Deleting node A with current count: %d", a->count);
__quicklistDelNode(quicklist, a);
}
return target;
}
/* Attempt to merge ziplists within two nodes on either side of 'center'.
*
* We attempt to merge:
* - (center->prev->prev, center->prev)
* - (center->next, center->next->next)
* - (center->prev, center)
* - (center, center->next)
*/
static void _quicklistMergeNodes(quicklist *quicklist, const size_t fill,
quicklistNode *center) {
quicklistNode *prev, *prev_prev, *next, *next_next, *target;
prev = prev_prev = next = next_next = target = NULL;
if (center->prev) {
prev = center->prev;
if (center->prev->prev)
prev_prev = center->prev->prev;
}
if (center->next) {
next = center->next;
if (center->next->next)
next_next = center->next->next;
}
/* Try to merge prev_prev and prev */
if (prev && prev_prev && (prev->count + prev_prev->count) <= fill) {
_quicklistZiplistMerge(quicklist, prev_prev, prev);
prev_prev = prev = NULL; /* they could have moved, invalidate them. */
}
/* Try to merge next and next_next */
if (next && next_next && (next->count + next_next->count) <= fill) {
_quicklistZiplistMerge(quicklist, next, next_next);
next = next_next = NULL; /* they could have moved, invalidate them. */
}
/* Try to merge center node and previous node */
if (center->prev && (center->count + center->prev->count) <= fill) {
target = _quicklistZiplistMerge(quicklist, center->prev, center);
center = NULL; /* center could have been deleted, invalidate it. */
} else {
/* else, we didn't merge here, but target needs to be valid below. */
target = center;
}
/* Use result of center merge (or original) to merge with next node. */
if (target && target->next &&
(target->count + target->next->count) <= fill) {
_quicklistZiplistMerge(quicklist, target, target->next);
}
}
/* Split 'node' into two parts, parameterized by 'offset' and 'after'.
*
* The 'after' argument controls which quicklistNode gets returned.
* If 'after'==1, returned node has elements after 'offset'.
* input node keeps elements up to 'offset', including 'offset'.
* If 'after'==0, returned node has elements up to 'offset', including 'offset'.
* input node keeps elements after 'offset'.
*
* If 'after'==1, returned node will have elements _after_ 'offset'.
* The returned node will have elements [OFFSET+1, END].
* The input node keeps elements [0, OFFSET].
*
* If 'after'==0, returned node will keep elements up to and including 'offset'.
* The returned node will have elements [0, OFFSET].
* The input node keeps elements [OFFSET+1, END].
*
* The input node keeps all elements not taken by the returned node.
*
* Returns newly created node or NULL if split not possible. */
static quicklistNode *_quicklistSplitNode(quicklistNode *node, int offset,
int after) {
size_t zl_sz = ziplistBlobLen(node->zl);
quicklistNode *new_node = quicklistCreateNode();
if (!new_node)
return NULL;
new_node->zl = zmalloc(zl_sz);
if (!new_node->zl) {
zfree(new_node);
return NULL;
}
/* Copy original ziplist so we can split it */
memcpy(new_node->zl, node->zl, zl_sz);
/* -1 here means "continue deleting until the list ends" */
int orig_start = after ? offset + 1 : 0;
int orig_extent = after ? -1 : offset;
int new_start = after ? 0 : offset;
int new_extent = after ? offset + 1 : -1;
D("After %d (%d); ranges: [%d, %d], [%d, %d]", after, offset, orig_start,
orig_extent, new_start, new_extent);
node->zl = ziplistDeleteRange(node->zl, orig_start, orig_extent);
node->count = ziplistLen(node->zl);
new_node->zl = ziplistDeleteRange(new_node->zl, new_start, new_extent);
new_node->count = ziplistLen(new_node->zl);
D("After split lengths: orig (%d), new (%d)", node->count, new_node->count);
return new_node;
}
/* Insert a new entry before or after existing entry 'entry'.
*
* If after==1, the new value is inserted after 'entry', otherwise
* the new value is inserted before 'entry'. */
static void _quicklistInsert(quicklist *quicklist, const size_t fill,
quicklistEntry *entry, void *value,
const size_t sz, int after) {
int full = 0, at_tail = 0, at_head = 0, full_next = 0, full_prev = 0;
quicklistNode *node = entry->node;
quicklistNode *new_node = NULL;
if (!node) {
/* we have no reference node, so let's create only node in the list */
D("No node given!");
new_node = quicklistCreateNode();
new_node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
__quicklistInsertNode(quicklist, NULL, new_node, after);
new_node->count++;
quicklist->count++;
return;
}
/* Populate accounting flags for easier boolean checks later */
if (node->count >= fill) {
D("Current node is full with count %d with requested fill %lu",
node->count, fill);
full = 1;
}
if (after && (ziplistNext(node->zl, entry->zi) == NULL)) {
D("At Tail of current ziplist");
at_tail = 1;
if (node->next && node->next->count >= fill) {
D("Next node is full too.");
full_next = 1;
}
}
if (!after && (ziplistPrev(node->zl, entry->zi) == NULL)) {
D("At Head");
at_head = 1;
if (node->prev && node->prev->count >= fill) {
D("Prev node is full too.");
full_prev = 1;
}
}
/* Now determine where and how to insert the new element */
if (!full && after) {
D("Not full, inserting after current position.");
unsigned char *next = ziplistNext(node->zl, entry->zi);
if (next == NULL) {
node->zl = ziplistPush(node->zl, value, sz, ZIPLIST_TAIL);
} else {
node->zl = ziplistInsert(node->zl, next, value, sz);
}
node->count++;
} else if (!full && !after) {
D("Not full, inserting before current position.");
node->zl = ziplistInsert(node->zl, entry->zi, value, sz);
node->count++;
} else if (full && at_tail && node->next && !full_next && after) {
/* If we are: at tail, next has free space, and inserting after:
* - insert entry at head of next node. */
D("Full and tail, but next isn't full; inserting next node head");
new_node = node->next;
new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_HEAD);
new_node->count++;
} else if (full && at_head && node->prev && !full_prev && !after) {
/* If we are: at head, previous has free space, and inserting before:
* - insert entry at tail of previous node. */
D("Full and head, but prev isn't full, inserting prev node tail");
new_node = node->prev;
new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_TAIL);
new_node->count++;
} else if (full && ((at_tail && node->next && full_next && after) ||
(at_head && node->prev && full_prev && !after))) {
/* If we are: full, and our prev/next is full, then:
* - create new node and attach to quicklist */
D("\tprovisioning new node...");
new_node = quicklistCreateNode();
new_node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
new_node->count++;
__quicklistInsertNode(quicklist, node, new_node, after);
} else if (full) {
/* else, node is full we need to split it. */
/* covers both after and !after cases */
D("\tsplitting node...");
new_node = _quicklistSplitNode(node, entry->offset, after);
new_node->zl = ziplistPush(new_node->zl, value, sz,
after ? ZIPLIST_HEAD : ZIPLIST_TAIL);
new_node->count++;
__quicklistInsertNode(quicklist, node, new_node, after);
_quicklistMergeNodes(quicklist, fill, node);
}
quicklist->count++;
}
void quicklistInsertBefore(quicklist *quicklist, const size_t fill,
quicklistEntry *entry, void *value,
const size_t sz) {
_quicklistInsert(quicklist, fill, entry, value, sz, 0);
}
void quicklistInsertAfter(quicklist *quicklist, const size_t fill,
quicklistEntry *entry, void *value, const size_t sz) {
_quicklistInsert(quicklist, fill, entry, value, sz, 1);
}
/* Delete a range of elements from the quicklist.
*
* elements may span across multiple quicklistNodes, so we
* have to be careful about tracking where we start and end.
*
* Returns 1 if entries were deleted, 0 if nothing was deleted. */
int quicklistDelRange(quicklist *quicklist, const long start,
const long count) {
if (count <= 0)
return 0;
unsigned long extent = count; /* range is inclusive of start position */
if (start >= 0 && extent > (quicklist->count - start)) {
/* if requesting delete more elements than exist, limit to list size. */
extent = quicklist->count - start;
} else if (start < 0 && extent > (unsigned long)(-start)) {
/* else, if at negative offset, limit max size to rest of list. */
extent = -start; /* c.f. LREM -29 29; just delete until end. */
}
quicklistEntry entry;
if (!quicklistIndex(quicklist, start, &entry))
return 0;
D("Quicklist delete request for start %ld, count %ld, extent: %ld", start,
count, extent);
quicklistNode *node = entry.node;
/* iterate over next nodes until everything is deleted. */
while (extent) {
quicklistNode *next = node->next;
unsigned long del;
int delete_entire_node = 0;
if (entry.offset == 0 && extent >= node->count) {
/* If we are deleting more than the count of this node, we
* can just delete the entire node without ziplist math. */
delete_entire_node = 1;
del = node->count;
} else if (entry.offset >= 0 && extent >= node->count) {
/* If deleting more nodes after this one, calculate delete based
* on size of current node. */
del = node->count - entry.offset;
} else if (entry.offset < 0) {
/* If offset is negative, we are in the first run of this loop
* and we are deleting the entire range
* from this start offset to end of list. Since the Negative
* offset is the number of elements until the tail of the list,
* just use it directly as the deletion count. */
del = -entry.offset;
/* If the positive offset is greater than the remaining extent,
* we only delete the remaining extent, not the entire offset.
*/
if (del > extent)
del = extent;
} else {
/* else, we are deleting less than the extent of this node, so
* use extent directly. */
del = extent;
}
D("[%ld]: asking to del: %ld because offset: %d; (ENTIRE NODE: %d), "
"node count: %u",
extent, del, entry.offset, delete_entire_node, node->count);
if (delete_entire_node) {
__quicklistDelNode(quicklist, node);
} else {
node->zl = ziplistDeleteRange(node->zl, entry.offset, del);
node->count -= del;
quicklist->count -= del;
quicklistDeleteIfEmpty(quicklist, node);
}
extent -= del;
node = next;
entry.offset = 0;
}
return 1;
}
/* Passthrough to ziplistCompare() */
int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len) {
return ziplistCompare(p1, p2, p2_len);
}
/* Returns a quicklist iterator 'iter'. After the initialization every
* call to quicklistNext() will return the next element of the quicklist. */
quicklistIter *quicklistGetIterator(const quicklist *quicklist, int direction) {
quicklistIter *iter;
if ((iter = zmalloc(sizeof(*iter))) == NULL)
return NULL;
if (direction == AL_START_HEAD) {
iter->current = quicklist->head;
iter->offset = 0;
} else if (direction == AL_START_TAIL) {
iter->current = quicklist->tail;
iter->offset = -1;
}
iter->direction = direction;
iter->quicklist = quicklist;
iter->zi = NULL;
return iter;
}
/* Initialize an iterator at a specific offset 'idx' and make the iterator
* return nodes in 'direction' direction. */
quicklistIter *quicklistGetIteratorAtIdx(const quicklist *quicklist,
const int direction,
const long long idx) {
quicklistEntry entry;
if (quicklistIndex(quicklist, idx, &entry)) {
quicklistIter *base = quicklistGetIterator(quicklist, direction);
base->zi = NULL;
base->current = entry.node;
base->offset = entry.offset;
return base;
} else {
return NULL;
}
}
/* Release iterator */
void quicklistReleaseIterator(quicklistIter *iter) { zfree(iter); }
/* Get next element in iterator.
*
* Note: You must NOT insert into the list while iterating over it.
* You *may* delete from the list while iterating using the
* quicklistDelEntry() function.
* If you insert into the quicklist while iterating, you should
* re-create the iterator after your addition.
*
* iter = quicklistGetIterator(quicklist,<direction>);
* quicklistEntry entry;
* while (quicklistNext(iter, &entry)) {
* if (entry.value)
* [[ use entry.value with entry.sz ]]
* else
* [[ use entry.longval ]]
* }
*
* Populates 'entry' with values for this iteration.
* Returns 0 when iteration is complete or if iteration not possible.
* If return value is 0, the contents of 'entry' are not valid.
*/
int quicklistNext(quicklistIter *iter, quicklistEntry *entry) {
initEntry(entry);
if (!iter) {
D("Returning because no iter!");
return 0;
}
entry->quicklist = iter->quicklist;
entry->node = iter->current;
if (!iter->current) {
D("Returning because current node is NULL")
return 0;
}
unsigned char *(*nextFn)(unsigned char *, unsigned char *) = NULL;
int offset_update = 0;
if (!iter->zi) {
/* If !zi, use current index. */
iter->zi = ziplistIndex(iter->current->zl, iter->offset);
} else {
/* else, use existing iterator offset and get prev/next as necessary. */
if (iter->direction == AL_START_HEAD) {
nextFn = ziplistNext;
offset_update = 1;
} else if (iter->direction == AL_START_TAIL) {
nextFn = ziplistPrev;
offset_update = -1;
}
iter->zi = nextFn(iter->current->zl, iter->zi);
iter->offset += offset_update;
}
entry->zi = iter->zi;
entry->offset = iter->offset;
if (iter->zi) {
/* Populate value from existing ziplist position */
ziplistGet(entry->zi, &entry->value, &entry->sz, &entry->longval);
return 1;
} else {
/* We ran out of ziplist entries.
* Pick next node, update offset, then re-run retrieval. */
if (iter->direction == AL_START_HEAD) {
/* Forward traversal */
D("Jumping to start of next node");
iter->current = iter->current->next;
iter->offset = 0;
} else if (iter->direction == AL_START_TAIL) {
/* Reverse traversal */
D("Jumping to end of previous node");
iter->current = iter->current->prev;
iter->offset = -1;
}
iter->zi = NULL;
return quicklistNext(iter, entry);
}
}
/* Duplicate the quicklist. On out of memory NULL is returned.
* On success a copy of the original quicklist is returned.
*
* The original quicklist both on success or error is never modified.
*
* Returns newly allocated quicklist. */
quicklist *quicklistDup(quicklist *orig) {
quicklist *copy;
int failure = 0;
if ((copy = quicklistCreate()) == NULL)
return NULL;
for (quicklistNode *current = orig->head; current;
current = current->next) {
quicklistNode *node = quicklistCreateNode();
size_t ziplen = ziplistBlobLen(current->zl);
if ((node->zl = zmalloc(ziplen)) == NULL) {
failure = 1;
break;
}
memcpy(node->zl, current->zl, ziplen);
node->count = current->count;
copy->count += node->count;
_quicklistInsertNodeAfter(copy, copy->tail, node);
}
/* copy->count must equal orig->count here */
if (failure) {
quicklistRelease(copy);
return NULL;
}
return copy;
}
/* Populate 'entry' with the element at the specified zero-based index
* where 0 is the head, 1 is the element next to head
* and so on. Negative integers are used in order to count
* from the tail, -1 is the last element, -2 the penultimate
* and so on. If the index is out of range 0 is returned.
*
* Returns 1 if element found
* Returns 0 if element not found */
int quicklistIndex(const quicklist *quicklist, const long long idx,
quicklistEntry *entry) {
quicklistNode *n;
unsigned long long accum = 0;
unsigned long long index;
int forward = idx < 0 ? 0 : 1; /* < 0 -> reverse, 0+ -> forward */
initEntry(entry);
entry->quicklist = quicklist;
if (!forward) {
index = (-idx) - 1;
n = quicklist->tail;
} else {
index = idx;
n = quicklist->head;
}
if (index >= quicklist->count)
return 0;
while (n) {
if ((accum + n->count) > index) {
break;
} else {
D("Skipping over (%p) %u at accum %lld", (void *)n, n->count,
accum);
accum += n->count;
n = forward ? n->next : n->prev;
}
}
if (!n)
return 0;
D("Found node: %p at accum %llu, idx %llu, sub+ %llu, sub- %llu", (void *)n,
accum, index, index - accum, (-index) - 1 + accum);
entry->node = n;
if (forward) {
/* forward = normal head-to-tail offset. */
entry->offset = index - accum;
} else {
/* reverse = need negative offset for tail-to-head, so undo
* the result of the original if (index < 0) above. */
entry->offset = (-index) - 1 + accum;
}
entry->zi = ziplistIndex(entry->node->zl, entry->offset);
ziplistGet(entry->zi, &entry->value, &entry->sz, &entry->longval);
return 1;
}
/* Rotate quicklist by moving the tail element to the head. */
void quicklistRotate(quicklist *quicklist, const size_t fill) {
if (quicklist->count <= 1)
return;
/* First, get the tail entry */
quicklistNode *tail = quicklist->tail;
unsigned char *p = ziplistIndex(tail->zl, -1);
unsigned char *value;
long long longval;
unsigned int sz;
char longstr[32] = { 0 };
ziplistGet(p, &value, &sz, &longval);
/* If value found is NULL, then ziplistGet populated longval instead */
if (!value) {
/* Write the longval as a string so we can re-add it */
sz = ll2string(longstr, sizeof(longstr), longval);
value = (unsigned char *)longstr;
}
/* Add tail entry to head (must happen before tail is deleted). */
quicklistPushHead(quicklist, fill, value, sz);
/* If quicklist has only one node, the head ziplist is also the
* tail ziplist and PushHead() could have reallocated our single ziplist,
* which would make our pre-existing 'p' unusable. */
if (quicklist->len == 1)
p = ziplistIndex(tail->zl, -1);
/* Remove tail entry. */
quicklistDelIndex(quicklist, tail, &p);
}
/* pop from quicklist and return result in 'data' ptr. Value of 'data'
* is the return value of 'saver' function pointer if the data is NOT a number.
*
* If the quicklist element is a long long, then the return value is returned in
* 'sval'.
*
* Return value of 0 means no elements available.
* Return value of 1 means check 'data' and 'sval' for values.
* If 'data' is set, use 'data' and 'sz'. Otherwise, use 'sval'. */
int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
unsigned int *sz, long long *sval,
void *(*saver)(unsigned char *data, unsigned int sz)) {
unsigned char *p;
unsigned char *vstr;
unsigned int vlen;
long long vlong;
int pos = (where == QUICKLIST_HEAD) ? 0 : -1;
if (quicklist->count == 0)
return 0;
if (data)
*data = NULL;
if (sz)
*sz = 0;
if (sval)
*sval = -123456789;
quicklistNode *node;
if (where == QUICKLIST_HEAD && quicklist->head) {
node = quicklist->head;
} else if (where == QUICKLIST_TAIL && quicklist->tail) {
node = quicklist->tail;
} else {
return 0;
}
p = ziplistIndex(node->zl, pos);
if (ziplistGet(p, &vstr, &vlen, &vlong)) {
if (vstr) {
if (data)
*data = saver(vstr, vlen);
if (sz)
*sz = vlen;
} else {
if (data)
*data = NULL;
if (sval)
*sval = vlong;
}
quicklistDelIndex(quicklist, node, &p);
return 1;
}
return 0;
}
/* Return a malloc'd copy of data passed in */
static void *_quicklistSaver(unsigned char *data, unsigned int sz) {
unsigned char *vstr;
if (data) {
if ((vstr = zmalloc(sz)) == NULL)
return 0;
memcpy(data, vstr, sz);
return vstr;
}
return NULL;
}
/* Default pop function
*
* Returns malloc'd value from quicklist */
int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
unsigned int *sz, long long *slong) {
unsigned char *vstr;
unsigned int vlen;
long long vlong;
if (quicklist->count == 0)
return 0;
int ret = quicklistPopCustom(quicklist, where, &vstr, &vlen, &vlong,
_quicklistSaver);
if (data)
*data = vstr;
if (slong)
*slong = vlong;
if (sz)
*sz = vlen;
return ret;
}
/* Wrapper to allow argument-based switching between HEAD/TAIL pop */
void quicklistPush(quicklist *quicklist, const size_t fill, void *value,
const size_t sz, int where) {
if (where == QUICKLIST_HEAD) {
quicklistPushHead(quicklist, fill, value, sz);
} else if (where == QUICKLIST_TAIL) {
quicklistPushTail(quicklist, fill, value, sz);
}
}
/* The rest of this file is test cases and test helpers. */
#ifdef REDIS_TEST
#include <stdint.h>
#define assert(_e) \
do { \
if (!(_e)) { \
printf("\n\n=== ASSERTION FAILED ===\n"); \
printf("==> %s:%d '%s' is not true\n", __FILE__, __LINE__, #_e); \
err++; \
} \
} while (0)
#define yell(str, ...) printf("ERROR! " str "\n\n", __VA_ARGS__)
#define OK printf("\tOK\n")
#define ERROR \
do { \
printf("\tERROR!\n"); \
err++; \
} while (0)
#define ERR(x, ...) \
do { \
printf("%s:%s:%d:\t", __FILE__, __FUNCTION__, __LINE__); \
printf("ERROR! " x "\n", __VA_ARGS__); \
err++; \
} while (0)
#define TEST(name) printf("test — %s\n", name);
#define TEST_DESC(name, ...) printf("test — " name "\n", __VA_ARGS__);
#define QL_TEST_VERBOSE 0
#define UNUSED(x) (void)(x)
static void ql_info(quicklist *ql) {
#if QL_TEST_VERBOSE
printf("Container length: %lu\n", ql->len);
printf("Container size: %lu\n", ql->count);
if (ql->head)
printf("\t(zsize head: %d)\n", ziplistLen(ql->head->zl));
if (ql->tail)
printf("\t(zsize tail: %d)\n", ziplistLen(ql->tail->zl));
printf("\n");
#else
UNUSED(ql);
#endif
}
/* Iterate over an entire quicklist.
* Print the list if 'print' == 1.
*
* Returns physical count of elements found by iterating over the list. */
static int _itrprintr(quicklist *ql, int print, int forward) {
quicklistIter *iter =
quicklistGetIterator(ql, forward ? AL_START_HEAD : AL_START_TAIL);
quicklistEntry entry;
int i = 0;
int p = 0;
quicklistNode *prev = NULL;
while (quicklistNext(iter, &entry)) {
if (entry.node != prev) {
/* Count the number of list nodes too */
p++;
prev = entry.node;
}
if (print) {
printf("[%3d (%2d)]: [%.*s] (%lld)\n", i, p, entry.sz,
(char *)entry.value, entry.longval);
}
i++;
}
quicklistReleaseIterator(iter);
return i;
}
static int itrprintr(quicklist *ql, int print) {
return _itrprintr(ql, print, 1);
}
static int itrprintr_rev(quicklist *ql, int print) {
return _itrprintr(ql, print, 0);
}
#define ql_verify(a, b, c, d, e) \
do { \
err += _ql_verify((a), (b), (c), (d), (e)); \
} while (0)
/* Verify list metadata matches physical list contents. */
static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count,
uint32_t head_count, uint32_t tail_count) {
int ok = 1;
ql_info(ql);
if (len != ql->len) {
yell("quicklist length wrong: expected %d, got %lu", len, ql->len);
ok = 0;
}
if (count != ql->count) {
yell("quicklist count wrong: expected %d, got %lu", count, ql->count);
ok = 0;
}
int loopr = itrprintr(ql, 0);
if (loopr != (int)ql->count) {
yell("quicklist cached count not match actual count: expected %lu, got "
"%d",
ql->count, loopr);
ok = 0;
}
int rloopr = itrprintr_rev(ql, 0);
if (loopr != rloopr) {
yell("quicklist has different forward count than reverse count! "
"Forward count is %d, reverse count is %d.",
loopr, rloopr);
ok = 0;
}
if (ql->len == 0 && ok) {
OK;
return !ok;
}
if (head_count != ql->head->count &&
head_count != ziplistLen(ql->head->zl)) {
yell("quicklist head count wrong: expected %d, "
"got cached %d vs. actual %d",
head_count, ql->head->count, ziplistLen(ql->head->zl));
ok = 0;
}
if (tail_count != ql->tail->count &&
tail_count != ziplistLen(ql->tail->zl)) {
yell("quicklist tail count wrong: expected %d, "
"got cached %u vs. actual %d",
tail_count, ql->tail->count, ziplistLen(ql->tail->zl));
ok = 0;
}
if (ok)
OK;
return !ok;
}
/* Generate new string concatenating integer i against string 'prefix' */
static char *genstr(char *prefix, int i) {
static char result[64] = { 0 };
snprintf(result, sizeof(result), "%s%d", prefix, i);
return result;
}
/* Test fill cap */
#define F 32
/* main test, but callable from other files */
int quicklistTest(int argc, char *argv[]) {
unsigned int err = 0;
UNUSED(argc);
UNUSED(argv);
TEST("create list") {
quicklist *ql = quicklistCreate();
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("add to tail of empty list") {
quicklist *ql = quicklistCreate();
quicklistPushTail(ql, F, "hello", 6);
/* 1 for head and 1 for tail beacuse 1 node = head = tail */
ql_verify(ql, 1, 1, 1, 1);
quicklistRelease(ql);
}
TEST("add to head of empty list") {
quicklist *ql = quicklistCreate();
quicklistPushHead(ql, F, "hello", 6);
/* 1 for head and 1 for tail beacuse 1 node = head = tail */
ql_verify(ql, 1, 1, 1, 1);
quicklistRelease(ql);
}
for (size_t f = 0; f < 32; f++) {
TEST_DESC("add to tail 5x at fill %lu", f) {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 5; i++)
quicklistPushTail(ql, f, genstr("hello", i), 32);
if (ql->count != 5)
ERROR;
if (f == 32)
ql_verify(ql, 1, 5, 5, 5);
quicklistRelease(ql);
}
}
for (size_t f = 0; f < 32; f++) {
TEST_DESC("add to head 5x at fill %lu", f) {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 5; i++)
quicklistPushHead(ql, f, genstr("hello", i), 32);
if (ql->count != 5)
ERROR;
if (f == 32)
ql_verify(ql, 1, 5, 5, 5);
quicklistRelease(ql);
}
}
for (size_t f = 0; f < 512; f++) {
TEST_DESC("add to tail 500x at fill %lu", f) {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, f, genstr("hello", i), 32);
if (ql->count != 500)
ERROR;
if (f == 32)
ql_verify(ql, 16, 500, 32, 20);
quicklistRelease(ql);
}
}
for (size_t f = 0; f < 512; f++) {
TEST_DESC("add to head 500x at fill %lu", f) {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, f, genstr("hello", i), 32);
if (ql->count != 500)
ERROR;
if (f == 32)
ql_verify(ql, 16, 500, 20, 32);
quicklistRelease(ql);
}
}
TEST("rotate empty") {
quicklist *ql = quicklistCreate();
quicklistRotate(ql, F);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
for (size_t f = 0; f < 32; f++) {
TEST("rotate one val once") {
quicklist *ql = quicklistCreate();
quicklistPushHead(ql, F, "hello", 6);
quicklistRotate(ql, F);
ql_verify(ql, 1, 1, 1, 1);
quicklistRelease(ql);
}
}
for (size_t f = 0; f < 1024; f++) {
TEST_DESC("rotate 500 val 5000 times at fill %lu", f) {
quicklist *ql = quicklistCreate();
quicklistPushHead(ql, f, "900", 3);
quicklistPushHead(ql, f, "7000", 4);
quicklistPushHead(ql, f, "-1200", 5);
quicklistPushHead(ql, f, "42", 2);
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, f, genstr("hello", i), 32);
ql_info(ql);
for (int i = 0; i < 5000; i++) {
ql_info(ql);
quicklistRotate(ql, f);
}
if (f == 1)
ql_verify(ql, 504, 504, 1, 1);
else if (f == 2)
ql_verify(ql, 252, 504, 2, 2);
else if (f == 32)
ql_verify(ql, 16, 504, 32, 24);
quicklistRelease(ql);
}
}
TEST("pop empty") {
quicklist *ql = quicklistCreate();
quicklistPop(ql, QUICKLIST_HEAD, NULL, NULL, NULL);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("pop 1 string from 1") {
quicklist *ql = quicklistCreate();
quicklistPushHead(ql, F, genstr("hello", 331), 32);
unsigned char *data;
unsigned int sz;
long long lv;
ql_info(ql);
quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
assert(data != NULL);
assert(sz == 32);
zfree(data);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("pop head 1 number from 1") {
quicklist *ql = quicklistCreate();
quicklistPushHead(ql, F, "55513", 5);
unsigned char *data;
unsigned int sz;
long long lv;
ql_info(ql);
quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
assert(data == NULL);
assert(lv == 55513);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("pop head 500 from 500") {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, F, genstr("hello", i), 32);
ql_info(ql);
for (int i = 0; i < 500; i++) {
unsigned char *data;
unsigned int sz;
long long lv;
int ret = quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
assert(ret == 1);
assert(data != NULL);
assert(sz == 32);
zfree(data);
}
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("pop head 5000 from 500") {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, F, genstr("hello", i), 32);
for (int i = 0; i < 5000; i++) {
unsigned char *data;
unsigned int sz;
long long lv;
int ret = quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
if (i < 500) {
assert(ret == 1);
assert(data != NULL);
assert(sz == 32);
zfree(data);
} else {
assert(ret == 0);
}
}
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("iterate forward over 500 list") {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, F, genstr("hello", i), 32);
quicklistIter *iter = quicklistGetIterator(ql, AL_START_HEAD);
quicklistEntry entry;
int i = 499, count = 0;
while (quicklistNext(iter, &entry)) {
char *h = genstr("hello", i);
if (strcmp((char *)entry.value, h))
ERR("value [%s] didn't match [%s] at position %d", entry.value,
h, i);
i--;
count++;
}
if (count != 500)
ERR("Didn't iterate over exactly 500 elements (%d)", i);
ql_verify(ql, 16, 500, 20, 32);
quicklistReleaseIterator(iter);
quicklistRelease(ql);
}
TEST("iterate reverse over 500 list") {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, F, genstr("hello", i), 32);
quicklistIter *iter = quicklistGetIterator(ql, AL_START_TAIL);
quicklistEntry entry;
int i = 0;
while (quicklistNext(iter, &entry)) {
char *h = genstr("hello", i);
if (strcmp((char *)entry.value, h))
ERR("value [%s] didn't match [%s] at position %d", entry.value,
h, i);
i++;
}
if (i != 500)
ERR("Didn't iterate over exactly 500 elements (%d)", i);
ql_verify(ql, 16, 500, 20, 32);
quicklistReleaseIterator(iter);
quicklistRelease(ql);
}
TEST("insert before with 0 elements") {
quicklist *ql = quicklistCreate();
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
quicklistInsertBefore(ql, F, &entry, "abc", 4);
ql_verify(ql, 1, 1, 1, 1);
quicklistRelease(ql);
}
TEST("insert after with 0 elements") {
quicklist *ql = quicklistCreate();
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
quicklistInsertAfter(ql, F, &entry, "abc", 4);
ql_verify(ql, 1, 1, 1, 1);
quicklistRelease(ql);
}
TEST("insert after 1 element") {
quicklist *ql = quicklistCreate();
quicklistPushHead(ql, F, "hello", 6);
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
quicklistInsertAfter(ql, F, &entry, "abc", 4);
ql_verify(ql, 1, 2, 2, 2);
quicklistRelease(ql);
}
TEST("insert before 1 element") {
quicklist *ql = quicklistCreate();
quicklistPushHead(ql, F, "hello", 6);
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
quicklistInsertAfter(ql, F, &entry, "abc", 4);
ql_verify(ql, 1, 2, 2, 2);
quicklistRelease(ql);
}
for (size_t f = 0; f < 12; f++) {
TEST_DESC("insert once in elements while iterating at fill %lu\n", f) {
quicklist *ql = quicklistCreate();
quicklistPushTail(ql, f, "abc", 3);
quicklistPushTail(ql, 1, "def", 3); /* force to unique node */
quicklistPushTail(ql, f, "bob", 3); /* force to reset for +3 */
quicklistPushTail(ql, f, "foo", 3);
quicklistPushTail(ql, f, "zoo", 3);
itrprintr(ql, 1);
/* insert "bar" before "bob" while iterating over list. */
quicklistIter *iter = quicklistGetIterator(ql, AL_START_HEAD);
quicklistEntry entry;
while (quicklistNext(iter, &entry)) {
if (!strncmp((char *)entry.value, "bob", 3)) {
/* Insert as fill = 1 so it spills into new node. */
quicklistInsertBefore(ql, f, &entry, "bar", 3);
/* NOTE! You can't continue iterating after an insert into
* the list. You *must* re-create your iterator again if
* you want to traverse all entires. */
break;
}
}
itrprintr(ql, 1);
/* verify results */
quicklistIndex(ql, 0, &entry);
if (strncmp((char *)entry.value, "abc", 3))
ERR("Value 0 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistIndex(ql, 1, &entry);
if (strncmp((char *)entry.value, "def", 3))
ERR("Value 1 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistIndex(ql, 2, &entry);
if (strncmp((char *)entry.value, "bar", 3))
ERR("Value 2 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistIndex(ql, 3, &entry);
if (strncmp((char *)entry.value, "bob", 3))
ERR("Value 3 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistIndex(ql, 4, &entry);
if (strncmp((char *)entry.value, "foo", 3))
ERR("Value 4 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistIndex(ql, 5, &entry);
if (strncmp((char *)entry.value, "zoo", 3))
ERR("Value 5 didn't match, instead got: %.*s", entry.sz,
entry.value);
quicklistReleaseIterator(iter);
quicklistRelease(ql);
}
}
for (size_t f = 0; f < 1024; f++) {
TEST_DESC("insert [before] 250 new in middle of 500 elements at fill"
" %ld",
f) {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, f, genstr("hello", i), 32);
for (int i = 0; i < 250; i++) {
quicklistEntry entry;
quicklistIndex(ql, 250, &entry);
quicklistInsertBefore(ql, f, &entry, genstr("abc", i), 32);
}
if (f == 32)
ql_verify(ql, 25, 750, 32, 20);
quicklistRelease(ql);
}
}
for (size_t f = 0; f < 1024; f++) {
TEST_DESC(
"insert [after] 250 new in middle of 500 elements at fill %ld", f) {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, f, genstr("hello", i), 32);
for (int i = 0; i < 250; i++) {
quicklistEntry entry;
quicklistIndex(ql, 250, &entry);
quicklistInsertAfter(ql, f, &entry, genstr("abc", i), 32);
}
if (ql->count != 750)
ERR("List size not 750, but rather %ld", ql->count);
if (f == 32)
ql_verify(ql, 26, 750, 20, 32);
quicklistRelease(ql);
}
}
TEST("duplicate empty list") {
quicklist *ql = quicklistCreate();
ql_verify(ql, 0, 0, 0, 0);
quicklist *copy = quicklistDup(ql);
ql_verify(copy, 0, 0, 0, 0);
quicklistRelease(ql);
quicklistRelease(copy);
}
TEST("duplicate list of 1 element") {
quicklist *ql = quicklistCreate();
quicklistPushHead(ql, F, genstr("hello", 3), 32);
ql_verify(ql, 1, 1, 1, 1);
quicklist *copy = quicklistDup(ql);
ql_verify(copy, 1, 1, 1, 1);
quicklistRelease(ql);
quicklistRelease(copy);
}
TEST("duplicate list of 500") {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushHead(ql, F, genstr("hello", i), 32);
ql_verify(ql, 16, 500, 20, 32);
quicklist *copy = quicklistDup(ql);
ql_verify(copy, 16, 500, 20, 32);
quicklistRelease(ql);
quicklistRelease(copy);
}
for (size_t f = 0; f < 512; f++) {
TEST_DESC("index 1,200 from 500 list at fill %lu", f) {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, f, genstr("hello", i + 1), 32);
quicklistEntry entry;
quicklistIndex(ql, 1, &entry);
if (!strcmp((char *)entry.value, "hello2"))
OK;
else
ERR("Value: %s", entry.value);
quicklistIndex(ql, 200, &entry);
if (!strcmp((char *)entry.value, "hello201"))
OK;
else
ERR("Value: %s", entry.value);
quicklistRelease(ql);
}
TEST_DESC("index -1,-2 from 500 list at fill %lu", f) {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, f, genstr("hello", i + 1), 32);
quicklistEntry entry;
quicklistIndex(ql, -1, &entry);
if (!strcmp((char *)entry.value, "hello500"))
OK;
else
ERR("Value: %s", entry.value);
quicklistIndex(ql, -2, &entry);
if (!strcmp((char *)entry.value, "hello499"))
OK;
else
ERR("Value: %s", entry.value);
quicklistRelease(ql);
}
TEST_DESC("index -100 from 500 list at fill %lu", f) {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, f, genstr("hello", i + 1), 32);
quicklistEntry entry;
quicklistIndex(ql, -100, &entry);
if (!strcmp((char *)entry.value, "hello401"))
OK;
else
ERR("Value: %s", entry.value);
quicklistRelease(ql);
}
TEST_DESC("index too big +1 from 50 list at fill %lu", f) {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 50; i++)
quicklistPushTail(ql, f, genstr("hello", i + 1), 32);
quicklistEntry entry;
if (quicklistIndex(ql, 50, &entry))
ERR("Index found at 50 with 50 list: %.*s", entry.sz,
entry.value);
else
OK;
quicklistRelease(ql);
}
}
TEST("delete range empty list") {
quicklist *ql = quicklistCreate();
quicklistDelRange(ql, 5, 20);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("delete range of entire node in list of one node") {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 32; i++)
quicklistPushHead(ql, F, genstr("hello", i), 32);
ql_verify(ql, 1, 32, 32, 32);
quicklistDelRange(ql, 0, 32);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("delete range of entire node with overflow counts") {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 32; i++)
quicklistPushHead(ql, F, genstr("hello", i), 32);
ql_verify(ql, 1, 32, 32, 32);
quicklistDelRange(ql, 0, 128);
ql_verify(ql, 0, 0, 0, 0);
quicklistRelease(ql);
}
TEST("delete middle 100 of 500 list") {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, F, genstr("hello", i + 1), 32);
ql_verify(ql, 16, 500, 32, 20);
quicklistDelRange(ql, 200, 100);
ql_verify(ql, 14, 400, 32, 20);
quicklistRelease(ql);
}
TEST("delete negative 1 from 500 list") {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, F, genstr("hello", i + 1), 32);
ql_verify(ql, 16, 500, 32, 20);
quicklistDelRange(ql, -1, 1);
ql_verify(ql, 16, 499, 32, 19);
quicklistRelease(ql);
}
TEST("delete negative 1 from 500 list with overflow counts") {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, F, genstr("hello", i + 1), 32);
ql_verify(ql, 16, 500, 32, 20);
quicklistDelRange(ql, -1, 128);
ql_verify(ql, 16, 499, 32, 19);
quicklistRelease(ql);
}
TEST("delete negative 100 from 500 list") {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 500; i++)
quicklistPushTail(ql, F, genstr("hello", i + 1), 32);
quicklistDelRange(ql, -100, 100);
ql_verify(ql, 13, 400, 32, 16);
quicklistRelease(ql);
}
TEST("delete -10 count 5 from 50 list") {
quicklist *ql = quicklistCreate();
for (int i = 0; i < 50; i++)
quicklistPushTail(ql, F, genstr("hello", i + 1), 32);
ql_verify(ql, 2, 50, 32, 18);
quicklistDelRange(ql, -10, 5);
ql_verify(ql, 2, 45, 32, 13);
quicklistRelease(ql);
}
TEST("numbers only list read") {
quicklist *ql = quicklistCreate();
quicklistPushTail(ql, F, "1111", 4);
quicklistPushTail(ql, F, "2222", 4);
quicklistPushTail(ql, F, "3333", 4);
quicklistPushTail(ql, F, "4444", 4);
ql_verify(ql, 1, 4, 4, 4);
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
if (entry.longval != 1111)
ERR("Not 1111, %lld", entry.longval);
quicklistIndex(ql, 1, &entry);
if (entry.longval != 2222)
ERR("Not 2222, %lld", entry.longval);
quicklistIndex(ql, 2, &entry);
if (entry.longval != 3333)
ERR("Not 3333, %lld", entry.longval);
quicklistIndex(ql, 3, &entry);
if (entry.longval != 4444)
ERR("Not 4444, %lld", entry.longval);
if (quicklistIndex(ql, 4, &entry))
ERR("Index past elements: %lld", entry.longval);
quicklistIndex(ql, -1, &entry);
if (entry.longval != 4444)
ERR("Not 4444 (reverse), %lld", entry.longval);
quicklistIndex(ql, -2, &entry);
if (entry.longval != 3333)
ERR("Not 3333 (reverse), %lld", entry.longval);
quicklistIndex(ql, -3, &entry);
if (entry.longval != 2222)
ERR("Not 2222 (reverse), %lld", entry.longval);
quicklistIndex(ql, -4, &entry);
if (entry.longval != 1111)
ERR("Not 1111 (reverse), %lld", entry.longval);
if (quicklistIndex(ql, -5, &entry))
ERR("Index past elements (reverse), %lld", entry.longval);
quicklistRelease(ql);
}
TEST("numbers larger list read") {
quicklist *ql = quicklistCreate();
char num[32];
long long nums[5000];
for (int i = 0; i < 5000; i++) {
nums[i] = -5157318210846258176 + i;
int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, F, num, sz);
}
quicklistPushTail(ql, F, "xxxxxxxxxxxxxxxxxxxx", 20);
quicklistEntry entry;
for (int i = 0; i < 5000; i++) {
quicklistIndex(ql, i, &entry);
if (entry.longval != nums[i])
ERR("[%d] Not longval %lld but rather %lld", i, nums[i],
entry.longval);
entry.longval = 0xdeadbeef;
}
quicklistIndex(ql, 5000, &entry);
if (strncmp((char *)entry.value, "xxxxxxxxxxxxxxxxxxxx", 20))
ERR("String val not match: %s", entry.value);
ql_verify(ql, 157, 5001, 32, 9);
quicklistRelease(ql);
}
TEST("numbers larger list read B") {
quicklist *ql = quicklistCreate();
quicklistPushTail(ql, F, "99", 2);
quicklistPushTail(ql, F, "98", 2);
quicklistPushTail(ql, F, "xxxxxxxxxxxxxxxxxxxx", 20);
quicklistPushTail(ql, F, "96", 2);
quicklistPushTail(ql, F, "95", 2);
quicklistReplaceAtIndex(ql, 1, "foo", 3);
quicklistReplaceAtIndex(ql, -1, "bar", 3);
quicklistRelease(ql);
OK;
}
for (size_t f = 0; f < 16; f++) {
TEST_DESC("lrem test at fill %lu", f) {
quicklist *ql = quicklistCreate();
char *words[] = { "abc", "foo", "bar", "foobar", "foobared",
"zap", "bar", "test", "foo" };
char *result[] = { "abc", "foo", "foobar", "foobared",
"zap", "test", "foo" };
char *resultB[] = { "abc", "foo", "foobar",
"foobared", "zap", "test" };
for (int i = 0; i < 9; i++)
quicklistPushTail(ql, f, words[i], strlen(words[i]));
/* lrem 0 bar */
quicklistIter *iter = quicklistGetIterator(ql, AL_START_HEAD);
quicklistEntry entry;
int i = 0;
while (quicklistNext(iter, &entry)) {
if (quicklistCompare(entry.zi, (unsigned char *)"bar", 3)) {
quicklistDelEntry(iter, &entry);
}
i++;
}
quicklistReleaseIterator(iter);
/* check result of lrem 0 bar */
iter = quicklistGetIterator(ql, AL_START_HEAD);
i = 0;
int ok = 1;
while (quicklistNext(iter, &entry)) {
/* Result must be: abc, foo, foobar, foobared, zap, test, foo */
if (strncmp((char *)entry.value, result[i], entry.sz)) {
ERR("No match at position %d, got %.*s instead of %s", i,
entry.sz, entry.value, result[i]);
ok = 0;
}
i++;
}
quicklistReleaseIterator(iter);
quicklistPushTail(ql, f, "foo", 3);
/* lrem -2 foo */
iter = quicklistGetIterator(ql, AL_START_TAIL);
i = 0;
int del = 2;
while (quicklistNext(iter, &entry)) {
if (quicklistCompare(entry.zi, (unsigned char *)"foo", 3)) {
quicklistDelEntry(iter, &entry);
del--;
}
if (!del)
break;
i++;
}
quicklistReleaseIterator(iter);
/* check result of lrem -2 foo */
/* (we're ignoring the '2' part and still deleting all foo because
* we only have two foo) */
iter = quicklistGetIterator(ql, AL_START_TAIL);
i = 0;
size_t resB = sizeof(resultB) / sizeof(*resultB);
while (quicklistNext(iter, &entry)) {
/* Result must be: abc, foo, foobar, foobared, zap, test, foo */
if (strncmp((char *)entry.value, resultB[resB - 1 - i],
entry.sz)) {
ERR("No match at position %d, got %.*s instead of %s", i,
entry.sz, entry.value, resultB[resB - 1 - i]);
ok = 0;
}
i++;
}
quicklistReleaseIterator(iter);
/* final result of all tests */
if (ok)
OK;
quicklistRelease(ql);
}
}
for (size_t f = 0; f < 16; f++) {
TEST_DESC("iterate reverse + delete at fill %lu", f) {
quicklist *ql = quicklistCreate();
quicklistPushTail(ql, f, "abc", 3);
quicklistPushTail(ql, f, "def", 3);
quicklistPushTail(ql, f, "hij", 3);
quicklistPushTail(ql, f, "jkl", 3);
quicklistPushTail(ql, f, "oop", 3);
quicklistEntry entry;
quicklistIter *iter = quicklistGetIterator(ql, AL_START_TAIL);
int i = 0;
while (quicklistNext(iter, &entry)) {
if (quicklistCompare(entry.zi, (unsigned char *)"hij", 3)) {
quicklistDelEntry(iter, &entry);
}
i++;
}
quicklistReleaseIterator(iter);
if (i != 5)
ERR("Didn't iterate 5 times, iterated %d times.", i);
/* Check results after deletion of "hij" */
iter = quicklistGetIterator(ql, AL_START_HEAD);
i = 0;
char *vals[] = { "abc", "def", "jkl", "oop" };
while (quicklistNext(iter, &entry)) {
if (!quicklistCompare(entry.zi, (unsigned char *)vals[i], 3)) {
ERR("Value at %d didn't match %s\n", i, vals[i]);
}
i++;
}
quicklistReleaseIterator(iter);
quicklistRelease(ql);
}
}
for (size_t f = 0; f < 800; f++) {
TEST_DESC("iterator at index test at fill %lu", f) {
quicklist *ql = quicklistCreate();
char num[32];
long long nums[5000];
for (int i = 0; i < 760; i++) {
nums[i] = -5157318210846258176 + i;
int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, f, num, sz);
}
quicklistEntry entry;
quicklistIter *iter =
quicklistGetIteratorAtIdx(ql, AL_START_HEAD, 437);
int i = 437;
while (quicklistNext(iter, &entry)) {
if (entry.longval != nums[i])
ERR("Expected %lld, but got %lld", entry.longval, nums[i]);
i++;
}
quicklistReleaseIterator(iter);
quicklistRelease(ql);
}
}
for (size_t f = 0; f < 40; f++) {
TEST_DESC("ltrim test A at fill %lu", f) {
quicklist *ql = quicklistCreate();
char num[32];
long long nums[5000];
for (int i = 0; i < 32; i++) {
nums[i] = -5157318210846258176 + i;
int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, f, num, sz);
}
if (f == 32)
ql_verify(ql, 1, 32, 32, 32);
/* ltrim 25 53 (keep [25,32] inclusive = 7 remaining) */
quicklistDelRange(ql, 0, 25);
quicklistDelRange(ql, 0, 0);
quicklistEntry entry;
for (int i = 0; i < 7; i++) {
quicklistIndex(ql, i, &entry);
if (entry.longval != nums[25 + i])
ERR("Deleted invalid range! Expected %lld but got %lld",
entry.longval, nums[25 + i]);
}
if (f == 32)
ql_verify(ql, 1, 7, 7, 7);
quicklistRelease(ql);
}
}
for (size_t f = 0; f < 40; f++) {
TEST_DESC("ltrim test B at fill %lu", f) {
quicklist *ql = quicklistCreate();
char num[32];
long long nums[5000];
for (int i = 0; i < 33; i++) {
nums[i] = i;
int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, f, num, sz);
}
if (f == 32)
ql_verify(ql, 2, 33, 32, 1);
/* ltrim 5 16 (keep [5,16] inclusive = 12 remaining) */
quicklistDelRange(ql, 0, 5);
quicklistDelRange(ql, -16, 16);
if (f == 32)
ql_verify(ql, 1, 12, 12, 12);
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
if (entry.longval != 5)
ERR("A: longval not 5, but %lld", entry.longval);
else
OK;
quicklistIndex(ql, -1, &entry);
if (entry.longval != 16)
ERR("B! got instead: %lld", entry.longval);
else
OK;
quicklistPushTail(ql, f, "bobobob", 7);
quicklistIndex(ql, -1, &entry);
if (strncmp((char *)entry.value, "bobobob", 7))
ERR("Tail doesn't match bobobob, it's %.*s instead", entry.sz,
entry.value);
for (int i = 0; i < 12; i++) {
quicklistIndex(ql, i, &entry);
if (entry.longval != nums[5 + i])
ERR("Deleted invalid range! Expected %lld but got %lld",
entry.longval, nums[5 + i]);
}
quicklistRelease(ql);
}
}
for (size_t f = 0; f < 40; f++) {
TEST_DESC("ltrim test C at fill %lu", f) {
quicklist *ql = quicklistCreate();
char num[32];
long long nums[5000];
for (int i = 0; i < 33; i++) {
nums[i] = -5157318210846258176 + i;
int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, f, num, sz);
}
if (f == 32)
ql_verify(ql, 2, 33, 32, 1);
/* ltrim 3 3 (keep [3,3] inclusive = 1 remaining) */
quicklistDelRange(ql, 0, 3);
quicklistDelRange(ql, -29, 4000); /* make sure not loop forever */
if (f == 32)
ql_verify(ql, 1, 1, 1, 1);
quicklistEntry entry;
quicklistIndex(ql, 0, &entry);
if (entry.longval != -5157318210846258173)
ERROR;
else
OK;
quicklistRelease(ql);
}
}
for (size_t f = 0; f < 40; f++) {
TEST_DESC("ltrim test D at fill %lu", f) {
quicklist *ql = quicklistCreate();
char num[32];
long long nums[5000];
for (int i = 0; i < 33; i++) {
nums[i] = -5157318210846258176 + i;
int sz = ll2string(num, sizeof(num), nums[i]);
quicklistPushTail(ql, f, num, sz);
}
if (f == 32)
ql_verify(ql, 2, 33, 32, 1);
quicklistDelRange(ql, -12, 3);
if (ql->count != 30)
ERR("Didn't delete exactly three elements! Count is: %lu",
ql->count);
quicklistRelease(ql);
}
}
for (size_t f = 0; f < 72; f++) {
TEST_DESC("create quicklist from ziplist at fill %lu", f) {
unsigned char *zl = ziplistNew();
long long nums[32];
char num[32];
for (int i = 0; i < 33; i++) {
nums[i] = -5157318210846258176 + i;
int sz = ll2string(num, sizeof(num), nums[i]);
zl = ziplistPush(zl, (unsigned char *)num, sz, ZIPLIST_TAIL);
}
for (int i = 0; i < 33; i++) {
zl = ziplistPush(zl, (unsigned char *)genstr("hello", i), 32,
ZIPLIST_TAIL);
}
quicklist *ql = quicklistCreateFromZiplist(f, zl);
if (f == 1)
ql_verify(ql, 66, 66, 1, 1);
else if (f == 32)
ql_verify(ql, 3, 66, 32, 2);
else if (f == 66)
ql_verify(ql, 1, 66, 66, 66);
quicklistRelease(ql);
}
}
if (!err)
printf("ALL TESTS PASSED!\n");
else
ERR("Sorry, not all tests passed! In fact, %d tests failed.", err);
return err;
}
#endif
/* 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__ */
...@@ -433,9 +433,7 @@ int rdbSaveObjectType(rio *rdb, robj *o) { ...@@ -433,9 +433,7 @@ int rdbSaveObjectType(rio *rdb, robj *o) {
case REDIS_STRING: case REDIS_STRING:
return rdbSaveType(rdb,REDIS_RDB_TYPE_STRING); return rdbSaveType(rdb,REDIS_RDB_TYPE_STRING);
case REDIS_LIST: case REDIS_LIST:
if (o->encoding == REDIS_ENCODING_ZIPLIST) if (o->encoding == REDIS_ENCODING_QUICKLIST)
return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST_ZIPLIST);
else if (o->encoding == REDIS_ENCODING_LINKEDLIST)
return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST); return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST);
else else
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
...@@ -477,7 +475,7 @@ int rdbLoadObjectType(rio *rdb) { ...@@ -477,7 +475,7 @@ int rdbLoadObjectType(rio *rdb) {
/* Save a Redis object. Returns -1 on error, number of bytes written on success. */ /* Save a Redis object. Returns -1 on error, number of bytes written on success. */
int rdbSaveObject(rio *rdb, robj *o) { int rdbSaveObject(rio *rdb, robj *o) {
int n, nwritten = 0; int n = 0, nwritten = 0;
if (o->type == REDIS_STRING) { if (o->type == REDIS_STRING) {
/* Save a string value */ /* Save a string value */
...@@ -485,25 +483,23 @@ int rdbSaveObject(rio *rdb, robj *o) { ...@@ -485,25 +483,23 @@ int rdbSaveObject(rio *rdb, robj *o) {
nwritten += n; nwritten += n;
} else if (o->type == REDIS_LIST) { } else if (o->type == REDIS_LIST) {
/* Save a list value */ /* Save a list value */
if (o->encoding == REDIS_ENCODING_ZIPLIST) { if (o->encoding == REDIS_ENCODING_QUICKLIST) {
size_t l = ziplistBlobLen((unsigned char*)o->ptr); quicklist *list = o->ptr;
quicklistIter *li = quicklistGetIterator(list, AL_START_HEAD);
quicklistEntry entry;
if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1; if ((n = rdbSaveLen(rdb,quicklistCount(list))) == -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;
nwritten += n; nwritten += n;
listRewind(list,&li); while (quicklistNext(li,&entry)) {
while((ln = listNext(&li))) { if (entry.value) {
robj *eleobj = listNodeValue(ln); if ((n = rdbSaveRawString(rdb,entry.value,entry.sz)) == -1) return -1;
if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1; } else {
if ((n = rdbSaveLongLongAsStringObject(rdb,entry.longval)) == -1) return -1;
}
nwritten += n; nwritten += n;
} }
quicklistReleaseIterator(li);
} else { } else {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
} }
...@@ -831,33 +827,17 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) { ...@@ -831,33 +827,17 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
/* Read list value */ /* Read list value */
if ((len = rdbLoadLen(rdb,NULL)) == REDIS_RDB_LENERR) return NULL; if ((len = rdbLoadLen(rdb,NULL)) == REDIS_RDB_LENERR) return NULL;
/* Use a real list when there are too many entries */ o = createQuicklistObject();
if (len > server.list_max_ziplist_entries) {
o = createListObject();
} else {
o = createZiplistObject();
}
/* Load every single element of the list */ /* Load every single element of the list */
while(len--) { while(len--) {
if ((ele = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL; 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); dec = getDecodedObject(ele);
o->ptr = ziplistPush(o->ptr,dec->ptr,sdslen(dec->ptr),REDIS_TAIL); 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(dec);
decrRefCount(ele); decrRefCount(ele);
} else {
ele = tryObjectEncoding(ele);
listAddNodeTail(o->ptr,ele);
}
} }
} else if (rdbtype == REDIS_RDB_TYPE_SET) { } else if (rdbtype == REDIS_RDB_TYPE_SET) {
/* Read list/set value */ /* Read list/set value */
...@@ -1048,8 +1028,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) { ...@@ -1048,8 +1028,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
case REDIS_RDB_TYPE_LIST_ZIPLIST: case REDIS_RDB_TYPE_LIST_ZIPLIST:
o->type = REDIS_LIST; o->type = REDIS_LIST;
o->encoding = REDIS_ENCODING_ZIPLIST; o->encoding = REDIS_ENCODING_ZIPLIST;
if (ziplistLen(o->ptr) > server.list_max_ziplist_entries) listTypeConvert(o,REDIS_ENCODING_QUICKLIST);
listTypeConvert(o,REDIS_ENCODING_LINKEDLIST);
break; break;
case REDIS_RDB_TYPE_SET_INTSET: case REDIS_RDB_TYPE_SET_INTSET:
o->type = REDIS_SET; o->type = REDIS_SET;
......
...@@ -3659,6 +3659,8 @@ int main(int argc, char **argv) { ...@@ -3659,6 +3659,8 @@ int main(int argc, char **argv) {
if (argc == 3 && !strcasecmp(argv[1], "test")) { if (argc == 3 && !strcasecmp(argv[1], "test")) {
if (!strcasecmp(argv[2], "ziplist")) { if (!strcasecmp(argv[2], "ziplist")) {
return ziplistTest(argc, argv); return ziplistTest(argc, argv);
} else if (!strcasecmp(argv[2], "quicklist")) {
quicklistTest(argc, argv);
} else if (!strcasecmp(argv[2], "intset")) { } else if (!strcasecmp(argv[2], "intset")) {
return intsetTest(argc, argv); return intsetTest(argc, argv);
} else if (!strcasecmp(argv[2], "zipmap")) { } else if (!strcasecmp(argv[2], "zipmap")) {
......
...@@ -65,6 +65,7 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -65,6 +65,7 @@ typedef long long mstime_t; /* millisecond time type. */
#include "util.h" /* Misc functions useful in many places */ #include "util.h" /* Misc functions useful in many places */
#include "latency.h" /* Latency monitor API */ #include "latency.h" /* Latency monitor API */
#include "sparkline.h" /* ASII graphs API */ #include "sparkline.h" /* ASII graphs API */
#include "quicklist.h"
/* Following includes allow test functions to be called from Redis main() */ /* Following includes allow test functions to be called from Redis main() */
#include "zipmap.h" #include "zipmap.h"
...@@ -204,6 +205,7 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -204,6 +205,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */ #define REDIS_ENCODING_INTSET 6 /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */ #define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
#define REDIS_ENCODING_EMBSTR 8 /* Embedded sds string encoding */ #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 /* 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 * keys requires a lot of space, so we check the most significant 2 bits of
...@@ -964,15 +966,13 @@ typedef struct { ...@@ -964,15 +966,13 @@ typedef struct {
robj *subject; robj *subject;
unsigned char encoding; unsigned char encoding;
unsigned char direction; /* Iteration direction */ unsigned char direction; /* Iteration direction */
unsigned char *zi; quicklistIter *iter;
listNode *ln;
} listTypeIterator; } listTypeIterator;
/* Structure for an entry while iterating over a list. */ /* Structure for an entry while iterating over a list. */
typedef struct { typedef struct {
listTypeIterator *li; listTypeIterator *li;
unsigned char *zi; /* Entry in ziplist */ quicklistEntry entry; /* Entry in quicklist */
listNode *ln; /* Entry in linked list */
} listTypeEntry; } listTypeEntry;
/* Structure to hold set iteration abstraction. */ /* Structure to hold set iteration abstraction. */
...@@ -1099,7 +1099,7 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry); ...@@ -1099,7 +1099,7 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry);
robj *listTypeGet(listTypeEntry *entry); robj *listTypeGet(listTypeEntry *entry);
void listTypeInsert(listTypeEntry *entry, robj *value, int where); void listTypeInsert(listTypeEntry *entry, robj *value, int where);
int listTypeEqual(listTypeEntry *entry, robj *o); int listTypeEqual(listTypeEntry *entry, robj *o);
void listTypeDelete(listTypeEntry *entry); void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry);
void listTypeConvert(robj *subject, int enc); void listTypeConvert(robj *subject, int enc);
void unblockClientWaitingData(redisClient *c); void unblockClientWaitingData(redisClient *c);
void handleClientsBlockedOnLists(void); void handleClientsBlockedOnLists(void);
...@@ -1137,7 +1137,7 @@ robj *getDecodedObject(robj *o); ...@@ -1137,7 +1137,7 @@ robj *getDecodedObject(robj *o);
size_t stringObjectLen(robj *o); size_t stringObjectLen(robj *o);
robj *createStringObjectFromLongLong(long long value); robj *createStringObjectFromLongLong(long long value);
robj *createStringObjectFromLongDouble(long double value, int humanfriendly); robj *createStringObjectFromLongDouble(long double value, int humanfriendly);
robj *createListObject(void); robj *createQuicklistObject(void);
robj *createZiplistObject(void); robj *createZiplistObject(void);
robj *createSetObject(void); robj *createSetObject(void);
robj *createIntsetObject(void); robj *createIntsetObject(void);
......
...@@ -220,7 +220,7 @@ void sortCommand(redisClient *c) { ...@@ -220,7 +220,7 @@ void sortCommand(redisClient *c) {
if (sortval) if (sortval)
incrRefCount(sortval); incrRefCount(sortval);
else else
sortval = createListObject(); sortval = createQuicklistObject();
/* The SORT command has an SQL-alike syntax, parse it */ /* The SORT command has an SQL-alike syntax, parse it */
while(j < c->argc) { while(j < c->argc) {
...@@ -420,6 +420,7 @@ void sortCommand(redisClient *c) { ...@@ -420,6 +420,7 @@ void sortCommand(redisClient *c) {
} else { } else {
redisPanic("Unknown type"); redisPanic("Unknown type");
} }
printf("j: %d; vectorlen: %d\n", j, vectorlen);
redisAssertWithInfo(c,sortval,j == vectorlen); redisAssertWithInfo(c,sortval,j == vectorlen);
/* Now it's time to load the right scores in the sorting vector */ /* Now it's time to load the right scores in the sorting vector */
...@@ -509,7 +510,7 @@ void sortCommand(redisClient *c) { ...@@ -509,7 +510,7 @@ void sortCommand(redisClient *c) {
} }
} }
} else { } else {
robj *sobj = createZiplistObject(); robj *sobj = createQuicklistObject();
/* 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++) {
......
...@@ -33,76 +33,41 @@ ...@@ -33,76 +33,41 @@
* List API * 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', /* The function pushes an element to the specified list object 'subject',
* at head or tail position as specified by 'where'. * at head or tail position as specified by 'where'.
* *
* There is no need for the caller to increment the refcount of 'value' as * There is no need for the caller to increment the refcount of 'value' as
* the function takes care of it if needed. */ * the function takes care of it if needed. */
void listTypePush(robj *subject, robj *value, int where) { void listTypePush(robj *subject, robj *value, int where) {
/* Check if we need to convert the ziplist */ if (subject->encoding == REDIS_ENCODING_QUICKLIST) {
listTypeTryConversion(subject,value); int pos = (where == REDIS_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL;
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;
value = getDecodedObject(value); 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); 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 { } else {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
} }
} }
void *listPopSaver(unsigned char *data, unsigned int sz) {
return createStringObject((char*)data,sz);
}
robj *listTypePop(robj *subject, int where) { 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; long long vlong;
int pos = (where == REDIS_HEAD) ? 0 : -1; robj *value = NULL;
p = ziplistIndex(subject->ptr,pos);
if (ziplistGet(p,&vstr,&vlen,&vlong)) { int ql_where = where == REDIS_HEAD ? QUICKLIST_HEAD : QUICKLIST_TAIL;
if (vstr) { if (subject->encoding == REDIS_ENCODING_QUICKLIST) {
value = createStringObject((char*)vstr,vlen); if (quicklistPopCustom(subject->ptr, ql_where, (unsigned char **)&value,
} else { NULL, &vlong, listPopSaver)) {
if (!value)
value = createStringObjectFromLongLong(vlong); 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 { } else {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
} }
...@@ -110,25 +75,28 @@ robj *listTypePop(robj *subject, int where) { ...@@ -110,25 +75,28 @@ robj *listTypePop(robj *subject, int where) {
} }
unsigned long listTypeLength(robj *subject) { unsigned long listTypeLength(robj *subject) {
if (subject->encoding == REDIS_ENCODING_ZIPLIST) { if (subject->encoding == REDIS_ENCODING_QUICKLIST) {
return ziplistLen(subject->ptr); return quicklistCount(subject->ptr);
} else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
return listLength((list*)subject->ptr);
} else { } else {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
} }
} }
/* Initialize an iterator at the specified index. */ /* 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)); listTypeIterator *li = zmalloc(sizeof(listTypeIterator));
li->subject = subject; li->subject = subject;
li->encoding = subject->encoding; li->encoding = subject->encoding;
li->direction = direction; li->direction = direction;
if (li->encoding == REDIS_ENCODING_ZIPLIST) { li->iter = NULL;
li->zi = ziplistIndex(subject->ptr,index); /* REDIS_HEAD means start at TAIL and move *towards* head.
} else if (li->encoding == REDIS_ENCODING_LINKEDLIST) { * REDIS_TAIL means start at HEAD and move *towards tail. */
li->ln = listIndex(subject->ptr,index); 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 { } else {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
} }
...@@ -137,6 +105,7 @@ listTypeIterator *listTypeInitIterator(robj *subject, long index, unsigned char ...@@ -137,6 +105,7 @@ listTypeIterator *listTypeInitIterator(robj *subject, long index, unsigned char
/* Clean up the iterator. */ /* Clean up the iterator. */
void listTypeReleaseIterator(listTypeIterator *li) { void listTypeReleaseIterator(listTypeIterator *li) {
zfree(li->iter);
zfree(li); zfree(li);
} }
...@@ -148,24 +117,8 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry) { ...@@ -148,24 +117,8 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry) {
redisAssert(li->subject->encoding == li->encoding); redisAssert(li->subject->encoding == li->encoding);
entry->li = li; entry->li = li;
if (li->encoding == REDIS_ENCODING_ZIPLIST) { if (li->encoding == REDIS_ENCODING_QUICKLIST) {
entry->zi = li->zi; return quicklistNext(li->iter, &entry->entry);
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;
}
} else { } else {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
} }
...@@ -174,24 +127,14 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry) { ...@@ -174,24 +127,14 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry) {
/* Return entry or NULL at the current position of the iterator. */ /* Return entry or NULL at the current position of the iterator. */
robj *listTypeGet(listTypeEntry *entry) { robj *listTypeGet(listTypeEntry *entry) {
listTypeIterator *li = entry->li;
robj *value = NULL; robj *value = NULL;
if (li->encoding == REDIS_ENCODING_ZIPLIST) { if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
unsigned char *vstr; if (entry->entry.value) {
unsigned int vlen; value = createStringObject((char *)entry->entry.value,
long long vlong; entry->entry.sz);
redisAssert(entry->zi != NULL);
if (ziplistGet(entry->zi,&vstr,&vlen,&vlong)) {
if (vstr) {
value = createStringObject((char*)vstr,vlen);
} else { } else {
value = createStringObjectFromLongLong(vlong); value = createStringObjectFromLongLong(entry->entry.longval);
}
} }
} else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
redisAssert(entry->ln != NULL);
value = listNodeValue(entry->ln);
incrRefCount(value);
} else { } else {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
} }
...@@ -199,30 +142,19 @@ robj *listTypeGet(listTypeEntry *entry) { ...@@ -199,30 +142,19 @@ robj *listTypeGet(listTypeEntry *entry) {
} }
void listTypeInsert(listTypeEntry *entry, robj *value, int where) { void listTypeInsert(listTypeEntry *entry, robj *value, int where) {
robj *subject = entry->li->subject; if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
if (entry->li->encoding == REDIS_ENCODING_ZIPLIST) {
value = getDecodedObject(value); value = getDecodedObject(value);
sds str = value->ptr;
size_t len = sdslen(str);
size_t zlen = server.list_max_ziplist_entries;
if (where == REDIS_TAIL) { if (where == REDIS_TAIL) {
unsigned char *next = ziplistNext(subject->ptr,entry->zi); quicklistInsertAfter((quicklist *)entry->entry.quicklist, zlen,
&entry->entry, str, len);
/* When we insert after the current element, but the current element } else if (where == REDIS_HEAD) {
* is the tail of the list, we need to do a push. */ quicklistInsertBefore((quicklist *)entry->entry.quicklist, zlen,
if (next == NULL) { &entry->entry, str, len);
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));
} }
decrRefCount(value); 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 { } else {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
} }
...@@ -230,59 +162,33 @@ void listTypeInsert(listTypeEntry *entry, robj *value, int where) { ...@@ -230,59 +162,33 @@ void listTypeInsert(listTypeEntry *entry, robj *value, int where) {
/* Compare the given object with the entry at the current position. */ /* Compare the given object with the entry at the current position. */
int listTypeEqual(listTypeEntry *entry, robj *o) { int listTypeEqual(listTypeEntry *entry, robj *o) {
listTypeIterator *li = entry->li; if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
redisAssertWithInfo(NULL,o,sdsEncodedObject(o)); redisAssertWithInfo(NULL,o,sdsEncodedObject(o));
return ziplistCompare(entry->zi,o->ptr,sdslen(o->ptr)); return quicklistCompare(entry->entry.zi,o->ptr,sdslen(o->ptr));
} else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
return equalStringObjects(o,listNodeValue(entry->ln));
} else { } else {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
} }
} }
/* Delete the element pointed to. */ /* Delete the element pointed to. */
void listTypeDelete(listTypeEntry *entry) { void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry) {
listTypeIterator *li = entry->li; if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
if (li->encoding == REDIS_ENCODING_ZIPLIST) { quicklistDelEntry(iter->iter, &entry->entry);
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;
} else { } else {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
} }
} }
/* Create a quicklist from a single ziplist */
void listTypeConvert(robj *subject, int enc) { void listTypeConvert(robj *subject, int enc) {
listTypeIterator *li; redisAssertWithInfo(NULL,subject,subject->type==REDIS_LIST);
listTypeEntry entry; redisAssertWithInfo(NULL,subject,subject->encoding==REDIS_ENCODING_ZIPLIST);
redisAssertWithInfo(NULL,subject,subject->type == REDIS_LIST);
if (enc == REDIS_ENCODING_LINKEDLIST) { if (enc == REDIS_ENCODING_QUICKLIST) {
list *l = listCreate(); size_t zlen = server.list_max_ziplist_entries;
listSetFreeMethod(l,decrRefCountVoid);
/* listTypeGet returns a robj with incremented refcount */ subject->encoding = REDIS_ENCODING_QUICKLIST;
li = listTypeInitIterator(subject,0,REDIS_TAIL); subject->ptr = quicklistCreateFromZiplist(zlen, subject->ptr);
while (listTypeNext(li,&entry)) listAddNodeTail(l,listTypeGet(&entry));
listTypeReleaseIterator(li);
subject->encoding = REDIS_ENCODING_LINKEDLIST;
zfree(subject->ptr);
subject->ptr = l;
} else { } else {
redisPanic("Unsupported list conversion"); redisPanic("Unsupported list conversion");
} }
...@@ -304,7 +210,7 @@ void pushGenericCommand(redisClient *c, int where) { ...@@ -304,7 +210,7 @@ void pushGenericCommand(redisClient *c, int where) {
for (j = 2; j < c->argc; j++) { for (j = 2; j < c->argc; j++) {
c->argv[j] = tryObjectEncoding(c->argv[j]); c->argv[j] = tryObjectEncoding(c->argv[j]);
if (!lobj) { if (!lobj) {
lobj = createZiplistObject(); lobj = createQuicklistObject();
dbAdd(c->db,c->argv[1],lobj); dbAdd(c->db,c->argv[1],lobj);
} }
listTypePush(lobj,c->argv[j],where); listTypePush(lobj,c->argv[j],where);
...@@ -338,13 +244,6 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) { ...@@ -338,13 +244,6 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
checkType(c,subject,REDIS_LIST)) return; checkType(c,subject,REDIS_LIST)) return;
if (refval != NULL) { 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 */ /* Seek refval from head to tail */
iter = listTypeInitIterator(subject,0,REDIS_TAIL); iter = listTypeInitIterator(subject,0,REDIS_TAIL);
while (listTypeNext(iter,&entry)) { while (listTypeNext(iter,&entry)) {
...@@ -357,10 +256,6 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) { ...@@ -357,10 +256,6 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
listTypeReleaseIterator(iter); listTypeReleaseIterator(iter);
if (inserted) { 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]); signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"linsert", notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"linsert",
c->argv[1],c->db->id); c->argv[1],c->db->id);
...@@ -418,31 +313,19 @@ void lindexCommand(redisClient *c) { ...@@ -418,31 +313,19 @@ void lindexCommand(redisClient *c) {
if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != REDIS_OK)) if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != REDIS_OK))
return; return;
if (o->encoding == REDIS_ENCODING_ZIPLIST) { if (o->encoding == REDIS_ENCODING_QUICKLIST) {
unsigned char *p; quicklistEntry entry;
unsigned char *vstr; if (quicklistIndex(o->ptr, index, &entry)) {
unsigned int vlen; if (entry.value) {
long long vlong; value = createStringObject((char*)entry.value,entry.sz);
p = ziplistIndex(o->ptr,index);
if (ziplistGet(p,&vstr,&vlen,&vlong)) {
if (vstr) {
value = createStringObject((char*)vstr,vlen);
} else { } else {
value = createStringObjectFromLongLong(vlong); value = createStringObjectFromLongLong(entry.longval);
} }
addReplyBulk(c,value); addReplyBulk(c,value);
decrRefCount(value); decrRefCount(value);
} else { } else {
addReply(c,shared.nullbulk); 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 { } else {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
} }
...@@ -452,35 +335,18 @@ void lsetCommand(redisClient *c) { ...@@ -452,35 +335,18 @@ void lsetCommand(redisClient *c) {
robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr); robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);
if (o == NULL || checkType(c,o,REDIS_LIST)) return; if (o == NULL || checkType(c,o,REDIS_LIST)) return;
long index; 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)) if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != REDIS_OK))
return; return;
listTypeTryConversion(o,value); if (o->encoding == REDIS_ENCODING_QUICKLIST) {
if (o->encoding == REDIS_ENCODING_ZIPLIST) { quicklist *ql = o->ptr;
unsigned char *p, *zl = o->ptr; int replaced = quicklistReplaceAtIndex(ql, index,
p = ziplistIndex(zl,index); value->ptr, sdslen(value->ptr));
if (p == NULL) { 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); addReply(c,shared.outofrangeerr);
} else { } else {
decrRefCount((robj*)listNodeValue(ln));
listNodeValue(ln) = value;
incrRefCount(value);
addReply(c,shared.ok); addReply(c,shared.ok);
signalModifiedKey(c->db,c->argv[1]); signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lset",c->argv[1],c->db->id); notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lset",c->argv[1],c->db->id);
...@@ -549,43 +415,28 @@ void lrangeCommand(redisClient *c) { ...@@ -549,43 +415,28 @@ void lrangeCommand(redisClient *c) {
/* Return the result in form of a multi-bulk reply */ /* Return the result in form of a multi-bulk reply */
addReplyMultiBulkLen(c,rangelen); addReplyMultiBulkLen(c,rangelen);
if (o->encoding == REDIS_ENCODING_ZIPLIST) { if (o->encoding == REDIS_ENCODING_QUICKLIST) {
unsigned char *p = ziplistIndex(o->ptr,start); listTypeIterator *iter = listTypeInitIterator(o, start, REDIS_TAIL);
unsigned char *vstr;
unsigned int vlen;
long long vlong;
while(rangelen--) { while(rangelen--) {
ziplistGet(p,&vstr,&vlen,&vlong); listTypeEntry entry;
if (vstr) { listTypeNext(iter, &entry);
addReplyBulkCBuffer(c,vstr,vlen); quicklistEntry *qe = &entry.entry;
if (qe->value) {
addReplyBulkCBuffer(c,qe->value,qe->sz);
} else { } 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 { } else {
redisPanic("List encoding is not LINKEDLIST nor ZIPLIST!"); redisPanic("List encoding is not QUICKLIST!");
} }
} }
void ltrimCommand(redisClient *c) { void ltrimCommand(redisClient *c) {
robj *o; robj *o;
long start, end, llen, j, ltrim, rtrim; long start, end, llen, ltrim, rtrim;
list *list;
listNode *ln;
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) || if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return; (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;
...@@ -612,19 +463,9 @@ void ltrimCommand(redisClient *c) { ...@@ -612,19 +463,9 @@ void ltrimCommand(redisClient *c) {
} }
/* Remove list elements to perform the trim */ /* Remove list elements to perform the trim */
if (o->encoding == REDIS_ENCODING_ZIPLIST) { if (o->encoding == REDIS_ENCODING_QUICKLIST) {
o->ptr = ziplistDeleteRange(o->ptr,0,ltrim); quicklistDelRange(o->ptr,0,ltrim);
o->ptr = ziplistDeleteRange(o->ptr,-rtrim,rtrim); quicklistDelRange(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);
}
} else { } else {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
} }
...@@ -641,10 +482,9 @@ void ltrimCommand(redisClient *c) { ...@@ -641,10 +482,9 @@ void ltrimCommand(redisClient *c) {
void lremCommand(redisClient *c) { void lremCommand(redisClient *c) {
robj *subject, *obj; robj *subject, *obj;
obj = c->argv[3] = tryObjectEncoding(c->argv[3]); obj = c->argv[3];
long toremove; long toremove;
long removed = 0; long removed = 0;
listTypeEntry entry;
if ((getLongFromObjectOrReply(c, c->argv[2], &toremove, NULL) != REDIS_OK)) if ((getLongFromObjectOrReply(c, c->argv[2], &toremove, NULL) != REDIS_OK))
return; return;
...@@ -652,10 +492,6 @@ void lremCommand(redisClient *c) { ...@@ -652,10 +492,6 @@ void lremCommand(redisClient *c) {
subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero); subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero);
if (subject == NULL || checkType(c,subject,REDIS_LIST)) return; 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; listTypeIterator *li;
if (toremove < 0) { if (toremove < 0) {
toremove = -toremove; toremove = -toremove;
...@@ -664,9 +500,10 @@ void lremCommand(redisClient *c) { ...@@ -664,9 +500,10 @@ void lremCommand(redisClient *c) {
li = listTypeInitIterator(subject,0,REDIS_TAIL); li = listTypeInitIterator(subject,0,REDIS_TAIL);
} }
listTypeEntry entry;
while (listTypeNext(li,&entry)) { while (listTypeNext(li,&entry)) {
if (listTypeEqual(&entry,obj)) { if (listTypeEqual(&entry,obj)) {
listTypeDelete(&entry); listTypeDelete(li, &entry);
server.dirty++; server.dirty++;
removed++; removed++;
if (toremove && removed == toremove) break; if (toremove && removed == toremove) break;
...@@ -674,11 +511,10 @@ void lremCommand(redisClient *c) { ...@@ -674,11 +511,10 @@ void lremCommand(redisClient *c) {
} }
listTypeReleaseIterator(li); listTypeReleaseIterator(li);
/* Clean up raw encoded object */ if (listTypeLength(subject) == 0) {
if (subject->encoding == REDIS_ENCODING_ZIPLIST) dbDelete(c->db,c->argv[1]);
decrRefCount(obj); }
if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
addReplyLongLong(c,removed); addReplyLongLong(c,removed);
if (removed) signalModifiedKey(c->db,c->argv[1]); if (removed) signalModifiedKey(c->db,c->argv[1]);
} }
...@@ -702,7 +538,7 @@ void lremCommand(redisClient *c) { ...@@ -702,7 +538,7 @@ void lremCommand(redisClient *c) {
void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value) { void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value) {
/* Create the list if the key does not exist */ /* Create the list if the key does not exist */
if (!dstobj) { if (!dstobj) {
dstobj = createZiplistObject(); dstobj = createQuicklistObject();
dbAdd(c->db,dstkey,dstobj); dbAdd(c->db,dstkey,dstobj);
} }
signalModifiedKey(c->db,dstkey); signalModifiedKey(c->db,dstkey);
...@@ -1010,7 +846,9 @@ void handleClientsBlockedOnLists(void) { ...@@ -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 /* We don't call signalModifiedKey() as it was already called
* when an element was pushed on the list. */ * when an element was pushed on the list. */
} }
......
...@@ -19,9 +19,12 @@ proc assert_match {pattern value} { ...@@ -19,9 +19,12 @@ proc assert_match {pattern value} {
} }
} }
proc assert_equal {expected value} { proc assert_equal {expected value {detail ""}} {
if {$expected ne $value} { 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"
} }
} }
......
...@@ -77,10 +77,10 @@ start_server {tags {"aofrw"}} { ...@@ -77,10 +77,10 @@ start_server {tags {"aofrw"}} {
} }
foreach d {string int} { foreach d {string int} {
foreach e {ziplist linkedlist} { foreach e {quicklist} {
test "AOF rewrite of list with $e encoding, $d data" { test "AOF rewrite of list with $e encoding, $d data" {
r flushall r flushall
if {$e eq {ziplist}} {set len 10} else {set len 1000} set len 1000
for {set j 0} {$j < $len} {incr j} { for {set j 0} {$j < $len} {incr j} {
if {$d eq {string}} { if {$d eq {string}} {
set data [randstring 0 16 alpha] set data [randstring 0 16 alpha]
......
...@@ -36,9 +36,9 @@ start_server { ...@@ -36,9 +36,9 @@ start_server {
} }
foreach {num cmd enc title} { foreach {num cmd enc title} {
16 lpush ziplist "Ziplist" 16 lpush quicklist "Old Ziplist"
1000 lpush linkedlist "Linked list" 1000 lpush quicklist "Old Linked list"
10000 lpush linkedlist "Big Linked list" 10000 lpush quicklist "Old Big Linked list"
16 sadd intset "Intset" 16 sadd intset "Intset"
1000 sadd hashtable "Hash table" 1000 sadd hashtable "Hash table"
10000 sadd hashtable "Big Hash table" 10000 sadd hashtable "Big Hash table"
...@@ -85,14 +85,14 @@ start_server { ...@@ -85,14 +85,14 @@ start_server {
r sort tosort BY weight_* store sort-res r sort tosort BY weight_* store sort-res
assert_equal $result [r lrange sort-res 0 -1] assert_equal $result [r lrange sort-res 0 -1]
assert_equal 16 [r llen sort-res] assert_equal 16 [r llen sort-res]
assert_encoding ziplist sort-res assert_encoding quicklist sort-res
} }
test "SORT BY hash field STORE" { test "SORT BY hash field STORE" {
r sort tosort BY wobj_*->weight store sort-res r sort tosort BY wobj_*->weight store sort-res
assert_equal $result [r lrange sort-res 0 -1] assert_equal $result [r lrange sort-res 0 -1]
assert_equal 16 [r llen sort-res] assert_equal 16 [r llen sort-res]
assert_encoding ziplist sort-res assert_encoding quicklist sort-res
} }
test "SORT extracts STORE correctly" { test "SORT extracts STORE correctly" {
......
...@@ -2,7 +2,7 @@ start_server { ...@@ -2,7 +2,7 @@ start_server {
tags {"list"} tags {"list"}
overrides { overrides {
"list-max-ziplist-value" 16 "list-max-ziplist-value" 16
"list-max-ziplist-entries" 256 "list-max-ziplist-entries" 4
} }
} { } {
source "tests/unit/type/list-common.tcl" source "tests/unit/type/list-common.tcl"
...@@ -28,14 +28,18 @@ start_server { ...@@ -28,14 +28,18 @@ start_server {
for {set i 0} {$i < 1000} {incr i} { for {set i 0} {$i < 1000} {incr i} {
set min [expr {int(rand()*$startlen)}] set min [expr {int(rand()*$startlen)}]
set max [expr {$min+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] set mylist [lrange $mylist $min $max]
r ltrim 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} { for {set j [r llen mylist]} {$j < $startlen} {incr j} {
set str [randomInt 9223372036854775807] set str [randomInt 9223372036854775807]
r rpush mylist $str r rpush mylist $str
lappend mylist $str lappend mylist $str
assert_equal $mylist [r lrange mylist 0 -1] "failed append match"
} }
} }
} }
......
...@@ -2,7 +2,7 @@ start_server { ...@@ -2,7 +2,7 @@ start_server {
tags {list ziplist} tags {list ziplist}
overrides { overrides {
"list-max-ziplist-value" 200000 "list-max-ziplist-value" 200000
"list-max-ziplist-entries" 256 "list-max-ziplist-entries" 16
} }
} { } {
test {Explicit regression for a list bug} { test {Explicit regression for a list bug} {
......
...@@ -2,24 +2,24 @@ start_server { ...@@ -2,24 +2,24 @@ start_server {
tags {"list"} tags {"list"}
overrides { overrides {
"list-max-ziplist-value" 16 "list-max-ziplist-value" 16
"list-max-ziplist-entries" 256 "list-max-ziplist-entries" 5
} }
} { } {
source "tests/unit/type/list-common.tcl" source "tests/unit/type/list-common.tcl"
test {LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - ziplist} { test {LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - ziplist} {
# first lpush then rpush # first lpush then rpush
assert_equal 1 [r lpush myziplist1 a] assert_equal 1 [r lpush myziplist1 aa]
assert_equal 2 [r rpush myziplist1 b] assert_equal 2 [r rpush myziplist1 bb]
assert_equal 3 [r rpush myziplist1 c] assert_equal 3 [r rpush myziplist1 cc]
assert_equal 3 [r llen myziplist1] assert_equal 3 [r llen myziplist1]
assert_equal a [r lindex myziplist1 0] assert_equal aa [r lindex myziplist1 0]
assert_equal b [r lindex myziplist1 1] assert_equal bb [r lindex myziplist1 1]
assert_equal c [r lindex myziplist1 2] assert_equal cc [r lindex myziplist1 2]
assert_equal {} [r lindex myziplist2 3] assert_equal {} [r lindex myziplist2 3]
assert_equal c [r rpop myziplist1] assert_equal cc [r rpop myziplist1]
assert_equal a [r lpop myziplist1] assert_equal aa [r lpop myziplist1]
assert_encoding ziplist myziplist1 assert_encoding quicklist myziplist1
# first rpush then lpush # first rpush then lpush
assert_equal 1 [r rpush myziplist2 a] assert_equal 1 [r rpush myziplist2 a]
...@@ -32,13 +32,13 @@ start_server { ...@@ -32,13 +32,13 @@ start_server {
assert_equal {} [r lindex myziplist2 3] assert_equal {} [r lindex myziplist2 3]
assert_equal a [r rpop myziplist2] assert_equal a [r rpop myziplist2]
assert_equal c [r lpop myziplist2] assert_equal c [r lpop myziplist2]
assert_encoding ziplist myziplist2 assert_encoding quicklist myziplist2
} }
test {LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - regular list} { test {LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - regular list} {
# first lpush then rpush # first lpush then rpush
assert_equal 1 [r lpush mylist1 $largevalue(linkedlist)] 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 2 [r rpush mylist1 b]
assert_equal 3 [r rpush mylist1 c] assert_equal 3 [r rpush mylist1 c]
assert_equal 3 [r llen mylist1] assert_equal 3 [r llen mylist1]
...@@ -51,7 +51,7 @@ start_server { ...@@ -51,7 +51,7 @@ start_server {
# first rpush then lpush # first rpush then lpush
assert_equal 1 [r rpush mylist2 $largevalue(linkedlist)] 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 2 [r lpush mylist2 b]
assert_equal 3 [r lpush mylist2 c] assert_equal 3 [r lpush mylist2 c]
assert_equal 3 [r llen mylist2] assert_equal 3 [r llen mylist2]
...@@ -74,34 +74,22 @@ start_server { ...@@ -74,34 +74,22 @@ start_server {
assert_equal {d c b a 0 1 2 3} [r lrange mylist 0 -1] assert_equal {d c b a 0 1 2 3} [r lrange mylist 0 -1]
} }
test {DEL a list - ziplist} { test {DEL a list} {
assert_equal 1 [r del myziplist2]
assert_equal 0 [r exists myziplist2]
assert_equal 0 [r llen myziplist2]
}
test {DEL a list - regular list} {
assert_equal 1 [r del mylist2] assert_equal 1 [r del mylist2]
assert_equal 0 [r exists mylist2] assert_equal 0 [r exists mylist2]
assert_equal 0 [r llen 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 r del $key
foreach entry $entries { r rpush $key $entry } foreach entry $entries { r rpush $key $entry }
assert_encoding linkedlist $key assert_encoding quicklist $key
} }
foreach {type large} [array get largevalue] { foreach {type large} [array get largevalue] {
test "BLPOP, BRPOP: single existing list - $type" { test "BLPOP, BRPOP: single existing list - $type" {
set rd [redis_deferring_client] 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 $rd blpop blist 1
assert_equal {blist a} [$rd read] assert_equal {blist a} [$rd read]
...@@ -116,8 +104,8 @@ start_server { ...@@ -116,8 +104,8 @@ start_server {
test "BLPOP, BRPOP: multiple existing lists - $type" { test "BLPOP, BRPOP: multiple existing lists - $type" {
set rd [redis_deferring_client] set rd [redis_deferring_client]
create_$type blist1 "a $large c" create_list blist1 "a $large c"
create_$type blist2 "d $large f" create_list blist2 "d $large f"
$rd blpop blist1 blist2 1 $rd blpop blist1 blist2 1
assert_equal {blist1 a} [$rd read] assert_equal {blist1 a} [$rd read]
...@@ -137,7 +125,7 @@ start_server { ...@@ -137,7 +125,7 @@ start_server {
test "BLPOP, BRPOP: second list has an entry - $type" { test "BLPOP, BRPOP: second list has an entry - $type" {
set rd [redis_deferring_client] set rd [redis_deferring_client]
r del blist1 r del blist1
create_$type blist2 "d $large f" create_list blist2 "d $large f"
$rd blpop blist1 blist2 1 $rd blpop blist1 blist2 1
assert_equal {blist2 d} [$rd read] assert_equal {blist2 d} [$rd read]
...@@ -151,7 +139,7 @@ start_server { ...@@ -151,7 +139,7 @@ start_server {
r del target r del target
set rd [redis_deferring_client] 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 $rd brpoplpush blist target 1
assert_equal d [$rd read] assert_equal d [$rd read]
...@@ -517,28 +505,28 @@ start_server { ...@@ -517,28 +505,28 @@ start_server {
foreach {type large} [array get largevalue] { foreach {type large} [array get largevalue] {
test "LPUSHX, RPUSHX - $type" { test "LPUSHX, RPUSHX - $type" {
create_$type xlist "$large c" create_list xlist "$large c"
assert_equal 3 [r rpushx xlist d] assert_equal 3 [r rpushx xlist d]
assert_equal 4 [r lpushx xlist a] assert_equal 4 [r lpushx xlist a]
assert_equal "a $large c d" [r lrange xlist 0 -1] assert_equal "a $large c d" [r lrange xlist 0 -1]
} }
test "LINSERT - $type" { test "LINSERT - $type" {
create_$type xlist "a $large c d" create_list xlist "a $large c d"
assert_equal 5 [r linsert xlist before c zz] assert_equal 5 [r linsert xlist before c zz] "before c"
assert_equal "a $large zz c d" [r lrange xlist 0 10] assert_equal "a $large zz c d" [r lrange xlist 0 10] "lrangeA"
assert_equal 6 [r linsert xlist after c yy] assert_equal 6 [r linsert xlist after c yy] "after c"
assert_equal "a $large zz c yy d" [r lrange xlist 0 10] assert_equal "a $large zz c yy d" [r lrange xlist 0 10] "lrangeB"
assert_equal 7 [r linsert xlist after d dd] assert_equal 7 [r linsert xlist after d dd] "after d"
assert_equal -1 [r linsert xlist after bad ddd] 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] assert_equal "a $large zz c yy d dd" [r lrange xlist 0 10] "lrangeC"
assert_equal 8 [r linsert xlist before a aa] assert_equal 8 [r linsert xlist before a aa] "before a"
assert_equal -1 [r linsert xlist before bad aaa] 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] assert_equal "aa a $large zz c yy d dd" [r lrange xlist 0 10] "lrangeD"
# check inserting integer encoded value # check inserting integer encoded value
assert_equal 9 [r linsert xlist before aa 42] assert_equal 9 [r linsert xlist before aa 42] "before aa"
assert_equal 42 [r lrange xlist 0 0] assert_equal 42 [r lrange xlist 0 0] "lrangeE"
} }
} }
...@@ -547,55 +535,7 @@ start_server { ...@@ -547,55 +535,7 @@ start_server {
set e set e
} {*ERR*syntax*error*} } {*ERR*syntax*error*}
test {LPUSHX, RPUSHX convert from ziplist to list} { foreach {type num} {quicklist 250 quicklist 500} {
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} {
proc check_numbered_list_consistency {key} { proc check_numbered_list_consistency {key} {
set len [r llen $key] set len [r llen $key]
for {set i 0} {$i < $len} {incr i} { for {set i 0} {$i < $len} {incr i} {
...@@ -664,16 +604,16 @@ start_server { ...@@ -664,16 +604,16 @@ start_server {
foreach {type large} [array get largevalue] { foreach {type large} [array get largevalue] {
test "RPOPLPUSH base case - $type" { test "RPOPLPUSH base case - $type" {
r del mylist1 mylist2 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 d [r rpoplpush mylist1 mylist2]
assert_equal c [r rpoplpush mylist1 mylist2] assert_equal c [r rpoplpush mylist1 mylist2]
assert_equal "a $large" [r lrange mylist1 0 -1] assert_equal "a $large" [r lrange mylist1 0 -1]
assert_equal "c d" [r lrange mylist2 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" { 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 "a $large c" [r lrange mylist 0 -1]
assert_equal c [r rpoplpush mylist mylist] assert_equal c [r rpoplpush mylist mylist]
assert_equal "c a $large" [r lrange mylist 0 -1] assert_equal "c a $large" [r lrange mylist 0 -1]
...@@ -681,8 +621,8 @@ start_server { ...@@ -681,8 +621,8 @@ start_server {
foreach {othertype otherlarge} [array get largevalue] { foreach {othertype otherlarge} [array get largevalue] {
test "RPOPLPUSH with $type source and existing target $othertype" { test "RPOPLPUSH with $type source and existing target $othertype" {
create_$type srclist "a b c $large" create_list srclist "a b c $large"
create_$othertype dstlist "$otherlarge" create_list dstlist "$otherlarge"
assert_equal $large [r rpoplpush srclist dstlist] assert_equal $large [r rpoplpush srclist dstlist]
assert_equal c [r rpoplpush srclist dstlist] assert_equal c [r rpoplpush srclist dstlist]
assert_equal "a b" [r lrange srclist 0 -1] assert_equal "a b" [r lrange srclist 0 -1]
...@@ -691,7 +631,7 @@ start_server { ...@@ -691,7 +631,7 @@ start_server {
# When we rpoplpush'ed a large value, dstlist should be # When we rpoplpush'ed a large value, dstlist should be
# converted to the same encoding as srclist. # converted to the same encoding as srclist.
if {$type eq "linkedlist"} { if {$type eq "linkedlist"} {
assert_encoding linkedlist dstlist assert_encoding quicklist dstlist
} }
} }
} }
...@@ -713,7 +653,7 @@ start_server { ...@@ -713,7 +653,7 @@ start_server {
} }
test {RPOPLPUSH against non list dst key} { 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 r set dstlist x
assert_error WRONGTYPE* {r rpoplpush srclist dstlist} assert_error WRONGTYPE* {r rpoplpush srclist dstlist}
assert_type string dstlist assert_type string dstlist
...@@ -727,7 +667,7 @@ start_server { ...@@ -727,7 +667,7 @@ start_server {
foreach {type large} [array get largevalue] { foreach {type large} [array get largevalue] {
test "Basic LPOP/RPOP - $type" { 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 $large [r lpop mylist]
assert_equal 2 [r rpop mylist] assert_equal 2 [r rpop mylist]
assert_equal 1 [r lpop mylist] assert_equal 1 [r lpop mylist]
...@@ -745,7 +685,7 @@ start_server { ...@@ -745,7 +685,7 @@ start_server {
assert_error WRONGTYPE* {r rpop notalist} 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" { test "Mass RPOP/LPOP - $type" {
r del mylist r del mylist
set sum1 0 set sum1 0
...@@ -765,24 +705,24 @@ start_server { ...@@ -765,24 +705,24 @@ start_server {
foreach {type large} [array get largevalue] { foreach {type large} [array get largevalue] {
test "LRANGE basics - $type" { 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 {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 {7 8 9} [r lrange mylist -3 -1]
assert_equal {4} [r lrange mylist 4 4] assert_equal {4} [r lrange mylist 4 4]
} }
test "LRANGE inverted indexes - $type" { 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] assert_equal {} [r lrange mylist 6 2]
} }
test "LRANGE out of range indexes including the full list - $type" { 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] assert_equal "$large 1 2 3" [r lrange mylist -1000 1000]
} }
test "LRANGE out of range negative end index - $type" { 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 $large [r lrange mylist 0 -4]
assert_equal {} [r lrange mylist 0 -5] assert_equal {} [r lrange mylist 0 -5]
} }
...@@ -796,7 +736,7 @@ start_server { ...@@ -796,7 +736,7 @@ start_server {
proc trim_list {type min max} { proc trim_list {type min max} {
upvar 1 large large upvar 1 large large
r del mylist 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 ltrim mylist $min $max
r lrange mylist 0 -1 r lrange mylist 0 -1
} }
...@@ -825,7 +765,7 @@ start_server { ...@@ -825,7 +765,7 @@ start_server {
foreach {type large} [array get largevalue] { foreach {type large} [array get largevalue] {
test "LSET - $type" { 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 foo
r lset mylist -1 bar r lset mylist -1 bar
assert_equal "99 foo $large 96 bar" [r lrange mylist 0 -1] assert_equal "99 foo $large 96 bar" [r lrange mylist 0 -1]
...@@ -847,7 +787,7 @@ start_server { ...@@ -847,7 +787,7 @@ start_server {
foreach {type e} [array get largevalue] { foreach {type e} [array get largevalue] {
test "LREM remove all the occurrences - $type" { 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 2 [r lrem mylist 0 bar]
assert_equal "$e foo foobar foobared zap test foo" [r lrange mylist 0 -1] assert_equal "$e foo foobar foobared zap test foo" [r lrange mylist 0 -1]
} }
...@@ -863,7 +803,7 @@ start_server { ...@@ -863,7 +803,7 @@ start_server {
} }
test "LREM starting from tail with negative count - $type" { 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 1 [r lrem mylist -1 bar]
assert_equal "$e foo bar foobar foobared zap test foo foo" [r lrange mylist 0 -1] assert_equal "$e foo bar foobar foobared zap test foo foo" [r lrange mylist 0 -1]
} }
...@@ -874,7 +814,7 @@ start_server { ...@@ -874,7 +814,7 @@ start_server {
} }
test "LREM deleting objects that may be int encoded - $type" { 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 1 [r lrem myotherlist 1 2]
assert_equal 3 [r llen myotherlist] assert_equal 3 [r llen myotherlist]
} }
......
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