void popGenericCommand(redisClient *c, int where) { robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk); if (o == NULL || checkType(c,o,REDIS_LIST)) return; robj *value = listTypePop(o,where); if (value == NULL) { addReply(c,shared.nullbulk); } else { addReplyBulk(c,value); decrRefCount(value); if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]); signalModifiedKey(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; encoding = setTypeRandomElement(set,&ele,&llele); if (encoding == REDIS_ENCODING_INTSET) { addReplyBulkLongLong(c,llele); } else { addReplyBulk(c,ele); } }
/* Blocking RPOP/LPOP */ void blockingPopGenericCommand(redisClient *c, int where) { robj *o; time_t timeout; int j; for (j = 1; j < c->argc-1; j++) { o = lookupKeyWrite(c->db,c->argv[j]); if (o != NULL) { if (o->type != REDIS_LIST) { addReply(c,shared.wrongtypeerr); return; } else { if (listTypeLength(o) != 0) { /* If the list contains elements fall back to the usual * non-blocking POP operation */ robj *argv[2], **orig_argv; int orig_argc; /* We need to alter the command arguments before to call * popGenericCommand() as the command takes a single key. */ orig_argv = c->argv; orig_argc = c->argc; argv[1] = c->argv[j]; c->argv = argv; c->argc = 2; /* Also the return value is different, we need to output * the multi bulk reply header and the key name. The * "real" command will add the last element (the value) * for us. If this souds like an hack to you it's just * because it is... */ addReplySds(c,sdsnew("*2\r\n")); 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 = strtol(c->argv[c->argc-1]->ptr,NULL,10); if (timeout > 0) timeout += time(NULL); blockForKeys(c,c->argv+1,c->argc-2,timeout); }
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); }
/* 退订频道,即取消客户端对某频道的订阅。如果操作成功返回1,如果该客户端没有订阅该频道则返回0 */ int pubsubUnsubscribeChannel(redisClient *c, robj *channel, int notify) { dictEntry *de; list *clients; listNode *ln; int retval = 0; /* Remove the channel from the client -> channels hash table */ incrRefCount(channel); /* channel may be just a pointer to the same object we have in the hash tables. Protect it... */ // 将频道从客户端client -> channels字典中移除,如果移除成功,说明客户端的确订阅了该频道 if (dictDelete(c->pubsub_channels,channel) == DICT_OK) { retval = 1; /* Remove the client from the channel -> clients list hash table */ /* 将客户端从server.pubsub_channels字典中移除 */ // 找到订阅该频道的客户端链表 de = dictFind(server.pubsub_channels,channel); redisAssertWithInfo(c,NULL,de != NULL); clients = dictGetVal(de); // 在链表中查找该客户端 ln = listSearchKey(clients,c); redisAssertWithInfo(c,NULL,ln != NULL); // 移除客户端 listDelNode(clients,ln); // 如果订阅该频道的客户端链表为空,则删除之 if (listLength(clients) == 0) { /* Free the list and associated hash entry at all if this was * the latest client, so that it will be possible to abuse * Redis PUBSUB creating millions of channels. */ dictDelete(server.pubsub_channels,channel); } } /* Notify the client */ // 回复客户端 if (notify) { addReply(c,shared.mbulkhdr[3]); addReply(c,shared.unsubscribebulk); // 被退订的客户端 addReplyBulk(c,channel); // 客户端仍在订阅的频道和模式数量 addReplyLongLong(c,dictSize(c->pubsub_channels)+ listLength(c->pubsub_patterns)); } decrRefCount(channel); /* it is finally safe to release it */ return retval; }
void mgetCommand(redisClient *c) { int j; addReplyMultiBulkLen(c,c->argc-1); for (j = 1; j < c->argc; j++) { robj *o = lookupKeyRead(c->db,c->argv[j]); if (o == NULL) { addReply(c,shared.nullbulk); } else { if (o->type != REDIS_STRING) { addReply(c,shared.nullbulk); } else { addReplyBulk(c,o); } } } }
void mgetCommand(redisClient *c) { int j; addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-1)); for (j = 1; j < c->argc; j++) { robj *o = lookupKeyRead(c->db,c->argv[j]); if (o == NULL) { addReply(c,shared.nullbulk); } else { if (o->type != REDIS_STRING) { addReply(c,shared.nullbulk); } else { addReplyBulk(c,o); } } } }
/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. * * 设置客户端 c 订阅模式 pattern 。 * * 订阅成功返回 1 ,如果客户端已经订阅了该模式,那么返回 0 。 */ int pubsubSubscribePattern(redisClient *c, robj *pattern) { int retval = 0; // 在链表中查找模式,看客户端是否已经订阅了这个模式 // 这里为什么不像 channel 那样,用字典来进行检测呢? // 虽然 pattern 的数量一般来说并不多 if (listSearchKey(c->pubsub_patterns,pattern) == NULL) { // 如果没有的话,执行以下代码 retval = 1; pubsubPattern *pat; // 将 pattern 添加到 c->pubsub_patterns 链表中 listAddNodeTail(c->pubsub_patterns,pattern); incrRefCount(pattern); // 创建并设置新的 pubsubPattern 结构 pat = zmalloc(sizeof(*pat)); pat->pattern = getDecodedObject(pattern); pat->client = c; // 添加到末尾 listAddNodeTail(server.pubsub_patterns,pat); } /* Notify the client */ // 回复客户端。 // 示例: // redis 127.0.0.1:6379> PSUBSCRIBE xxx* // Reading messages... (press Ctrl-C to quit) // 1) "psubscribe" // 2) "xxx*" // 3) (integer) 1 addReply(c,shared.mbulkhdr[3]); // 回复 "psubscribe" 字符串 addReply(c,shared.psubscribebulk); // 回复被订阅的模式 addReplyBulk(c,pattern); // 回复客户端订阅的频道和模式的总数 addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns)); return retval; }
static void condSelectReply(fr_t *fr, aobj *akey, void *rrow, long *card) { uchar hit = 0; if (fr->nowc) hit = 1; else hit = passFilters(fr->btr, akey, rrow, fr->w->flist, fr->w->tmatch); if (hit) { *card = *card + 1; if (fr->cstar) return; /* just counting */ robj *r = outputRow(fr->btr, rrow, fr->qcols, fr->cmatchs, akey, fr->w->tmatch, 0); if (fr->q->qed) addRow2OBList(fr->ll, fr->w, fr->btr, r, fr->ofree, rrow, akey); else addReplyBulk(fr->c, r); decrRefCount(r); } }
int getGenericCommand(redisClient *c) { robj *o; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL) { printf("1\n"); return REDIS_OK; } if (o->type != REDIS_STRING) { printf("2\n"); addReply(c,shared.wrongtypeerr); return REDIS_ERR; } else { printf("3\n"); addReplyBulk(c,o); return REDIS_OK; } }
/* The SLOWLOG command. Implements all the subcommands needed to handle the * Redis slow log. */ void slowlogCommand(redisClient *c) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"reset")) { slowlogReset(); addReply(c,shared.ok); } else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"len")) { addReplyLongLong(c,listLength(server.slowlog)); } else if ((c->argc == 2 || c->argc == 3) && !strcasecmp(c->argv[1]->ptr,"get")) { #ifdef _WIN64 long long count = 10, sent = 0; #else long count = 10, sent = 0; #endif listIter li; void *totentries; listNode *ln; slowlogEntry *se; if (c->argc == 3 && getLongFromObjectOrReply(c,c->argv[2],&count,NULL) != REDIS_OK) return; listRewind(server.slowlog,&li); totentries = addDeferredMultiBulkLength(c); while(count-- && (ln = listNext(&li))) { int j; se = ln->value; addReplyMultiBulkLen(c,4); addReplyLongLong(c,se->id); addReplyLongLong(c,se->time); addReplyLongLong(c,se->duration); addReplyMultiBulkLen(c,se->argc); for (j = 0; j < se->argc; j++) addReplyBulk(c,se->argv[j]); sent++; } setDeferredMultiBulkLength(c,totentries,sent); } else { addReplyError(c, "Unknown SLOWLOG subcommand or wrong # of args. Try GET, RESET, LEN."); } }
static bool nonRelIndRespHandler(redisClient *c, void *x, robj *key, long *card, int b, /* variable ignored */ int n) { /* variable ignored */ x = 0; b = 0; n = 0; /* compiler warnings */ if (NriFlag == PIPE_ONE_LINER_FLAG) { char *s = key->ptr; robj *r = _createStringObject(s + 1); /* +1 skips '+','-',':' */ decrRefCount(key); key = r; } addReplyBulk(c, key); decrRefCount(key); *card = *card + 1; CurrCard++; return 1; }
/* COMMAND <subcommand> <args> */ void commandCommand(client *c) { dictIterator *di; dictEntry *de; if (c->argc == 1) { addReplyMultiBulkLen(c, dictSize(server.commands)); di = dictGetIterator(server.commands); while ((de = dictNext(di)) != NULL) { addReplyCommand(c, dictGetVal(de)); } dictReleaseIterator(di); } else if (!strcasecmp(c->argv[1]->ptr, "info")) { int i; addReplyMultiBulkLen(c, c->argc-2); for (i = 2; i < c->argc; i++) { addReplyCommand(c, dictFetchValue(server.commands, c->argv[i]->ptr)); } } else if (!strcasecmp(c->argv[1]->ptr, "count") && c->argc == 2) { addReplyLongLong(c, dictSize(server.commands)); } else if (!strcasecmp(c->argv[1]->ptr,"getkeys") && c->argc >= 3) { struct redisCommand *cmd = lookupCommand(c->argv[2]->ptr); int *keys, numkeys, j; if (!cmd) { addReplyErrorFormat(c,"Invalid command specified"); return; } else if ((cmd->arity > 0 && cmd->arity != c->argc-2) || ((c->argc-2) < -cmd->arity)) { addReplyError(c,"Invalid number of arguments specified for command"); return; } keys = getKeysFromCommand(cmd,c->argv+2,c->argc-2,&numkeys); addReplyMultiBulkLen(c,numkeys); for (j = 0; j < numkeys; j++) addReplyBulk(c,c->argv[keys[j]+2]); getKeysFreeResult(keys); } else { addReplyError(c, "Unknown subcommand or wrong number of arguments."); return; } }
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; encoding = setTypeRandomElement(set,&ele,&llele); if (encoding == REDIS_ENCODING_INTSET) { addReplyBulkLongLong(c,llele); set->ptr = intsetRemove(set->ptr,llele,NULL); } else { addReplyBulk(c,ele); setTypeRemove(set,ele); } if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]); touchWatchedKey(c->db,c->argv[1]); server.dirty++; }
int getGenericCommand(redisClient *c) { robj *o; #ifdef AUTH_FEATURE if (authCheckPathOrReply(c, c->argv[1]) == REDIS_ERR) { return REDIS_ERR; } #endif if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL) return REDIS_OK; if (o->type != REDIS_STRING) { addReply(c,shared.wrongtypeerr); return REDIS_ERR; } else { addReplyBulk(c,o); return REDIS_OK; } }
/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. */ int pubsubSubscribePattern(redisClient *c, robj *pattern) { int retval = 0; if (listSearchKey(c->pubsub_patterns,pattern) == NULL) { retval = 1; pubsubPattern *pat; listAddNodeTail(c->pubsub_patterns,pattern); incrRefCount(pattern); pat = zmalloc(sizeof(*pat)); pat->pattern = getDecodedObject(pattern); pat->client = c; listAddNodeTail(server.pubsub_patterns,pat); } /* Notify the client */ addReply(c,shared.mbulkhdr[3]); addReply(c,shared.psubscribebulk); addReplyBulk(c,pattern); addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns)); return retval; }
/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or * 0 if the client was not subscribed to the specified channel. * * 取消客户端 c 对模式 pattern 的订阅。 * * 取消成功返回 1 ,因为客户端未订阅 pattern 而造成取消失败,返回 0 。 */ int pubsubUnsubscribePattern(redisClient *c, robj *pattern, int notify) { listNode *ln; pubsubPattern pat; int retval = 0; incrRefCount(pattern); /* Protect the object. May be the same we remove */ // 先确认一下,客户端是否订阅了这个模式 if ((ln = listSearchKey(c->pubsub_patterns,pattern)) != NULL) { retval = 1; // 将模式从客户端的订阅列表中删除 listDelNode(c->pubsub_patterns,ln); // 设置 pubsubPattern 结构 pat.client = c; pat.pattern = pattern; // 在服务器中查找 ln = listSearchKey(server.pubsub_patterns,&pat); listDelNode(server.pubsub_patterns,ln); } /* Notify the client */ // 回复客户端 if (notify) { addReply(c,shared.mbulkhdr[3]); // "punsubscribe" 字符串 addReply(c,shared.punsubscribebulk); // 被退订的模式 addReplyBulk(c,pattern); // 退订频道之后客户端仍在订阅的频道和模式的总数 addReplyLongLong(c,dictSize(c->pubsub_channels)+ listLength(c->pubsub_patterns)); } decrRefCount(pattern); return retval; }
//将一个value推入到列表头部,被rpoplpushCommand调用 void rpoplpushHandlePush(client *c, robj *dstkey, robj *dstobj, robj *value) { /* Create the list if the key does not exist */ //如果目标dstkey不存在 if (!dstobj) { //创建一个quicklist对象 dstobj = createQuicklistObject(); //设置ziplist的最大长度和压缩程度 quicklistSetOptions(dstobj->ptr, server.list_max_ziplist_size, server.list_compress_depth); //将key添加到数据库中 dbAdd(c->db,dstkey,dstobj); } //当数据库的键被改动,则会调用该函数发送信号 signalModifiedKey(c->db,dstkey); //将vlaue推入到列表的头部 listTypePush(dstobj,value,LIST_HEAD); //发送"lpush"时间通知 notifyKeyspaceEvent(NOTIFY_LIST,"lpush",dstkey,c->db->id); /* Always send the pushed value to the client. */ //将value值发送给client addReplyBulk(c,value); }
void popGenericCommand(redisClient *c, int where) { robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk); if (o == NULL || checkType(c,o,REDIS_LIST)) return; robj *value = listTypePop(o,where); if (value == NULL) { addReply(c,shared.nullbulk); } else { char *event = (where == REDIS_HEAD) ? "lpop" : "rpop"; addReplyBulk(c,value); decrRefCount(value); notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id); if (listTypeLength(o) == 0) { notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del", c->argv[1],c->db->id); dbDelete(c->db,c->argv[1]); } signalModifiedKey(c->db,c->argv[1]); server.dirty++; } }
void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) { //propagateExpire删除超时key,或者propagate函数分发同步客户端写操作时会调用这里。 //将这条指令发送给所有的slaves,放到其缓冲里面以待发送。 listNode *ln; listIter li; int j; listRewind(slaves,&li);//获取slaves的第一个节点 while((ln = listNext(&li))) { redisClient *slave = ln->value; //下面不发送给这个slave,那么数据怎么办? 后面记着了的。这个状态的客户端是个从库,并且sync后rdb还没有开始,没必要为其追加写操作日志。 /* Don't feed slaves that are still waiting for BGSAVE to start */ if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) continue; /* Feed slaves that are waiting for the initial SYNC (so these commands * are queued in the output buffer until the initial SYNC completes), * or are already in sync with the master. */ if (slave->slaveseldb != dictid) { robj *selectcmd; if (dictid >= 0 && dictid < REDIS_SHARED_SELECT_CMDS) { selectcmd = shared.select[dictid]; incrRefCount(selectcmd); } else { selectcmd = createObject(REDIS_STRING, sdscatprintf(sdsempty(),"select %d\r\n",dictid)); } addReply(slave,selectcmd);//追加一条选择db的指令给当前这个slave decrRefCount(selectcmd); slave->slaveseldb = dictid; } addReplyMultiBulkLen(slave,argc);//增加参数数目指令 for (j = 0; j < argc; j++) //一个个将参数追加到slave上面去。 addReplyBulk(slave,argv[j]); } }
void lrangeCommand(redisClient *c) { robj *o, *value; int start = atoi(c->argv[2]->ptr); int end = atoi(c->argv[3]->ptr); int llen; int rangelen, j; listTypeEntry entry; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL || checkType(c,o,REDIS_LIST)) return; llen = listTypeLength(o); /* convert negative indexes */ if (start < 0) start = llen+start; if (end < 0) end = llen+end; if (start < 0) start = 0; /* Invariant: start >= 0, so this test will be true when end < 0. * The range is empty when start > end or start >= length. */ if (start > end || start >= llen) { addReply(c,shared.emptymultibulk); return; } if (end >= llen) end = llen-1; rangelen = (end-start)+1; /* Return the result in form of a multi-bulk reply */ addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",rangelen)); listTypeIterator *li = listTypeInitIterator(o,start,REDIS_TAIL); for (j = 0; j < rangelen; j++) { redisAssert(listTypeNext(li,&entry)); value = listTypeGet(&entry); addReplyBulk(c,value); decrRefCount(value); } listTypeReleaseIterator(li); }
/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or * 0 if the client was not subscribed to the specified channel. */ int smempubsubUnsubscribeChannel(client *c, robj *channel, int notify) { dictEntry *de; list *clients; listNode *ln; int retval = 0; /* Remove the channel from the client -> channels hash table */ incrRefCount(channel); /* channel may be just a pointer to the same object we have in the hash tables. Protect it... */ if (dictDelete(c->smempubsub_channels,channel) == DICT_OK) { retval = 1; /* Remove the client from the channel -> clients list hash table */ de = dictFind(server.smempubsub_channels,channel); serverAssertWithInfo(c,NULL,de != NULL); clients = dictGetVal(de); ln = listSearchKey(clients,c); serverAssertWithInfo(c,NULL,ln != NULL); listDelNode(clients,ln); if (listLength(clients) == 0) { /* Free the list and associated hash entry at all if this was * the latest client, so that it will be possible to abuse * Redis PUBSUB creating millions of channels. */ dictDelete(server.smempubsub_channels,channel); } } /* Notify the client */ if (notify) { addReply(c,shared.mbulkhdr[3]); addReply(c,shared.unsubscribebulk); addReplyBulk(c,channel); addReplyLongLong(c,dictSize(c->smempubsub_channels)); } decrRefCount(channel); /* it is finally safe to release it */ return retval; }
void keysCommand(client *c) { dictIterator *di; dictEntry *de; sds pattern = c->argv[1]->ptr; int plen = sdslen(pattern), allkeys; unsigned long numkeys = 0; void *replylen = addDeferredMultiBulkLength(c); di = dictGetSafeIterator(server.counters); allkeys = (pattern[0] == '*' && pattern[1] == '\0'); while((de = dictNext(di)) != NULL) { sds key = dictGetKey(de); robj *keyobj; if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) { keyobj = createStringObject(key,sdslen(key)); addReplyBulk(c,keyobj); numkeys++; decrRefCount(keyobj); } } dictReleaseIterator(di); setDeferredMultiBulkLength(c,replylen,numkeys); }
void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *dstkey, int op) { robj **sets = zmalloc(sizeof(robj*)*setnum); setTypeIterator *si; robj *ele, *dstset = NULL; int j, cardinality = 0; int diff_algo = 1; for (j = 0; j < setnum; j++) { robj *setobj = dstkey ? lookupKeyWrite(c->db,setkeys[j]) : lookupKeyRead(c->db,setkeys[j]); if (!setobj) { sets[j] = NULL; continue; } if (checkType(c,setobj,REDIS_SET)) { zfree(sets); return; } sets[j] = setobj; } /* Select what DIFF algorithm to use. * * Algorithm 1 is O(N*M) where N is the size of the element first set * and M the total number of sets. * * Algorithm 2 is O(N) where N is the total number of elements in all * the sets. * * We compute what is the best bet with the current input here. */ if (op == REDIS_OP_DIFF && sets[0]) { long long algo_one_work = 0, algo_two_work = 0; for (j = 0; j < setnum; j++) { if (sets[j] == NULL) continue; algo_one_work += setTypeSize(sets[0]); algo_two_work += setTypeSize(sets[j]); } /* Algorithm 1 has better constant times and performs less operations * if there are elements in common. Give it some advantage. */ algo_one_work /= 2; diff_algo = (algo_one_work <= algo_two_work) ? 1 : 2; if (diff_algo == 1 && setnum > 1) { /* With algorithm 1 it is better to order the sets to subtract * by decreasing size, so that we are more likely to find * duplicated elements ASAP. */ qsort(sets+1,setnum-1,sizeof(robj*), qsortCompareSetsByRevCardinality); } } /* We need a temp set object to store our union. If the dstkey * is not NULL (that is, we are inside an SUNIONSTORE operation) then * this set object will be the resulting object to set into the target key*/ dstset = createIntsetObject(); if (op == REDIS_OP_UNION) { /* Union is trivial, just add every element of every set to the * temporary set. */ for (j = 0; j < setnum; j++) { if (!sets[j]) continue; /* non existing keys are like empty sets */ si = setTypeInitIterator(sets[j]); while((ele = setTypeNextObject(si)) != NULL) { if (setTypeAdd(dstset,ele)) cardinality++; decrRefCount(ele); } setTypeReleaseIterator(si); } } else if (op == REDIS_OP_DIFF && sets[0] && diff_algo == 1) { /* DIFF Algorithm 1: * * We perform the diff by iterating all the elements of the first set, * and only adding it to the target set if the element does not exist * into all the other sets. * * This way we perform at max N*M operations, where N is the size of * the first set, and M the number of sets. */ si = setTypeInitIterator(sets[0]); while((ele = setTypeNextObject(si)) != NULL) { for (j = 1; j < setnum; j++) { if (!sets[j]) continue; /* no key is an empty set. */ if (sets[j] == sets[0]) break; /* same set! */ if (setTypeIsMember(sets[j],ele)) break; } if (j == setnum) { /* There is no other set with this element. Add it. */ setTypeAdd(dstset,ele); cardinality++; } decrRefCount(ele); } setTypeReleaseIterator(si); } else if (op == REDIS_OP_DIFF && sets[0] && diff_algo == 2) { /* DIFF Algorithm 2: * * Add all the elements of the first set to the auxiliary set. * Then remove all the elements of all the next sets from it. * * This is O(N) where N is the sum of all the elements in every * set. */ for (j = 0; j < setnum; j++) { if (!sets[j]) continue; /* non existing keys are like empty sets */ si = setTypeInitIterator(sets[j]); while((ele = setTypeNextObject(si)) != NULL) { if (j == 0) { if (setTypeAdd(dstset,ele)) cardinality++; } else { if (setTypeRemove(dstset,ele)) cardinality--; } decrRefCount(ele); } setTypeReleaseIterator(si); /* Exit if result set is empty as any additional removal * of elements will have no effect. */ if (cardinality == 0) break; } } /* Output the content of the resulting set, if not in STORE mode */ if (!dstkey) { addReplyMultiBulkLen(c,cardinality); si = setTypeInitIterator(dstset); while((ele = setTypeNextObject(si)) != NULL) { addReplyBulk(c,ele); decrRefCount(ele); } setTypeReleaseIterator(si); decrRefCount(dstset); } else { /* If we have a target key where to store the resulting set * create this key with the result set inside */ int deleted = dbDelete(c->db,dstkey); if (setTypeSize(dstset) > 0) { dbAdd(c->db,dstkey,dstset); addReplyLongLong(c,setTypeSize(dstset)); notifyKeyspaceEvent(REDIS_NOTIFY_SET, op == REDIS_OP_UNION ? "sunionstore" : "sdiffstore", dstkey,c->db->id); } else { decrRefCount(dstset); addReply(c,shared.czero); if (deleted) notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del", dstkey,c->db->id); } signalModifiedKey(c->db,dstkey); server.dirty++; } zfree(sets); }
/* The SORT command is the most complex command in Redis. Warning: this code * is optimized for speed and a bit less for readability */ void sortCommand(redisClient *c) { list *operations; unsigned int outputlen = 0; int desc = 0, alpha = 0; long limit_start = 0, limit_count = -1, start, end; int j, dontsort = 0, vectorlen; int getop = 0; /* GET operation counter */ int int_convertion_error = 0; robj *sortval, *sortby = NULL, *storekey = NULL; redisSortObject *vector; /* Resulting vector to sort */ /* Lookup the key to sort. It must be of the right types */ sortval = lookupKeyRead(c->db,c->argv[1]); if (sortval && sortval->type != REDIS_SET && sortval->type != REDIS_LIST && sortval->type != REDIS_ZSET) { addReply(c,shared.wrongtypeerr); return; } /* Create a list of operations to perform for every sorted element. * Operations can be GET/DEL/INCR/DECR */ operations = listCreate(); listSetFreeMethod(operations,zfree); j = 2; /* Now we need to protect sortval incrementing its count, in the future * SORT may have options able to overwrite/delete keys during the sorting * and the sorted key itself may get destroied */ if (sortval) incrRefCount(sortval); else sortval = createListObject(); /* The SORT command has an SQL-alike syntax, parse it */ while(j < c->argc) { int leftargs = c->argc-j-1; if (!strcasecmp(c->argv[j]->ptr,"asc")) { desc = 0; } else if (!strcasecmp(c->argv[j]->ptr,"desc")) { desc = 1; } else if (!strcasecmp(c->argv[j]->ptr,"alpha")) { alpha = 1; } else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) { if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL) != REDIS_OK) || (getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL) != REDIS_OK)) return; j+=2; } else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) { storekey = c->argv[j+1]; j++; } else if (!strcasecmp(c->argv[j]->ptr,"by") && leftargs >= 1) { sortby = c->argv[j+1]; /* If the BY pattern does not contain '*', i.e. it is constant, * we don't need to sort nor to lookup the weight keys. */ if (strchr(c->argv[j+1]->ptr,'*') == NULL) dontsort = 1; j++; } else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) { listAddNodeTail(operations,createSortOperation( REDIS_SORT_GET,c->argv[j+1])); getop++; j++; } else { decrRefCount(sortval); listRelease(operations); addReply(c,shared.syntaxerr); return; } j++; } /* If we have STORE we need to force sorting for deterministic output * and replication. We use alpha sorting since this is guaranteed to * work with any input. */ if (storekey && dontsort) { dontsort = 0; alpha = 1; sortby = NULL; } /* Destructively convert encoded sorted sets for SORT. */ if (sortval->type == REDIS_ZSET) zsetConvert(sortval, REDIS_ENCODING_SKIPLIST); /* Load the sorting vector with all the objects to sort */ switch(sortval->type) { case REDIS_LIST: vectorlen = listTypeLength(sortval); break; case REDIS_SET: vectorlen = setTypeSize(sortval); break; case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break; default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */ } vector = zmalloc(sizeof(redisSortObject)*vectorlen); j = 0; if (sortval->type == REDIS_LIST) { listTypeIterator *li = listTypeInitIterator(sortval,0,REDIS_TAIL); listTypeEntry entry; while(listTypeNext(li,&entry)) { vector[j].obj = listTypeGet(&entry); vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } listTypeReleaseIterator(li); } else if (sortval->type == REDIS_SET) { setTypeIterator *si = setTypeInitIterator(sortval); robj *ele; while((ele = setTypeNextObject(si)) != NULL) { vector[j].obj = ele; vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } setTypeReleaseIterator(si); } else if (sortval->type == REDIS_ZSET) { dict *set = ((zset*)sortval->ptr)->dict; dictIterator *di; dictEntry *setele; di = dictGetIterator(set); while((setele = dictNext(di)) != NULL) { vector[j].obj = dictGetKey(setele); vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } dictReleaseIterator(di); } else { redisPanic("Unknown type"); } redisAssertWithInfo(c,sortval,j == vectorlen); /* Now it's time to load the right scores in the sorting vector */ if (dontsort == 0) { for (j = 0; j < vectorlen; j++) { robj *byval; if (sortby) { /* lookup value to sort by */ byval = lookupKeyByPattern(c->db,sortby,vector[j].obj); if (!byval) continue; } else { /* use object itself to sort by */ byval = vector[j].obj; } if (alpha) { if (sortby) vector[j].u.cmpobj = getDecodedObject(byval); } else { if (byval->encoding == REDIS_ENCODING_RAW) { char *eptr; vector[j].u.score = strtod(byval->ptr,&eptr); if (eptr[0] != '\0' || errno == ERANGE || isnan(vector[j].u.score)) { int_convertion_error = 1; } } else if (byval->encoding == REDIS_ENCODING_INT) { /* Don't need to decode the object if it's * integer-encoded (the only encoding supported) so * far. We can just cast it */ vector[j].u.score = (long)byval->ptr; } else { redisAssertWithInfo(c,sortval,1 != 1); } } /* when the object was retrieved using lookupKeyByPattern, * its refcount needs to be decreased. */ if (sortby) { decrRefCount(byval); } } } /* We are ready to sort the vector... perform a bit of sanity check * on the LIMIT option too. We'll use a partial version of quicksort. */ start = (limit_start < 0) ? 0 : limit_start; end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1; if (start >= vectorlen) { start = vectorlen-1; end = vectorlen-2; } if (end >= vectorlen) end = vectorlen-1; server.sort_dontsort = dontsort; if (dontsort == 0) { server.sort_desc = desc; server.sort_alpha = alpha; server.sort_bypattern = sortby ? 1 : 0; if (sortby && (start != 0 || end != vectorlen-1)) pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end); else qsort(vector,vectorlen,sizeof(redisSortObject),sortCompare); } /* Send command output to the output buffer, performing the specified * GET/DEL/INCR/DECR operations if any. */ outputlen = getop ? getop*(end-start+1) : end-start+1; if (int_convertion_error) { addReplyError(c,"One or more scores can't be converted into double"); } else if (storekey == NULL) { /* STORE option not specified, sent the sorting result to client */ addReplyMultiBulkLen(c,outputlen); for (j = start; j <= end; j++) { listNode *ln; listIter li; if (!getop) addReplyBulk(c,vector[j].obj); listRewind(operations,&li); while((ln = listNext(&li))) { redisSortOperation *sop = ln->value; robj *val = lookupKeyByPattern(c->db,sop->pattern, vector[j].obj); if (sop->type == REDIS_SORT_GET) { if (!val) { addReply(c,shared.nullbulk); } else { addReplyBulk(c,val); decrRefCount(val); } } else { /* Always fails */ redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET); } } } } else { robj *sobj = createZiplistObject(); /* STORE option specified, set the sorting result as a List object */ for (j = start; j <= end; j++) { listNode *ln; listIter li; if (!getop) { listTypePush(sobj,vector[j].obj,REDIS_TAIL); } else { listRewind(operations,&li); while((ln = listNext(&li))) { redisSortOperation *sop = ln->value; robj *val = lookupKeyByPattern(c->db,sop->pattern, vector[j].obj); if (sop->type == REDIS_SORT_GET) { if (!val) val = createStringObject("",0); /* listTypePush does an incrRefCount, so we should take care * care of the incremented refcount caused by either * lookupKeyByPattern or createStringObject("",0) */ listTypePush(sobj,val,REDIS_TAIL); decrRefCount(val); } else { /* Always fails */ redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET); } } } } if (outputlen) { setKey(c->db,storekey,sobj); server.dirty += outputlen; } else if (dbDelete(c->db,storekey)) { signalModifiedKey(c->db,storekey); server.dirty++; } decrRefCount(sobj); addReplyLongLong(c,outputlen); } /* Cleanup */ if (sortval->type == REDIS_LIST || sortval->type == REDIS_SET) for (j = 0; j < vectorlen; j++) decrRefCount(vector[j].obj); decrRefCount(sortval); listRelease(operations); for (j = 0; j < vectorlen; j++) { if (alpha && vector[j].u.cmpobj) decrRefCount(vector[j].u.cmpobj); } zfree(vector); }
/* This command implements ZRANGEBYSCORE, ZREVRANGEBYSCORE and ZCOUNT. * If "justcount", only the number of elements in the range is returned. */ void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) { zrangespec range; robj *o, *emptyreply; zset *zsetobj; zskiplist *zsl; zskiplistNode *ln; int offset = 0, limit = -1; int withscores = 0; unsigned long rangelen = 0; void *replylen = NULL; /* Parse the range arguments. */ if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) { addReplyError(c,"min or max is not a double"); return; } /* Parse optional extra arguments. Note that ZCOUNT will exactly have * 4 arguments, so we'll never enter the following code path. */ if (c->argc > 4) { int remaining = c->argc - 4; int pos = 4; while (remaining) { if (remaining >= 1 && !strcasecmp(c->argv[pos]->ptr,"withscores")) { pos++; remaining--; withscores = 1; } else if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) { offset = atoi(c->argv[pos+1]->ptr); limit = atoi(c->argv[pos+2]->ptr); pos += 3; remaining -= 3; } else { addReply(c,shared.syntaxerr); return; } } } /* Ok, lookup the key and get the range */ emptyreply = justcount ? shared.czero : shared.emptymultibulk; if ((o = lookupKeyReadOrReply(c,c->argv[1],emptyreply)) == NULL || checkType(c,o,REDIS_ZSET)) return; zsetobj = o->ptr; zsl = zsetobj->zsl; /* If reversed, assume the elements are sorted from high to low score. */ ln = zslFirstWithScore(zsl,range.min); if (reverse) { /* If range.min is out of range, ln will be NULL and we need to use * the tail of the skiplist as first node of the range. */ if (ln == NULL) ln = zsl->tail; /* zslFirstWithScore returns the first element with where with * score >= range.min, so backtrack to make sure the element we use * here has score <= range.min. */ while (ln && ln->score > range.min) ln = ln->backward; /* Move to the right element according to the range spec. */ if (range.minex) { /* Find last element with score < range.min */ while (ln && ln->score == range.min) ln = ln->backward; } else { /* Find last element with score <= range.min */ while (ln && ln->level[0].forward && ln->level[0].forward->score == range.min) ln = ln->level[0].forward; } } else { if (range.minex) { /* Find first element with score > range.min */ while (ln && ln->score == range.min) ln = ln->level[0].forward; } } /* No "first" element in the specified interval. */ if (ln == NULL) { addReply(c,emptyreply); return; } /* We don't know in advance how many matching elements there * are in the list, so we push this object that will represent * the multi-bulk length in the output buffer, and will "fix" * it later */ if (!justcount) replylen = addDeferredMultiBulkLength(c); /* If there is an offset, just traverse the number of elements without * checking the score because that is done in the next loop. */ while(ln && offset--) { if (reverse) ln = ln->backward; else ln = ln->level[0].forward; } while (ln && limit--) { /* Check if this this element is in range. */ if (reverse) { if (range.maxex) { /* Element should have score > range.max */ if (ln->score <= range.max) break; } else { /* Element should have score >= range.max */ if (ln->score < range.max) break; } } else { if (range.maxex) { /* Element should have score < range.max */ if (ln->score >= range.max) break; } else { /* Element should have score <= range.max */ if (ln->score > range.max) break; } } /* Do our magic */ rangelen++; if (!justcount) { addReplyBulk(c,ln->obj); if (withscores) addReplyDouble(c,ln->score); } if (reverse) ln = ln->backward; else ln = ln->level[0].forward; } if (justcount) { addReplyLongLong(c,(long)rangelen); } else { setDeferredMultiBulkLength(c,replylen, withscores ? (rangelen*2) : rangelen); } }
void zrangeGenericCommand(redisClient *c, int reverse) { robj *o; long start; long end; int withscores = 0; int llen; int rangelen, j; zset *zsetobj; zskiplist *zsl; zskiplistNode *ln; robj *ele; if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) || (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return; if (c->argc == 5 && !strcasecmp(c->argv[4]->ptr,"withscores")) { withscores = 1; } else if (c->argc >= 5) { addReply(c,shared.syntaxerr); return; } if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL || checkType(c,o,REDIS_ZSET)) return; zsetobj = o->ptr; zsl = zsetobj->zsl; llen = zsl->length; /* convert negative indexes */ if (start < 0) start = llen+start; if (end < 0) end = llen+end; if (start < 0) start = 0; /* Invariant: start >= 0, so this test will be true when end < 0. * The range is empty when start > end or start >= length. */ if (start > end || start >= llen) { addReply(c,shared.emptymultibulk); return; } if (end >= llen) end = llen-1; rangelen = (end-start)+1; /* check if starting point is trivial, before searching * the element in log(N) time */ if (reverse) { ln = start == 0 ? zsl->tail : zslGetElementByRank(zsl, llen-start); } else { ln = start == 0 ? zsl->header->level[0].forward : zslGetElementByRank(zsl, start+1); } /* Return the result in form of a multi-bulk reply */ addReplyMultiBulkLen(c,withscores ? (rangelen*2) : rangelen); for (j = 0; j < rangelen; j++) { ele = ln->obj; addReplyBulk(c,ele); if (withscores) addReplyDouble(c,ln->score); ln = reverse ? ln->backward : ln->level[0].forward; } }
void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *dstkey, int op) { robj **sets = zmalloc(sizeof(robj*)*setnum); setTypeIterator *si; robj *ele, *dstset = NULL; int j, cardinality = 0; for (j = 0; j < setnum; j++) { robj *setobj = dstkey ? lookupKeyWrite(c->db,setkeys[j]) : lookupKeyRead(c->db,setkeys[j]); if (!setobj) { sets[j] = NULL; continue; } if (checkType(c,setobj,REDIS_SET)) { zfree(sets); return; } sets[j] = setobj; } /* We need a temp set object to store our union. If the dstkey * is not NULL (that is, we are inside an SUNIONSTORE operation) then * this set object will be the resulting object to set into the target key*/ dstset = createIntsetObject(); /* Iterate all the elements of all the sets, add every element a single * time to the result set */ for (j = 0; j < setnum; j++) { if (op == REDIS_OP_DIFF && j == 0 && !sets[j]) break; /* result set is empty */ if (!sets[j]) continue; /* non existing keys are like empty sets */ si = setTypeInitIterator(sets[j]); while((ele = setTypeNextObject(si)) != NULL) { if (op == REDIS_OP_UNION || j == 0) { if (setTypeAdd(dstset,ele)) { cardinality++; } } else if (op == REDIS_OP_DIFF) { if (setTypeRemove(dstset,ele)) { cardinality--; } } decrRefCount(ele); } setTypeReleaseIterator(si); /* Exit when result set is empty. */ if (op == REDIS_OP_DIFF && cardinality == 0) break; } /* Output the content of the resulting set, if not in STORE mode */ if (!dstkey) { addReplyMultiBulkLen(c,cardinality); si = setTypeInitIterator(dstset); while((ele = setTypeNextObject(si)) != NULL) { addReplyBulk(c,ele); decrRefCount(ele); } setTypeReleaseIterator(si); decrRefCount(dstset); } else { /* If we have a target key where to store the resulting set * create this key with the result set inside */ dbDelete(c->db,dstkey); if (setTypeSize(dstset) > 0) { dbAdd(c->db,dstkey,dstset); addReplyLongLong(c,setTypeSize(dstset)); } else { decrRefCount(dstset); addReply(c,shared.czero); } touchWatchedKey(c->db,dstkey); server.dirty++; } zfree(sets); }
void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) { robj **sets = zmalloc(sizeof(robj*)*setnum); setTypeIterator *si; 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 ? lookupKeyWrite(c->db,setkeys[j]) : lookupKeyRead(c->db,setkeys[j]); if (!setobj) { zfree(sets); if (dstkey) { if (dbDelete(c->db,dstkey)) { touchWatchedKey(c->db,dstkey); server.dirty++; } addReply(c,shared.czero); } else { addReply(c,shared.emptymultibulk); } return; } if (checkType(c,setobj,REDIS_SET)) { zfree(sets); return; } sets[j] = setobj; } /* Sort sets from the smallest to largest, this will improve our * algorithm's performace */ qsort(sets,setnum,sizeof(robj*),qsortCompareSetsByCardinality); /* The first thing we should output is the total number of elements... * since this is a multi-bulk write, but at this stage we don't know * the intersection set size, so we use a trick, append an empty object * to the output list and save the pointer to later modify it with the * right length */ if (!dstkey) { replylen = addDeferredMultiBulkLength(c); } else { /* If we have a target key where to store the resulting set * create this key with an empty set inside */ dstset = createIntsetObject(); } /* Iterate all the elements of the first (smallest) set, and test * 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((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) { if (encoding == REDIS_ENCODING_HT) addReplyBulk(c,eleobj); else addReplyBulkLongLong(c,intobj); cardinality++; } else { if (encoding == REDIS_ENCODING_INTSET) { eleobj = createStringObjectFromLongLong(intobj); setTypeAdd(dstset,eleobj); decrRefCount(eleobj); } else { setTypeAdd(dstset,eleobj); } } } } setTypeReleaseIterator(si); if (dstkey) { /* Store the resulting set into the target, if the intersection * is not an empty set. */ dbDelete(c->db,dstkey); if (setTypeSize(dstset) > 0) { dbAdd(c->db,dstkey,dstset); addReplyLongLong(c,setTypeSize(dstset)); } else { decrRefCount(dstset); addReply(c,shared.czero); } touchWatchedKey(c->db,dstkey); server.dirty++; } else { setDeferredMultiBulkLength(c,replylen,cardinality); } zfree(sets); }
/* This command implements SCAN, HSCAN and SSCAN commands. * If object 'o' is passed, then it must be a Hash or Set object, otherwise * if 'o' is NULL the command will operate on the dictionary associated with * the current database. * * When 'o' is not NULL the function assumes that the first argument in * the client arguments vector is a key so it skips it before iterating * in order to parse options. * * In the case of a Hash object the function returns both the field and value * of every element on the Hash. */ void scanGenericCommand(client *c, robj *o, unsigned long cursor) { int i, j; list *keys = listCreate(); listNode *node, *nextnode; long count = 10; sds pat = NULL; int patlen = 0, use_pattern = 0; dict *ht; /* Object must be NULL (to iterate keys names), or the type of the object * must be Set, Sorted Set, or Hash. */ serverAssert(o == NULL || o->type == OBJ_SET || o->type == OBJ_HASH || o->type == OBJ_ZSET); /* Set i to the first option argument. The previous one is the cursor. */ i = (o == NULL) ? 2 : 3; /* Skip the key argument if needed. */ /* Step 1: Parse options. */ while (i < c->argc) { j = c->argc - i; if (!strcasecmp(c->argv[i]->ptr, "count") && j >= 2) { if (getLongFromObjectOrReply(c, c->argv[i+1], &count, NULL) != C_OK) { goto cleanup; } if (count < 1) { addReply(c,shared.syntaxerr); goto cleanup; } i += 2; } else if (!strcasecmp(c->argv[i]->ptr, "match") && j >= 2) { pat = c->argv[i+1]->ptr; patlen = sdslen(pat); /* The pattern always matches if it is exactly "*", so it is * equivalent to disabling it. */ use_pattern = !(pat[0] == '*' && patlen == 1); i += 2; } else { addReply(c,shared.syntaxerr); goto cleanup; } } /* Step 2: Iterate the collection. * * Note that if the object is encoded with a ziplist, intset, or any other * representation that is not a hash table, we are sure that it is also * composed of a small number of elements. So to avoid taking state we * just return everything inside the object in a single call, setting the * cursor to zero to signal the end of the iteration. */ /* Handle the case of a hash table. */ ht = NULL; if (o == NULL) { ht = c->db->dict; } else if (o->type == OBJ_SET && o->encoding == OBJ_ENCODING_HT) { ht = o->ptr; } else if (o->type == OBJ_HASH && o->encoding == OBJ_ENCODING_HT) { ht = o->ptr; count *= 2; /* We return key / value for this type. */ } else if (o->type == OBJ_ZSET && o->encoding == OBJ_ENCODING_SKIPLIST) { zset *zs = o->ptr; ht = zs->dict; count *= 2; /* We return key / value for this type. */ } if (ht) { void *privdata[2]; /* We set the max number of iterations to ten times the specified * COUNT, so if the hash table is in a pathological state (very * sparsely populated) we avoid to block too much time at the cost * of returning no or very few elements. */ long maxiterations = count*10; /* We pass two pointers to the callback: the list to which it will * add new elements, and the object containing the dictionary so that * it is possible to fetch more data in a type-dependent way. */ privdata[0] = keys; privdata[1] = o; do { cursor = dictScan(ht, cursor, scanCallback, privdata); } while (cursor && maxiterations-- && listLength(keys) < (unsigned long)count); } else if (o->type == OBJ_SET) { int pos = 0; int64_t ll; while(intsetGet(o->ptr,pos++,&ll)) listAddNodeTail(keys,createStringObjectFromLongLong(ll)); cursor = 0; } else if (o->type == OBJ_HASH || o->type == OBJ_ZSET) { unsigned char *p = ziplistIndex(o->ptr,0); unsigned char *vstr; unsigned int vlen; long long vll; while(p) { ziplistGet(p,&vstr,&vlen,&vll); listAddNodeTail(keys, (vstr != NULL) ? createStringObject((char*)vstr,vlen) : createStringObjectFromLongLong(vll)); p = ziplistNext(o->ptr,p); } cursor = 0; } else { serverPanic("Not handled encoding in SCAN."); } /* Step 3: Filter elements. */ node = listFirst(keys); while (node) { robj *kobj = listNodeValue(node); nextnode = listNextNode(node); int filter = 0; /* Filter element if it does not match the pattern. */ if (!filter && use_pattern) { if (sdsEncodedObject(kobj)) { if (!stringmatchlen(pat, patlen, kobj->ptr, sdslen(kobj->ptr), 0)) filter = 1; } else { char buf[LONG_STR_SIZE]; int len; serverAssert(kobj->encoding == OBJ_ENCODING_INT); len = ll2string(buf,sizeof(buf),(long)kobj->ptr); if (!stringmatchlen(pat, patlen, buf, len, 0)) filter = 1; } } /* Filter element if it is an expired key. */ if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1; /* Remove the element and its associted value if needed. */ if (filter) { decrRefCount(kobj); listDelNode(keys, node); } /* If this is a hash or a sorted set, we have a flat list of * key-value elements, so if this element was filtered, remove the * value, or skip it if it was not filtered: we only match keys. */ if (o && (o->type == OBJ_ZSET || o->type == OBJ_HASH)) { node = nextnode; nextnode = listNextNode(node); if (filter) { kobj = listNodeValue(node); decrRefCount(kobj); listDelNode(keys, node); } } node = nextnode; } /* Step 4: Reply to the client. */ addReplyMultiBulkLen(c, 2); addReplyBulkLongLong(c,cursor); addReplyMultiBulkLen(c, listLength(keys)); while ((node = listFirst(keys)) != NULL) { robj *kobj = listNodeValue(node); addReplyBulk(c, kobj); decrRefCount(kobj); listDelNode(keys, node); } cleanup: listSetFreeMethod(keys,decrRefCountVoid); listRelease(keys); }