}
void linenoiseClearScreen(void) {
- write(STDIN_FILENO,"\x1b[H\x1b[2J",7);
+ if (write(STDIN_FILENO,"\x1b[H\x1b[2J",7) <= 0) {
+ /* nothing to do, just to avoid warning. */
+ }
}
static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt) {
} else if (o->type == REDIS_SET) {
setTypeIterator *si = setTypeInitIterator(o);
robj *ele;
- while((ele = setTypeNext(si)) != NULL) {
+ while((ele = setTypeNextObject(si)) != NULL) {
xorObjectDigest(digest,ele);
decrRefCount(ele);
}
unsigned char eledigest[20];
memset(eledigest,0,20);
- obj = hashTypeCurrent(hi,REDIS_HASH_KEY);
+ obj = hashTypeCurrentObject(hi,REDIS_HASH_KEY);
mixObjectDigest(eledigest,obj);
decrRefCount(obj);
- obj = hashTypeCurrent(hi,REDIS_HASH_VALUE);
+ obj = hashTypeCurrentObject(hi,REDIS_HASH_VALUE);
mixObjectDigest(eledigest,obj);
decrRefCount(obj);
xorDigest(digest,eledigest,20);
}
/* Delete integer from intset */
-intset *intsetRemove(intset *is, int64_t value, uint8_t *success) {
+intset *intsetRemove(intset *is, int64_t value, int *success) {
uint8_t valenc = _intsetValueEncoding(value);
uint32_t pos;
if (success) *success = 0;
intset *intsetNew(void);
intset *intsetAdd(intset *is, int64_t value, uint8_t *success);
-intset *intsetRemove(intset *is, int64_t value, uint8_t *success);
+intset *intsetRemove(intset *is, int64_t value, int *success);
uint8_t intsetFind(intset *is, int64_t value);
int64_t intsetRandom(intset *is);
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);
c->reply = listCreate();
listSetFreeMethod(c->reply,decrRefCount);
listSetDupMethod(c->reply,dupClientReplyValue);
- c->blocking_keys = NULL;
- c->blocking_keys_num = 0;
+ c->bpop.keys = NULL;
+ c->bpop.count = 0;
+ c->bpop.timeout = 0;
+ c->bpop.target = NULL;
c->io_keys = listCreate();
c->watched_keys = listCreate();
listSetFreeMethod(c->io_keys,decrRefCount);
redisLog(REDIS_VERBOSE,"Closing idle client");
freeClient(c);
} else if (c->flags & REDIS_BLOCKED) {
- if (c->blockingto != 0 && c->blockingto < now) {
+ if (c->bpop.timeout != 0 && c->bpop.timeout < now) {
addReply(c,shared.nullmultibulk);
unblockClientWaitingData(c);
}
{"setnx",setnxCommand,3,REDIS_CMD_DENYOOM,NULL,0,0,0},
{"setex",setexCommand,4,REDIS_CMD_DENYOOM,NULL,0,0,0},
{"append",appendCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
- {"substr",substrCommand,4,0,NULL,1,1,1},
{"strlen",strlenCommand,2,0,NULL,1,1,1},
{"del",delCommand,-2,0,NULL,0,0,0},
{"exists",existsCommand,2,0,NULL,1,1,1},
+ {"setbit",setbitCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1},
+ {"getbit",getbitCommand,3,0,NULL,1,1,1},
+ {"setrange",setrangeCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1},
+ {"getrange",getrangeCommand,4,0,NULL,1,1,1},
+ {"substr",getrangeCommand,4,0,NULL,1,1,1},
{"incr",incrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"decr",decrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"mget",mgetCommand,-2,0,NULL,1,-1,1},
{"rpop",rpopCommand,2,0,NULL,1,1,1},
{"lpop",lpopCommand,2,0,NULL,1,1,1},
{"brpop",brpopCommand,-3,0,NULL,1,1,1},
+ {"brpoplpush",brpoplpushCommand,4,REDIS_CMD_DENYOOM,NULL,1,2,1},
{"blpop",blpopCommand,-3,0,NULL,1,1,1},
{"llen",llenCommand,2,0,NULL,1,1,1},
{"lindex",lindexCommand,3,0,NULL,1,1,1},
{"lrange",lrangeCommand,4,0,NULL,1,1,1},
{"ltrim",ltrimCommand,4,0,NULL,1,1,1},
{"lrem",lremCommand,4,0,NULL,1,1,1},
- {"rpoplpush",rpoplpushcommand,3,REDIS_CMD_DENYOOM,NULL,1,2,1},
+ {"rpoplpush",rpoplpushCommand,3,REDIS_CMD_DENYOOM,NULL,1,2,1},
{"sadd",saddCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"srem",sremCommand,3,0,NULL,1,1,1},
{"smove",smoveCommand,4,0,NULL,1,2,1},
}
/* Close connections of timedout clients */
- if ((server.maxidletime && !(loops % 100)) || server.blpop_blocked_clients)
+ if ((server.maxidletime && !(loops % 100)) || server.bpop_blocked_clients)
closeTimedoutClients();
/* Check if a background saving or AOF rewrite in progress terminated */
* for ready file descriptors. */
void beforeSleep(struct aeEventLoop *eventLoop) {
REDIS_NOTUSED(eventLoop);
+ listNode *ln;
+ redisClient *c;
/* Awake clients that got all the swapped keys they requested */
if (server.vm_enabled && listLength(server.io_ready_clients)) {
listIter li;
- listNode *ln;
listRewind(server.io_ready_clients,&li);
while((ln = listNext(&li))) {
- redisClient *c = ln->value;
+ c = ln->value;
struct redisCommand *cmd;
/* Resume the client. */
processInputBuffer(c);
}
}
+
+ /* Try to process pending commands for clients that were just unblocked. */
+ while (listLength(server.unblocked_clients)) {
+ ln = listFirst(server.unblocked_clients);
+ redisAssert(ln != NULL);
+ c = ln->value;
+ listDelNode(server.unblocked_clients,ln);
+
+ /* Process remaining data in the input buffer. */
+ if (c->querybuf && sdslen(c->querybuf) > 0)
+ processInputBuffer(c);
+ }
+
/* Write the AOF buffer on disk */
flushAppendOnlyFile();
}
server.rdbcompression = 1;
server.activerehashing = 1;
server.maxclients = 0;
- server.blpop_blocked_clients = 0;
+ server.bpop_blocked_clients = 0;
server.maxmemory = 0;
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
server.maxmemory_samples = 3;
server.clients = listCreate();
server.slaves = listCreate();
server.monitors = listCreate();
+ server.unblocked_clients = listCreate();
createSharedObjects();
server.el = aeCreateEventLoop();
server.db = zmalloc(sizeof(redisDb)*server.dbnum);
(float)c_ru.ru_stime.tv_sec+(float)c_ru.ru_stime.tv_usec/1000000,
listLength(server.clients)-listLength(server.slaves),
listLength(server.slaves),
- server.blpop_blocked_clients,
+ server.bpop_blocked_clients,
zmalloc_used_memory(),
hmem,
zmalloc_get_rss(),
int count; /* Total number of MULTI commands */
} multiState;
+typedef struct blockingState {
+ robj **keys; /* The key we are waiting to terminate a blocking
+ * operation such as BLPOP. Otherwise NULL. */
+ int count; /* Number of blocking keys */
+ time_t timeout; /* Blocking operation timeout. If UNIX current time
+ * is >= timeout then the operation timed out. */
+ robj *target; /* The key that should receive the element,
+ * for BRPOPLPUSH. */
+} blockingState;
+
/* With multiplexing we need to take per-clinet state.
* Clients are taken in a liked list. */
typedef struct redisClient {
long repldboff; /* replication DB file offset */
off_t repldbsize; /* replication DB file size */
multiState mstate; /* MULTI/EXEC state */
- robj **blocking_keys; /* The key we are waiting to terminate a blocking
- * operation such as BLPOP. Otherwise NULL. */
- int blocking_keys_num; /* Number of blocking keys */
- time_t blockingto; /* Blocking operation timeout. If UNIX current time
- * is >= blockingto then the operation timed out. */
+ blockingState bpop; /* blocking state */
list *io_keys; /* Keys this client is waiting to be loaded from the
* swap file in order to continue. */
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
int maxmemory_policy;
int maxmemory_samples;
/* Blocked clients */
- unsigned int blpop_blocked_clients;
+ unsigned int bpop_blocked_clients;
unsigned int vm_blocked_clients;
+ list *unblocked_clients;
/* Sort parameters - qsort_r() is only available under BSD so we
* have to take this state global, in order to pass it to sortCompare() */
int sort_desc;
int setTypeIsMember(robj *subject, robj *value);
setTypeIterator *setTypeInitIterator(robj *subject);
void setTypeReleaseIterator(setTypeIterator *si);
-robj *setTypeNext(setTypeIterator *si);
-robj *setTypeRandomElement(robj *subject);
+int setTypeNext(setTypeIterator *si, robj **objele, int64_t *llele);
+robj *setTypeNextObject(setTypeIterator *si);
+int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele);
unsigned long setTypeSize(robj *subject);
void setTypeConvert(robj *subject, int enc);
void convertToRealHash(robj *o);
void hashTypeTryConversion(robj *subject, robj **argv, int start, int end);
void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2);
-robj *hashTypeGet(robj *o, robj *key);
+int hashTypeGet(robj *o, robj *key, robj **objval, unsigned char **v, unsigned int *vlen);
+robj *hashTypeGetObject(robj *o, robj *key);
int hashTypeExists(robj *o, robj *key);
int hashTypeSet(robj *o, robj *key, robj *value);
int hashTypeDelete(robj *o, robj *key);
hashTypeIterator *hashTypeInitIterator(robj *subject);
void hashTypeReleaseIterator(hashTypeIterator *hi);
int hashTypeNext(hashTypeIterator *hi);
-robj *hashTypeCurrent(hashTypeIterator *hi, int what);
+int hashTypeCurrent(hashTypeIterator *hi, int what, robj **objval, unsigned char **v, unsigned int *vlen);
+robj *hashTypeCurrentObject(hashTypeIterator *hi, int what);
robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key);
/* Pub / Sub */
void getCommand(redisClient *c);
void delCommand(redisClient *c);
void existsCommand(redisClient *c);
+void setbitCommand(redisClient *c);
+void getbitCommand(redisClient *c);
+void setrangeCommand(redisClient *c);
+void getrangeCommand(redisClient *c);
void incrCommand(redisClient *c);
void decrCommand(redisClient *c);
void incrbyCommand(redisClient *c);
void flushallCommand(redisClient *c);
void sortCommand(redisClient *c);
void lremCommand(redisClient *c);
-void rpoplpushcommand(redisClient *c);
+void rpoplpushCommand(redisClient *c);
void infoCommand(redisClient *c);
void mgetCommand(redisClient *c);
void monitorCommand(redisClient *c);
void discardCommand(redisClient *c);
void blpopCommand(redisClient *c);
void brpopCommand(redisClient *c);
+void brpoplpushCommand(redisClient *c);
void appendCommand(redisClient *c);
-void substrCommand(redisClient *c);
void strlenCommand(redisClient *c);
void zrankCommand(redisClient *c);
void zrevrankCommand(redisClient *c);
struct timeval tv;
gettimeofday(&tv,NULL);
- cmdrepr = sdscatprintf(cmdrepr,"%ld.%ld ",(long)tv.tv_sec,(long)tv.tv_usec);
+ cmdrepr = sdscatprintf(cmdrepr,"%ld.%06ld ",(long)tv.tv_sec,(long)tv.tv_usec);
if (dictid != 0) cmdrepr = sdscatprintf(cmdrepr,"(db %d) ", dictid);
for (j = 0; j < argc; j++) {
return newsh->buf;
}
+/* Grow the sds to have the specified length. Bytes that were not part of
+ * the original length of the sds will be set to zero. */
+sds sdsgrowzero(sds s, size_t len) {
+ struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
+ size_t totlen, curlen = sh->len;
+
+ if (len <= curlen) return s;
+ s = sdsMakeRoomFor(s,len-curlen);
+ if (s == NULL) return NULL;
+
+ /* Make sure added region doesn't contain garbage */
+ sh = (void*)(s-(sizeof(struct sdshdr)));
+ memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */
+ totlen = sh->len+sh->free;
+ sh->len = len;
+ sh->free = totlen-sh->len;
+ return s;
+}
+
sds sdscatlen(sds s, void *t, size_t len) {
struct sdshdr *sh;
size_t curlen = sdslen(s);
sds sdsdup(const sds s);
void sdsfree(sds s);
size_t sdsavail(sds s);
+sds sdsgrowzero(sds s, size_t len);
sds sdscatlen(sds s, void *t, size_t len);
sds sdscat(sds s, char *t);
sds sdscpylen(sds s, char *t, size_t len);
/* Retrieve value from hash by the field name. This operation
* already increases the refcount of the returned object. */
initStaticStringObject(fieldobj,((char*)&fieldname)+(sizeof(struct sdshdr)));
- o = hashTypeGet(o, &fieldobj);
+ o = hashTypeGetObject(o, &fieldobj);
} else {
if (o->type != REDIS_STRING) return NULL;
} else if (sortval->type == REDIS_SET) {
setTypeIterator *si = setTypeInitIterator(sortval);
robj *ele;
- while((ele = setTypeNext(si)) != NULL) {
+ while((ele = setTypeNextObject(si)) != NULL) {
vector[j].obj = ele;
vector[j].u.score = 0;
vector[j].u.cmpobj = NULL;
}
}
-/* Get the value from a hash identified by key. Returns either a string
- * object or NULL if the value cannot be found. The refcount of the object
- * is always increased by 1 when the value was found. */
-robj *hashTypeGet(robj *o, robj *key) {
- robj *value = NULL;
+/* Get the value from a hash identified by key.
+ *
+ * If the string is found either REDIS_ENCODING_HT or REDIS_ENCODING_ZIPMAP
+ * is returned, and either **objval or **v and *vlen are set accordingly,
+ * so that objects in hash tables are returend as objects and pointers
+ * inside a zipmap are returned as such.
+ *
+ * If the object was not found -1 is returned.
+ *
+ * This function is copy on write friendly as there is no incr/decr
+ * of refcount needed if objects are accessed just for reading operations. */
+int hashTypeGet(robj *o, robj *key, robj **objval, unsigned char **v,
+ unsigned int *vlen)
+{
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
- unsigned char *v;
- unsigned int vlen;
+ int found;
+
key = getDecodedObject(key);
- if (zipmapGet(o->ptr,key->ptr,sdslen(key->ptr),&v,&vlen)) {
- value = createStringObject((char*)v,vlen);
- }
+ found = zipmapGet(o->ptr,key->ptr,sdslen(key->ptr),v,vlen);
decrRefCount(key);
+ if (!found) return -1;
} else {
dictEntry *de = dictFind(o->ptr,key);
- if (de != NULL) {
- value = dictGetEntryVal(de);
- incrRefCount(value);
- }
+ if (de == NULL) return -1;
+ *objval = dictGetEntryVal(de);
+ }
+ return o->encoding;
+}
+
+/* Higher level function of hashTypeGet() that always returns a Redis
+ * object (either new or with refcount incremented), so that the caller
+ * can retain a reference or call decrRefCount after the usage.
+ *
+ * The lower level function can prevent copy on write so it is
+ * the preferred way of doing read operations. */
+robj *hashTypeGetObject(robj *o, robj *key) {
+ robj *objval;
+ unsigned char *v;
+ unsigned int vlen;
+
+ int encoding = hashTypeGet(o,key,&objval,&v,&vlen);
+ switch(encoding) {
+ case REDIS_ENCODING_HT:
+ incrRefCount(objval);
+ return objval;
+ case REDIS_ENCODING_ZIPMAP:
+ objval = createStringObject((char*)v,vlen);
+ return objval;
+ default: return NULL;
}
- return value;
}
/* Test if the key exists in the given hash. Returns 1 if the key
}
/* Get key or value object at current iteration position.
- * This increases the refcount of the field object by 1. */
-robj *hashTypeCurrent(hashTypeIterator *hi, int what) {
- robj *o;
+ * The returned item differs with the hash object encoding:
+ * - When encoding is REDIS_ENCODING_HT, the objval pointer is populated
+ * with the original object.
+ * - When encoding is REDIS_ENCODING_ZIPMAP, a pointer to the string and
+ * its length is retunred populating the v and vlen pointers.
+ * This function is copy on write friendly as accessing objects in read only
+ * does not require writing to any memory page.
+ *
+ * The function returns the encoding of the object, so that the caller
+ * can underestand if the key or value was returned as object or C string. */
+int hashTypeCurrent(hashTypeIterator *hi, int what, robj **objval, unsigned char **v, unsigned int *vlen) {
if (hi->encoding == REDIS_ENCODING_ZIPMAP) {
if (what & REDIS_HASH_KEY) {
- o = createStringObject((char*)hi->zk,hi->zklen);
+ *v = hi->zk;
+ *vlen = hi->zklen;
} else {
- o = createStringObject((char*)hi->zv,hi->zvlen);
+ *v = hi->zv;
+ *vlen = hi->zvlen;
}
} else {
- if (what & REDIS_HASH_KEY) {
- o = dictGetEntryKey(hi->de);
- } else {
- o = dictGetEntryVal(hi->de);
- }
- incrRefCount(o);
+ if (what & REDIS_HASH_KEY)
+ *objval = dictGetEntryKey(hi->de);
+ else
+ *objval = dictGetEntryVal(hi->de);
+ }
+ return hi->encoding;
+}
+
+/* A non copy-on-write friendly but higher level version of hashTypeCurrent()
+ * that always returns an object with refcount incremented by one (or a new
+ * object), so it's up to the caller to decrRefCount() the object if no
+ * reference is retained. */
+robj *hashTypeCurrentObject(hashTypeIterator *hi, int what) {
+ robj *obj;
+ unsigned char *v = NULL;
+ unsigned int vlen = 0;
+ int encoding = hashTypeCurrent(hi,what,&obj,&v,&vlen);
+
+ if (encoding == REDIS_ENCODING_HT) {
+ incrRefCount(obj);
+ return obj;
+ } else {
+ return createStringObject((char*)v,vlen);
}
- return o;
}
robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) {
if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
- if ((current = hashTypeGet(o,c->argv[2])) != NULL) {
+ if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
if (getLongLongFromObjectOrReply(c,current,&value,
"hash value is not an integer") != REDIS_OK) {
decrRefCount(current);
void hgetCommand(redisClient *c) {
robj *o, *value;
+ unsigned char *v;
+ unsigned int vlen;
+ int encoding;
+
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
- if ((value = hashTypeGet(o,c->argv[2])) != NULL) {
- addReplyBulk(c,value);
- decrRefCount(value);
+ if ((encoding = hashTypeGet(o,c->argv[2],&value,&v,&vlen)) != -1) {
+ if (encoding == REDIS_ENCODING_HT)
+ addReplyBulk(c,value);
+ else
+ addReplyBulkCBuffer(c,v,vlen);
} else {
addReply(c,shared.nullbulk);
}
}
void hmgetCommand(redisClient *c) {
- int i;
+ int i, encoding;
robj *o, *value;
+ unsigned char *v;
+ unsigned int vlen;
+
o = lookupKeyRead(c->db,c->argv[1]);
if (o != NULL && o->type != REDIS_HASH) {
addReply(c,shared.wrongtypeerr);
* an empty hash. The reply should then be a series of NULLs. */
addReplyMultiBulkLen(c,c->argc-2);
for (i = 2; i < c->argc; i++) {
- if (o != NULL && (value = hashTypeGet(o,c->argv[i])) != NULL) {
- addReplyBulk(c,value);
- decrRefCount(value);
+ if (o != NULL &&
+ (encoding = hashTypeGet(o,c->argv[i],&value,&v,&vlen)) != -1) {
+ if (encoding == REDIS_ENCODING_HT)
+ addReplyBulk(c,value);
+ else
+ addReplyBulkCBuffer(c,v,vlen);
} else {
addReply(c,shared.nullbulk);
}
}
void genericHgetallCommand(redisClient *c, int flags) {
- robj *o, *obj;
+ robj *o;
unsigned long count = 0;
hashTypeIterator *hi;
void *replylen = NULL;
replylen = addDeferredMultiBulkLength(c);
hi = hashTypeInitIterator(o);
while (hashTypeNext(hi) != REDIS_ERR) {
+ robj *obj;
+ unsigned char *v = NULL;
+ unsigned int vlen = 0;
+ int encoding;
+
if (flags & REDIS_HASH_KEY) {
- obj = hashTypeCurrent(hi,REDIS_HASH_KEY);
- addReplyBulk(c,obj);
- decrRefCount(obj);
+ encoding = hashTypeCurrent(hi,REDIS_HASH_KEY,&obj,&v,&vlen);
+ if (encoding == REDIS_ENCODING_HT)
+ addReplyBulk(c,obj);
+ else
+ addReplyBulkCBuffer(c,v,vlen);
count++;
}
if (flags & REDIS_HASH_VALUE) {
- obj = hashTypeCurrent(hi,REDIS_HASH_VALUE);
- addReplyBulk(c,obj);
- decrRefCount(obj);
+ encoding = hashTypeCurrent(hi,REDIS_HASH_VALUE,&obj,&v,&vlen);
+ if (encoding == REDIS_ENCODING_HT)
+ addReplyBulk(c,obj);
+ else
+ addReplyBulkCBuffer(c,v,vlen);
count++;
}
}
/* This is the semantic of this command:
* RPOPLPUSH srclist dstlist:
- * IF LLEN(srclist) > 0
- * element = RPOP srclist
- * LPUSH dstlist element
- * RETURN element
- * ELSE
- * RETURN nil
- * END
+ * IF LLEN(srclist) > 0
+ * element = RPOP srclist
+ * LPUSH dstlist element
+ * RETURN element
+ * ELSE
+ * RETURN nil
+ * END
* END
*
* The idea is to be able to get an element from a list in a reliable way
* since the element is not just returned but pushed against another list
* as well. This command was originally proposed by Ezra Zygmuntowicz.
*/
-void rpoplpushcommand(redisClient *c) {
+
+void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value) {
+ if (!handleClientsWaitingListPush(c,dstkey,value)) {
+ /* Create the list if the key does not exist */
+ if (!dstobj) {
+ dstobj = createZiplistObject();
+ dbAdd(c->db,dstkey,dstobj);
+ } else {
+ touchWatchedKey(c->db,dstkey);
+ server.dirty++;
+ }
+ listTypePush(dstobj,value,REDIS_HEAD);
+ }
+
+ /* Always send the pushed value to the client. */
+ addReplyBulk(c,value);
+}
+
+void rpoplpushCommand(redisClient *c) {
robj *sobj, *value;
if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,sobj,REDIS_LIST)) return;
robj *dobj = lookupKeyWrite(c->db,c->argv[2]);
if (dobj && checkType(c,dobj,REDIS_LIST)) return;
value = listTypePop(sobj,REDIS_TAIL);
-
- /* Add the element to the target list (unless it's directly
- * passed to some BLPOP-ing client */
- if (!handleClientsWaitingListPush(c,c->argv[2],value)) {
- /* Create the list if the key does not exist */
- if (!dobj) {
- dobj = createZiplistObject();
- dbAdd(c->db,c->argv[2],dobj);
- }
- listTypePush(dobj,value,REDIS_HEAD);
- }
-
- /* Send the element to the client as reply as well */
- addReplyBulk(c,value);
+ rpoplpushHandlePush(c,c->argv[2],dobj,value);
/* listTypePop returns an object with its refcount incremented */
decrRefCount(value);
/* Set a client in blocking mode for the specified key, with the specified
* timeout */
-void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout) {
+void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout, robj *target) {
dictEntry *de;
list *l;
int j;
- c->blocking_keys = zmalloc(sizeof(robj*)*numkeys);
- c->blocking_keys_num = numkeys;
- c->blockingto = timeout;
+ c->bpop.keys = zmalloc(sizeof(robj*)*numkeys);
+ c->bpop.count = numkeys;
+ c->bpop.timeout = timeout;
+ c->bpop.target = target;
+
+ if (target != NULL) {
+ incrRefCount(target);
+ }
+
for (j = 0; j < numkeys; j++) {
/* Add the key in the client structure, to map clients -> keys */
- c->blocking_keys[j] = keys[j];
+ c->bpop.keys[j] = keys[j];
incrRefCount(keys[j]);
/* And in the other "side", to map keys -> clients */
}
/* Mark the client as a blocked client */
c->flags |= REDIS_BLOCKED;
- server.blpop_blocked_clients++;
+ server.bpop_blocked_clients++;
}
/* Unblock a client that's waiting in a blocking operation such as BLPOP */
list *l;
int j;
- redisAssert(c->blocking_keys != NULL);
+ redisAssert(c->bpop.keys != NULL);
/* The client may wait for multiple keys, so unblock it for every key. */
- for (j = 0; j < c->blocking_keys_num; j++) {
+ for (j = 0; j < c->bpop.count; j++) {
/* Remove this client from the list of clients waiting for this key. */
- de = dictFind(c->db->blocking_keys,c->blocking_keys[j]);
+ de = dictFind(c->db->blocking_keys,c->bpop.keys[j]);
redisAssert(de != NULL);
l = dictGetEntryVal(de);
listDelNode(l,listSearchKey(l,c));
/* If the list is empty we need to remove it to avoid wasting memory */
if (listLength(l) == 0)
- dictDelete(c->db->blocking_keys,c->blocking_keys[j]);
- decrRefCount(c->blocking_keys[j]);
+ dictDelete(c->db->blocking_keys,c->bpop.keys[j]);
+ decrRefCount(c->bpop.keys[j]);
}
+
/* Cleanup the client structure */
- zfree(c->blocking_keys);
- c->blocking_keys = NULL;
+ zfree(c->bpop.keys);
+ c->bpop.keys = NULL;
+ c->bpop.target = NULL;
c->flags &= (~REDIS_BLOCKED);
- server.blpop_blocked_clients--;
- /* We want to process data if there is some command waiting
- * in the input buffer. Note that this is safe even if
- * unblockClientWaitingData() gets called from freeClient() because
- * freeClient() will be smart enough to call this function
- * *after* c->querybuf was set to NULL. */
- if (c->querybuf && sdslen(c->querybuf) > 0) processInputBuffer(c);
+ server.bpop_blocked_clients--;
+ listAddNodeTail(server.unblocked_clients,c);
}
/* This should be called from any function PUSHing into lists.
int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
struct dictEntry *de;
redisClient *receiver;
- list *l;
+ int numclients;
+ list *clients;
listNode *ln;
+ robj *dstkey, *dstobj;
de = dictFind(c->db->blocking_keys,key);
if (de == NULL) return 0;
- l = dictGetEntryVal(de);
- ln = listFirst(l);
- redisAssert(ln != NULL);
- receiver = ln->value;
+ clients = dictGetEntryVal(de);
+ numclients = listLength(clients);
+
+ /* Try to handle the push as long as there are clients waiting for a push.
+ * Note that "numclients" is used because the list of clients waiting for a
+ * push on "key" is deleted by unblockClient() when empty.
+ *
+ * This loop will have more than 1 iteration when there is a BRPOPLPUSH
+ * that cannot push the target list because it does not contain a list. If
+ * this happens, it simply tries the next client waiting for a push. */
+ while (numclients--) {
+ ln = listFirst(clients);
+ redisAssert(ln != NULL);
+ receiver = ln->value;
+ dstkey = receiver->bpop.target;
+
+ /* This should remove the first element of the "clients" list. */
+ unblockClientWaitingData(receiver);
+ redisAssert(ln != listFirst(clients));
+
+ if (dstkey == NULL) {
+ /* BRPOP/BLPOP */
+ addReplyMultiBulkLen(receiver,2);
+ addReplyBulk(receiver,key);
+ addReplyBulk(receiver,ele);
+ return 1;
+ } else {
+ /* BRPOPLPUSH */
+ dstobj = lookupKeyWrite(receiver->db,dstkey);
+ if (dstobj && checkType(receiver,dstobj,REDIS_LIST)) {
+ decrRefCount(dstkey);
+ } else {
+ rpoplpushHandlePush(receiver,dstkey,dstobj,ele);
+ decrRefCount(dstkey);
+ return 1;
+ }
+ }
+ }
- addReplyMultiBulkLen(receiver,2);
- addReplyBulk(receiver,key);
- addReplyBulk(receiver,ele);
- unblockClientWaitingData(receiver);
- return 1;
+ return 0;
+}
+
+int getTimeoutFromObjectOrReply(redisClient *c, robj *object, time_t *timeout) {
+ long tval;
+
+ if (getLongFromObjectOrReply(c,object,&tval,
+ "timeout is not an integer or out of range") != REDIS_OK)
+ return REDIS_ERR;
+
+ if (tval < 0) {
+ addReplyError(c,"timeout is negative");
+ return REDIS_ERR;
+ }
+
+ if (tval > 0) tval += time(NULL);
+ *timeout = tval;
+
+ return REDIS_OK;
}
/* Blocking RPOP/LPOP */
void blockingPopGenericCommand(redisClient *c, int where) {
robj *o;
- long long lltimeout;
time_t timeout;
int j;
- /* Make sure timeout is an integer value */
- if (getLongLongFromObjectOrReply(c,c->argv[c->argc-1],&lltimeout,
- "timeout is not an integer") != REDIS_OK) return;
-
- /* Make sure the timeout is not negative */
- if (lltimeout < 0) {
- addReplyError(c,"timeout is negative");
+ if (getTimeoutFromObjectOrReply(c,c->argv[c->argc-1],&timeout) != REDIS_OK)
return;
- }
for (j = 1; j < c->argc-1; j++) {
o = lookupKeyWrite(c->db,c->argv[j]);
* because it is... */
addReplyMultiBulkLen(c,2);
addReplyBulk(c,argv[1]);
+
popGenericCommand(c,where);
/* Fix the client structure with the original stuff */
c->argv = orig_argv;
c->argc = orig_argc;
+
return;
}
}
}
/* If the list is empty or the key does not exists we must block */
- timeout = lltimeout;
- if (timeout > 0) timeout += time(NULL);
- blockForKeys(c,c->argv+1,c->argc-2,timeout);
+ blockForKeys(c, c->argv + 1, c->argc - 2, timeout, NULL);
}
void blpopCommand(redisClient *c) {
void brpopCommand(redisClient *c) {
blockingPopGenericCommand(c,REDIS_TAIL);
}
+
+void brpoplpushCommand(redisClient *c) {
+ time_t timeout;
+
+ if (getTimeoutFromObjectOrReply(c,c->argv[3],&timeout) != REDIS_OK)
+ return;
+
+ robj *key = lookupKeyWrite(c->db, c->argv[1]);
+
+ if (key == NULL) {
+ if (c->flags & REDIS_MULTI) {
+
+ /* Blocking against an empty list in a multi state
+ * returns immediately. */
+ addReply(c, shared.nullmultibulk);
+ } else {
+ /* The list is empty and the client blocks. */
+ blockForKeys(c, c->argv + 1, 1, timeout, c->argv[2]);
+ }
+ } else {
+ if (key->type != REDIS_LIST) {
+ addReply(c, shared.wrongtypeerr);
+ } else {
+
+ /* The list exists and has elements, so
+ * the regular rpoplpushCommand is executed. */
+ redisAssert(listTypeLength(key) > 0);
+ rpoplpushCommand(c);
+ }
+ }
+}
return 0;
}
-int setTypeRemove(robj *subject, robj *value) {
+int setTypeRemove(robj *setobj, robj *value) {
long long llval;
- if (subject->encoding == REDIS_ENCODING_HT) {
- if (dictDelete(subject->ptr,value) == DICT_OK) {
- if (htNeedsResize(subject->ptr)) dictResize(subject->ptr);
+ if (setobj->encoding == REDIS_ENCODING_HT) {
+ if (dictDelete(setobj->ptr,value) == DICT_OK) {
+ if (htNeedsResize(setobj->ptr)) dictResize(setobj->ptr);
return 1;
}
- } else if (subject->encoding == REDIS_ENCODING_INTSET) {
+ } else if (setobj->encoding == REDIS_ENCODING_INTSET) {
if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
- uint8_t success;
- subject->ptr = intsetRemove(subject->ptr,llval,&success);
+ int success;
+ setobj->ptr = intsetRemove(setobj->ptr,llval,&success);
if (success) return 1;
}
} else {
}
/* Move to the next entry in the set. Returns the object at the current
- * position, or NULL when the end is reached. This object will have its
- * refcount incremented, so the caller needs to take care of this. */
-robj *setTypeNext(setTypeIterator *si) {
- robj *ret = NULL;
+ * position.
+ *
+ * Since set elements can be internally be stored as redis objects or
+ * simple arrays of integers, setTypeNext returns the encoding of the
+ * set object you are iterating, and will populate the appropriate pointer
+ * (eobj) or (llobj) accordingly.
+ *
+ * When there are no longer elements -1 is returned.
+ * Returned objects ref count is not incremented, so this function is
+ * copy on write friendly. */
+int setTypeNext(setTypeIterator *si, robj **objele, int64_t *llele) {
if (si->encoding == REDIS_ENCODING_HT) {
dictEntry *de = dictNext(si->di);
- if (de != NULL) {
- ret = dictGetEntryKey(de);
- incrRefCount(ret);
- }
+ if (de == NULL) return -1;
+ *objele = dictGetEntryKey(de);
} else if (si->encoding == REDIS_ENCODING_INTSET) {
- int64_t llval;
- if (intsetGet(si->subject->ptr,si->ii++,&llval))
- ret = createStringObjectFromLongLong(llval);
+ if (!intsetGet(si->subject->ptr,si->ii++,llele))
+ return -1;
}
- return ret;
+ return si->encoding;
}
+/* The not copy on write friendly version but easy to use version
+ * of setTypeNext() is setTypeNextObject(), returning new objects
+ * or incrementing the ref count of returned objects. So if you don't
+ * retain a pointer to this object you should call decrRefCount() against it.
+ *
+ * This function is the way to go for write operations where COW is not
+ * an issue as the result will be anyway of incrementing the ref count. */
+robj *setTypeNextObject(setTypeIterator *si) {
+ int64_t intele;
+ robj *objele;
+ int encoding;
+
+ encoding = setTypeNext(si,&objele,&intele);
+ switch(encoding) {
+ case -1: return NULL;
+ case REDIS_ENCODING_INTSET:
+ return createStringObjectFromLongLong(intele);
+ case REDIS_ENCODING_HT:
+ incrRefCount(objele);
+ return objele;
+ default:
+ redisPanic("Unsupported encoding");
+ }
+ return NULL; /* just to suppress warnings */
+}
-/* Return random element from set. The returned object will always have
- * an incremented refcount. */
-robj *setTypeRandomElement(robj *subject) {
- robj *ret = NULL;
- if (subject->encoding == REDIS_ENCODING_HT) {
- dictEntry *de = dictGetRandomKey(subject->ptr);
- ret = dictGetEntryKey(de);
- incrRefCount(ret);
- } else if (subject->encoding == REDIS_ENCODING_INTSET) {
- long long llval = intsetRandom(subject->ptr);
- ret = createStringObjectFromLongLong(llval);
+/* Return random element from a non empty set.
+ * The returned element can be a int64_t value if the set is encoded
+ * as an "intset" blob of integers, or a redis object if the set
+ * is a regular set.
+ *
+ * The caller provides both pointers to be populated with the right
+ * object. The return value of the function is the object->encoding
+ * field of the object and is used by the caller to check if the
+ * int64_t pointer or the redis object pointere was populated.
+ *
+ * When an object is returned (the set was a real set) the ref count
+ * of the object is not incremented so this function can be considered
+ * copy on write friendly. */
+int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele) {
+ if (setobj->encoding == REDIS_ENCODING_HT) {
+ dictEntry *de = dictGetRandomKey(setobj->ptr);
+ *objele = dictGetEntryKey(de);
+ } else if (setobj->encoding == REDIS_ENCODING_INTSET) {
+ *llele = intsetRandom(setobj->ptr);
} else {
redisPanic("Unknown set encoding");
}
- return ret;
+ return setobj->encoding;
}
unsigned long setTypeSize(robj *subject) {
/* Convert the set to specified encoding. The resulting dict (when converting
* to a hashtable) is presized to hold the number of elements in the original
* set. */
-void setTypeConvert(robj *subject, int enc) {
+void setTypeConvert(robj *setobj, int enc) {
setTypeIterator *si;
- robj *element;
- redisAssert(subject->type == REDIS_SET);
+ redisAssert(setobj->type == REDIS_SET &&
+ setobj->encoding == REDIS_ENCODING_INTSET);
if (enc == REDIS_ENCODING_HT) {
+ int64_t intele;
dict *d = dictCreate(&setDictType,NULL);
+ robj *element;
+
/* Presize the dict to avoid rehashing */
- dictExpand(d,intsetLen(subject->ptr));
+ dictExpand(d,intsetLen(setobj->ptr));
- /* setTypeGet returns a robj with incremented refcount */
- si = setTypeInitIterator(subject);
- while ((element = setTypeNext(si)) != NULL)
+ /* To add the elements we extract integers and create redis objects */
+ si = setTypeInitIterator(setobj);
+ while (setTypeNext(si,NULL,&intele) != -1) {
+ element = createStringObjectFromLongLong(intele);
redisAssert(dictAdd(d,element,NULL) == DICT_OK);
+ }
setTypeReleaseIterator(si);
- subject->encoding = REDIS_ENCODING_HT;
- zfree(subject->ptr);
- subject->ptr = d;
+ setobj->encoding = REDIS_ENCODING_HT;
+ zfree(setobj->ptr);
+ setobj->ptr = d;
} else {
redisPanic("Unsupported set conversion");
}
void spopCommand(redisClient *c) {
robj *set, *ele;
+ int64_t llele;
+ int encoding;
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,set,REDIS_SET)) return;
- ele = setTypeRandomElement(set);
- if (ele == NULL) {
- addReply(c,shared.nullbulk);
+ encoding = setTypeRandomElement(set,&ele,&llele);
+ if (encoding == REDIS_ENCODING_INTSET) {
+ addReplyBulkLongLong(c,llele);
+ set->ptr = intsetRemove(set->ptr,llele,NULL);
} else {
- setTypeRemove(set,ele);
addReplyBulk(c,ele);
- decrRefCount(ele);
- if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
- touchWatchedKey(c->db,c->argv[1]);
- server.dirty++;
+ setTypeRemove(set,ele);
}
+ if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
+ touchWatchedKey(c->db,c->argv[1]);
+ server.dirty++;
}
void srandmemberCommand(redisClient *c) {
robj *set, *ele;
+ int64_t llele;
+ int encoding;
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,set,REDIS_SET)) return;
- ele = setTypeRandomElement(set);
- if (ele == NULL) {
- addReply(c,shared.nullbulk);
+ encoding = setTypeRandomElement(set,&ele,&llele);
+ if (encoding == REDIS_ENCODING_INTSET) {
+ addReplyBulkLongLong(c,llele);
} else {
addReplyBulk(c,ele);
- decrRefCount(ele);
}
}
void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) {
robj **sets = zmalloc(sizeof(robj*)*setnum);
setTypeIterator *si;
- robj *ele, *dstset = NULL;
+ robj *eleobj, *dstset = NULL;
+ int64_t intobj;
void *replylen = NULL;
unsigned long j, cardinality = 0;
+ int encoding;
for (j = 0; j < setnum; j++) {
robj *setobj = dstkey ?
* the element against all the other sets, if at least one set does
* not include the element it is discarded */
si = setTypeInitIterator(sets[0]);
- while((ele = setTypeNext(si)) != NULL) {
- for (j = 1; j < setnum; j++)
- if (!setTypeIsMember(sets[j],ele)) break;
+ while((encoding = setTypeNext(si,&eleobj,&intobj)) != -1) {
+ for (j = 1; j < setnum; j++) {
+ if (encoding == REDIS_ENCODING_INTSET) {
+ /* intset with intset is simple... and fast */
+ if (sets[j]->encoding == REDIS_ENCODING_INTSET &&
+ !intsetFind((intset*)sets[j]->ptr,intobj))
+ {
+ break;
+ /* in order to compare an integer with an object we
+ * have to use the generic function, creating an object
+ * for this */
+ } else if (sets[j]->encoding == REDIS_ENCODING_HT) {
+ eleobj = createStringObjectFromLongLong(intobj);
+ if (!setTypeIsMember(sets[j],eleobj)) {
+ decrRefCount(eleobj);
+ break;
+ }
+ decrRefCount(eleobj);
+ }
+ } else if (encoding == REDIS_ENCODING_HT) {
+ /* Optimization... if the source object is integer
+ * encoded AND the target set is an intset, we can get
+ * a much faster path. */
+ if (eleobj->encoding == REDIS_ENCODING_INT &&
+ sets[j]->encoding == REDIS_ENCODING_INTSET &&
+ !intsetFind((intset*)sets[j]->ptr,(long)eleobj->ptr))
+ {
+ break;
+ /* else... object to object check is easy as we use the
+ * type agnostic API here. */
+ } else if (!setTypeIsMember(sets[j],eleobj)) {
+ break;
+ }
+ }
+ }
/* Only take action when all sets contain the member */
if (j == setnum) {
if (!dstkey) {
- addReplyBulk(c,ele);
+ if (encoding == REDIS_ENCODING_HT)
+ addReplyBulk(c,eleobj);
+ else
+ addReplyBulkLongLong(c,intobj);
cardinality++;
} else {
- setTypeAdd(dstset,ele);
+ if (encoding == REDIS_ENCODING_INTSET) {
+ eleobj = createStringObjectFromLongLong(intobj);
+ setTypeAdd(dstset,eleobj);
+ decrRefCount(eleobj);
+ } else {
+ setTypeAdd(dstset,eleobj);
+ }
}
}
- decrRefCount(ele);
}
setTypeReleaseIterator(si);
if (!sets[j]) continue; /* non existing keys are like empty sets */
si = setTypeInitIterator(sets[j]);
- while((ele = setTypeNext(si)) != NULL) {
+ while((ele = setTypeNextObject(si)) != NULL) {
if (op == REDIS_OP_UNION || j == 0) {
if (setTypeAdd(dstset,ele)) {
cardinality++;
if (!dstkey) {
addReplyMultiBulkLen(c,cardinality);
si = setTypeInitIterator(dstset);
- while((ele = setTypeNext(si)) != NULL) {
+ while((ele = setTypeNextObject(si)) != NULL) {
addReplyBulk(c,ele);
decrRefCount(ele);
}
+#include <limits.h>
#include "redis.h"
/*-----------------------------------------------------------------------------
* String Commands
*----------------------------------------------------------------------------*/
+static int checkStringLength(redisClient *c, long long size) {
+ if (size > 512*1024*1024) {
+ addReplyError(c,"string exceeds maximum allowed size (512MB)");
+ return REDIS_ERR;
+ }
+ return REDIS_OK;
+}
+
void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire) {
int retval;
long seconds = 0; /* initialized to avoid an harmness warning */
removeExpire(c->db,c->argv[1]);
}
+static int getBitOffsetFromArgument(redisClient *c, robj *o, size_t *offset) {
+ long long loffset;
+ char *err = "bit offset is not an integer or out of range";
+
+ if (getLongLongFromObjectOrReply(c,o,&loffset,err) != REDIS_OK)
+ return REDIS_ERR;
+
+ /* Limit offset to 512MB in bytes */
+ if ((loffset < 0) || ((unsigned long long)loffset >> 3) >= (512*1024*1024))
+ {
+ addReplyError(c,err);
+ return REDIS_ERR;
+ }
+
+ *offset = (size_t)loffset;
+ return REDIS_OK;
+}
+
+void setbitCommand(redisClient *c) {
+ robj *o;
+ char *err = "bit is not an integer or out of range";
+ size_t bitoffset;
+ long long bitvalue;
+ int byte, bit, on;
+
+ if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
+ return;
+
+ if (getLongLongFromObjectOrReply(c,c->argv[3],&bitvalue,err) != REDIS_OK)
+ return;
+
+ /* A bit can only be set to be on or off... */
+ if (bitvalue & ~1) {
+ addReplyError(c,err);
+ return;
+ }
+
+ o = lookupKeyWrite(c->db,c->argv[1]);
+ if (o == NULL) {
+ o = createObject(REDIS_STRING,sdsempty());
+ dbAdd(c->db,c->argv[1],o);
+ } else {
+ if (checkType(c,o,REDIS_STRING)) return;
+
+ /* Create a copy when the object is shared or encoded. */
+ if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
+ robj *decoded = getDecodedObject(o);
+ o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
+ decrRefCount(decoded);
+ dbReplace(c->db,c->argv[1],o);
+ }
+ }
+
+ byte = bitoffset >> 3;
+ bit = 7 - (bitoffset & 0x7);
+ on = bitvalue & 0x1;
+ o->ptr = sdsgrowzero(o->ptr,byte+1);
+ ((char*)o->ptr)[byte] |= on << bit;
+ ((char*)o->ptr)[byte] &= ~((!on) << bit);
+
+ touchWatchedKey(c->db,c->argv[1]);
+ server.dirty++;
+ addReply(c,shared.cone);
+}
+
+void getbitCommand(redisClient *c) {
+ robj *o;
+ size_t bitoffset, byte, bitmask;
+ int on = 0;
+ char llbuf[32];
+
+ if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
+ return;
+
+ if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
+ checkType(c,o,REDIS_STRING)) return;
+
+ byte = bitoffset >> 3;
+ bitmask = 1 << (7 - (bitoffset & 0x7));
+ if (o->encoding != REDIS_ENCODING_RAW) {
+ if (byte < (size_t)ll2string(llbuf,sizeof(llbuf),(long)o->ptr))
+ on = llbuf[byte] & bitmask;
+ } else {
+ if (byte < sdslen(o->ptr))
+ on = ((sds)o->ptr)[byte] & bitmask;
+ }
+ addReply(c, on ? shared.cone : shared.czero);
+}
+
+void setrangeCommand(redisClient *c) {
+ robj *o;
+ long offset;
+ sds value = c->argv[3]->ptr;
+
+ if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != REDIS_OK)
+ return;
+
+ o = lookupKeyWrite(c->db,c->argv[1]);
+ if (o == NULL) {
+ /* Negative offset is always 0 for non-existing keys */
+ if (offset < 0) offset = 0;
+
+ /* Return 0 when setting nothing on a non-existing string */
+ if (sdslen(value) == 0) {
+ addReply(c,shared.czero);
+ return;
+ }
+
+ /* Return when the resulting string exceeds allowed size */
+ if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
+ return;
+
+ o = createObject(REDIS_STRING,sdsempty());
+ dbAdd(c->db,c->argv[1],o);
+ } else {
+ int olen;
+
+ /* Key exists, check type */
+ if (checkType(c,o,REDIS_STRING))
+ return;
+
+ /* Find out existing value length */
+ if (o->encoding == REDIS_ENCODING_INT) {
+ char llbuf[32];
+ olen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
+ } else {
+ olen = sdslen(o->ptr);
+ }
+
+ /* Return existing string length when setting nothing */
+ if (sdslen(value) == 0) {
+ addReplyLongLong(c,olen);
+ return;
+ }
+
+ /* Convert negative indexes. Note that for SETRANGE, the meaning of a
+ * negative index is a little different than for other commands.
+ * Here, an offset of -1 points to the trailing NULL byte of the
+ * string instead of the last character. */
+ if (offset < 0) {
+ offset = olen+1+offset;
+ if (offset < 0) offset = 0;
+ }
+
+ /* Return when the resulting string exceeds allowed size */
+ if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
+ return;
+
+ /* Create a copy when the object is shared or encoded. */
+ if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
+ robj *decoded = getDecodedObject(o);
+ o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
+ decrRefCount(decoded);
+ dbReplace(c->db,c->argv[1],o);
+ }
+ }
+
+ if (sdslen(value) > 0) {
+ o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
+ memcpy((char*)o->ptr+offset,value,sdslen(value));
+ touchWatchedKey(c->db,c->argv[1]);
+ server.dirty++;
+ }
+ addReplyLongLong(c,sdslen(o->ptr));
+}
+
+void getrangeCommand(redisClient *c) {
+ robj *o;
+ long start, end;
+ char *str, llbuf[32];
+ size_t strlen;
+
+ if (getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK)
+ return;
+ if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK)
+ return;
+ if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
+ checkType(c,o,REDIS_STRING)) return;
+
+ if (o->encoding == REDIS_ENCODING_INT) {
+ str = llbuf;
+ strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
+ } else {
+ str = o->ptr;
+ strlen = sdslen(str);
+ }
+
+ /* Convert negative indexes */
+ if (start < 0) start = strlen+start;
+ if (end < 0) end = strlen+end;
+ if (start < 0) start = 0;
+ if (end < 0) end = 0;
+ if ((unsigned)end >= strlen) end = strlen-1;
+
+ /* Precondition: end >= 0 && end < strlen, so the only condition where
+ * nothing can be returned is: start > end. */
+ if (start > end) {
+ addReply(c,shared.nullbulk);
+ } else {
+ addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
+ }
+}
+
void mgetCommand(redisClient *c) {
int j;
void appendCommand(redisClient *c) {
int retval;
size_t totlen;
- robj *o;
+ robj *o, *append;
o = lookupKeyWrite(c->db,c->argv[1]);
c->argv[2] = tryObjectEncoding(c->argv[2]);
addReply(c,shared.wrongtypeerr);
return;
}
- /* If the object is specially encoded or shared we have to make
- * a copy */
+
+ append = getDecodedObject(c->argv[2]);
+ if (o->encoding == REDIS_ENCODING_RAW &&
+ (sdslen(o->ptr) + sdslen(append->ptr)) > 512*1024*1024)
+ {
+ addReplyError(c,"string exceeds maximum allowed size (512MB)");
+ decrRefCount(append);
+ return;
+ }
+
+ /* If the object is shared or encoded, we have to make a copy */
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
-
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbReplace(c->db,c->argv[1],o);
}
- /* APPEND! */
- if (c->argv[2]->encoding == REDIS_ENCODING_RAW) {
- o->ptr = sdscatlen(o->ptr,
- c->argv[2]->ptr, sdslen(c->argv[2]->ptr));
- } else {
- o->ptr = sdscatprintf(o->ptr, "%ld",
- (unsigned long) c->argv[2]->ptr);
- }
+
+ /* Append the value */
+ o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
+ decrRefCount(append);
totlen = sdslen(o->ptr);
}
touchWatchedKey(c->db,c->argv[1]);
addReplyLongLong(c,totlen);
}
-void substrCommand(redisClient *c) {
- robj *o;
- long start = atoi(c->argv[2]->ptr);
- long end = atoi(c->argv[3]->ptr);
- size_t rangelen, strlen;
- sds range;
-
- if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
- checkType(c,o,REDIS_STRING)) return;
-
- o = getDecodedObject(o);
- strlen = sdslen(o->ptr);
-
- /* convert negative indexes */
- if (start < 0) start = strlen+start;
- if (end < 0) end = strlen+end;
- if (start < 0) start = 0;
- if (end < 0) end = 0;
-
- /* indexes sanity checks */
- if (start > end || (size_t)start >= strlen) {
- /* Out of range start or start > end result in null reply */
- addReply(c,shared.nullbulk);
- decrRefCount(o);
- return;
- }
- if ((size_t)end >= strlen) end = strlen-1;
- rangelen = (end-start)+1;
-
- /* Return the result */
- addReplySds(c,sdscatprintf(sdsempty(),"$%zu\r\n",rangelen));
- range = sdsnewlen((char*)o->ptr+start,rangelen);
- addReplySds(c,range);
- addReply(c,shared.crlf);
- decrRefCount(o);
-}
-
void strlenCommand(redisClient *c) {
robj *o;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_STRING)) return;
- o = getDecodedObject(o);
- addReplyLongLong(c,sdslen(o->ptr));
- decrRefCount(o);
+ if (o->encoding == REDIS_ENCODING_RAW) {
+ addReplyLongLong(c,sdslen(o->ptr));
+ } else if (o->encoding == REDIS_ENCODING_INT) {
+ char llbuf[32];
+ int len = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
+ addReplyLongLong(c,len);
+ } else {
+ redisPanic("Unknown string encoding");
+ }
}
+
--- /dev/null
+{
+ <lzf_unitialized_hash_table>
+ Memcheck:Cond
+ fun:lzf_compress
+}
}
close $fd
} e]} {
- puts -nonewline "."
+ if {$::verbose} {
+ puts -nonewline "."
+ }
} else {
- puts -nonewline "ok"
+ if {$::verbose} {
+ puts -nonewline "ok"
+ }
}
return $retval
}
set stderr [format "%s/%s" [dict get $config "dir"] "stderr"]
if {$::valgrind} {
- exec valgrind src/redis-server $config_file > $stdout 2> $stderr &
+ exec valgrind --suppressions=src/valgrind.sup src/redis-server $config_file > $stdout 2> $stderr &
} else {
exec src/redis-server $config_file > $stdout 2> $stderr &
}
set retrynum 20
set serverisup 0
- puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
+ if {$::verbose} {
+ puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
+ }
+
after 10
if {$code ne "undefined"} {
while {[incr retrynum -1]} {
} else {
set serverisup 1
}
- puts {}
+
+ if {$::verbose} {
+ puts ""
+ }
if {!$serverisup} {
error_and_quit $config_file [exec cat $stderr]
reconnect
# execute provided block
- set curnum $::testnum
- if {![catch { uplevel 1 $code } err]} {
- # zero exit status is good
- unset err
+ set num_tests $::num_tests
+ if {[catch { uplevel 1 $code } error]} {
+ set backtrace $::errorInfo
+
+ # Kill the server without checking for leaks
+ dict set srv "skipleaks" 1
+ kill_server $srv
+
+ # Print warnings from log
+ puts [format "\nLogged warnings (pid %d):" [dict get $srv "pid"]]
+ set warnings [warnings_from_file [dict get $srv "stdout"]]
+ if {[string length $warnings] > 0} {
+ puts "$warnings"
+ } else {
+ puts "(none)"
+ }
+ puts ""
+
+ error $error $backtrace
}
- if {$curnum == $::testnum} {
- # don't check for leaks when no tests were executed
+ # Don't do the leak check when no tests were run
+ if {$num_tests == $::num_tests} {
dict set srv "skipleaks" 1
}
# pop the server object
set ::servers [lrange $::servers 0 end-1]
-
- # allow an exception to bubble up the call chain but still kill this
- # server, because we want to reuse the ports when the tests are re-run
- if {[info exists err]} {
- if {$err eq "exception"} {
- puts [format "Logged warnings (pid %d):" [dict get $srv "pid"]]
- set warnings [warnings_from_file [dict get $srv "stdout"]]
- if {[string length $warnings] > 0} {
- puts "$warnings"
- } else {
- puts "(none)"
- }
- # kill this server without checking for leaks
- dict set srv "skipleaks" 1
- kill_server $srv
- error "exception"
- } elseif {[string length $err] > 0} {
- puts "Error executing the suite, aborting..."
- puts $err
- exit 1
- }
- }
set ::tags [lrange $::tags 0 end-[llength $tags]]
kill_server $srv
-set ::passed 0
-set ::failed 0
-set ::testnum 0
+set ::num_tests 0
+set ::num_passed 0
+set ::num_failed 0
+set ::tests_failed {}
proc assert {condition} {
if {![uplevel 1 expr $condition]} {
- puts "!! ERROR\nExpected '$value' to evaluate to true"
- error "assertion"
+ error "assertion:Expected '$value' to be true"
}
}
proc assert_match {pattern value} {
if {![string match $pattern $value]} {
- puts "!! ERROR\nExpected '$value' to match '$pattern'"
- error "assertion"
+ error "assertion:Expected '$value' to match '$pattern'"
}
}
proc assert_equal {expected value} {
if {$expected ne $value} {
- puts "!! ERROR\nExpected '$value' to be equal to '$expected'"
- error "assertion"
+ error "assertion:Expected '$value' to be equal to '$expected'"
}
}
if {[catch {uplevel 1 $code} error]} {
assert_match $pattern $error
} else {
- puts "!! ERROR\nExpected an error but nothing was catched"
- error "assertion"
+ error "assertion:Expected an error but nothing was catched"
}
}
assert_equal $type [r type $key]
}
-proc test {name code {okpattern notspecified}} {
+proc test {name code {okpattern undefined}} {
# abort if tagged with a tag to deny
foreach tag $::denytags {
if {[lsearch $::tags $tag] >= 0} {
}
}
- incr ::testnum
- puts -nonewline [format "#%03d %-68s " $::testnum $name]
- flush stdout
+ incr ::num_tests
+ set details {}
+ lappend details $::curfile
+ lappend details $::tags
+ lappend details $name
+
+ if {$::verbose} {
+ puts -nonewline [format "#%03d %-68s " $::num_tests $name]
+ flush stdout
+ }
+
if {[catch {set retval [uplevel 1 $code]} error]} {
- if {$error eq "assertion"} {
- incr ::failed
+ if {[string match "assertion:*" $error]} {
+ set msg [string range $error 10 end]
+ lappend details $msg
+ lappend ::tests_failed $details
+
+ incr ::num_failed
+ if {$::verbose} {
+ puts "FAILED"
+ puts "$msg\n"
+ } else {
+ puts -nonewline "F"
+ }
} else {
- puts "EXCEPTION"
- puts "\nCaught error: $error"
- error "exception"
+ # Re-raise, let handler up the stack take care of this.
+ error $error $::errorInfo
}
} else {
- if {$okpattern eq "notspecified" || $okpattern eq $retval || [string match $okpattern $retval]} {
- puts "PASSED"
- incr ::passed
+ if {$okpattern eq "undefined" || $okpattern eq $retval || [string match $okpattern $retval]} {
+ incr ::num_passed
+ if {$::verbose} {
+ puts "PASSED"
+ } else {
+ puts -nonewline "."
+ }
} else {
- puts "!! ERROR expected\n'$okpattern'\nbut got\n'$retval'"
- incr ::failed
+ set msg "Expected '$okpattern' to equal or match '$retval'"
+ lappend details $msg
+ lappend ::tests_failed $details
+
+ incr ::num_failed
+ if {$::verbose} {
+ puts "FAILED"
+ puts "$msg\n"
+ } else {
+ puts -nonewline "F"
+ }
}
}
+ flush stdout
+
if {$::traceleaks} {
set output [exec leaks redis-server]
if {![string match {*0 leaks*} $output]} {
- puts "--------- Test $::testnum LEAKED! --------"
+ puts "--- Test \"$name\" leaked! ---"
puts $output
exit 1
}
proc waitForBgsave r {
while 1 {
if {[status r bgsave_in_progress] eq 1} {
- puts -nonewline "\nWaiting for background save to finish... "
- flush stdout
+ if {$::verbose} {
+ puts -nonewline "\nWaiting for background save to finish... "
+ flush stdout
+ }
after 1000
} else {
break
proc waitForBgrewriteaof r {
while 1 {
if {[status r bgrewriteaof_in_progress] eq 1} {
- puts -nonewline "\nWaiting for background AOF rewrite to finish... "
- flush stdout
+ if {$::verbose} {
+ puts -nonewline "\nWaiting for background AOF rewrite to finish... "
+ flush stdout
+ }
after 1000
} else {
break
set ::port 16379
set ::traceleaks 0
set ::valgrind 0
+set ::verbose 0
set ::denytags {}
set ::allowtags {}
set ::external 0; # If "1" this means, we are running against external instance
set ::file ""; # If set, runs only the tests in this comma separated list
+set ::curfile ""; # Hold the filename of the current suite
proc execute_tests name {
- source "tests/$name.tcl"
+ set path "tests/$name.tcl"
+ set ::curfile $path
+ source $path
}
# Setup a list to hold a stack of server configs. When calls to start_server
}
cleanup
- puts "\n[expr $::passed+$::failed] tests, $::passed passed, $::failed failed"
- if {$::failed > 0} {
- puts "\n*** WARNING!!! $::failed FAILED TESTS ***\n"
+ puts "\n[expr $::num_tests] tests, $::num_passed passed, $::num_failed failed\n"
+ if {$::num_failed > 0} {
+ set curheader ""
+ puts "Failures:"
+ foreach {test} $::tests_failed {
+ set header [lindex $test 0]
+ append header " ("
+ append header [join [lindex $test 1] ","]
+ append header ")"
+
+ if {$curheader ne $header} {
+ set curheader $header
+ puts "\n$curheader:"
+ }
+
+ set name [lindex $test 2]
+ set msg [lindex $test 3]
+ puts "- $name: $msg"
+ }
+
+ puts ""
exit 1
}
}
}
}
incr j
+ } elseif {$opt eq {--valgrind}} {
+ set ::valgrind 1
} elseif {$opt eq {--file}} {
set ::file $arg
incr j
} elseif {$opt eq {--port}} {
set ::port $arg
incr j
+ } elseif {$opt eq {--verbose}} {
+ set ::verbose 1
} else {
puts "Wrong argument: $opt"
exit 1
if {[string length $err] > 0} {
# only display error when not generated by the test suite
if {$err ne "exception"} {
- puts $err
+ puts $::errorInfo
}
exit 1
}
r set mystring "foozzz0123456789 baz"
r strlen mystring
}
+
+ test "SETBIT against non-existing key" {
+ r del mykey
+
+ # Setting 2nd bit to on is integer 64, ascii "@"
+ assert_equal 1 [r setbit mykey 1 1]
+ assert_equal "@" [r get mykey]
+ }
+
+ test "SETBIT against string-encoded key" {
+ # Single byte with 2nd bit set
+ r set mykey "@"
+
+ # 64 + 32 = 96 => ascii "`" (backtick)
+ assert_equal 1 [r setbit mykey 2 1]
+ assert_equal "`" [r get mykey]
+ }
+
+ test "SETBIT against integer-encoded key" {
+ r set mykey 1
+ assert_encoding int mykey
+
+ # Ascii "1" is integer 49 = 00 11 00 01
+ # Setting 7th bit = 51 => ascii "3"
+ assert_equal 1 [r setbit mykey 6 1]
+ assert_equal "3" [r get mykey]
+ }
+
+ test "SETBIT against key with wrong type" {
+ r del mykey
+ r lpush mykey "foo"
+ assert_error "*wrong kind*" {r setbit mykey 0 1}
+ }
+
+ test "SETBIT with out of range bit offset" {
+ r del mykey
+ assert_error "*out of range*" {r setbit mykey [expr 4*1024*1024*1024] 1}
+ assert_error "*out of range*" {r setbit mykey -1 1}
+ }
+
+ test "SETBIT with non-bit argument" {
+ r del mykey
+ assert_error "*out of range*" {r setbit mykey 0 -1}
+ assert_error "*out of range*" {r setbit mykey 0 2}
+ assert_error "*out of range*" {r setbit mykey 0 10}
+ assert_error "*out of range*" {r setbit mykey 0 20}
+ }
+
+ test "GETBIT against non-existing key" {
+ r del mykey
+ assert_equal 0 [r getbit mykey 0]
+ }
+
+ test "GETBIT against string-encoded key" {
+ # Single byte with 2nd and 3rd bit set
+ r set mykey "`"
+
+ # In-range
+ assert_equal 0 [r getbit mykey 0]
+ assert_equal 1 [r getbit mykey 1]
+ assert_equal 1 [r getbit mykey 2]
+ assert_equal 0 [r getbit mykey 3]
+
+ # Out-range
+ assert_equal 0 [r getbit mykey 8]
+ assert_equal 0 [r getbit mykey 100]
+ assert_equal 0 [r getbit mykey 10000]
+ }
+
+ test "GETBIT against integer-encoded key" {
+ r set mykey 1
+ assert_encoding int mykey
+
+ # Ascii "1" is integer 49 = 00 11 00 01
+ assert_equal 0 [r getbit mykey 0]
+ assert_equal 0 [r getbit mykey 1]
+ assert_equal 1 [r getbit mykey 2]
+ assert_equal 1 [r getbit mykey 3]
+
+ # Out-range
+ assert_equal 0 [r getbit mykey 8]
+ assert_equal 0 [r getbit mykey 100]
+ assert_equal 0 [r getbit mykey 10000]
+ }
+
+ test "SETRANGE against non-existing key" {
+ r del mykey
+ assert_equal 3 [r setrange mykey 0 foo]
+ assert_equal "foo" [r get mykey]
+
+ r del mykey
+ assert_equal 0 [r setrange mykey 0 ""]
+ assert_equal 0 [r exists mykey]
+
+ r del mykey
+ assert_equal 4 [r setrange mykey 1 foo]
+ assert_equal "\000foo" [r get mykey]
+
+ r del mykey
+ assert_equal 3 [r setrange mykey -1 foo]
+ assert_equal "foo" [r get mykey]
+
+ r del mykey
+ assert_equal 3 [r setrange mykey -100 foo]
+ assert_equal "foo" [r get mykey]
+ }
+
+ test "SETRANGE against string-encoded key" {
+ r set mykey "foo"
+ assert_equal 3 [r setrange mykey 0 b]
+ assert_equal "boo" [r get mykey]
+
+ r set mykey "foo"
+ assert_equal 3 [r setrange mykey 0 ""]
+ assert_equal "foo" [r get mykey]
+
+ r set mykey "foo"
+ assert_equal 3 [r setrange mykey 1 b]
+ assert_equal "fbo" [r get mykey]
+
+ r set mykey "foo"
+ assert_equal 6 [r setrange mykey -1 bar]
+ assert_equal "foobar" [r get mykey]
+
+ r set mykey "foo"
+ assert_equal 5 [r setrange mykey -2 bar]
+ assert_equal "fobar" [r get mykey]
+
+ r set mykey "foo"
+ assert_equal 3 [r setrange mykey -20 bar]
+ assert_equal "bar" [r get mykey]
+
+ r set mykey "foo"
+ assert_equal 7 [r setrange mykey 4 bar]
+ assert_equal "foo\000bar" [r get mykey]
+ }
+
+ test "SETRANGE against integer-encoded key" {
+ r set mykey 1234
+ assert_encoding int mykey
+ assert_equal 4 [r setrange mykey 0 2]
+ assert_encoding raw mykey
+ assert_equal 2234 [r get mykey]
+
+ # Shouldn't change encoding when nothing is set
+ r set mykey 1234
+ assert_encoding int mykey
+ assert_equal 4 [r setrange mykey 0 ""]
+ assert_encoding int mykey
+ assert_equal 1234 [r get mykey]
+
+ r set mykey 1234
+ assert_encoding int mykey
+ assert_equal 4 [r setrange mykey 1 3]
+ assert_encoding raw mykey
+ assert_equal 1334 [r get mykey]
+
+ r set mykey 1234
+ assert_encoding int mykey
+ assert_equal 5 [r setrange mykey -1 5]
+ assert_encoding raw mykey
+ assert_equal 12345 [r get mykey]
+
+ r set mykey 1234
+ assert_encoding int mykey
+ assert_equal 4 [r setrange mykey -2 5]
+ assert_encoding raw mykey
+ assert_equal 1235 [r get mykey]
+
+ r set mykey 1234
+ assert_encoding int mykey
+ assert_equal 6 [r setrange mykey 5 2]
+ assert_encoding raw mykey
+ assert_equal "1234\0002" [r get mykey]
+ }
+
+ test "SETRANGE against key with wrong type" {
+ r del mykey
+ r lpush mykey "foo"
+ assert_error "*wrong kind*" {r setrange mykey 0 bar}
+ }
+
+ test "SETRANGE with out of range offset" {
+ r del mykey
+ assert_error "*maximum allowed size*" {r setrange mykey [expr 512*1024*1024-4] world}
+ r set mykey "hello"
+ assert_error "*maximum allowed size*" {r setrange mykey [expr 512*1024*1024-4] world}
+ }
+
+ test "GETRANGE against non-existing key" {
+ r del mykey
+ assert_equal "" [r getrange mykey 0 -1]
+ }
+
+ test "GETRANGE against string value" {
+ r set mykey "Hello World"
+ assert_equal "Hell" [r getrange mykey 0 3]
+ assert_equal "Hello World" [r getrange mykey 0 -1]
+ assert_equal "orld" [r getrange mykey -4 -1]
+ assert_equal "" [r getrange mykey 5 3]
+ assert_equal " World" [r getrange mykey 5 5000]
+ assert_equal "Hello World" [r getrange mykey -5000 10000]
+ }
+
+ test "GETRANGE against integer-encoded value" {
+ r set mykey 1234
+ assert_equal "123" [r getrange mykey 0 2]
+ assert_equal "1234" [r getrange mykey 0 -1]
+ assert_equal "234" [r getrange mykey -3 -1]
+ assert_equal "" [r getrange mykey 5 3]
+ assert_equal "4" [r getrange mykey 3 5000]
+ assert_equal "1234" [r getrange mykey -5000 10000]
+ }
+
+ test "GETRANGE fuzzing" {
+ for {set i 0} {$i < 1000} {incr i} {
+ r set bin [set bin [randstring 0 1024 binary]]
+ set _start [set start [randomInt 1500]]
+ set _end [set end [randomInt 1500]]
+ if {$_start < 0} {set _start "end-[abs($_start)-1]"}
+ if {$_end < 0} {set _end "end-[abs($_end)-1]"}
+ assert_equal [string range $bin $_start $_end] [r getrange bin $start $end]
+ }
+ }
}
set _ $err
} {}
- test {SUBSTR basics} {
- set res {}
- r set foo "Hello World"
- lappend res [r substr foo 0 3]
- lappend res [r substr foo 0 -1]
- lappend res [r substr foo -4 -1]
- lappend res [r substr foo 5 3]
- lappend res [r substr foo 5 5000]
- lappend res [r substr foo -5000 10000]
- set _ $res
- } {Hell {Hello World} orld {} { World} {Hello World}}
-
- test {SUBSTR against integer encoded values} {
- r set foo 123
- r substr foo 0 -2
- } {12}
-
- test {SUBSTR fuzzing} {
- set err {}
- for {set i 0} {$i < 1000} {incr i} {
- set bin [randstring 0 1024 binary]
- set _start [set start [randomInt 1500]]
- set _end [set end [randomInt 1500]]
- if {$_start < 0} {set _start "end-[abs($_start)-1]"}
- if {$_end < 0} {set _end "end-[abs($_end)-1]"}
- set s1 [string range $bin $_start $_end]
- r set bin $bin
- set s2 [r substr bin $start $end]
- if {$s1 != $s2} {
- set err "String mismatch"
- break
- }
- }
- set _ $err
- } {}
-
# Leave the user with a clean DB before to exit
test {FLUSHDB} {
set aux {}
set sorted [r sort tosort BY weight_* LIMIT 0 10]
}
set elapsed [expr [clock clicks -milliseconds]-$start]
- puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
- flush stdout
+ if {$::verbose} {
+ puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
+ flush stdout
+ }
}
test "SORT speed, $num element list BY hash field, 100 times" {
set sorted [r sort tosort BY wobj_*->weight LIMIT 0 10]
}
set elapsed [expr [clock clicks -milliseconds]-$start]
- puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
- flush stdout
+ if {$::verbose} {
+ puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
+ flush stdout
+ }
}
test "SORT speed, $num element list directly, 100 times" {
set sorted [r sort tosort LIMIT 0 10]
}
set elapsed [expr [clock clicks -milliseconds]-$start]
- puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
- flush stdout
+ if {$::verbose} {
+ puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
+ flush stdout
+ }
}
test "SORT speed, $num element list BY <const>, 100 times" {
set sorted [r sort tosort BY nokey LIMIT 0 10]
}
set elapsed [expr [clock clicks -milliseconds]-$start]
- puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
- flush stdout
+ if {$::verbose} {
+ puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
+ flush stdout
+ }
}
}
}
assert_equal 0 [r llen blist1]
assert_equal 1 [r llen blist2]
}
+
+ test "BRPOPLPUSH - $type" {
+ r del target
+
+ set rd [redis_deferring_client]
+ create_$type blist "a b $large c d"
+
+ $rd brpoplpush blist target 1
+ assert_equal d [$rd read]
+
+ assert_equal d [r rpop target]
+ assert_equal "a b $large c" [r lrange blist 0 -1]
+ }
+ }
+
+ test "BRPOPLPUSH with zero timeout should block indefinitely" {
+ set rd [redis_deferring_client]
+ r del blist target
+ $rd brpoplpush blist target 0
+ after 1000
+ r rpush blist foo
+ assert_equal foo [$rd read]
+ assert_equal {foo} [r lrange target 0 -1]
+ }
+
+ test "BRPOPLPUSH with a client BLPOPing the target list" {
+ set rd [redis_deferring_client]
+ set rd2 [redis_deferring_client]
+ r del blist target
+ $rd2 blpop target 0
+ $rd brpoplpush blist target 0
+ after 1000
+ r rpush blist foo
+ assert_equal foo [$rd read]
+ assert_equal {target foo} [$rd2 read]
+ assert_equal 0 [r exists target]
}
+ test "BRPOPLPUSH with wrong source type" {
+ set rd [redis_deferring_client]
+ r del blist target
+ r set blist nolist
+ $rd brpoplpush blist target 1
+ assert_error "ERR*wrong kind*" {$rd read}
+ }
+
+ test "BRPOPLPUSH with wrong destination type" {
+ set rd [redis_deferring_client]
+ r del blist target
+ r set target nolist
+ r lpush blist foo
+ $rd brpoplpush blist target 1
+ assert_error "ERR*wrong kind*" {$rd read}
+
+ set rd [redis_deferring_client]
+ r del blist target
+ r set target nolist
+ $rd brpoplpush blist target 0
+ after 1000
+ r rpush blist foo
+ assert_error "ERR*wrong kind*" {$rd read}
+ assert_equal {foo} [r lrange blist 0 -1]
+ }
+
+ test "BRPOPLPUSH with multiple blocked clients" {
+ set rd1 [redis_deferring_client]
+ set rd2 [redis_deferring_client]
+ r del blist target1 target2
+ r set target1 nolist
+ $rd1 brpoplpush blist target1 0
+ $rd2 brpoplpush blist target2 0
+ r lpush blist foo
+
+ assert_error "ERR*wrong kind*" {$rd1 read}
+ assert_equal {foo} [$rd2 read]
+ assert_equal {foo} [r lrange target2 0 -1]
+ }
+
+ test "Linked BRPOPLPUSH" {
+ set rd1 [redis_deferring_client]
+ set rd2 [redis_deferring_client]
+
+ r del list1 list2 list3
+
+ $rd1 brpoplpush list1 list2 0
+ $rd2 brpoplpush list2 list3 0
+
+ r rpush list1 foo
+
+ assert_equal {} [r lrange list1 0 -1]
+ assert_equal {} [r lrange list2 0 -1]
+ assert_equal {foo} [r lrange list3 0 -1]
+ }
+
+ test "Circular BRPOPLPUSH" {
+ set rd1 [redis_deferring_client]
+ set rd2 [redis_deferring_client]
+
+ r del list1 list2
+
+ $rd1 brpoplpush list1 list2 0
+ $rd2 brpoplpush list2 list1 0
+
+ r rpush list1 foo
+
+ assert_equal {foo} [r lrange list1 0 -1]
+ assert_equal {} [r lrange list2 0 -1]
+ }
+
+ test "Self-referential BRPOPLPUSH" {
+ set rd [redis_deferring_client]
+
+ r del blist
+
+ $rd brpoplpush blist blist 0
+
+ r rpush blist foo
+
+ assert_equal {foo} [r lrange blist 0 -1]
+ }
+
+ test "BRPOPLPUSH inside a transaction" {
+ r del xlist target
+ r lpush xlist foo
+ r lpush xlist bar
+
+ r multi
+ r brpoplpush xlist target 0
+ r brpoplpush xlist target 0
+ r brpoplpush xlist target 0
+ r lrange xlist 0 -1
+ r lrange target 0 -1
+ r exec
+ } {foo bar {} {} {bar foo}}
+
foreach {pop} {BLPOP BRPOP} {
test "$pop: with single empty list argument" {
set rd [redis_deferring_client]