void lindexCommand(client *c) { robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk); if (o == NULL || checkType(c,o,OBJ_LIST)) return; long index; robj *value = NULL; if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != C_OK)) return; if (o->encoding == OBJ_ENCODING_QUICKLIST) { quicklistEntry entry; if (quicklistIndex(o->ptr, index, &entry)) { if (entry.value) { value = createStringObject((char*)entry.value,entry.sz); } else { value = createStringObjectFromLongLong(entry.longval); } addReplyBulk(c,value); decrRefCount(value); } else { addReply(c,shared.nullbulk); } } else { serverPanic("Unknown list encoding"); } }
void zrankGenericCommand(redisClient *c, int reverse) { robj *o; zset *zs; zskiplist *zsl; dictEntry *de; unsigned long rank; double *score; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || checkType(c,o,REDIS_ZSET)) return; zs = o->ptr; zsl = zs->zsl; c->argv[2] = tryObjectEncoding(c->argv[2]); de = dictFind(zs->dict,c->argv[2]); if (!de) { addReply(c,shared.nullbulk); return; } score = dictGetEntryVal(de); rank = zslGetRank(zsl, *score, c->argv[2]); if (rank) { if (reverse) { addReplyLongLong(c, zsl->length - rank); } else { addReplyLongLong(c, rank-1); } } else { addReply(c,shared.nullbulk); } }
// LINDEX key index // LINDEX命令的实现 void lindexCommand(client *c) { //以读操作取出key对象的value值 robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk); //如果key没找到或value对象不是列表类型则直接返回 if (o == NULL || checkType(c,o,OBJ_LIST)) return; long index; robj *value = NULL; //将index参数转换为long类型的整数,保存在index中 if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != C_OK)) return; //只对编码为quicklist类型的value对象操作 if (o->encoding == OBJ_ENCODING_QUICKLIST) { quicklistEntry entry; //将下标为index的entry节点保存到entry中 if (quicklistIndex(o->ptr, index, &entry)) { if (entry.value) { //如果vlaue是字符串类型 //创建一个字符串类型的对象,保存value值 value = createStringObject((char*)entry.value,entry.sz); } else { //将整型的value值转换为字符串类型并创建字符串类型的对象 value = createStringObjectFromLongLong(entry.longval); } addReplyBulk(c,value); //发送value对象 decrRefCount(value); //释放value对象 } else { addReply(c,shared.nullbulk); //如果下标为index没找到,则发送空信息 } } else { serverPanic("Unknown list encoding"); //发送未知的列表编码类型 } }
void srandmemberCommand(redisClient* c) { robj* set, *ele; int64_t llele; int encoding; if (c->argc == 3) { srandmemberWithCountCommand(c); return; } else if (c->argc > 3) { addReply(c, shared.syntaxerr); return; } 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); } }
void lindexCommand(redisClient *c) { robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk); if (o == NULL || checkType(c,o,REDIS_LIST)) return; int index = atoi(c->argv[2]->ptr); robj *value = NULL; if (o->encoding == REDIS_ENCODING_ZIPLIST) { unsigned char *p; unsigned char *vstr; unsigned int vlen; long long vlong; p = ziplistIndex(o->ptr,index); if (ziplistGet(p,&vstr,&vlen,&vlong)) { if (vstr) { value = createStringObject((char*)vstr,vlen); } else { value = createStringObjectFromLongLong(vlong); } addReplyBulk(c,value); decrRefCount(value); } else { addReply(c,shared.nullbulk); } } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) { listNode *ln = listIndex(o->ptr,index); if (ln != NULL) { value = listNodeValue(ln); addReplyBulk(c,value); } else { addReply(c,shared.nullbulk); } } else { redisPanic("Unknown list encoding"); } }
void scardCommand(redisClient *c) { robj *o; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,REDIS_SET)) return; addReplyLongLong(c,setTypeSize(o)); }
// LLEN key //LLEN命令实现 void llenCommand(client *c) { //以读操作取出key大小的value值 robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.czero); //如果key没找到或value大小不是列表类型则直接返回 if (o == NULL || checkType(c,o,OBJ_LIST)) return; //发送列表中的元素数量给client addReplyLongLong(c,listTypeLength(o)); }
void lrangeCommand(redisClient *c) { robj *o; long start, end, llen, rangelen; if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) || (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return; 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 */ addReplyMultiBulkLen(c,rangelen); if (o->encoding == REDIS_ENCODING_ZIPLIST) { unsigned char *p = ziplistIndex(o->ptr,start); unsigned char *vstr; unsigned int vlen; long long vlong; while(rangelen--) { ziplistGet(p,&vstr,&vlen,&vlong); if (vstr) { addReplyBulkCBuffer(c,vstr,vlen); } else { addReplyBulkLongLong(c,vlong); } p = ziplistNext(o->ptr,p); } } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) { listNode *ln; /* If we are nearest to the end of the list, reach the element * starting from tail and going backward, as it is faster. */ if (start > llen/2) start -= llen; ln = listIndex(o->ptr,start); while(rangelen--) { addReplyBulk(c,ln->value); ln = ln->next; } } else { redisPanic("List encoding is not LINKEDLIST nor ZIPLIST!"); } }
// t_hash.c void hgetCommand(client *c) { robj *o; // will invoke expireIfNeeded() if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || checkType(c,o,OBJ_HASH)) return; addHashFieldToReply(c, o, c->argv[2]); }
void sscanCommand(redisClient *c) { robj *set; unsigned long cursor; if (parseScanCursorOrReply(c,c->argv[2],&cursor) == REDIS_ERR) return; if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL || checkType(c,set,REDIS_SET)) return; scanGenericCommand(c,set,cursor); }
void zcardCommand(redisClient *c) { robj *o; zset *zs; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,REDIS_ZSET)) return; zs = o->ptr; addReplyLongLong(c,zs->zsl->length); }
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); }
void sismemberCommand(client *c) { robj *set; if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,set,OBJ_SET)) return; if (setTypeIsMember(set,c->argv[2]->ptr)) addReply(c,shared.cone); else addReply(c,shared.czero); }
void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) { robj *subject; listTypeIterator *iter; listTypeEntry entry; int inserted = 0; int slotnum = keyHashSlot(c->argv[1]->ptr, sdslen(c->argv[1]->ptr)); if ((subject = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,subject,REDIS_LIST)) return; if (refval != NULL) { /* We're not sure if this value can be inserted yet, but we cannot * convert the list inside the iterator. We don't want to loop over * the list twice (once to see if the value can be inserted and once * to do the actual insert), so we assume this value can be inserted * and convert the ziplist to a regular list if necessary. */ listTypeTryConversion(subject,val); /* Seek refval from head to tail */ iter = listTypeInitIterator(subject,0,REDIS_TAIL); while (listTypeNext(iter,&entry)) { if (listTypeEqual(&entry,refval)) { listTypeInsert(&entry,val,where); inserted = 1; break; } } listTypeReleaseIterator(iter); if (inserted) { /* Check if the length exceeds the ziplist length threshold. */ if (subject->encoding == REDIS_ENCODING_ZIPLIST && ziplistLen(subject->ptr) > server.list_max_ziplist_entries) listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST); signalModifiedKey(c->db,c->argv[1],slotnum); notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"linsert", c->argv[1],c->db->id); server.dirty++; } else { /* Notify client of a failed insert */ addReply(c,shared.cnegone); return; } } else { char *event = (where == REDIS_HEAD) ? "lpush" : "rpush"; listTypePush(subject,val,where); signalModifiedKey(c->db,c->argv[1],slotnum); notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id); server.dirty++; } addReplyLongLong(c,listTypeLength(subject)); }
void sismemberCommand(redisClient *c) { robj *set; if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,set,REDIS_SET)) return; c->argv[2] = tryObjectEncoding(c->argv[2]); if (setTypeIsMember(set,c->argv[2])) addReply(c,shared.cone); else addReply(c,shared.czero); }
void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) { robj *subject; listTypeIterator *iter; listTypeEntry entry; int inserted = 0; if ((subject = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,subject,REDIS_LIST)) return; if (refval != NULL) { /* Note: we expect refval to be string-encoded because it is *not* the * last argument of the multi-bulk LINSERT. */ redisAssertWithInfo(c,refval,refval->encoding == REDIS_ENCODING_RAW); /* We're not sure if this value can be inserted yet, but we cannot * convert the list inside the iterator. We don't want to loop over * the list twice (once to see if the value can be inserted and once * to do the actual insert), so we assume this value can be inserted * and convert the ziplist to a regular list if necessary. */ listTypeTryConversion(subject,val); /* Seek refval from head to tail */ iter = listTypeInitIterator(subject,0,REDIS_TAIL); while (listTypeNext(iter,&entry)) { if (listTypeEqual(&entry,refval)) { listTypeInsert(&entry,val,where); inserted = 1; break; } } listTypeReleaseIterator(iter); if (inserted) { /* Check if the length exceeds the ziplist length threshold. */ if (subject->encoding == REDIS_ENCODING_ZIPLIST && ziplistLen(subject->ptr) > server.list_max_ziplist_entries) listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST); signalModifiedKey(c->db,c->argv[1]); server.dirty++; } else { /* Notify client of a failed insert */ addReply(c,shared.cnegone); return; } } else { listTypePush(subject,val,where); signalModifiedKey(c->db,c->argv[1]); server.dirty++; } addReplyLongLong(c,listTypeLength(subject)); }
int getGenericCommand(client *c) { robj *o; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL) return C_OK; if (o->type != OBJ_STRING) { addReply(c,shared.wrongtypeerr); return C_ERR; } else { addReplyBulk(c,o); return C_OK; } }
int getGenericCommand(redisClient *c) { robj *o; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL) return REDIS_OK; // 这里比的是type字段, 不是encoding. if (o->type != REDIS_STRING) { addReply(c,shared.wrongtypeerr); return REDIS_ERR; } else { addReplyBulk(c,o); return REDIS_OK; } }
void lrangeCommand(client *c) { robj *o; long start, end, llen, rangelen; if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) || (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL || checkType(c,o,OBJ_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 */ addReplyMultiBulkLen(c,rangelen); if (o->encoding == OBJ_ENCODING_QUICKLIST) { listTypeIterator *iter = listTypeInitIterator(o, start, LIST_TAIL); while(rangelen--) { listTypeEntry entry; listTypeNext(iter, &entry); quicklistEntry *qe = &entry.entry; if (qe->value) { addReplyBulkCBuffer(c,qe->value,qe->sz); } else { addReplyBulkLongLong(c,qe->longval); } } listTypeReleaseIterator(iter); } else { serverPanic("List encoding is not QUICKLIST!"); } }
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; } }
void zscoreCommand(redisClient *c) { robj *o; zset *zs; dictEntry *de; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || checkType(c,o,REDIS_ZSET)) return; zs = o->ptr; de = dictFind(zs->dict,c->argv[2]); if (!de) { addReply(c,shared.nullbulk); } else { double *score = dictGetEntryVal(de); addReplyDouble(c,*score); } }
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; } }
void getrangeCommand(client *c) { robj *o; long long start, end; char *str, llbuf[32]; size_t strlen; if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK) return; if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK) return; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL || checkType(c,o,OBJ_STRING)) return; if (o->encoding == OBJ_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 && end < 0 && start > end) { addReply(c,shared.emptybulk); return; } if (start < 0) start = strlen+start; if (end < 0) end = strlen+end; if (start < 0) start = 0; if (end < 0) end = 0; if ((unsigned long long)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 || strlen == 0) { addReply(c,shared.emptybulk); } else { addReplyBulkCBuffer(c,(char*)str+start,end-start+1); } }
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); }
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 spopWithCountCommand(redisClient *c) { long l; unsigned long count, size; robj *set; /* Get the count argument */ if (getLongFromObjectOrReply(c,c->argv[2],&l,NULL) != REDIS_OK) return; if (l >= 0) { count = (unsigned) l; } else { addReply(c,shared.outofrangeerr); return; } /* Make sure a key with the name inputted exists, and that it's type is * indeed a set. Otherwise, return nil */ if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL || checkType(c,set,REDIS_SET)) return; /* If count is zero, serve an empty multibulk ASAP to avoid special * cases later. */ if (count == 0) { addReply(c,shared.emptymultibulk); return; } size = setTypeSize(set); /* Generate an SPOP keyspace notification */ notifyKeyspaceEvent(REDIS_NOTIFY_SET,"spop",c->argv[1],c->db->id); server.dirty += count; /* CASE 1: * The number of requested elements is greater than or equal to * the number of elements inside the set: simply return the whole set. */ if (count >= size) { /* We just return the entire set */ sunionDiffGenericCommand(c,c->argv+1,1,NULL,REDIS_OP_UNION); /* Delete the set as it is now empty */ dbDelete(c->db,c->argv[1]); notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id); /* Propagate this command as an DEL operation */ rewriteClientCommandVector(c,2,shared.del,c->argv[1]); signalModifiedKey(c->db,c->argv[1]); server.dirty++; return; } /* Case 2 and 3 require to replicate SPOP as a set of SERM commands. * Prepare our replication argument vector. Also send the array length * which is common to both the code paths. */ robj *propargv[3]; propargv[0] = createStringObject("SREM",4); propargv[1] = c->argv[1]; addReplyMultiBulkLen(c,count); /* Common iteration vars. */ robj *objele; int encoding; int64_t llele; unsigned long remaining = size-count; /* Elements left after SPOP. */ /* If we are here, the number of requested elements is less than the * number of elements inside the set. Also we are sure that count < size. * Use two different strategies. * * CASE 2: The number of elements to return is small compared to the * set size. We can just extract random elements and return them to * the set. */ if (remaining*SPOP_MOVE_STRATEGY_MUL > count) { while(count--) { encoding = setTypeRandomElement(set,&objele,&llele); if (encoding == REDIS_ENCODING_INTSET) { objele = createStringObjectFromLongLong(llele); } else { incrRefCount(objele); } /* Return the element to the client and remove from the set. */ addReplyBulk(c,objele); setTypeRemove(set,objele); /* Replicate/AOF this command as an SREM operation */ propargv[2] = objele; alsoPropagate(server.sremCommand,c->db->id,propargv,3, REDIS_PROPAGATE_AOF|REDIS_PROPAGATE_REPL); decrRefCount(objele); } } else { /* CASE 3: The number of elements to return is very big, approaching * the size of the set itself. After some time extracting random elements * from such a set becomes computationally expensive, so we use * a different strategy, we extract random elements that we don't * want to return (the elements that will remain part of the set), * creating a new set as we do this (that will be stored as the original * set). Then we return the elements left in the original set and * release it. */ robj *newset = NULL; /* Create a new set with just the remaining elements. */ while(remaining--) { encoding = setTypeRandomElement(set,&objele,&llele); if (encoding == REDIS_ENCODING_INTSET) { objele = createStringObjectFromLongLong(llele); } else { incrRefCount(objele); } if (!newset) newset = setTypeCreate(objele); setTypeAdd(newset,objele); setTypeRemove(set,objele); decrRefCount(objele); } /* Assign the new set as the key value. */ incrRefCount(set); /* Protect the old set value. */ dbOverwrite(c->db,c->argv[1],newset); /* Tranfer the old set to the client and release it. */ setTypeIterator *si; si = setTypeInitIterator(set); while((encoding = setTypeNext(si,&objele,&llele)) != -1) { if (encoding == REDIS_ENCODING_INTSET) { objele = createStringObjectFromLongLong(llele); } else { incrRefCount(objele); } addReplyBulk(c,objele); /* Replicate/AOF this command as an SREM operation */ propargv[2] = objele; alsoPropagate(server.sremCommand,c->db->id,propargv,3, REDIS_PROPAGATE_AOF|REDIS_PROPAGATE_REPL); decrRefCount(objele); } setTypeReleaseIterator(si); decrRefCount(set); } /* Don't propagate the command itself even if we incremented the * dirty counter. We don't want to propagate an SPOP command since * we propagated the command as a set of SREMs operations using * the alsoPropagate() API. */ decrRefCount(propargv[0]); preventCommandPropagation(c); }
void srandmemberWithCountCommand(redisClient *c) { long l; unsigned long count, size; int uniq = 1; robj *set, *ele; int64_t llele; int encoding; dict *d; if (getLongFromObjectOrReply(c,c->argv[2],&l,NULL) != REDIS_OK) return; if (l >= 0) { count = (unsigned) l; } else { /* A negative count means: return the same elements multiple times * (i.e. don't remove the extracted element after every extraction). */ count = -l; uniq = 0; } if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL || checkType(c,set,REDIS_SET)) return; size = setTypeSize(set); /* If count is zero, serve it ASAP to avoid special cases later. */ if (count == 0) { addReply(c,shared.emptymultibulk); return; } /* CASE 1: The count was negative, so the extraction method is just: * "return N random elements" sampling the whole set every time. * This case is trivial and can be served without auxiliary data * structures. */ if (!uniq) { addReplyMultiBulkLen(c,count); while(count--) { encoding = setTypeRandomElement(set,&ele,&llele); if (encoding == REDIS_ENCODING_INTSET) { addReplyBulkLongLong(c,llele); } else { addReplyBulk(c,ele); } } return; } /* CASE 2: * The number of requested elements is greater than the number of * elements inside the set: simply return the whole set. */ if (count >= size) { sunionDiffGenericCommand(c,c->argv+1,1,NULL,REDIS_OP_UNION); return; } /* For CASE 3 and CASE 4 we need an auxiliary dictionary. */ d = dictCreate(&setDictType,NULL); /* CASE 3: * The number of elements inside the set is not greater than * SRANDMEMBER_SUB_STRATEGY_MUL times the number of requested elements. * In this case we create a set from scratch with all the elements, and * subtract random elements to reach the requested number of elements. * * This is done because if the number of requsted elements is just * a bit less than the number of elements in the set, the natural approach * used into CASE 3 is highly inefficient. */ if (count*SRANDMEMBER_SUB_STRATEGY_MUL > size) { setTypeIterator *si; /* Add all the elements into the temporary dictionary. */ si = setTypeInitIterator(set); while((encoding = setTypeNext(si,&ele,&llele)) != -1) { int retval = DICT_ERR; if (encoding == REDIS_ENCODING_INTSET) { retval = dictAdd(d,createStringObjectFromLongLong(llele),NULL); } else if (ele->encoding == REDIS_ENCODING_RAW) { retval = dictAdd(d,dupStringObject(ele),NULL); } else if (ele->encoding == REDIS_ENCODING_INT) { retval = dictAdd(d, createStringObjectFromLongLong((long)ele->ptr),NULL); } redisAssert(retval == DICT_OK); } setTypeReleaseIterator(si); redisAssert(dictSize(d) == size); /* Remove random elements to reach the right count. */ while(size > count) { dictEntry *de; de = dictGetRandomKey(d); dictDelete(d,dictGetKey(de)); size--; } } /* CASE 4: We have a big set compared to the requested number of elements. * In this case we can simply get random elements from the set and add * to the temporary set, trying to eventually get enough unique elements * to reach the specified count. */ else { unsigned long added = 0; while(added < count) { encoding = setTypeRandomElement(set,&ele,&llele); if (encoding == REDIS_ENCODING_INTSET) { ele = createStringObjectFromLongLong(llele); } else if (ele->encoding == REDIS_ENCODING_RAW) { ele = dupStringObject(ele); } else if (ele->encoding == REDIS_ENCODING_INT) { ele = createStringObjectFromLongLong((long)ele->ptr); } /* Try to add the object to the dictionary. If it already exists * free it, otherwise increment the number of objects we have * in the result dictionary. */ if (dictAdd(d,ele,NULL) == DICT_OK) added++; else decrRefCount(ele); } } /* CASE 3 & 4: send the result to the user. */ { dictIterator *di; dictEntry *de; addReplyMultiBulkLen(c,count); di = dictGetIterator(d); while((de = dictNext(di)) != NULL) addReplyBulk(c,dictGetKey(de)); dictReleaseIterator(di); dictRelease(d); } }
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 llenCommand(redisClient *c) { robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.czero); if (o == NULL || checkType(c,o,REDIS_LIST)) return; addReplyLongLong(c,listTypeLength(o)); }
/* 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); } }